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 process_cmd(char *cmd
, int s
, int s6 __unused
, FILE *fp __unused
)
362 uint32_t fmode
, ifindex
;
364 int af
, error
, i
, level
, n
= 0, optname
;
367 #endif /* __APPLE__ */
370 su
.sa
.sa_family
= AF_UNSPEC
;
371 su2
.sa
.sa_family
= AF_UNSPEC
;
374 while (isblank(*++line
))
375 ; /* Skip whitespace. */
387 if ((sscanf(line
, "%d", &n
) != 1) || (n
< 1)) {
399 sscanf(line
, "%s %s %s", str1
, str2
, str3
);
400 ifindex
= parse_cmd_args(&su
, &su2
, str1
, str2
, str3
);
405 af
= su
.sa
.sa_family
;
409 error
= __ifindex_to_primary_ip(ifindex
, &ina
);
411 warn("primary_ip_lookup %s", str2
);
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
);
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
);
443 if (setsockopt(s
, level
, optname
, optval
,
448 warn("setsockopt %s", toptname
);
455 if (af
== AF_INET6
) {
456 level
= IPPROTO_IPV6
;
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
);
472 mr
.mr6
.ipv6mr_multiaddr
= su
.sin6
.sin6_addr
;
473 mr
.mr6
.ipv6mr_interface
= ifindex
;
474 optname
= (*cmd
== 'j') ?
477 toptname
= (*cmd
== 'j') ?
480 optval
= (void *)&mr
.mr6
;
481 optlen
= sizeof(mr
.mr6
);
485 if (setsockopt(s6
, level
, optname
, optval
,
490 warn("setsockopt %s", toptname
);
500 * Set the socket to include or exclude filter mode, and
501 * add some sources to the filterlist, using the full-state API.
505 sockunion_t sources
[MAX_ADDRS
];
506 struct addrinfo hints
;
507 struct addrinfo
*res
;
512 fmode
= (*cmd
== 'i') ? MCAST_INCLUDE
: MCAST_EXCLUDE
;
513 if ((sscanf(line
, "%s %s %d", str1
, str2
, &n
)) != 3) {
518 ifindex
= parse_cmd_args(&su
, NULL
, str1
, str2
, NULL
);
519 if (ifindex
== 0 || n
< 0 || n
> MAX_ADDRS
) {
523 af
= su
.sa
.sa_family
;
525 memset(&hints
, 0, sizeof(struct addrinfo
));
526 hints
.ai_flags
= AI_NUMERICHOST
;
527 hints
.ai_family
= af
;
528 hints
.ai_socktype
= SOCK_DGRAM
;
530 for (i
= 0; i
< n
; i
++) {
531 sockunion_t
*psu
= (sockunion_t
*)&sources
[i
];
533 * Trim trailing whitespace, as getaddrinfo()
534 * can't cope with it.
536 fgets(str1
, sizeof(str1
), fp
);
537 cp
= strchr(str1
, '\n');
542 error
= getaddrinfo(str1
, "0", &hints
, &res
);
547 memset(psu
, 0, sizeof(sockunion_t
));
548 af1
= res
->ai_family
;
550 memcpy(psu
, res
->ai_addr
, res
->ai_addrlen
);
557 warnx("getaddrinfo: %s", gai_strerror(error
));
561 if (setsourcefilter(af2sock(af
, s
, s6
), ifindex
,
562 &su
.sa
, su
.sa
.sa_len
, fmode
, n
, &sources
[0].ss
) != 0)
563 warn("setsourcefilter");
569 * Allow or block traffic from a source, using the
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
) {
582 af
= su
.sa
.sa_family
;
584 /* First determine our current filter mode. */
586 if (getsourcefilter(af2sock(af
, s
, s6
), ifindex
,
587 &su
.sa
, su
.sa
.sa_len
, &fmode
, (uint32_t *)&n
, NULL
) != 0) {
588 warn("getsourcefilter");
594 error
= __ifindex_to_primary_ip(ifindex
, &ina
);
596 warn("primary_ip_lookup %s", str2
);
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') ?
611 toptname
= (*cmd
== 't') ?
612 "IP_UNBLOCK_SOURCE" :
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";
623 if (setsockopt(s
, level
, optname
, optval
,
628 warn("setsockopt %s", toptname
);
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
:
645 toptname
= (*cmd
== 't') ?
646 "MCAST_UNBLOCK_SOURCE" :
647 "MCAST_BLOCK_SOURCE";
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";
657 optval
= (void *)&mr
.gr
;
658 optlen
= sizeof(mr
.gr
);
659 if (setsockopt(s6
, level
, optname
, optval
,
664 warn("setsockopt %s", toptname
);
673 sockunion_t sources
[MAX_ADDRS
];
674 char addrbuf
[NI_MAXHOST
];
677 if ((sscanf(line
, "%s %s %d", str1
, str2
, &nreqsrc
)) != 3) {
681 ifindex
= parse_cmd_args(&su
, NULL
, str1
, str2
, NULL
);
682 if (ifindex
== 0 || (n
< 0 || n
> MAX_ADDRS
)) {
687 af
= su
.sa
.sa_family
;
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");
696 printf("%s\n", (fmode
== MCAST_INCLUDE
) ? "include" :
698 printf("%d\n", nsrc
);
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
]);
706 fprintf(stderr
, "\nend hexdump\n");
708 qsort(sources
, nsrc
, sizeof (sockunion_t
), su_cmp
);
709 for (i
= 0; i
< nsrc
; i
++) {
710 sockunion_t
*psu
= (sockunion_t
*)&sources
[i
];
712 error
= getnameinfo(&psu
->sa
, psu
->sa
.sa_len
,
713 addrbuf
, sizeof(addrbuf
), NULL
, 0,
716 warnx("getnameinfo: %s", gai_strerror(error
));
718 printf("%s\n", addrbuf
);
723 /* link-layer stuff follows. */
727 struct sockaddr_dl
*dlp
;
728 struct ether_addr
*ep
;
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
;
736 dlp
->sdl_alen
= ETHER_ADDR_LEN
;
738 if (sscanf(line
, "%s %s", str1
, str2
) != 2) {
739 warnc(EINVAL
, "sscanf");
742 ep
= ether_aton(str2
);
744 warnc(EINVAL
, "ether_aton");
747 strlcpy(ifr
.ifr_name
, str1
, IF_NAMESIZE
);
748 memcpy(LLADDR(dlp
), ep
, ETHER_ADDR_LEN
);
749 if (ioctl(s
, (*cmd
== 'a') ? SIOCADDMULTI
: SIOCDELMULTI
,
751 warn("ioctl SIOCADDMULTI/SIOCDELMULTI");
760 "warning: IFF_ALLMULTI cannot be set from userland "
761 "in Darwin; command ignored.\n");
767 if (sscanf(line
, "%s %u", ifr
.ifr_name
, &f
) != 2) {
771 if (ioctl(s
, SIOCGIFFLAGS
, &ifr
) == -1) {
772 warn("ioctl SIOCGIFFLAGS");
775 flags
= (ifr
.ifr_flags
& 0xffff) | (ifr
.ifr_flagshigh
<< 16);
777 flags
&= ~IFF_PPROMISC
;
779 flags
|= IFF_PPROMISC
;
781 ifr
.ifr_flags
= flags
& 0xffff;
782 ifr
.ifr_flagshigh
= flags
>> 16;
783 if (ioctl(s
, SIOCSIFFLAGS
, &ifr
) == -1)
784 warn("ioctl SIOCGIFFLAGS");
786 printf( "changed to 0x%08x\n", flags
);
788 #endif /* __APPLE__ */
792 printf("invalid command\n");
802 printf("j mcast-addr ifname - join IP multicast group\n");
803 printf("l mcast-addr ifname - leave IP multicast group\n");
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");
808 "i mcast-addr ifname n - set n include mode src filter\n");
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");
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");
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");