2 * Copyright (c) 2007-2009 Bruce Simpson.
3 * Copyright (c) 2000 Wilbert De Graaf.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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
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.
40 #include <sys/cdefs.h>
42 #include <sys/types.h>
43 #include <sys/param.h>
44 #include <sys/errno.h>
45 #include <sys/socket.h>
47 #include <sys/ioctl.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>
57 #include <netinet/in.h>
58 #include <netinet/ip6.h>
70 #include <arpa/inet.h>
74 #ifdef IP_ADD_SOURCE_MEMBERSHIP
79 struct sockaddr_storage ss
;
81 struct sockaddr_dl sdl
;
82 struct sockaddr_in sin
;
84 struct sockaddr_in6 sin6
;
87 typedef union sockunion sockunion_t
;
92 struct ip_mreq_source mrs
;
97 struct group_source_req gr
;
101 typedef union mrequnion mrequnion_t
;
105 #define LINE_LENGTH 80
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);
116 * Ordering predicate for qsort().
119 su_cmp(const void *a
, const void *b
)
121 const sockunion_t
*sua
= (const sockunion_t
*)a
;
122 const sockunion_t
*sub
= (const sockunion_t
*)b
;
124 assert(sua
->sa
.sa_family
== sub
->sa
.sa_family
);
126 switch (sua
->sa
.sa_family
) {
128 return ((int)(sua
->sin
.sin_addr
.s_addr
-
129 sub
->sin
.sin_addr
.s_addr
));
133 return (memcmp(&sua
->sin6
.sin6_addr
, &sub
->sin6
.sin6_addr
,
134 sizeof(struct in6_addr
)));
141 assert(sua
->sa
.sa_len
== sub
->sa
.sa_len
);
142 return (memcmp(sua
, sub
, sua
->sa
.sa_len
));
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.
152 __ifindex_to_primary_ip(const uint32_t ifindex
, struct in_addr
*pina
)
154 char ifname
[IFNAMSIZ
];
156 struct ifaddrs
*ifaddrs
;
160 assert(ifindex
!= 0);
163 if (if_indextoname(ifindex
, ifname
) == NULL
)
165 if (getifaddrs(&ifaddrs
) < 0)
169 * Find the ifaddr entry corresponding to the interface name,
170 * and return the first matching IPv4 address.
173 for (ifa
= ifaddrs
; ifa
!= NULL
; ifa
= ifa
->ifa_next
) {
174 if (strcmp(ifa
->ifa_name
, ifname
) != 0)
176 psu
= (sockunion_t
*)ifa
->ifa_addr
;
177 if (psu
&& psu
->sa
.sa_family
== AF_INET
) {
179 memcpy(pina
, &psu
->sin
.sin_addr
,
180 sizeof(struct in_addr
));
186 errno
= EADDRNOTAVAIL
; /* XXX */
188 freeifaddrs(ifaddrs
);
193 main(int argc
, char **argv
)
195 char line
[LINE_LENGTH
];
201 s
= socket(AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
203 err(1, "can't open IPv4 socket");
205 s6
= socket(AF_INET6
, SOCK_DGRAM
, IPPROTO_UDP
);
207 err(1, "can't open IPv6 socket");
211 if (isatty(STDIN_FILENO
)) {
212 printf("multicast membership test program; "
213 "enter ? for list of commands\n");
216 if (fgets(line
, sizeof(line
), stdin
) != NULL
) {
218 process_cmd(line
, s
, s6
, stdin
);
220 /* Get the filename */
221 for (i
= 1; isblank(line
[i
]); i
++);
222 if ((p
= (char*)strchr(line
, '\n'))
225 process_file(&line
[i
], s
, s6
);
228 } while (!feof(stdin
));
230 for (i
= 1; i
< argc
; i
++) {
231 process_file(argv
[i
], s
, s6
);
244 process_file(char *fname
, int s
, int s6
)
250 fp
= fopen(fname
, "r");
256 /* Skip comments and empty lines. */
257 while (fgets(line
, sizeof(line
), fp
) != NULL
) {
259 while (isblank(*lineptr
))
261 if (*lineptr
!= '#' && *lineptr
!= '\n')
262 process_cmd(lineptr
, s
, s6
, fp
);
269 * Parse join/leave/allow/block arguments, given:
270 * str1: group (as AF_INET or AF_INET6 printable)
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.
277 parse_cmd_args(sockunion_t
*psu
, sockunion_t
*psu2
,
278 const char *str1
, const char *str2
, const char *str3
)
280 struct addrinfo hints
;
281 struct addrinfo
*res
;
286 assert(str1
!= NULL
);
287 assert(str2
!= NULL
);
291 ifindex
= if_nametoindex(str2
);
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
;
300 memset(psu
, 0, sizeof(sockunion_t
));
301 psu
->sa
.sa_family
= AF_UNSPEC
;
303 error
= getaddrinfo(str1
, "0", &hints
, &res
);
305 warnx("getaddrinfo: %s", gai_strerror(error
));
310 memcpy(psu
, res
->ai_addr
, res
->ai_addrlen
);
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
;
318 /* look for following address family; str3 is *optional*. */
319 hints
.ai_family
= af
;
320 error
= getaddrinfo(str3
, "0", &hints
, &res
);
322 warnx("getaddrinfo: %s", gai_strerror(error
));
325 if (af
!= res
->ai_family
) {
326 errno
= EINVAL
; /* XXX */
329 memcpy(psu2
, res
->ai_addr
, res
->ai_addrlen
);
338 af2sock(const int af
, int s
, int s6
)
351 af2socklen(const int af
)
355 return (sizeof(struct sockaddr_in
));
358 return (sizeof(struct sockaddr_in6
));
364 process_cmd(char *cmd
, int s
, int s6 __unused
, FILE *fp __unused
)
375 uint32_t fmode
, ifindex
;
377 int af
, error
, i
, level
, n
, optname
;
380 #endif /* __APPLE__ */
383 su
.sa
.sa_family
= AF_UNSPEC
;
384 su2
.sa
.sa_family
= AF_UNSPEC
;
387 while (isblank(*++line
))
388 ; /* Skip whitespace. */
400 if ((sscanf(line
, "%d", &n
) != 1) || (n
< 1)) {
412 sscanf(line
, "%s %s %s", str1
, str2
, str3
);
413 ifindex
= parse_cmd_args(&su
, &su2
, str1
, str2
, str3
);
418 af
= su
.sa
.sa_family
;
422 error
= __ifindex_to_primary_ip(ifindex
, &ina
);
424 warn("primary_ip_lookup %s", str2
);
431 if (su2
.sa
.sa_family
!= AF_UNSPEC
) {
432 mr
.mrs
.imr_multiaddr
= su
.sin
.sin_addr
;
433 mr
.mrs
.imr_sourceaddr
= su2
.sin
.sin_addr
;
434 mr
.mrs
.imr_interface
= ina
;
435 optname
= (*cmd
== 'j') ?
436 IP_ADD_SOURCE_MEMBERSHIP
:
437 IP_DROP_SOURCE_MEMBERSHIP
;
438 toptname
= (*cmd
== 'j') ?
439 "IP_ADD_SOURCE_MEMBERSHIP" :
440 "IP_DROP_SOURCE_MEMBERSHIP";
441 optval
= (void *)&mr
.mrs
;
442 optlen
= sizeof(mr
.mrs
);
445 mr
.mr
.imr_multiaddr
= su
.sin
.sin_addr
;
446 mr
.mr
.imr_interface
= ina
;
447 optname
= (*cmd
== 'j') ?
448 IP_ADD_MEMBERSHIP
: IP_DROP_MEMBERSHIP
;
449 toptname
= (*cmd
== 'j') ?
450 "IP_ADD_MEMBERSHIP" : "IP_DROP_MEMBERSHIP";
451 optval
= (void *)&mr
.mr
;
452 optlen
= sizeof(mr
.mr
);
456 if (setsockopt(s
, level
, optname
, optval
,
461 warn("setsockopt %s", toptname
);
468 if (af
== AF_INET6
) {
469 level
= IPPROTO_IPV6
;
471 if (su2
.sa
.sa_family
!= AF_UNSPEC
) {
472 mr
.gr
.gsr_interface
= ifindex
;
473 mr
.gr
.gsr_group
= su
.ss
;
474 mr
.gr
.gsr_source
= su2
.ss
;
475 optname
= (*cmd
== 'j') ?
476 MCAST_JOIN_SOURCE_GROUP
:
477 MCAST_LEAVE_SOURCE_GROUP
;
478 toptname
= (*cmd
== 'j') ?
479 "MCAST_JOIN_SOURCE_GROUP":
480 "MCAST_LEAVE_SOURCE_GROUP";
481 optval
= (void *)&mr
.gr
;
482 optlen
= sizeof(mr
.gr
);
485 mr
.mr6
.ipv6mr_multiaddr
= su
.sin6
.sin6_addr
;
486 mr
.mr6
.ipv6mr_interface
= ifindex
;
487 optname
= (*cmd
== 'j') ?
490 toptname
= (*cmd
== 'j') ?
493 optval
= (void *)&mr
.mr6
;
494 optlen
= sizeof(mr
.mr6
);
498 if (setsockopt(s6
, level
, optname
, optval
,
503 warn("setsockopt %s", toptname
);
513 * Set the socket to include or exclude filter mode, and
514 * add some sources to the filterlist, using the full-state API.
518 sockunion_t sources
[MAX_ADDRS
];
519 struct addrinfo hints
;
520 struct addrinfo
*res
;
525 fmode
= (*cmd
== 'i') ? MCAST_INCLUDE
: MCAST_EXCLUDE
;
526 if ((sscanf(line
, "%s %s %d", str1
, str2
, &n
)) != 3) {
531 ifindex
= parse_cmd_args(&su
, NULL
, str1
, str2
, NULL
);
532 if (ifindex
== 0 || n
< 0 || n
> MAX_ADDRS
) {
536 af
= su
.sa
.sa_family
;
538 memset(&hints
, 0, sizeof(struct addrinfo
));
539 hints
.ai_flags
= AI_NUMERICHOST
;
540 hints
.ai_family
= af
;
541 hints
.ai_socktype
= SOCK_DGRAM
;
543 for (i
= 0; i
< n
; i
++) {
544 sockunion_t
*psu
= (sockunion_t
*)&sources
[i
];
546 * Trim trailing whitespace, as getaddrinfo()
547 * can't cope with it.
549 fgets(str1
, sizeof(str1
), fp
);
550 cp
= strchr(str1
, '\n');
555 error
= getaddrinfo(str1
, "0", &hints
, &res
);
560 memset(psu
, 0, sizeof(sockunion_t
));
561 af1
= res
->ai_family
;
563 memcpy(psu
, res
->ai_addr
, res
->ai_addrlen
);
570 warnx("getaddrinfo: %s", gai_strerror(error
));
574 if (setsourcefilter(af2sock(af
, s
, s6
), ifindex
,
575 &su
.sa
, su
.sa
.sa_len
, fmode
, n
, &sources
[0].ss
) != 0)
576 warn("setsourcefilter");
582 * Allow or block traffic from a source, using the
589 sscanf(line
, "%s %s %s", str1
, str2
, str3
);
590 ifindex
= parse_cmd_args(&su
, &su2
, str1
, str2
, str3
);
591 if (ifindex
== 0 || su2
.sa
.sa_family
== AF_UNSPEC
) {
595 af
= su
.sa
.sa_family
;
597 /* First determine our current filter mode. */
599 if (getsourcefilter(af2sock(af
, s
, s6
), ifindex
,
600 &su
.sa
, su
.sa
.sa_len
, &fmode
, (uint32_t *)&n
, NULL
) != 0) {
601 warn("getsourcefilter");
607 error
= __ifindex_to_primary_ip(ifindex
, &ina
);
609 warn("primary_ip_lookup %s", str2
);
614 optval
= (void *)&mr
.mrs
;
615 optlen
= sizeof(mr
.mrs
);
616 mr
.mrs
.imr_multiaddr
= su
.sin
.sin_addr
;
617 mr
.mrs
.imr_sourceaddr
= su2
.sin
.sin_addr
;
618 mr
.mrs
.imr_interface
= ina
;
619 if (fmode
== MCAST_EXCLUDE
) {
620 /* Any-source mode socket membership. */
621 optname
= (*cmd
== 't') ?
624 toptname
= (*cmd
== 't') ?
625 "IP_UNBLOCK_SOURCE" :
628 /* Source-specific mode socket membership. */
629 optname
= (*cmd
== 't') ?
630 IP_ADD_SOURCE_MEMBERSHIP
:
631 IP_DROP_SOURCE_MEMBERSHIP
;
632 toptname
= (*cmd
== 't') ?
633 "IP_ADD_SOURCE_MEMBERSHIP" :
634 "IP_DROP_SOURCE_MEMBERSHIP";
636 if (setsockopt(s
, level
, optname
, optval
,
641 warn("setsockopt %s", toptname
);
648 if (af
== AF_INET6
) {
649 level
= IPPROTO_IPV6
;
650 mr
.gr
.gsr_interface
= ifindex
;
651 mr
.gr
.gsr_group
= su
.ss
;
652 mr
.gr
.gsr_source
= su2
.ss
;
653 if (fmode
== MCAST_EXCLUDE
) {
654 /* Any-source mode socket membership. */
655 optname
= (*cmd
== 't') ?
656 MCAST_UNBLOCK_SOURCE
:
658 toptname
= (*cmd
== 't') ?
659 "MCAST_UNBLOCK_SOURCE" :
660 "MCAST_BLOCK_SOURCE";
662 /* Source-specific mode socket membership. */
663 optname
= (*cmd
== 't') ?
664 MCAST_JOIN_SOURCE_GROUP
:
665 MCAST_LEAVE_SOURCE_GROUP
;
666 toptname
= (*cmd
== 't') ?
667 "MCAST_JOIN_SOURCE_GROUP":
668 "MCAST_LEAVE_SOURCE_GROUP";
670 optval
= (void *)&mr
.gr
;
671 optlen
= sizeof(mr
.gr
);
672 if (setsockopt(s6
, level
, optname
, optval
,
677 warn("setsockopt %s", toptname
);
686 sockunion_t sources
[MAX_ADDRS
];
687 char addrbuf
[NI_MAXHOST
];
690 if ((sscanf(line
, "%s %s %d", str1
, str2
, &nreqsrc
)) != 3) {
694 ifindex
= parse_cmd_args(&su
, NULL
, str1
, str2
, NULL
);
695 if (ifindex
== 0 || (n
< 0 || n
> MAX_ADDRS
)) {
700 af
= su
.sa
.sa_family
;
702 if (getsourcefilter(af2sock(af
, s
, s6
), ifindex
, &su
.sa
,
703 su
.sa
.sa_len
, &fmode
, (uint32_t *)&nsrc
,
704 &sources
[0].ss
) != 0) {
705 warn("getsourcefilter");
709 printf("%s\n", (fmode
== MCAST_INCLUDE
) ? "include" :
711 printf("%d\n", nsrc
);
713 nsrc
= MIN(nreqsrc
, nsrc
);
714 fprintf(stderr
, "hexdump of sources:\n");
715 uint8_t *bp
= (uint8_t *)&sources
[0];
716 for (i
= 0; i
< (nsrc
* sizeof(sources
[0])); i
++) {
717 fprintf(stderr
, "%02x", bp
[i
]);
719 fprintf(stderr
, "\nend hexdump\n");
721 qsort(sources
, nsrc
, sizeof (sockunion_t
), su_cmp
);
722 for (i
= 0; i
< nsrc
; i
++) {
723 sockunion_t
*psu
= (sockunion_t
*)&sources
[i
];
725 error
= getnameinfo(&psu
->sa
, psu
->sa
.sa_len
,
726 addrbuf
, sizeof(addrbuf
), NULL
, 0,
729 warnx("getnameinfo: %s", gai_strerror(error
));
731 printf("%s\n", addrbuf
);
736 /* link-layer stuff follows. */
740 struct sockaddr_dl
*dlp
;
741 struct ether_addr
*ep
;
743 memset(&ifr
, 0, sizeof(struct ifreq
));
744 dlp
= (struct sockaddr_dl
*)&ifr
.ifr_addr
;
745 dlp
->sdl_len
= sizeof(struct sockaddr_dl
);
746 dlp
->sdl_family
= AF_LINK
;
749 dlp
->sdl_alen
= ETHER_ADDR_LEN
;
751 if (sscanf(line
, "%s %s", str1
, str2
) != 2) {
752 warnc(EINVAL
, "sscanf");
755 ep
= ether_aton(str2
);
757 warnc(EINVAL
, "ether_aton");
760 strlcpy(ifr
.ifr_name
, str1
, IF_NAMESIZE
);
761 memcpy(LLADDR(dlp
), ep
, ETHER_ADDR_LEN
);
762 if (ioctl(s
, (*cmd
== 'a') ? SIOCADDMULTI
: SIOCDELMULTI
,
764 warn("ioctl SIOCADDMULTI/SIOCDELMULTI");
773 "warning: IFF_ALLMULTI cannot be set from userland "
774 "in Darwin; command ignored.\n");
780 if (sscanf(line
, "%s %u", ifr
.ifr_name
, &f
) != 2) {
784 if (ioctl(s
, SIOCGIFFLAGS
, &ifr
) == -1) {
785 warn("ioctl SIOCGIFFLAGS");
788 flags
= (ifr
.ifr_flags
& 0xffff) | (ifr
.ifr_flagshigh
<< 16);
790 flags
&= ~IFF_PPROMISC
;
792 flags
|= IFF_PPROMISC
;
794 ifr
.ifr_flags
= flags
& 0xffff;
795 ifr
.ifr_flagshigh
= flags
>> 16;
796 if (ioctl(s
, SIOCSIFFLAGS
, &ifr
) == -1)
797 warn("ioctl SIOCGIFFLAGS");
799 printf( "changed to 0x%08x\n", flags
);
801 #endif /* __APPLE__ */
805 printf("invalid command\n");
815 printf("j mcast-addr ifname - join IP multicast group\n");
816 printf("l mcast-addr ifname - leave IP multicast group\n");
818 printf("j mcast-addr ifname [src-addr] - join IP multicast group\n");
819 printf("l mcast-addr ifname [src-addr] - leave IP multicast group\n");
821 "i mcast-addr ifname n - set n include mode src filter\n");
823 "e mcast-addr ifname n - set n exclude mode src filter\n");
824 printf("t mcast-addr ifname src-addr - allow traffic from src\n");
825 printf("b mcast-addr ifname src-addr - block traffic from src\n");
826 printf("g mcast-addr ifname n - get and show n src filters\n");
828 printf("a ifname mac-addr - add link multicast filter\n");
829 printf("d ifname mac-addr - delete link multicast filter\n");
830 printf("m ifname 1/0 - set/clear ether allmulti flag\n");
832 printf("p ifname 1/0 - set/clear ether promisc flag\n");
833 #endif /* __APPLE__ */
834 printf("f filename - read command(s) from file\n");
835 printf("s seconds - sleep for some time\n");
836 printf("q - quit\n");