1170614Sbms/*-
2170614Sbms * Copyright (c) 2007 Bruce M. Simpson
3170614Sbms * All rights reserved.
4170614Sbms *
5170614Sbms * Redistribution and use in source and binary forms, with or without
6170614Sbms * modification, are permitted provided that the following conditions
7170614Sbms * are met:
8170614Sbms * 1. Redistributions of source code must retain the above copyright
9170614Sbms *    notice, this list of conditions and the following disclaimer.
10170614Sbms * 2. Redistributions in binary form must reproduce the above copyright
11170614Sbms *    notice, this list of conditions and the following disclaimer in the
12170614Sbms *    documentation and/or other materials provided with the distribution.
13170614Sbms *
14170614Sbms * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15170614Sbms * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16170614Sbms * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17170614Sbms * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18170614Sbms * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19170614Sbms * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20170614Sbms * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21170614Sbms * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22170614Sbms * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23170614Sbms * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24170614Sbms * SUCH DAMAGE.
25170614Sbms */
26170614Sbms
27170614Sbms/*
28170614Sbms * Regression test utility for RFC 3678 Advanced Multicast API in FreeBSD.
29170614Sbms *
30170614Sbms * TODO: Test the SSM paths.
31170614Sbms * TODO: Support INET6. The code has been written to facilitate this later.
32170614Sbms * TODO: Merge multicast socket option tests from ipsockopt.
33170614Sbms */
34170614Sbms
35170614Sbms#include <sys/cdefs.h>
36170614Sbms__FBSDID("$FreeBSD: releng/11.0/tools/regression/netinet/ipmulticast/ipmulticast.c 170614 2007-06-12 16:29:22Z bms $");
37170614Sbms
38170614Sbms#include <sys/param.h>
39170614Sbms#include <sys/types.h>
40170614Sbms#include <sys/ioctl.h>
41170614Sbms#include <sys/socket.h>
42170614Sbms
43170614Sbms#include <net/if.h>
44170614Sbms#include <net/if_dl.h>
45170614Sbms#include <netinet/in.h>
46170614Sbms#include <arpa/inet.h>
47170614Sbms#include <netdb.h>
48170614Sbms
49170614Sbms#include <assert.h>
50170614Sbms#include <err.h>
51170614Sbms#include <errno.h>
52170614Sbms#include <getopt.h>
53170614Sbms#include <libgen.h>
54170614Sbms#include <pwd.h>
55170614Sbms#include <setjmp.h>
56170614Sbms#include <signal.h>
57170614Sbms#include <stddef.h>
58170614Sbms#include <stdio.h>
59170614Sbms#include <stdlib.h>
60170614Sbms#include <string.h>
61170614Sbms#include <sysexits.h>
62170614Sbms#include <time.h>
63170614Sbms#include <unistd.h>
64170614Sbms
65170614Sbms#ifndef __SOCKUNION_DECLARED
66170614Sbmsunion sockunion {
67170614Sbms	struct sockaddr_storage	ss;
68170614Sbms	struct sockaddr		sa;
69170614Sbms	struct sockaddr_dl	sdl;
70170614Sbms	struct sockaddr_in	sin;
71170614Sbms#ifdef INET6
72170614Sbms	struct sockaddr_in6	sin6;
73170614Sbms#endif
74170614Sbms};
75170614Sbmstypedef union sockunion sockunion_t;
76170614Sbms#define __SOCKUNION_DECLARED
77170614Sbms#endif /* __SOCKUNION_DECLARED */
78170614Sbms
79170614Sbms#define ADDRBUF_LEN		16
80170614Sbms#define DEFAULT_GROUP_STR	"238.1.1.0"
81170614Sbms#define DEFAULT_IFNAME		"lo0"
82170614Sbms#define DEFAULT_IFADDR_STR	"127.0.0.1"
83170614Sbms#define DEFAULT_PORT		6698
84170614Sbms#define DEFAULT_TIMEOUT		0		/* don't wait for traffic */
85170614Sbms#define RXBUFSIZE		2048
86170614Sbms
87170614Sbmsstatic sockunion_t	 basegroup;
88170614Sbmsstatic const char	*basegroup_str = NULL;
89170614Sbmsstatic int		 dobindaddr = 0;
90170614Sbmsstatic int		 dodebug = 1;
91170614Sbmsstatic int		 doipv4 = 0;
92170614Sbmsstatic int		 domiscopts = 0;
93170614Sbmsstatic int		 dorandom = 0;
94170614Sbmsstatic int		 doreuseport = 0;
95170614Sbmsstatic int		 dossm = 0;
96170614Sbmsstatic int		 dossf = 0;
97170614Sbmsstatic int		 doverbose = 0;
98170614Sbmsstatic sockunion_t	 ifaddr;
99170614Sbmsstatic const char	*ifaddr_str = NULL;
100170614Sbmsstatic uint32_t		 ifindex = 0;
101170614Sbmsstatic const char	*ifname = NULL;
102170614Sbmsstruct in_addr		*ipv4_sources = NULL;
103170614Sbmsstatic jmp_buf		 jmpbuf;
104170614Sbmsstatic size_t		 nmcastgroups = IP_MAX_MEMBERSHIPS;
105170614Sbmsstatic size_t		 nmcastsources = 0;
106170614Sbmsstatic uint16_t		 portno = DEFAULT_PORT;
107170614Sbmsstatic char		*progname = NULL;
108170614Sbmsstruct sockaddr_storage	*ss_sources = NULL;
109170614Sbmsstatic uint32_t		 timeout = 0;
110170614Sbms
111170614Sbmsstatic int	do_asm_ipv4(void);
112170614Sbmsstatic int	do_asm_pim(void);
113170614Sbms#ifdef notyet
114170614Sbmsstatic int	do_misc_opts(void);
115170614Sbms#endif
116170614Sbmsstatic int	do_ssf_ipv4(void);
117170614Sbmsstatic int	do_ssf_pim(void);
118170614Sbmsstatic int	do_ssm_ipv4(void);
119170614Sbmsstatic int	do_ssm_pim(void);
120170614Sbmsstatic int	open_and_bind_socket(sockunion_t *);
121170614Sbmsstatic int	recv_loop_with_match(int, sockunion_t *, sockunion_t *);
122170614Sbmsstatic void	signal_handler(int);
123170614Sbmsstatic void	usage(void);
124170614Sbms
125170614Sbms/*
126170614Sbms * Test the IPv4 set/getipv4sourcefilter() libc API functions.
127170614Sbms * Build a single socket.
128170614Sbms * Join a source group.
129170614Sbms * Repeatedly change the source filters via setipv4sourcefilter.
130170614Sbms * Read it back with getipv4sourcefilter up to IP_MAX_SOURCES
131170614Sbms * and check for inconsistency.
132170614Sbms */
133170614Sbmsstatic int
134170614Sbmsdo_ssf_ipv4(void)
135170614Sbms{
136170614Sbms
137170614Sbms	fprintf(stderr, "not yet implemented\n");
138170614Sbms	return (0);
139170614Sbms}
140170614Sbms
141170614Sbms/*
142170614Sbms * Test the protocol-independent set/getsourcefilter() functions.
143170614Sbms */
144170614Sbmsstatic int
145170614Sbmsdo_ssf_pim(void)
146170614Sbms{
147170614Sbms
148170614Sbms	fprintf(stderr, "not yet implemented\n");
149170614Sbms	return (0);
150170614Sbms}
151170614Sbms
152170614Sbms/*
153170614Sbms * Test the IPv4 ASM API.
154170614Sbms * Repeatedly join, block sources, unblock and leave groups.
155170614Sbms */
156170614Sbmsstatic int
157170614Sbmsdo_asm_ipv4(void)
158170614Sbms{
159170614Sbms	int			 error;
160170614Sbms	char			 gaddrbuf[ADDRBUF_LEN];
161170614Sbms	int			 i;
162170614Sbms	sockunion_t		 laddr;
163170614Sbms	struct ip_mreq		 mreq;
164170614Sbms	struct ip_mreq_source	 mreqs;
165170614Sbms	in_addr_t		 ngroupbase;
166170614Sbms	char			 saddrbuf[ADDRBUF_LEN];
167170614Sbms	int			 sock;
168170614Sbms	sockunion_t		 tmpgroup;
169170614Sbms	sockunion_t		 tmpsource;
170170614Sbms
171170614Sbms	memset(&mreq, 0, sizeof(struct ip_mreq));
172170614Sbms	memset(&mreqs, 0, sizeof(struct ip_mreq_source));
173170614Sbms	memset(&laddr, 0, sizeof(sockunion_t));
174170614Sbms
175170614Sbms	if (dobindaddr) {
176170614Sbms		laddr = ifaddr;
177170614Sbms	} else {
178170614Sbms		laddr.sin.sin_family = AF_INET;
179170614Sbms		laddr.sin.sin_len = sizeof(struct sockaddr_in);
180170614Sbms		laddr.sin.sin_addr.s_addr = INADDR_ANY;
181170614Sbms	}
182170614Sbms	laddr.sin.sin_port = htons(portno);
183170614Sbms
184170614Sbms	tmpgroup = basegroup;
185170614Sbms	ngroupbase = ntohl(basegroup.sin.sin_addr.s_addr) + 1;	/* XXX */
186170614Sbms	tmpgroup.sin.sin_addr.s_addr = htonl(ngroupbase);
187170614Sbms
188170614Sbms	sock = open_and_bind_socket(&laddr);
189170614Sbms	if (sock == -1)
190170614Sbms		return (EX_OSERR);
191170614Sbms
192170614Sbms	for (i = 0; i < (signed)nmcastgroups; i++) {
193170614Sbms		mreq.imr_multiaddr.s_addr = htonl((ngroupbase + i));
194170614Sbms		mreq.imr_interface = ifaddr.sin.sin_addr;
195170614Sbms		if (doverbose) {
196170614Sbms			inet_ntop(AF_INET, &mreq.imr_multiaddr, gaddrbuf,
197170614Sbms			    sizeof(gaddrbuf));
198170614Sbms			fprintf(stderr, "IP_ADD_MEMBERSHIP %s %s\n",
199170614Sbms			    gaddrbuf, inet_ntoa(mreq.imr_interface));
200170614Sbms		}
201170614Sbms		error = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
202170614Sbms		    &mreq, sizeof(struct ip_mreq));
203170614Sbms		if (error < 0) {
204170614Sbms			warn("setsockopt IP_ADD_MEMBERSHIP");
205170614Sbms			close(sock);
206170614Sbms			return (EX_OSERR);
207170614Sbms		}
208170614Sbms	}
209170614Sbms
210170614Sbms	/*
211170614Sbms	 * If no test sources auto-generated or specified on command line,
212170614Sbms	 * skip source filter portion of ASM test.
213170614Sbms	*/
214170614Sbms	if (nmcastsources == 0)
215170614Sbms		goto skipsources;
216170614Sbms
217170614Sbms	/*
218170614Sbms	 * Begin blocking sources on the first group chosen.
219170614Sbms	 */
220170614Sbms	for (i = 0; i < (signed)nmcastsources; i++) {
221170614Sbms		mreqs.imr_multiaddr = tmpgroup.sin.sin_addr;
222170614Sbms		mreqs.imr_interface = ifaddr.sin.sin_addr;
223170614Sbms		mreqs.imr_sourceaddr = ipv4_sources[i];
224170614Sbms		if (doverbose) {
225170614Sbms			inet_ntop(AF_INET, &mreqs.imr_multiaddr, gaddrbuf,
226170614Sbms			    sizeof(gaddrbuf));
227170614Sbms			inet_ntop(AF_INET, &mreqs.imr_sourceaddr, saddrbuf,
228170614Sbms			    sizeof(saddrbuf));
229170614Sbms			fprintf(stderr, "IP_BLOCK_SOURCE %s %s %s\n",
230170614Sbms			    gaddrbuf, inet_ntoa(mreqs.imr_interface),
231170614Sbms			    saddrbuf);
232170614Sbms		}
233170614Sbms		error = setsockopt(sock, IPPROTO_IP, IP_BLOCK_SOURCE, &mreqs,
234170614Sbms		    sizeof(struct ip_mreq_source));
235170614Sbms		if (error < 0) {
236170614Sbms			warn("setsockopt IP_BLOCK_SOURCE");
237170614Sbms			close(sock);
238170614Sbms			return (EX_OSERR);
239170614Sbms		}
240170614Sbms	}
241170614Sbms
242170614Sbms	/*
243170614Sbms	 * Choose the first group and source for a match.
244170614Sbms	 * Enter the I/O loop.
245170614Sbms	 */
246170614Sbms	memset(&tmpsource, 0, sizeof(sockunion_t));
247170614Sbms	tmpsource.sin.sin_family = AF_INET;
248170614Sbms	tmpsource.sin.sin_len = sizeof(struct sockaddr_in);
249170614Sbms	tmpsource.sin.sin_addr = ipv4_sources[0];
250170614Sbms
251170614Sbms	error = recv_loop_with_match(sock, &tmpgroup, &tmpsource);
252170614Sbms
253170614Sbms	/*
254170614Sbms	 * Unblock sources.
255170614Sbms	 */
256170614Sbms	for (i = nmcastsources-1; i >= 0; i--) {
257170614Sbms		mreqs.imr_multiaddr = tmpgroup.sin.sin_addr;
258170614Sbms		mreqs.imr_interface = ifaddr.sin.sin_addr;
259170614Sbms		mreqs.imr_sourceaddr = ipv4_sources[i];
260170614Sbms		if (doverbose) {
261170614Sbms			inet_ntop(AF_INET, &mreqs.imr_multiaddr, gaddrbuf,
262170614Sbms			    sizeof(gaddrbuf));
263170614Sbms			inet_ntop(AF_INET, &mreqs.imr_sourceaddr, saddrbuf,
264170614Sbms			    sizeof(saddrbuf));
265170614Sbms			fprintf(stderr, "IP_UNBLOCK_SOURCE %s %s %s\n",
266170614Sbms			    gaddrbuf, inet_ntoa(mreqs.imr_interface),
267170614Sbms			    saddrbuf);
268170614Sbms		}
269170614Sbms		error = setsockopt(sock, IPPROTO_IP, IP_UNBLOCK_SOURCE, &mreqs,
270170614Sbms		    sizeof(struct ip_mreq_source));
271170614Sbms		if (error < 0) {
272170614Sbms			warn("setsockopt IP_UNBLOCK_SOURCE");
273170614Sbms			close(sock);
274170614Sbms			return (EX_OSERR);
275170614Sbms		}
276170614Sbms	}
277170614Sbms
278170614Sbmsskipsources:
279170614Sbms	/*
280170614Sbms	 * Leave groups.
281170614Sbms	 */
282170614Sbms	for (i = nmcastgroups-1; i >= 0; i--) {
283170614Sbms		mreq.imr_multiaddr.s_addr = htonl((ngroupbase + i));
284170614Sbms		mreq.imr_interface = ifaddr.sin.sin_addr;
285170614Sbms		if (doverbose) {
286170614Sbms			inet_ntop(AF_INET, &mreq.imr_multiaddr, gaddrbuf,
287170614Sbms			    sizeof(gaddrbuf));
288170614Sbms			fprintf(stderr, "IP_DROP_MEMBERSHIP %s %s\n",
289170614Sbms			    gaddrbuf, inet_ntoa(mreq.imr_interface));
290170614Sbms		}
291170614Sbms		error = setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP,
292170614Sbms		    &mreq, sizeof(struct ip_mreq));
293170614Sbms		if (error < 0) {
294170614Sbms			warn("setsockopt IP_DROP_MEMBERSHIP");
295170614Sbms			close(sock);
296170614Sbms			return (EX_OSERR);
297170614Sbms		}
298170614Sbms	}
299170614Sbms
300170614Sbms	return (0);
301170614Sbms}
302170614Sbms
303170614Sbmsstatic int
304170614Sbmsdo_asm_pim(void)
305170614Sbms{
306170614Sbms
307170614Sbms	fprintf(stderr, "not yet implemented\n");
308170614Sbms	return (0);
309170614Sbms}
310170614Sbms
311170614Sbms#ifdef notyet
312170614Sbms/*
313170614Sbms * Test misceallaneous IPv4 options.
314170614Sbms */
315170614Sbmsstatic int
316170614Sbmsdo_misc_opts(void)
317170614Sbms{
318170614Sbms	int sock;
319170614Sbms
320170614Sbms	sock = open_and_bind_socket(NULL);
321170614Sbms	if (sock == -1)
322170614Sbms		return (EX_OSERR);
323170614Sbms	test_ip_uchar(sock, socktypename, IP_MULTICAST_TTL,
324170614Sbms	    "IP_MULTICAST_TTL", 1);
325170614Sbms	close(sock);
326170614Sbms
327170614Sbms	sock = open_and_bind_socket(NULL);
328170614Sbms	if (sock == -1)
329170614Sbms		return (EX_OSERR);
330170614Sbms	test_ip_boolean(sock, socktypename, IP_MULTICAST_LOOP,
331170614Sbms	    "IP_MULTICAST_LOOP", 1, BOOLEAN_ANYONE);
332170614Sbms	close(sock);
333170614Sbms
334170614Sbms	return (0);
335170614Sbms}
336170614Sbms#endif
337170614Sbms
338170614Sbms/*
339170614Sbms * Test the IPv4 SSM API.
340170614Sbms */
341170614Sbmsstatic int
342170614Sbmsdo_ssm_ipv4(void)
343170614Sbms{
344170614Sbms
345170614Sbms	fprintf(stderr, "not yet implemented\n");
346170614Sbms	return (0);
347170614Sbms}
348170614Sbms
349170614Sbms/*
350170614Sbms * Test the protocol-independent SSM API with IPv4 addresses.
351170614Sbms */
352170614Sbmsstatic int
353170614Sbmsdo_ssm_pim(void)
354170614Sbms{
355170614Sbms
356170614Sbms	fprintf(stderr, "not yet implemented\n");
357170614Sbms	return (0);
358170614Sbms}
359170614Sbms
360170614Sbmsint
361170614Sbmsmain(int argc, char *argv[])
362170614Sbms{
363170614Sbms	struct addrinfo		 aih;
364170614Sbms	struct addrinfo		*aip;
365170614Sbms	int			 ch;
366170614Sbms	int			 error;
367170614Sbms	int			 exitval;
368170614Sbms	size_t			 i;
369170614Sbms	struct in_addr		*pina;
370170614Sbms	struct sockaddr_storage	*pbss;
371170614Sbms
372170614Sbms	ifname = DEFAULT_IFNAME;
373170614Sbms	ifaddr_str = DEFAULT_IFADDR_STR;
374170614Sbms	basegroup_str = DEFAULT_GROUP_STR;
375170614Sbms	ifname = DEFAULT_IFNAME;
376170614Sbms	portno = DEFAULT_PORT;
377170614Sbms	basegroup.ss.ss_family = AF_UNSPEC;
378170614Sbms	ifaddr.ss.ss_family = AF_UNSPEC;
379170614Sbms
380170614Sbms	progname = basename(argv[0]);
381170614Sbms	while ((ch = getopt(argc, argv, "4bg:i:I:mM:p:rsS:tT:v")) != -1) {
382170614Sbms		switch (ch) {
383170614Sbms		case '4':
384170614Sbms			doipv4 = 1;
385170614Sbms			break;
386170614Sbms		case 'b':
387170614Sbms			dobindaddr = 1;
388170614Sbms			break;
389170614Sbms		case 'g':
390170614Sbms			basegroup_str = optarg;
391170614Sbms			break;
392170614Sbms		case 'i':
393170614Sbms			ifname = optarg;
394170614Sbms			break;
395170614Sbms		case 'I':
396170614Sbms			ifaddr_str = optarg;
397170614Sbms			break;
398170614Sbms		case 'm':
399170614Sbms			usage();	/* notyet */
400170614Sbms			/*NOTREACHED*/
401170614Sbms			domiscopts = 1;
402170614Sbms			break;
403170614Sbms		case 'M':
404170614Sbms			nmcastgroups = atoi(optarg);
405170614Sbms			break;
406170614Sbms		case 'p':
407170614Sbms			portno = atoi(optarg);
408170614Sbms			break;
409170614Sbms		case 'r':
410170614Sbms			doreuseport = 1;
411170614Sbms			break;
412170614Sbms		case 'S':
413170614Sbms			nmcastsources = atoi(optarg);
414170614Sbms			break;
415170614Sbms		case 's':
416170614Sbms			dossm = 1;
417170614Sbms			break;
418170614Sbms		case 't':
419170614Sbms			dossf = 1;
420170614Sbms			break;
421170614Sbms		case 'T':
422170614Sbms			timeout = atoi(optarg);
423170614Sbms			break;
424170614Sbms		case 'v':
425170614Sbms			doverbose = 1;
426170614Sbms			break;
427170614Sbms		default:
428170614Sbms			usage();
429170614Sbms			break;
430170614Sbms			/*NOTREACHED*/
431170614Sbms		}
432170614Sbms	}
433170614Sbms	argc -= optind;
434170614Sbms	argv += optind;
435170614Sbms
436170614Sbms	memset(&aih, 0, sizeof(struct addrinfo));
437170614Sbms	aih.ai_flags = AI_NUMERICHOST | AI_PASSIVE;
438170614Sbms	aih.ai_family = PF_INET;
439170614Sbms	aih.ai_socktype = SOCK_DGRAM;
440170614Sbms	aih.ai_protocol = IPPROTO_UDP;
441170614Sbms
442170614Sbms	/*
443170614Sbms	 * Fill out base group.
444170614Sbms	 */
445170614Sbms	aip = NULL;
446170614Sbms	error = getaddrinfo(basegroup_str, NULL, &aih, &aip);
447170614Sbms	if (error != 0) {
448170614Sbms		fprintf(stderr, "%s: getaddrinfo: %s\n", progname,
449170614Sbms		    gai_strerror(error));
450170614Sbms		exit(EX_USAGE);
451170614Sbms	}
452170614Sbms	memcpy(&basegroup, aip->ai_addr, aip->ai_addrlen);
453170614Sbms	if (dodebug) {
454170614Sbms		fprintf(stderr, "debug: gai thinks %s is %s\n",
455170614Sbms		    basegroup_str, inet_ntoa(basegroup.sin.sin_addr));
456170614Sbms	}
457170614Sbms	freeaddrinfo(aip);
458170614Sbms
459170614Sbms	assert(basegroup.ss.ss_family == AF_INET);
460170614Sbms
461170614Sbms	/*
462170614Sbms	 * If user specified interface as an address, and protocol
463170614Sbms	 * specific APIs were selected, parse it.
464170614Sbms	 * Otherwise, parse interface index from name if protocol
465170614Sbms	 * independent APIs were selected (the default).
466170614Sbms	 */
467170614Sbms	if (doipv4) {
468170614Sbms		if (ifaddr_str == NULL) {
469170614Sbms			warnx("required argument missing: ifaddr");
470170614Sbms			usage();
471170614Sbms			/* NOTREACHED */
472170614Sbms		}
473170614Sbms		aip = NULL;
474170614Sbms		error = getaddrinfo(ifaddr_str, NULL, &aih, &aip);
475170614Sbms		if (error != 0) {
476170614Sbms			fprintf(stderr, "%s: getaddrinfo: %s\n", progname,
477170614Sbms			    gai_strerror(error));
478170614Sbms			exit(EX_USAGE);
479170614Sbms		}
480170614Sbms		memcpy(&ifaddr, aip->ai_addr, aip->ai_addrlen);
481170614Sbms		if (dodebug) {
482170614Sbms			fprintf(stderr, "debug: gai thinks %s is %s\n",
483170614Sbms			    ifaddr_str, inet_ntoa(ifaddr.sin.sin_addr));
484170614Sbms		}
485170614Sbms		freeaddrinfo(aip);
486170614Sbms	}
487170614Sbms
488170614Sbms	if (!doipv4) {
489170614Sbms		if (ifname == NULL) {
490170614Sbms			warnx("required argument missing: ifname");
491170614Sbms			usage();
492170614Sbms			/* NOTREACHED */
493170614Sbms		}
494170614Sbms		ifindex = if_nametoindex(ifname);
495170614Sbms		if (ifindex == 0)
496170614Sbms			err(EX_USAGE, "if_nametoindex");
497170614Sbms	}
498170614Sbms
499170614Sbms	/*
500170614Sbms	 * Introduce randomness into group base if specified.
501170614Sbms	 */
502170614Sbms	if (dorandom) {
503170614Sbms		in_addr_t ngroupbase;
504170614Sbms
505170614Sbms		srandomdev();
506170614Sbms		ngroupbase = ntohl(basegroup.sin.sin_addr.s_addr);
507170614Sbms		ngroupbase |= ((random() % ((1 << 11) - 1)) << 16);
508170614Sbms		basegroup.sin.sin_addr.s_addr = htonl(ngroupbase);
509170614Sbms	}
510170614Sbms
511170614Sbms	if (argc > 0) {
512170614Sbms		nmcastsources = argc;
513170614Sbms		if (doipv4) {
514170614Sbms			ipv4_sources = calloc(nmcastsources,
515170614Sbms			    sizeof(struct in_addr));
516170614Sbms			if (ipv4_sources == NULL) {
517170614Sbms				exitval = EX_OSERR;
518170614Sbms				goto out;
519170614Sbms			}
520170614Sbms		} else {
521170614Sbms			ss_sources = calloc(nmcastsources,
522170614Sbms			    sizeof(struct sockaddr_storage));
523170614Sbms			if (ss_sources == NULL) {
524170614Sbms				exitval = EX_OSERR;
525170614Sbms				goto out;
526170614Sbms			}
527170614Sbms		}
528170614Sbms	}
529170614Sbms
530170614Sbms	/*
531170614Sbms	 * Parse source list, if any were specified on the command line.
532170614Sbms	 */
533170614Sbms	assert(aih.ai_family == PF_INET);
534170614Sbms	pbss = ss_sources;
535170614Sbms	pina = ipv4_sources;
536170614Sbms	for (i = 0; i < (size_t)argc; i++) {
537170614Sbms		aip = NULL;
538170614Sbms		error = getaddrinfo(argv[i], NULL, &aih, &aip);
539170614Sbms		if (error != 0) {
540170614Sbms			fprintf(stderr, "getaddrinfo: %s\n",
541170614Sbms			    gai_strerror(error));
542170614Sbms			exitval = EX_USAGE;
543170614Sbms			goto out;
544170614Sbms		}
545170614Sbms		if (doipv4) {
546170614Sbms			struct sockaddr_in *sin =
547170614Sbms			    (struct sockaddr_in *)aip->ai_addr;
548170614Sbms			*pina++ = sin->sin_addr;
549170614Sbms		} else {
550170614Sbms			memcpy(pbss++, aip->ai_addr, aip->ai_addrlen);
551170614Sbms		}
552170614Sbms		freeaddrinfo(aip);
553170614Sbms	}
554170614Sbms
555170614Sbms	/*
556170614Sbms	 * Perform the regression tests which the user requested.
557170614Sbms	 */
558170614Sbms#ifdef notyet
559170614Sbms	if (domiscopts) {
560170614Sbms		exitval = do_misc_opts();
561170614Sbms		if (exitval)
562170614Sbms			goto out;
563170614Sbms	}
564170614Sbms#endif
565170614Sbms	if (doipv4) {
566170614Sbms		/* IPv4 protocol specific API tests */
567170614Sbms		if (dossm) {
568170614Sbms			/* Source-specific multicast */
569170614Sbms			exitval = do_ssm_ipv4();
570170614Sbms			if (exitval)
571170614Sbms				goto out;
572170614Sbms			if (dossf) {
573170614Sbms				/* Do setipvsourcefilter() too */
574170614Sbms				exitval = do_ssf_ipv4();
575170614Sbms			}
576170614Sbms		} else {
577170614Sbms			/* Any-source multicast */
578170614Sbms			exitval = do_asm_ipv4();
579170614Sbms		}
580170614Sbms	} else {
581170614Sbms		/* Protocol independent API tests */
582170614Sbms		if (dossm) {
583170614Sbms			/* Source-specific multicast */
584170614Sbms			exitval = do_ssm_pim();
585170614Sbms			if (exitval)
586170614Sbms				goto out;
587170614Sbms			if (dossf) {
588170614Sbms				/* Do setsourcefilter() too */
589170614Sbms				exitval = do_ssf_pim();
590170614Sbms			}
591170614Sbms		} else {
592170614Sbms			/* Any-source multicast */
593170614Sbms			exitval = do_asm_pim();
594170614Sbms		}
595170614Sbms	}
596170614Sbms
597170614Sbmsout:
598170614Sbms	if (ipv4_sources != NULL)
599170614Sbms		free(ipv4_sources);
600170614Sbms
601170614Sbms	if (ss_sources != NULL)
602170614Sbms		free(ss_sources);
603170614Sbms
604170614Sbms	exit(exitval);
605170614Sbms}
606170614Sbms
607170614Sbmsstatic int
608170614Sbmsopen_and_bind_socket(sockunion_t *bsu)
609170614Sbms{
610170614Sbms	int	 error, optval, sock;
611170614Sbms
612170614Sbms	sock = -1;
613170614Sbms
614170614Sbms	sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
615170614Sbms	if (sock == -1) {
616170614Sbms		warn("socket");
617170614Sbms		return (-1);
618170614Sbms	}
619170614Sbms
620170614Sbms	if (doreuseport) {
621170614Sbms		optval = 1;
622170614Sbms		if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &optval,
623170614Sbms		    sizeof(optval)) < 0) {
624170614Sbms			warn("setsockopt SO_REUSEPORT");
625170614Sbms			close(sock);
626170614Sbms			return (-1);
627170614Sbms		}
628170614Sbms	}
629170614Sbms
630170614Sbms	if (bsu != NULL) {
631170614Sbms		error = bind(sock, &bsu->sa, bsu->sa.sa_len);
632170614Sbms		if (error == -1) {
633170614Sbms			warn("bind");
634170614Sbms			close(sock);
635170614Sbms			return (-1);
636170614Sbms		}
637170614Sbms	}
638170614Sbms
639170614Sbms	return (sock);
640170614Sbms}
641170614Sbms
642170614Sbms/*
643170614Sbms * Protocol-agnostic multicast I/O loop.
644170614Sbms *
645170614Sbms * Wait for 'timeout' seconds looking for traffic on group, so that manual
646170614Sbms * or automated regression tests (possibly running on another host) have an
647170614Sbms * opportunity to transmit within the group to test source filters.
648170614Sbms *
649170614Sbms * If the filter failed, this loop will report if we received traffic
650170614Sbms * from the source we elected to monitor.
651170614Sbms */
652170614Sbmsstatic int
653170614Sbmsrecv_loop_with_match(int sock, sockunion_t *group, sockunion_t *source)
654170614Sbms{
655170614Sbms	int		 error;
656170614Sbms	sockunion_t	 from;
657170614Sbms	char		 groupname[NI_MAXHOST];
658170614Sbms	ssize_t		 len;
659170614Sbms	size_t		 npackets;
660170614Sbms	int		 jmpretval;
661170614Sbms	char		 rxbuf[RXBUFSIZE];
662170614Sbms	char		 sourcename[NI_MAXHOST];
663170614Sbms
664170614Sbms	assert(source->sa.sa_family == AF_INET);
665170614Sbms
666170614Sbms	/*
667170614Sbms	 * Return immediately if we don't need to wait for traffic.
668170614Sbms	 */
669170614Sbms	if (timeout == 0)
670170614Sbms		return (0);
671170614Sbms
672170614Sbms	error = getnameinfo(&group->sa, group->sa.sa_len, groupname,
673170614Sbms	    NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
674170614Sbms	if (error) {
675170614Sbms		fprintf(stderr, "getnameinfo: %s\n", gai_strerror(error));
676170614Sbms		return (error);
677170614Sbms	}
678170614Sbms
679170614Sbms	error = getnameinfo(&source->sa, source->sa.sa_len, sourcename,
680170614Sbms	    NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
681170614Sbms	if (error) {
682170614Sbms		fprintf(stderr, "getnameinfo: %s\n", gai_strerror(error));
683170614Sbms		return (error);
684170614Sbms	}
685170614Sbms
686170614Sbms	fprintf(stdout,
687170614Sbms	    "Waiting %d seconds for inbound traffic on group %s\n"
688170614Sbms	    "Expecting no traffic from blocked source: %s\n",
689170614Sbms	    (int)timeout, groupname, sourcename);
690170614Sbms
691170614Sbms	signal(SIGINT, signal_handler);
692170614Sbms	signal(SIGALRM, signal_handler);
693170614Sbms
694170614Sbms	error = 0;
695170614Sbms	npackets = 0;
696170614Sbms	alarm(timeout);
697170614Sbms	while (0 == (jmpretval = setjmp(jmpbuf))) {
698170614Sbms		len = recvfrom(sock, rxbuf, RXBUFSIZE, 0, &from.sa,
699170614Sbms		    (socklen_t *)&from.sa.sa_len);
700170614Sbms		if (dodebug) {
701170614Sbms			fprintf(stderr, "debug: packet received from %s\n",
702170614Sbms			    inet_ntoa(from.sin.sin_addr));
703170614Sbms		}
704170614Sbms		if (source &&
705170614Sbms		    source->sin.sin_addr.s_addr == from.sin.sin_addr.s_addr)
706170614Sbms			break;
707170614Sbms		npackets++;
708170614Sbms	}
709170614Sbms
710170614Sbms	if (doverbose) {
711170614Sbms		fprintf(stderr, "Number of datagrams received from "
712170614Sbms		    "non-blocked sources: %d\n", (int)npackets);
713170614Sbms	}
714170614Sbms
715170614Sbms	switch (jmpretval) {
716170614Sbms	case SIGALRM:	/* ok */
717170614Sbms		break;
718170614Sbms	case SIGINT:	/* go bye bye */
719170614Sbms		fprintf(stderr, "interrupted\n");
720170614Sbms		error = 20;
721170614Sbms		break;
722170614Sbms	case 0:		/* Broke out of loop; saw a bad source. */
723170614Sbms		fprintf(stderr, "FAIL: got packet from blocked source\n");
724170614Sbms		error = EX_IOERR;
725170614Sbms		break;
726170614Sbms	default:
727170614Sbms		warnx("recvfrom");
728170614Sbms		error = EX_OSERR;
729170614Sbms		break;
730170614Sbms	}
731170614Sbms
732170614Sbms	signal(SIGINT, SIG_DFL);
733170614Sbms	signal(SIGALRM, SIG_DFL);
734170614Sbms
735170614Sbms	return (error);
736170614Sbms}
737170614Sbms
738170614Sbmsstatic void
739170614Sbmssignal_handler(int signo)
740170614Sbms{
741170614Sbms
742170614Sbms	longjmp(jmpbuf, signo);
743170614Sbms}
744170614Sbms
745170614Sbmsstatic void
746170614Sbmsusage(void)
747170614Sbms{
748170614Sbms
749170614Sbms	fprintf(stderr, "\nIP multicast regression test utility\n");
750170614Sbms	fprintf(stderr,
751170614Sbms"usage: %s [-4] [-b] [-g groupaddr] [-i ifname] [-I ifaddr] [-m]\n"
752170614Sbms"       [-M ngroups] [-p portno] [-r] [-R] [-s] [-S nsources] [-t] [-T timeout]\n"
753170614Sbms"       [-v] [blockaddr ...]\n\n", progname);
754170614Sbms	fprintf(stderr, "-4: Use IPv4 API "
755170614Sbms	                "(default: Use protocol-independent API)\n");
756170614Sbms	fprintf(stderr, "-b: bind listening socket to ifaddr "
757170614Sbms	    "(default: INADDR_ANY)\n");
758170614Sbms	fprintf(stderr, "-g: Base IPv4 multicast group to join (default: %s)\n",
759170614Sbms	    DEFAULT_GROUP_STR);
760170614Sbms	fprintf(stderr, "-i: interface for multicast joins (default: %s)\n",
761170614Sbms	    DEFAULT_IFNAME);
762170614Sbms	fprintf(stderr, "-I: IPv4 address to join groups on, if using IPv4 "
763170614Sbms	    "API\n    (default: %s)\n", DEFAULT_IFADDR_STR);
764170614Sbms#ifdef notyet
765170614Sbms	fprintf(stderr, "-m: Test misc IPv4 multicast socket options "
766170614Sbms	    "(default: off)\n");
767170614Sbms#endif
768170614Sbms	fprintf(stderr, "-M: Number of multicast groups to join "
769170614Sbms	    "(default: %d)\n", (int)nmcastgroups);
770170614Sbms	fprintf(stderr, "-p: Set local and remote port (default: %d)\n",
771170614Sbms	    DEFAULT_PORT);
772170614Sbms	fprintf(stderr, "-r: Set SO_REUSEPORT on (default: off)\n");
773170614Sbms	fprintf(stderr, "-R: Randomize groups/sources (default: off)\n");
774170614Sbms	fprintf(stderr, "-s: Test source-specific API "
775170614Sbms	    "(default: test any-source API)\n");
776170614Sbms	fprintf(stderr, "-S: Number of multicast sources to generate if\n"
777170614Sbms	    "    none specified on command line (default: %d)\n",
778170614Sbms	    (int)nmcastsources);
779170614Sbms	fprintf(stderr, "-t: Test get/setNsourcefilter() (default: off)\n");
780170614Sbms	fprintf(stderr, "-T: Timeout to wait for blocked traffic on first "
781170614Sbms	    "group (default: %d)\n", DEFAULT_TIMEOUT);
782170614Sbms	fprintf(stderr, "-v: Be verbose (default: off)\n");
783170614Sbms	fprintf(stderr, "\nRemaining arguments are treated as a list of IPv4 "
784170614Sbms	    "sources to filter.\n\n");
785170614Sbms
786170614Sbms	exit(EX_USAGE);
787170614Sbms}
788