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