]>
Commit | Line | Data |
---|---|---|
fdfd5971 A |
1 | /*- |
2 | * Copyright (c) 2007-2009 Bruce Simpson. | |
3 | * Copyright (c) 2000 Wilbert De Graaf. | |
4 | * All rights reserved. | |
5 | * | |
6 | * Redistribution and use in source and binary forms, with or without | |
7 | * modification, are permitted provided that the following conditions | |
8 | * are met: | |
9 | * 1. Redistributions of source code must retain the above copyright | |
10 | * notice, this list of conditions and the following disclaimer. | |
11 | * 2. Redistributions in binary form must reproduce the above copyright | |
12 | * notice, this list of conditions and the following disclaimer in the | |
13 | * documentation and/or other materials provided with the distribution. | |
14 | * | |
15 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
28 | * SUCH DAMAGE. | |
29 | */ | |
30 | ||
31 | /* | |
32 | * Diagnostic and test utility for multicast sockets. | |
33 | * TODO: Support embedded KAME Scope ID in IPv6 group addresses. | |
34 | * TODO: Use IPv4 link-local address when source address selection | |
35 | * is implemented; use MCAST_JOIN_SOURCE for IPv4. | |
36 | */ | |
37 | ||
38 | #define INET6 | |
39 | ||
40 | #include <sys/cdefs.h> | |
41 | ||
42 | #include <sys/types.h> | |
43 | #include <sys/param.h> | |
44 | #include <sys/errno.h> | |
45 | #include <sys/socket.h> | |
46 | #include <sys/time.h> | |
47 | #include <sys/ioctl.h> | |
48 | ||
49 | #include <net/if.h> | |
50 | #include <net/if_dl.h> | |
51 | #include <net/ethernet.h> | |
52 | #include <netinet/in.h> | |
53 | #include <netinet/in_systm.h> | |
54 | #include <netinet/ip.h> | |
55 | #include <netinet/ip_var.h> | |
56 | #ifdef INET6 | |
57 | #include <netinet/in.h> | |
58 | #include <netinet/ip6.h> | |
59 | #endif | |
60 | ||
61 | #include <assert.h> | |
62 | #include <stdlib.h> | |
63 | #include <stdio.h> | |
64 | #include <string.h> | |
65 | #include <ctype.h> | |
66 | #include <errno.h> | |
67 | #include <err.h> | |
68 | #include <unistd.h> | |
69 | ||
70 | #include <arpa/inet.h> | |
71 | #include <netdb.h> | |
72 | #include <ifaddrs.h> | |
73 | ||
74 | #ifdef IP_ADD_SOURCE_MEMBERSHIP | |
75 | #define HAS_SSM 1 | |
76 | #endif | |
77 | ||
78 | union sockunion { | |
79 | struct sockaddr_storage ss; | |
80 | struct sockaddr sa; | |
81 | struct sockaddr_dl sdl; | |
82 | struct sockaddr_in sin; | |
83 | #ifdef INET6 | |
84 | struct sockaddr_in6 sin6; | |
85 | #endif | |
86 | }; | |
87 | typedef union sockunion sockunion_t; | |
88 | ||
89 | union mrequnion { | |
90 | struct ip_mreq mr; | |
91 | #ifdef HAS_SSM | |
92 | struct ip_mreq_source mrs; | |
93 | #endif | |
94 | #ifdef INET6 | |
95 | struct ipv6_mreq mr6; | |
96 | #ifdef HAS_SSM | |
97 | struct group_source_req gr; | |
98 | #endif | |
99 | #endif | |
100 | }; | |
101 | typedef union mrequnion mrequnion_t; | |
102 | ||
103 | #define MAX_ADDRS 20 | |
104 | #define STR_SIZE 64 | |
105 | #define LINE_LENGTH 80 | |
106 | ||
107 | static int __ifindex_to_primary_ip(const uint32_t, struct in_addr *); | |
108 | static uint32_t parse_cmd_args(sockunion_t *, sockunion_t *, | |
109 | const char *, const char *, const char *); | |
110 | static void process_file(char *, int, int); | |
111 | static void process_cmd(char*, int, int, FILE *); | |
112 | static int su_cmp(const void *, const void *); | |
113 | static void usage(void); | |
114 | ||
115 | /* | |
116 | * Ordering predicate for qsort(). | |
117 | */ | |
118 | static int | |
119 | su_cmp(const void *a, const void *b) | |
120 | { | |
121 | const sockunion_t *sua = (const sockunion_t *)a; | |
122 | const sockunion_t *sub = (const sockunion_t *)b; | |
123 | ||
124 | assert(sua->sa.sa_family == sub->sa.sa_family); | |
125 | ||
126 | switch (sua->sa.sa_family) { | |
127 | case AF_INET: | |
128 | return ((int)(sua->sin.sin_addr.s_addr - | |
129 | sub->sin.sin_addr.s_addr)); | |
130 | break; | |
131 | #ifdef INET6 | |
132 | case AF_INET6: | |
133 | return (memcmp(&sua->sin6.sin6_addr, &sub->sin6.sin6_addr, | |
134 | sizeof(struct in6_addr))); | |
135 | break; | |
136 | #endif | |
137 | default: | |
138 | break; | |
139 | } | |
140 | ||
141 | assert(sua->sa.sa_len == sub->sa.sa_len); | |
142 | return (memcmp(sua, sub, sua->sa.sa_len)); | |
143 | } | |
144 | ||
145 | /* | |
146 | * Internal: Map an interface index to primary IPv4 address. | |
147 | * This is somewhat inefficient. This is a useful enough operation | |
148 | * that it probably belongs in the C library. | |
149 | * Return zero if found, -1 on error, 1 on not found. | |
150 | */ | |
151 | static int | |
152 | __ifindex_to_primary_ip(const uint32_t ifindex, struct in_addr *pina) | |
153 | { | |
154 | char ifname[IFNAMSIZ]; | |
155 | struct ifaddrs *ifa; | |
156 | struct ifaddrs *ifaddrs; | |
157 | sockunion_t *psu; | |
158 | int retval; | |
159 | ||
160 | assert(ifindex != 0); | |
161 | ||
162 | retval = -1; | |
163 | if (if_indextoname(ifindex, ifname) == NULL) | |
164 | return (retval); | |
165 | if (getifaddrs(&ifaddrs) < 0) | |
166 | return (retval); | |
167 | ||
168 | /* | |
169 | * Find the ifaddr entry corresponding to the interface name, | |
170 | * and return the first matching IPv4 address. | |
171 | */ | |
172 | retval = 1; | |
173 | for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { | |
174 | if (strcmp(ifa->ifa_name, ifname) != 0) | |
175 | continue; | |
176 | psu = (sockunion_t *)ifa->ifa_addr; | |
177 | if (psu && psu->sa.sa_family == AF_INET) { | |
178 | retval = 0; | |
179 | memcpy(pina, &psu->sin.sin_addr, | |
180 | sizeof(struct in_addr)); | |
181 | break; | |
182 | } | |
183 | } | |
184 | ||
185 | if (retval != 0) | |
186 | errno = EADDRNOTAVAIL; /* XXX */ | |
187 | ||
188 | freeifaddrs(ifaddrs); | |
189 | return (retval); | |
190 | } | |
191 | ||
192 | int | |
193 | main(int argc, char **argv) | |
194 | { | |
195 | char line[LINE_LENGTH]; | |
196 | char *p; | |
197 | int i, s, s6; | |
198 | ||
199 | s = -1; | |
200 | s6 = -1; | |
201 | s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); | |
202 | if (s == -1) | |
203 | err(1, "can't open IPv4 socket"); | |
204 | #ifdef INET6 | |
205 | s6 = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); | |
206 | if (s6 == -1) | |
207 | err(1, "can't open IPv6 socket"); | |
208 | #endif | |
209 | ||
210 | if (argc < 2) { | |
211 | if (isatty(STDIN_FILENO)) { | |
212 | printf("multicast membership test program; " | |
213 | "enter ? for list of commands\n"); | |
214 | } | |
215 | do { | |
216 | if (fgets(line, sizeof(line), stdin) != NULL) { | |
217 | if (line[0] != 'f') | |
218 | process_cmd(line, s, s6, stdin); | |
219 | else { | |
220 | /* Get the filename */ | |
221 | for (i = 1; isblank(line[i]); i++); | |
222 | if ((p = (char*)strchr(line, '\n')) | |
223 | != NULL) | |
224 | *p = '\0'; | |
225 | process_file(&line[i], s, s6); | |
226 | } | |
227 | } | |
228 | } while (!feof(stdin)); | |
229 | } else { | |
230 | for (i = 1; i < argc; i++) { | |
231 | process_file(argv[i], s, s6); | |
232 | } | |
233 | } | |
234 | ||
235 | if (s != -1) | |
236 | close(s); | |
237 | if (s6 != -1) | |
238 | close(s6); | |
239 | ||
240 | exit (0); | |
241 | } | |
242 | ||
243 | static void | |
244 | process_file(char *fname, int s, int s6) | |
245 | { | |
246 | char line[80]; | |
247 | FILE *fp; | |
248 | char *lineptr; | |
249 | ||
250 | fp = fopen(fname, "r"); | |
251 | if (fp == NULL) { | |
252 | warn("fopen"); | |
253 | return; | |
254 | } | |
255 | ||
256 | /* Skip comments and empty lines. */ | |
257 | while (fgets(line, sizeof(line), fp) != NULL) { | |
258 | lineptr = line; | |
259 | while (isblank(*lineptr)) | |
260 | lineptr++; | |
261 | if (*lineptr != '#' && *lineptr != '\n') | |
262 | process_cmd(lineptr, s, s6, fp); | |
263 | } | |
264 | ||
265 | fclose(fp); | |
266 | } | |
267 | ||
268 | /* | |
269 | * Parse join/leave/allow/block arguments, given: | |
270 | * str1: group (as AF_INET or AF_INET6 printable) | |
271 | * str2: ifname | |
272 | * str3: optional source address (may be NULL). | |
273 | * This argument must have the same parsed address family as str1. | |
274 | * Return the ifindex of ifname, or 0 if any parse element failed. | |
275 | */ | |
276 | static uint32_t | |
277 | parse_cmd_args(sockunion_t *psu, sockunion_t *psu2, | |
278 | const char *str1, const char *str2, const char *str3) | |
279 | { | |
280 | struct addrinfo hints; | |
281 | struct addrinfo *res; | |
282 | uint32_t ifindex; | |
283 | int af, error; | |
284 | ||
285 | assert(psu != NULL); | |
286 | assert(str1 != NULL); | |
287 | assert(str2 != NULL); | |
288 | ||
289 | af = AF_UNSPEC; | |
290 | ||
291 | ifindex = if_nametoindex(str2); | |
292 | if (ifindex == 0) | |
293 | return (0); | |
294 | ||
295 | memset(&hints, 0, sizeof(struct addrinfo)); | |
296 | hints.ai_flags = AI_NUMERICHOST; | |
297 | hints.ai_family = PF_UNSPEC; | |
298 | hints.ai_socktype = SOCK_DGRAM; | |
299 | ||
300 | memset(psu, 0, sizeof(sockunion_t)); | |
301 | psu->sa.sa_family = AF_UNSPEC; | |
302 | ||
303 | error = getaddrinfo(str1, "0", &hints, &res); | |
304 | if (error) { | |
305 | warnx("getaddrinfo: %s", gai_strerror(error)); | |
306 | return (0); | |
307 | } | |
308 | assert(res != NULL); | |
309 | af = res->ai_family; | |
310 | memcpy(psu, res->ai_addr, res->ai_addrlen); | |
311 | freeaddrinfo(res); | |
312 | ||
313 | /* sscanf() may pass the empty string. */ | |
314 | if (psu2 != NULL && str3 != NULL && *str3 != '\0') { | |
315 | memset(psu2, 0, sizeof(sockunion_t)); | |
316 | psu2->sa.sa_family = AF_UNSPEC; | |
317 | ||
318 | /* look for following address family; str3 is *optional*. */ | |
319 | hints.ai_family = af; | |
320 | error = getaddrinfo(str3, "0", &hints, &res); | |
321 | if (error) { | |
322 | warnx("getaddrinfo: %s", gai_strerror(error)); | |
323 | ifindex = 0; | |
324 | } else { | |
325 | if (af != res->ai_family) { | |
326 | errno = EINVAL; /* XXX */ | |
327 | ifindex = 0; | |
328 | } | |
329 | memcpy(psu2, res->ai_addr, res->ai_addrlen); | |
330 | freeaddrinfo(res); | |
331 | } | |
332 | } | |
333 | ||
334 | return (ifindex); | |
335 | } | |
336 | ||
337 | static __inline int | |
338 | af2sock(const int af, int s, int s6) | |
339 | { | |
340 | ||
341 | if (af == AF_INET) | |
342 | return (s); | |
343 | #ifdef INET6 | |
344 | if (af == AF_INET6) | |
345 | return (s6); | |
346 | #endif | |
347 | return (-1); | |
348 | } | |
349 | ||
fdfd5971 A |
350 | static void |
351 | process_cmd(char *cmd, int s, int s6 __unused, FILE *fp __unused) | |
352 | { | |
353 | char str1[STR_SIZE]; | |
354 | char str2[STR_SIZE]; | |
355 | char str3[STR_SIZE]; | |
356 | mrequnion_t mr; | |
357 | sockunion_t su, su2; | |
358 | struct ifreq ifr; | |
359 | char *line; | |
360 | char *toptname; | |
361 | void *optval; | |
362 | uint32_t fmode, ifindex; | |
363 | socklen_t optlen; | |
7af5ce03 | 364 | int af, error, i, level, n = 0, optname; |
fdfd5971 A |
365 | #ifndef __APPLE__ |
366 | int f, flags; | |
367 | #endif /* __APPLE__ */ | |
368 | ||
369 | af = AF_UNSPEC; | |
370 | su.sa.sa_family = AF_UNSPEC; | |
371 | su2.sa.sa_family = AF_UNSPEC; | |
372 | ||
373 | line = cmd; | |
374 | while (isblank(*++line)) | |
375 | ; /* Skip whitespace. */ | |
376 | ||
377 | switch (*cmd) { | |
378 | case '?': | |
379 | usage(); | |
380 | break; | |
381 | ||
382 | case 'q': | |
383 | close(s); | |
384 | exit(0); | |
385 | ||
386 | case 's': | |
387 | if ((sscanf(line, "%d", &n) != 1) || (n < 1)) { | |
388 | printf("-1\n"); | |
389 | break; | |
390 | } | |
391 | sleep(n); | |
392 | printf("ok\n"); | |
393 | break; | |
394 | ||
395 | case 'j': | |
396 | case 'l': | |
397 | str3[0] = '\0'; | |
398 | toptname = ""; | |
399 | sscanf(line, "%s %s %s", str1, str2, str3); | |
400 | ifindex = parse_cmd_args(&su, &su2, str1, str2, str3); | |
401 | if (ifindex == 0) { | |
402 | printf("-1\n"); | |
403 | break; | |
404 | } | |
405 | af = su.sa.sa_family; | |
406 | if (af == AF_INET) { | |
407 | struct in_addr ina; | |
408 | ||
409 | error = __ifindex_to_primary_ip(ifindex, &ina); | |
410 | if (error != 0) { | |
411 | warn("primary_ip_lookup %s", str2); | |
412 | printf("-1\n"); | |
413 | break; | |
414 | } | |
415 | level = IPPROTO_IP; | |
416 | ||
417 | #ifdef HAS_SSM | |
418 | if (su2.sa.sa_family != AF_UNSPEC) { | |
419 | mr.mrs.imr_multiaddr = su.sin.sin_addr; | |
420 | mr.mrs.imr_sourceaddr = su2.sin.sin_addr; | |
421 | mr.mrs.imr_interface = ina; | |
422 | optname = (*cmd == 'j') ? | |
423 | IP_ADD_SOURCE_MEMBERSHIP : | |
424 | IP_DROP_SOURCE_MEMBERSHIP; | |
425 | toptname = (*cmd == 'j') ? | |
426 | "IP_ADD_SOURCE_MEMBERSHIP" : | |
427 | "IP_DROP_SOURCE_MEMBERSHIP"; | |
428 | optval = (void *)&mr.mrs; | |
429 | optlen = sizeof(mr.mrs); | |
430 | } else { | |
431 | #endif | |
432 | mr.mr.imr_multiaddr = su.sin.sin_addr; | |
433 | mr.mr.imr_interface = ina; | |
434 | optname = (*cmd == 'j') ? | |
435 | IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP; | |
436 | toptname = (*cmd == 'j') ? | |
437 | "IP_ADD_MEMBERSHIP" : "IP_DROP_MEMBERSHIP"; | |
438 | optval = (void *)&mr.mr; | |
439 | optlen = sizeof(mr.mr); | |
440 | #ifdef HAS_SSM | |
441 | } | |
442 | #endif | |
443 | if (setsockopt(s, level, optname, optval, | |
444 | optlen) == 0) { | |
445 | printf("ok\n"); | |
446 | break; | |
447 | } else { | |
448 | warn("setsockopt %s", toptname); | |
449 | } | |
450 | } | |
451 | #ifdef INET6 | |
452 | else | |
453 | #endif /* INET6 */ | |
454 | #ifdef INET6 | |
455 | if (af == AF_INET6) { | |
456 | level = IPPROTO_IPV6; | |
457 | #ifdef HAS_SSM | |
458 | if (su2.sa.sa_family != AF_UNSPEC) { | |
459 | mr.gr.gsr_interface = ifindex; | |
460 | mr.gr.gsr_group = su.ss; | |
461 | mr.gr.gsr_source = su2.ss; | |
462 | optname = (*cmd == 'j') ? | |
463 | MCAST_JOIN_SOURCE_GROUP: | |
464 | MCAST_LEAVE_SOURCE_GROUP; | |
465 | toptname = (*cmd == 'j') ? | |
466 | "MCAST_JOIN_SOURCE_GROUP": | |
467 | "MCAST_LEAVE_SOURCE_GROUP"; | |
468 | optval = (void *)&mr.gr; | |
469 | optlen = sizeof(mr.gr); | |
470 | } else { | |
471 | #endif | |
472 | mr.mr6.ipv6mr_multiaddr = su.sin6.sin6_addr; | |
473 | mr.mr6.ipv6mr_interface = ifindex; | |
474 | optname = (*cmd == 'j') ? | |
475 | IPV6_JOIN_GROUP : | |
476 | IPV6_LEAVE_GROUP; | |
477 | toptname = (*cmd == 'j') ? | |
478 | "IPV6_JOIN_GROUP" : | |
479 | "IPV6_LEAVE_GROUP"; | |
480 | optval = (void *)&mr.mr6; | |
481 | optlen = sizeof(mr.mr6); | |
482 | #ifdef HAS_SSM | |
483 | } | |
484 | #endif | |
485 | if (setsockopt(s6, level, optname, optval, | |
486 | optlen) == 0) { | |
487 | printf("ok\n"); | |
488 | break; | |
489 | } else { | |
490 | warn("setsockopt %s", toptname); | |
491 | } | |
492 | } | |
493 | #endif /* INET6 */ | |
494 | /* FALLTHROUGH */ | |
495 | printf("-1\n"); | |
496 | break; | |
497 | ||
498 | #ifdef HAS_SSM | |
499 | /* | |
500 | * Set the socket to include or exclude filter mode, and | |
501 | * add some sources to the filterlist, using the full-state API. | |
502 | */ | |
503 | case 'i': | |
504 | case 'e': { | |
505 | sockunion_t sources[MAX_ADDRS]; | |
506 | struct addrinfo hints; | |
507 | struct addrinfo *res; | |
508 | char *cp; | |
509 | int af1; | |
510 | ||
511 | n = 0; | |
512 | fmode = (*cmd == 'i') ? MCAST_INCLUDE : MCAST_EXCLUDE; | |
513 | if ((sscanf(line, "%s %s %d", str1, str2, &n)) != 3) { | |
514 | printf("-1\n"); | |
515 | break; | |
516 | } | |
517 | ||
518 | ifindex = parse_cmd_args(&su, NULL, str1, str2, NULL); | |
519 | if (ifindex == 0 || n < 0 || n > MAX_ADDRS) { | |
520 | printf("-1\n"); | |
521 | break; | |
522 | } | |
523 | af = su.sa.sa_family; | |
524 | ||
525 | memset(&hints, 0, sizeof(struct addrinfo)); | |
526 | hints.ai_flags = AI_NUMERICHOST; | |
527 | hints.ai_family = af; | |
528 | hints.ai_socktype = SOCK_DGRAM; | |
529 | ||
530 | for (i = 0; i < n; i++) { | |
531 | sockunion_t *psu = (sockunion_t *)&sources[i]; | |
532 | /* | |
533 | * Trim trailing whitespace, as getaddrinfo() | |
534 | * can't cope with it. | |
535 | */ | |
536 | fgets(str1, sizeof(str1), fp); | |
537 | cp = strchr(str1, '\n'); | |
538 | if (cp != NULL) | |
539 | *cp = '\0'; | |
540 | ||
541 | res = NULL; | |
542 | error = getaddrinfo(str1, "0", &hints, &res); | |
543 | if (error) | |
544 | break; | |
545 | assert(res != NULL); | |
546 | ||
547 | memset(psu, 0, sizeof(sockunion_t)); | |
548 | af1 = res->ai_family; | |
549 | if (af1 == af) | |
550 | memcpy(psu, res->ai_addr, res->ai_addrlen); | |
551 | freeaddrinfo(res); | |
552 | if (af1 != af) | |
553 | break; | |
554 | } | |
555 | if (i < n) { | |
556 | if (error) | |
557 | warnx("getaddrinfo: %s", gai_strerror(error)); | |
558 | printf("-1\n"); | |
559 | break; | |
560 | } | |
561 | if (setsourcefilter(af2sock(af, s, s6), ifindex, | |
562 | &su.sa, su.sa.sa_len, fmode, n, &sources[0].ss) != 0) | |
563 | warn("setsourcefilter"); | |
564 | else | |
565 | printf("ok\n"); | |
566 | } break; | |
567 | ||
568 | /* | |
569 | * Allow or block traffic from a source, using the | |
570 | * delta based api. | |
571 | */ | |
572 | case 't': | |
573 | case 'b': { | |
574 | str3[0] = '\0'; | |
575 | toptname = ""; | |
576 | sscanf(line, "%s %s %s", str1, str2, str3); | |
577 | ifindex = parse_cmd_args(&su, &su2, str1, str2, str3); | |
578 | if (ifindex == 0 || su2.sa.sa_family == AF_UNSPEC) { | |
579 | printf("-1\n"); | |
580 | break; | |
581 | } | |
582 | af = su.sa.sa_family; | |
583 | ||
584 | /* First determine our current filter mode. */ | |
585 | n = 0; | |
586 | if (getsourcefilter(af2sock(af, s, s6), ifindex, | |
587 | &su.sa, su.sa.sa_len, &fmode, (uint32_t *)&n, NULL) != 0) { | |
588 | warn("getsourcefilter"); | |
589 | break; | |
590 | } | |
591 | if (af == AF_INET) { | |
592 | struct in_addr ina; | |
593 | ||
594 | error = __ifindex_to_primary_ip(ifindex, &ina); | |
595 | if (error != 0) { | |
596 | warn("primary_ip_lookup %s", str2); | |
597 | printf("-1\n"); | |
598 | break; | |
599 | } | |
600 | level = IPPROTO_IP; | |
601 | optval = (void *)&mr.mrs; | |
602 | optlen = sizeof(mr.mrs); | |
603 | mr.mrs.imr_multiaddr = su.sin.sin_addr; | |
604 | mr.mrs.imr_sourceaddr = su2.sin.sin_addr; | |
605 | mr.mrs.imr_interface = ina; | |
606 | if (fmode == MCAST_EXCLUDE) { | |
607 | /* Any-source mode socket membership. */ | |
608 | optname = (*cmd == 't') ? | |
609 | IP_UNBLOCK_SOURCE : | |
610 | IP_BLOCK_SOURCE; | |
611 | toptname = (*cmd == 't') ? | |
612 | "IP_UNBLOCK_SOURCE" : | |
613 | "IP_BLOCK_SOURCE"; | |
614 | } else { | |
615 | /* Source-specific mode socket membership. */ | |
616 | optname = (*cmd == 't') ? | |
617 | IP_ADD_SOURCE_MEMBERSHIP : | |
618 | IP_DROP_SOURCE_MEMBERSHIP; | |
619 | toptname = (*cmd == 't') ? | |
620 | "IP_ADD_SOURCE_MEMBERSHIP" : | |
621 | "IP_DROP_SOURCE_MEMBERSHIP"; | |
622 | } | |
623 | if (setsockopt(s, level, optname, optval, | |
624 | optlen) == 0) { | |
625 | printf("ok\n"); | |
626 | break; | |
627 | } else { | |
628 | warn("setsockopt %s", toptname); | |
629 | } | |
630 | } | |
631 | #ifdef INET6 | |
632 | else | |
633 | #endif /* INET6 */ | |
634 | #ifdef INET6 | |
635 | if (af == AF_INET6) { | |
636 | level = IPPROTO_IPV6; | |
637 | mr.gr.gsr_interface = ifindex; | |
638 | mr.gr.gsr_group = su.ss; | |
639 | mr.gr.gsr_source = su2.ss; | |
640 | if (fmode == MCAST_EXCLUDE) { | |
641 | /* Any-source mode socket membership. */ | |
642 | optname = (*cmd == 't') ? | |
643 | MCAST_UNBLOCK_SOURCE : | |
644 | MCAST_BLOCK_SOURCE; | |
645 | toptname = (*cmd == 't') ? | |
646 | "MCAST_UNBLOCK_SOURCE" : | |
647 | "MCAST_BLOCK_SOURCE"; | |
648 | } else { | |
649 | /* Source-specific mode socket membership. */ | |
650 | optname = (*cmd == 't') ? | |
651 | MCAST_JOIN_SOURCE_GROUP : | |
652 | MCAST_LEAVE_SOURCE_GROUP; | |
653 | toptname = (*cmd == 't') ? | |
654 | "MCAST_JOIN_SOURCE_GROUP": | |
655 | "MCAST_LEAVE_SOURCE_GROUP"; | |
656 | } | |
657 | optval = (void *)&mr.gr; | |
658 | optlen = sizeof(mr.gr); | |
659 | if (setsockopt(s6, level, optname, optval, | |
660 | optlen) == 0) { | |
661 | printf("ok\n"); | |
662 | break; | |
663 | } else { | |
664 | warn("setsockopt %s", toptname); | |
665 | } | |
666 | } | |
667 | #endif /* INET6 */ | |
668 | /* FALLTHROUGH */ | |
669 | printf("-1\n"); | |
670 | } break; | |
671 | ||
672 | case 'g': { | |
673 | sockunion_t sources[MAX_ADDRS]; | |
674 | char addrbuf[NI_MAXHOST]; | |
675 | int nreqsrc, nsrc; | |
676 | ||
677 | if ((sscanf(line, "%s %s %d", str1, str2, &nreqsrc)) != 3) { | |
678 | printf("-1\n"); | |
679 | break; | |
680 | } | |
681 | ifindex = parse_cmd_args(&su, NULL, str1, str2, NULL); | |
682 | if (ifindex == 0 || (n < 0 || n > MAX_ADDRS)) { | |
683 | printf("-1\n"); | |
684 | break; | |
685 | } | |
686 | ||
687 | af = su.sa.sa_family; | |
688 | nsrc = nreqsrc; | |
689 | if (getsourcefilter(af2sock(af, s, s6), ifindex, &su.sa, | |
690 | su.sa.sa_len, &fmode, (uint32_t *)&nsrc, | |
691 | &sources[0].ss) != 0) { | |
692 | warn("getsourcefilter"); | |
693 | printf("-1\n"); | |
694 | break; | |
695 | } | |
696 | printf("%s\n", (fmode == MCAST_INCLUDE) ? "include" : | |
697 | "exclude"); | |
698 | printf("%d\n", nsrc); | |
699 | ||
700 | nsrc = MIN(nreqsrc, nsrc); | |
701 | fprintf(stderr, "hexdump of sources:\n"); | |
702 | uint8_t *bp = (uint8_t *)&sources[0]; | |
703 | for (i = 0; i < (nsrc * sizeof(sources[0])); i++) { | |
704 | fprintf(stderr, "%02x", bp[i]); | |
705 | } | |
706 | fprintf(stderr, "\nend hexdump\n"); | |
707 | ||
708 | qsort(sources, nsrc, sizeof (sockunion_t), su_cmp); | |
709 | for (i = 0; i < nsrc; i++) { | |
710 | sockunion_t *psu = (sockunion_t *)&sources[i]; | |
711 | addrbuf[0] = '\0'; | |
712 | error = getnameinfo(&psu->sa, psu->sa.sa_len, | |
713 | addrbuf, sizeof(addrbuf), NULL, 0, | |
714 | NI_NUMERICHOST); | |
715 | if (error) | |
716 | warnx("getnameinfo: %s", gai_strerror(error)); | |
717 | else | |
718 | printf("%s\n", addrbuf); | |
719 | } | |
720 | printf("ok\n"); | |
721 | } break; | |
722 | #endif | |
723 | /* link-layer stuff follows. */ | |
724 | ||
725 | case 'a': | |
726 | case 'd': { | |
727 | struct sockaddr_dl *dlp; | |
728 | struct ether_addr *ep; | |
729 | ||
730 | memset(&ifr, 0, sizeof(struct ifreq)); | |
731 | dlp = (struct sockaddr_dl *)&ifr.ifr_addr; | |
732 | dlp->sdl_len = sizeof(struct sockaddr_dl); | |
733 | dlp->sdl_family = AF_LINK; | |
734 | dlp->sdl_index = 0; | |
735 | dlp->sdl_nlen = 0; | |
736 | dlp->sdl_alen = ETHER_ADDR_LEN; | |
737 | dlp->sdl_slen = 0; | |
738 | if (sscanf(line, "%s %s", str1, str2) != 2) { | |
739 | warnc(EINVAL, "sscanf"); | |
740 | break; | |
741 | } | |
742 | ep = ether_aton(str2); | |
743 | if (ep == NULL) { | |
744 | warnc(EINVAL, "ether_aton"); | |
745 | break; | |
746 | } | |
747 | strlcpy(ifr.ifr_name, str1, IF_NAMESIZE); | |
748 | memcpy(LLADDR(dlp), ep, ETHER_ADDR_LEN); | |
749 | if (ioctl(s, (*cmd == 'a') ? SIOCADDMULTI : SIOCDELMULTI, | |
750 | &ifr) == -1) { | |
751 | warn("ioctl SIOCADDMULTI/SIOCDELMULTI"); | |
752 | printf("-1\n"); | |
753 | } else | |
754 | printf("ok\n"); | |
755 | break; | |
756 | } | |
757 | ||
758 | case 'm': | |
759 | fprintf(stderr, | |
760 | "warning: IFF_ALLMULTI cannot be set from userland " | |
761 | "in Darwin; command ignored.\n"); | |
762 | printf("-1\n"); | |
763 | break; | |
764 | ||
765 | #ifndef __APPLE__ | |
766 | case 'p': | |
767 | if (sscanf(line, "%s %u", ifr.ifr_name, &f) != 2) { | |
768 | printf("-1\n"); | |
769 | break; | |
770 | } | |
771 | if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) { | |
772 | warn("ioctl SIOCGIFFLAGS"); | |
773 | break; | |
774 | } | |
775 | flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16); | |
776 | if (f == 0) { | |
777 | flags &= ~IFF_PPROMISC; | |
778 | } else { | |
779 | flags |= IFF_PPROMISC; | |
780 | } | |
781 | ifr.ifr_flags = flags & 0xffff; | |
782 | ifr.ifr_flagshigh = flags >> 16; | |
783 | if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1) | |
784 | warn("ioctl SIOCGIFFLAGS"); | |
785 | else | |
786 | printf( "changed to 0x%08x\n", flags ); | |
787 | break; | |
788 | #endif /* __APPLE__ */ | |
789 | case '\n': | |
790 | break; | |
791 | default: | |
792 | printf("invalid command\n"); | |
793 | break; | |
794 | } | |
795 | } | |
796 | ||
797 | static void | |
798 | usage(void) | |
799 | { | |
800 | ||
801 | #ifndef HAS_SSM | |
802 | printf("j mcast-addr ifname - join IP multicast group\n"); | |
803 | printf("l mcast-addr ifname - leave IP multicast group\n"); | |
804 | #else /* HAS_SSM */ | |
805 | printf("j mcast-addr ifname [src-addr] - join IP multicast group\n"); | |
806 | printf("l mcast-addr ifname [src-addr] - leave IP multicast group\n"); | |
807 | printf( | |
808 | "i mcast-addr ifname n - set n include mode src filter\n"); | |
809 | printf( | |
810 | "e mcast-addr ifname n - set n exclude mode src filter\n"); | |
811 | printf("t mcast-addr ifname src-addr - allow traffic from src\n"); | |
812 | printf("b mcast-addr ifname src-addr - block traffic from src\n"); | |
813 | printf("g mcast-addr ifname n - get and show n src filters\n"); | |
814 | #endif | |
815 | printf("a ifname mac-addr - add link multicast filter\n"); | |
816 | printf("d ifname mac-addr - delete link multicast filter\n"); | |
817 | printf("m ifname 1/0 - set/clear ether allmulti flag\n"); | |
818 | #ifndef __APPLE__ | |
819 | printf("p ifname 1/0 - set/clear ether promisc flag\n"); | |
820 | #endif /* __APPLE__ */ | |
821 | printf("f filename - read command(s) from file\n"); | |
822 | printf("s seconds - sleep for some time\n"); | |
823 | printf("q - quit\n"); | |
824 | } | |
825 |