1 /*-
2  * Copyright (c) 2007 Bruce M. Simpson
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 /*
28  * Regression test utility for RFC 3678 Advanced Multicast API in FreeBSD.
29  *
30  * TODO: Test the SSM paths.
31  * TODO: Support INET6. The code has been written to facilitate this later.
32  * TODO: Merge multicast socket option tests from ipsockopt.
33  */
34 
35 #include <sys/param.h>
36 #include <sys/types.h>
37 #include <sys/ioctl.h>
38 #include <sys/socket.h>
39 
40 #include <net/if.h>
41 #include <net/if_dl.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
44 #include <netdb.h>
45 
46 #include <assert.h>
47 #include <err.h>
48 #include <errno.h>
49 #include <getopt.h>
50 #include <libgen.h>
51 #include <pwd.h>
52 #include <setjmp.h>
53 #include <signal.h>
54 #include <stddef.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <sysexits.h>
59 #include <time.h>
60 #include <unistd.h>
61 
62 #ifndef __SOCKUNION_DECLARED
63 union sockunion {
64 	struct sockaddr_storage	ss;
65 	struct sockaddr		sa;
66 	struct sockaddr_dl	sdl;
67 	struct sockaddr_in	sin;
68 #ifdef INET6
69 	struct sockaddr_in6	sin6;
70 #endif
71 };
72 typedef union sockunion sockunion_t;
73 #define __SOCKUNION_DECLARED
74 #endif /* __SOCKUNION_DECLARED */
75 
76 #define ADDRBUF_LEN		16
77 #define DEFAULT_GROUP_STR	"238.1.1.0"
78 #define DEFAULT_IFNAME		"lo0"
79 #define DEFAULT_IFADDR_STR	"127.0.0.1"
80 #define DEFAULT_PORT		6698
81 #define DEFAULT_TIMEOUT		0		/* don't wait for traffic */
82 #define RXBUFSIZE		2048
83 
84 static sockunion_t	 basegroup;
85 static const char	*basegroup_str = NULL;
86 static int		 dobindaddr = 0;
87 static int		 dodebug = 1;
88 static int		 doipv4 = 0;
89 static int		 domiscopts = 0;
90 static int		 dorandom = 0;
91 static int		 doreuseport = 0;
92 static int		 dossm = 0;
93 static int		 dossf = 0;
94 static int		 doverbose = 0;
95 static sockunion_t	 ifaddr;
96 static const char	*ifaddr_str = NULL;
97 static uint32_t		 ifindex = 0;
98 static const char	*ifname = NULL;
99 struct in_addr		*ipv4_sources = NULL;
100 static jmp_buf		 jmpbuf;
101 static size_t		 nmcastgroups = IP_MAX_MEMBERSHIPS;
102 static size_t		 nmcastsources = 0;
103 static uint16_t		 portno = DEFAULT_PORT;
104 static char		*progname = NULL;
105 struct sockaddr_storage	*ss_sources = NULL;
106 static uint32_t		 timeout = 0;
107 
108 static int	do_asm_ipv4(void);
109 static int	do_asm_pim(void);
110 #ifdef notyet
111 static int	do_misc_opts(void);
112 #endif
113 static int	do_ssf_ipv4(void);
114 static int	do_ssf_pim(void);
115 static int	do_ssm_ipv4(void);
116 static int	do_ssm_pim(void);
117 static int	open_and_bind_socket(sockunion_t *);
118 static int	recv_loop_with_match(int, sockunion_t *, sockunion_t *);
119 static void	signal_handler(int);
120 static void	usage(void);
121 
122 /*
123  * Test the IPv4 set/getipv4sourcefilter() libc API functions.
124  * Build a single socket.
125  * Join a source group.
126  * Repeatedly change the source filters via setipv4sourcefilter.
127  * Read it back with getipv4sourcefilter up to IP_MAX_SOURCES
128  * and check for inconsistency.
129  */
130 static int
131 do_ssf_ipv4(void)
132 {
133 
134 	fprintf(stderr, "not yet implemented\n");
135 	return (0);
136 }
137 
138 /*
139  * Test the protocol-independent set/getsourcefilter() functions.
140  */
141 static int
142 do_ssf_pim(void)
143 {
144 
145 	fprintf(stderr, "not yet implemented\n");
146 	return (0);
147 }
148 
149 /*
150  * Test the IPv4 ASM API.
151  * Repeatedly join, block sources, unblock and leave groups.
152  */
153 static int
154 do_asm_ipv4(void)
155 {
156 	int			 error;
157 	char			 gaddrbuf[ADDRBUF_LEN];
158 	int			 i;
159 	sockunion_t		 laddr;
160 	struct ip_mreq		 mreq;
161 	struct ip_mreq_source	 mreqs;
162 	in_addr_t		 ngroupbase;
163 	char			 saddrbuf[ADDRBUF_LEN];
164 	int			 sock;
165 	sockunion_t		 tmpgroup;
166 	sockunion_t		 tmpsource;
167 
168 	memset(&mreq, 0, sizeof(struct ip_mreq));
169 	memset(&mreqs, 0, sizeof(struct ip_mreq_source));
170 	memset(&laddr, 0, sizeof(sockunion_t));
171 
172 	if (dobindaddr) {
173 		laddr = ifaddr;
174 	} else {
175 		laddr.sin.sin_family = AF_INET;
176 		laddr.sin.sin_len = sizeof(struct sockaddr_in);
177 		laddr.sin.sin_addr.s_addr = INADDR_ANY;
178 	}
179 	laddr.sin.sin_port = htons(portno);
180 
181 	tmpgroup = basegroup;
182 	ngroupbase = ntohl(basegroup.sin.sin_addr.s_addr) + 1;	/* XXX */
183 	tmpgroup.sin.sin_addr.s_addr = htonl(ngroupbase);
184 
185 	sock = open_and_bind_socket(&laddr);
186 	if (sock == -1)
187 		return (EX_OSERR);
188 
189 	for (i = 0; i < (signed)nmcastgroups; i++) {
190 		mreq.imr_multiaddr.s_addr = htonl((ngroupbase + i));
191 		mreq.imr_interface = ifaddr.sin.sin_addr;
192 		if (doverbose) {
193 			inet_ntop(AF_INET, &mreq.imr_multiaddr, gaddrbuf,
194 			    sizeof(gaddrbuf));
195 			fprintf(stderr, "IP_ADD_MEMBERSHIP %s %s\n",
196 			    gaddrbuf, inet_ntoa(mreq.imr_interface));
197 		}
198 		error = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
199 		    &mreq, sizeof(struct ip_mreq));
200 		if (error < 0) {
201 			warn("setsockopt IP_ADD_MEMBERSHIP");
202 			close(sock);
203 			return (EX_OSERR);
204 		}
205 	}
206 
207 	/*
208 	 * If no test sources auto-generated or specified on command line,
209 	 * skip source filter portion of ASM test.
210 	*/
211 	if (nmcastsources == 0)
212 		goto skipsources;
213 
214 	/*
215 	 * Begin blocking sources on the first group chosen.
216 	 */
217 	for (i = 0; i < (signed)nmcastsources; i++) {
218 		mreqs.imr_multiaddr = tmpgroup.sin.sin_addr;
219 		mreqs.imr_interface = ifaddr.sin.sin_addr;
220 		mreqs.imr_sourceaddr = ipv4_sources[i];
221 		if (doverbose) {
222 			inet_ntop(AF_INET, &mreqs.imr_multiaddr, gaddrbuf,
223 			    sizeof(gaddrbuf));
224 			inet_ntop(AF_INET, &mreqs.imr_sourceaddr, saddrbuf,
225 			    sizeof(saddrbuf));
226 			fprintf(stderr, "IP_BLOCK_SOURCE %s %s %s\n",
227 			    gaddrbuf, inet_ntoa(mreqs.imr_interface),
228 			    saddrbuf);
229 		}
230 		error = setsockopt(sock, IPPROTO_IP, IP_BLOCK_SOURCE, &mreqs,
231 		    sizeof(struct ip_mreq_source));
232 		if (error < 0) {
233 			warn("setsockopt IP_BLOCK_SOURCE");
234 			close(sock);
235 			return (EX_OSERR);
236 		}
237 	}
238 
239 	/*
240 	 * Choose the first group and source for a match.
241 	 * Enter the I/O loop.
242 	 */
243 	memset(&tmpsource, 0, sizeof(sockunion_t));
244 	tmpsource.sin.sin_family = AF_INET;
245 	tmpsource.sin.sin_len = sizeof(struct sockaddr_in);
246 	tmpsource.sin.sin_addr = ipv4_sources[0];
247 
248 	error = recv_loop_with_match(sock, &tmpgroup, &tmpsource);
249 
250 	/*
251 	 * Unblock sources.
252 	 */
253 	for (i = nmcastsources-1; i >= 0; i--) {
254 		mreqs.imr_multiaddr = tmpgroup.sin.sin_addr;
255 		mreqs.imr_interface = ifaddr.sin.sin_addr;
256 		mreqs.imr_sourceaddr = ipv4_sources[i];
257 		if (doverbose) {
258 			inet_ntop(AF_INET, &mreqs.imr_multiaddr, gaddrbuf,
259 			    sizeof(gaddrbuf));
260 			inet_ntop(AF_INET, &mreqs.imr_sourceaddr, saddrbuf,
261 			    sizeof(saddrbuf));
262 			fprintf(stderr, "IP_UNBLOCK_SOURCE %s %s %s\n",
263 			    gaddrbuf, inet_ntoa(mreqs.imr_interface),
264 			    saddrbuf);
265 		}
266 		error = setsockopt(sock, IPPROTO_IP, IP_UNBLOCK_SOURCE, &mreqs,
267 		    sizeof(struct ip_mreq_source));
268 		if (error < 0) {
269 			warn("setsockopt IP_UNBLOCK_SOURCE");
270 			close(sock);
271 			return (EX_OSERR);
272 		}
273 	}
274 
275 skipsources:
276 	/*
277 	 * Leave groups.
278 	 */
279 	for (i = nmcastgroups-1; i >= 0; i--) {
280 		mreq.imr_multiaddr.s_addr = htonl((ngroupbase + i));
281 		mreq.imr_interface = ifaddr.sin.sin_addr;
282 		if (doverbose) {
283 			inet_ntop(AF_INET, &mreq.imr_multiaddr, gaddrbuf,
284 			    sizeof(gaddrbuf));
285 			fprintf(stderr, "IP_DROP_MEMBERSHIP %s %s\n",
286 			    gaddrbuf, inet_ntoa(mreq.imr_interface));
287 		}
288 		error = setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP,
289 		    &mreq, sizeof(struct ip_mreq));
290 		if (error < 0) {
291 			warn("setsockopt IP_DROP_MEMBERSHIP");
292 			close(sock);
293 			return (EX_OSERR);
294 		}
295 	}
296 
297 	return (0);
298 }
299 
300 static int
301 do_asm_pim(void)
302 {
303 
304 	fprintf(stderr, "not yet implemented\n");
305 	return (0);
306 }
307 
308 #ifdef notyet
309 /*
310  * Test misceallaneous IPv4 options.
311  */
312 static int
313 do_misc_opts(void)
314 {
315 	int sock;
316 
317 	sock = open_and_bind_socket(NULL);
318 	if (sock == -1)
319 		return (EX_OSERR);
320 	test_ip_uchar(sock, socktypename, IP_MULTICAST_TTL,
321 	    "IP_MULTICAST_TTL", 1);
322 	close(sock);
323 
324 	sock = open_and_bind_socket(NULL);
325 	if (sock == -1)
326 		return (EX_OSERR);
327 	test_ip_boolean(sock, socktypename, IP_MULTICAST_LOOP,
328 	    "IP_MULTICAST_LOOP", 1, BOOLEAN_ANYONE);
329 	close(sock);
330 
331 	return (0);
332 }
333 #endif
334 
335 /*
336  * Test the IPv4 SSM API.
337  */
338 static int
339 do_ssm_ipv4(void)
340 {
341 
342 	fprintf(stderr, "not yet implemented\n");
343 	return (0);
344 }
345 
346 /*
347  * Test the protocol-independent SSM API with IPv4 addresses.
348  */
349 static int
350 do_ssm_pim(void)
351 {
352 
353 	fprintf(stderr, "not yet implemented\n");
354 	return (0);
355 }
356 
357 int
358 main(int argc, char *argv[])
359 {
360 	struct addrinfo		 aih;
361 	struct addrinfo		*aip;
362 	int			 ch;
363 	int			 error;
364 	int			 exitval;
365 	size_t			 i;
366 	struct in_addr		*pina;
367 	struct sockaddr_storage	*pbss;
368 
369 	ifname = DEFAULT_IFNAME;
370 	ifaddr_str = DEFAULT_IFADDR_STR;
371 	basegroup_str = DEFAULT_GROUP_STR;
372 	ifname = DEFAULT_IFNAME;
373 	portno = DEFAULT_PORT;
374 	basegroup.ss.ss_family = AF_UNSPEC;
375 	ifaddr.ss.ss_family = AF_UNSPEC;
376 
377 	progname = basename(argv[0]);
378 	while ((ch = getopt(argc, argv, "4bg:i:I:mM:p:rsS:tT:v")) != -1) {
379 		switch (ch) {
380 		case '4':
381 			doipv4 = 1;
382 			break;
383 		case 'b':
384 			dobindaddr = 1;
385 			break;
386 		case 'g':
387 			basegroup_str = optarg;
388 			break;
389 		case 'i':
390 			ifname = optarg;
391 			break;
392 		case 'I':
393 			ifaddr_str = optarg;
394 			break;
395 		case 'm':
396 			usage();	/* notyet */
397 			/*NOTREACHED*/
398 			domiscopts = 1;
399 			break;
400 		case 'M':
401 			nmcastgroups = atoi(optarg);
402 			break;
403 		case 'p':
404 			portno = atoi(optarg);
405 			break;
406 		case 'r':
407 			doreuseport = 1;
408 			break;
409 		case 'S':
410 			nmcastsources = atoi(optarg);
411 			break;
412 		case 's':
413 			dossm = 1;
414 			break;
415 		case 't':
416 			dossf = 1;
417 			break;
418 		case 'T':
419 			timeout = atoi(optarg);
420 			break;
421 		case 'v':
422 			doverbose = 1;
423 			break;
424 		default:
425 			usage();
426 			break;
427 			/*NOTREACHED*/
428 		}
429 	}
430 	argc -= optind;
431 	argv += optind;
432 
433 	memset(&aih, 0, sizeof(struct addrinfo));
434 	aih.ai_flags = AI_NUMERICHOST | AI_PASSIVE;
435 	aih.ai_family = PF_INET;
436 	aih.ai_socktype = SOCK_DGRAM;
437 	aih.ai_protocol = IPPROTO_UDP;
438 
439 	/*
440 	 * Fill out base group.
441 	 */
442 	aip = NULL;
443 	error = getaddrinfo(basegroup_str, NULL, &aih, &aip);
444 	if (error != 0) {
445 		fprintf(stderr, "%s: getaddrinfo: %s\n", progname,
446 		    gai_strerror(error));
447 		exit(EX_USAGE);
448 	}
449 	memcpy(&basegroup, aip->ai_addr, aip->ai_addrlen);
450 	if (dodebug) {
451 		fprintf(stderr, "debug: gai thinks %s is %s\n",
452 		    basegroup_str, inet_ntoa(basegroup.sin.sin_addr));
453 	}
454 	freeaddrinfo(aip);
455 
456 	assert(basegroup.ss.ss_family == AF_INET);
457 
458 	/*
459 	 * If user specified interface as an address, and protocol
460 	 * specific APIs were selected, parse it.
461 	 * Otherwise, parse interface index from name if protocol
462 	 * independent APIs were selected (the default).
463 	 */
464 	if (doipv4) {
465 		if (ifaddr_str == NULL) {
466 			warnx("required argument missing: ifaddr");
467 			usage();
468 			/* NOTREACHED */
469 		}
470 		aip = NULL;
471 		error = getaddrinfo(ifaddr_str, NULL, &aih, &aip);
472 		if (error != 0) {
473 			fprintf(stderr, "%s: getaddrinfo: %s\n", progname,
474 			    gai_strerror(error));
475 			exit(EX_USAGE);
476 		}
477 		memcpy(&ifaddr, aip->ai_addr, aip->ai_addrlen);
478 		if (dodebug) {
479 			fprintf(stderr, "debug: gai thinks %s is %s\n",
480 			    ifaddr_str, inet_ntoa(ifaddr.sin.sin_addr));
481 		}
482 		freeaddrinfo(aip);
483 	}
484 
485 	if (!doipv4) {
486 		if (ifname == NULL) {
487 			warnx("required argument missing: ifname");
488 			usage();
489 			/* NOTREACHED */
490 		}
491 		ifindex = if_nametoindex(ifname);
492 		if (ifindex == 0)
493 			err(EX_USAGE, "if_nametoindex");
494 	}
495 
496 	/*
497 	 * Introduce randomness into group base if specified.
498 	 */
499 	if (dorandom) {
500 		in_addr_t ngroupbase;
501 
502 		srandomdev();
503 		ngroupbase = ntohl(basegroup.sin.sin_addr.s_addr);
504 		ngroupbase |= ((random() % ((1 << 11) - 1)) << 16);
505 		basegroup.sin.sin_addr.s_addr = htonl(ngroupbase);
506 	}
507 
508 	if (argc > 0) {
509 		nmcastsources = argc;
510 		if (doipv4) {
511 			ipv4_sources = calloc(nmcastsources,
512 			    sizeof(struct in_addr));
513 			if (ipv4_sources == NULL) {
514 				exitval = EX_OSERR;
515 				goto out;
516 			}
517 		} else {
518 			ss_sources = calloc(nmcastsources,
519 			    sizeof(struct sockaddr_storage));
520 			if (ss_sources == NULL) {
521 				exitval = EX_OSERR;
522 				goto out;
523 			}
524 		}
525 	}
526 
527 	/*
528 	 * Parse source list, if any were specified on the command line.
529 	 */
530 	assert(aih.ai_family == PF_INET);
531 	pbss = ss_sources;
532 	pina = ipv4_sources;
533 	for (i = 0; i < (size_t)argc; i++) {
534 		aip = NULL;
535 		error = getaddrinfo(argv[i], NULL, &aih, &aip);
536 		if (error != 0) {
537 			fprintf(stderr, "getaddrinfo: %s\n",
538 			    gai_strerror(error));
539 			exitval = EX_USAGE;
540 			goto out;
541 		}
542 		if (doipv4) {
543 			struct sockaddr_in *sin =
544 			    (struct sockaddr_in *)aip->ai_addr;
545 			*pina++ = sin->sin_addr;
546 		} else {
547 			memcpy(pbss++, aip->ai_addr, aip->ai_addrlen);
548 		}
549 		freeaddrinfo(aip);
550 	}
551 
552 	/*
553 	 * Perform the regression tests which the user requested.
554 	 */
555 #ifdef notyet
556 	if (domiscopts) {
557 		exitval = do_misc_opts();
558 		if (exitval)
559 			goto out;
560 	}
561 #endif
562 	if (doipv4) {
563 		/* IPv4 protocol specific API tests */
564 		if (dossm) {
565 			/* Source-specific multicast */
566 			exitval = do_ssm_ipv4();
567 			if (exitval)
568 				goto out;
569 			if (dossf) {
570 				/* Do setipvsourcefilter() too */
571 				exitval = do_ssf_ipv4();
572 			}
573 		} else {
574 			/* Any-source multicast */
575 			exitval = do_asm_ipv4();
576 		}
577 	} else {
578 		/* Protocol independent API tests */
579 		if (dossm) {
580 			/* Source-specific multicast */
581 			exitval = do_ssm_pim();
582 			if (exitval)
583 				goto out;
584 			if (dossf) {
585 				/* Do setsourcefilter() too */
586 				exitval = do_ssf_pim();
587 			}
588 		} else {
589 			/* Any-source multicast */
590 			exitval = do_asm_pim();
591 		}
592 	}
593 
594 out:
595 	if (ipv4_sources != NULL)
596 		free(ipv4_sources);
597 
598 	if (ss_sources != NULL)
599 		free(ss_sources);
600 
601 	exit(exitval);
602 }
603 
604 static int
605 open_and_bind_socket(sockunion_t *bsu)
606 {
607 	int	 error, optval, sock;
608 
609 	sock = -1;
610 
611 	sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
612 	if (sock == -1) {
613 		warn("socket");
614 		return (-1);
615 	}
616 
617 	if (doreuseport) {
618 		optval = 1;
619 		if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &optval,
620 		    sizeof(optval)) < 0) {
621 			warn("setsockopt SO_REUSEPORT");
622 			close(sock);
623 			return (-1);
624 		}
625 	}
626 
627 	if (bsu != NULL) {
628 		error = bind(sock, &bsu->sa, bsu->sa.sa_len);
629 		if (error == -1) {
630 			warn("bind");
631 			close(sock);
632 			return (-1);
633 		}
634 	}
635 
636 	return (sock);
637 }
638 
639 /*
640  * Protocol-agnostic multicast I/O loop.
641  *
642  * Wait for 'timeout' seconds looking for traffic on group, so that manual
643  * or automated regression tests (possibly running on another host) have an
644  * opportunity to transmit within the group to test source filters.
645  *
646  * If the filter failed, this loop will report if we received traffic
647  * from the source we elected to monitor.
648  */
649 static int
650 recv_loop_with_match(int sock, sockunion_t *group, sockunion_t *source)
651 {
652 	int		 error;
653 	sockunion_t	 from;
654 	char		 groupname[NI_MAXHOST];
655 	ssize_t		 len;
656 	size_t		 npackets;
657 	int		 jmpretval;
658 	char		 rxbuf[RXBUFSIZE];
659 	char		 sourcename[NI_MAXHOST];
660 
661 	assert(source->sa.sa_family == AF_INET);
662 
663 	/*
664 	 * Return immediately if we don't need to wait for traffic.
665 	 */
666 	if (timeout == 0)
667 		return (0);
668 
669 	error = getnameinfo(&group->sa, group->sa.sa_len, groupname,
670 	    NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
671 	if (error) {
672 		fprintf(stderr, "getnameinfo: %s\n", gai_strerror(error));
673 		return (error);
674 	}
675 
676 	error = getnameinfo(&source->sa, source->sa.sa_len, sourcename,
677 	    NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
678 	if (error) {
679 		fprintf(stderr, "getnameinfo: %s\n", gai_strerror(error));
680 		return (error);
681 	}
682 
683 	fprintf(stdout,
684 	    "Waiting %d seconds for inbound traffic on group %s\n"
685 	    "Expecting no traffic from blocked source: %s\n",
686 	    (int)timeout, groupname, sourcename);
687 
688 	signal(SIGINT, signal_handler);
689 	signal(SIGALRM, signal_handler);
690 
691 	error = 0;
692 	npackets = 0;
693 	alarm(timeout);
694 	while (0 == (jmpretval = setjmp(jmpbuf))) {
695 		len = recvfrom(sock, rxbuf, RXBUFSIZE, 0, &from.sa,
696 		    (socklen_t *)&from.sa.sa_len);
697 		if (dodebug) {
698 			fprintf(stderr, "debug: packet received from %s\n",
699 			    inet_ntoa(from.sin.sin_addr));
700 		}
701 		if (source &&
702 		    source->sin.sin_addr.s_addr == from.sin.sin_addr.s_addr)
703 			break;
704 		npackets++;
705 	}
706 
707 	if (doverbose) {
708 		fprintf(stderr, "Number of datagrams received from "
709 		    "non-blocked sources: %d\n", (int)npackets);
710 	}
711 
712 	switch (jmpretval) {
713 	case SIGALRM:	/* ok */
714 		break;
715 	case SIGINT:	/* go bye bye */
716 		fprintf(stderr, "interrupted\n");
717 		error = 20;
718 		break;
719 	case 0:		/* Broke out of loop; saw a bad source. */
720 		fprintf(stderr, "FAIL: got packet from blocked source\n");
721 		error = EX_IOERR;
722 		break;
723 	default:
724 		warnx("recvfrom");
725 		error = EX_OSERR;
726 		break;
727 	}
728 
729 	signal(SIGINT, SIG_DFL);
730 	signal(SIGALRM, SIG_DFL);
731 
732 	return (error);
733 }
734 
735 static void
736 signal_handler(int signo)
737 {
738 
739 	longjmp(jmpbuf, signo);
740 }
741 
742 static void
743 usage(void)
744 {
745 
746 	fprintf(stderr, "\nIP multicast regression test utility\n");
747 	fprintf(stderr,
748 "usage: %s [-4] [-b] [-g groupaddr] [-i ifname] [-I ifaddr] [-m]\n"
749 "       [-M ngroups] [-p portno] [-r] [-R] [-s] [-S nsources] [-t] [-T timeout]\n"
750 "       [-v] [blockaddr ...]\n\n", progname);
751 	fprintf(stderr, "-4: Use IPv4 API "
752 	                "(default: Use protocol-independent API)\n");
753 	fprintf(stderr, "-b: bind listening socket to ifaddr "
754 	    "(default: INADDR_ANY)\n");
755 	fprintf(stderr, "-g: Base IPv4 multicast group to join (default: %s)\n",
756 	    DEFAULT_GROUP_STR);
757 	fprintf(stderr, "-i: interface for multicast joins (default: %s)\n",
758 	    DEFAULT_IFNAME);
759 	fprintf(stderr, "-I: IPv4 address to join groups on, if using IPv4 "
760 	    "API\n    (default: %s)\n", DEFAULT_IFADDR_STR);
761 #ifdef notyet
762 	fprintf(stderr, "-m: Test misc IPv4 multicast socket options "
763 	    "(default: off)\n");
764 #endif
765 	fprintf(stderr, "-M: Number of multicast groups to join "
766 	    "(default: %d)\n", (int)nmcastgroups);
767 	fprintf(stderr, "-p: Set local and remote port (default: %d)\n",
768 	    DEFAULT_PORT);
769 	fprintf(stderr, "-r: Set SO_REUSEPORT on (default: off)\n");
770 	fprintf(stderr, "-R: Randomize groups/sources (default: off)\n");
771 	fprintf(stderr, "-s: Test source-specific API "
772 	    "(default: test any-source API)\n");
773 	fprintf(stderr, "-S: Number of multicast sources to generate if\n"
774 	    "    none specified on command line (default: %d)\n",
775 	    (int)nmcastsources);
776 	fprintf(stderr, "-t: Test get/setNsourcefilter() (default: off)\n");
777 	fprintf(stderr, "-T: Timeout to wait for blocked traffic on first "
778 	    "group (default: %d)\n", DEFAULT_TIMEOUT);
779 	fprintf(stderr, "-v: Be verbose (default: off)\n");
780 	fprintf(stderr, "\nRemaining arguments are treated as a list of IPv4 "
781 	    "sources to filter.\n\n");
782 
783 	exit(EX_USAGE);
784 }
785