mtest.c revision 167346
1/*-
2 * Copyright (c) 2007 Bruce M. Simpson.
3 * Copyright (c) 2000 Wilbert De Graaf.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
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.
14 *
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
28 * SUCH DAMAGE.
29 */
30
31/*
32 * Diagnostic and test utility for IPv4 multicast sockets.
33 */
34
35#include <sys/cdefs.h>
36__FBSDID("$FreeBSD: head/usr.sbin/mtest/mtest.c 167346 2007-03-08 18:56:37Z bms $");
37
38#include <sys/types.h>
39#include <sys/errno.h>
40#include <sys/socket.h>
41#include <sys/time.h>
42#include <sys/ioctl.h>
43
44#include <net/if.h>
45#include <net/if_dl.h>
46#include <net/ethernet.h>
47#include <netinet/in.h>
48
49#include <arpa/inet.h>
50
51#include <stdlib.h>
52#include <stdio.h>
53#include <string.h>
54#include <ctype.h>
55#include <err.h>
56#include <unistd.h>
57
58static void	process_file(char *, int);
59static void	process_cmd(char*, int, FILE *fp);
60static void	usage(void);
61#ifdef WITH_IGMPV3
62static int	inaddr_cmp(const void *a, const void *b);
63#endif
64
65#define	MAX_ADDRS	20
66#define	STR_SIZE	20
67#define	LINE_LENGTH	80
68
69int
70main(int argc, char **argv)
71{
72	char	 line[LINE_LENGTH];
73	char	*p;
74	int	 i, s;
75
76	s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
77	if (s == -1)
78		err(1, "can't open socket");
79
80	if (argc < 2) {
81		if (isatty(STDIN_FILENO)) {
82			printf("multicast membership test program; "
83			    "enter ? for list of commands\n");
84		}
85		do {
86			if (fgets(line, sizeof(line), stdin) != NULL) {
87				if (line[0] != 'f')
88					process_cmd(line, s, stdin);
89				else {
90					/* Get the filename */
91					for (i = 1; isblank(line[i]); i++);
92					if ((p = (char*)strchr(line, '\n'))
93					    != NULL)
94						*p = '\0';
95					process_file(&line[i], s);
96				}
97			}
98		} while (!feof(stdin));
99	} else {
100		for (i = 1; i < argc; i++) {
101			process_file(argv[i], s);
102		}
103	}
104
105	exit (0);
106}
107
108static void
109process_file(char *fname, int s)
110{
111	char line[80];
112	FILE *fp;
113	char *lineptr;
114
115	fp = fopen(fname, "r");
116	if (fp == NULL) {
117		warn("fopen");
118		return;
119	}
120
121	/* Skip comments and empty lines. */
122	while (fgets(line, sizeof(line), fp) != NULL) {
123		lineptr = line;
124		while (isblank(*lineptr))
125			lineptr++;
126		if (*lineptr != '#' && *lineptr != '\n')
127			process_cmd(lineptr, s, fp);
128	}
129
130	fclose(fp);
131}
132
133static void
134process_cmd(char *cmd, int s, FILE *fp __unused)
135{
136	char			 str1[STR_SIZE];
137	char			 str2[STR_SIZE];
138#ifdef WITH_IGMPV3
139	char			 str3[STR_SIZE];
140	char			 filtbuf[IP_MSFILTER_SIZE(MAX_ADDRS)];
141#endif
142	struct ifreq		 ifr;
143	struct ip_mreq		 imr;
144#ifdef WITH_IGMPV3
145	struct ip_mreq_source	 imrs;
146	struct ip_msfilter	*imsfp;
147#endif
148	char			*line;
149	int			 n, opt, f, flags;
150
151	line = cmd;
152	while (isblank(*++line))
153		;	/* Skip whitespace. */
154
155	switch (*cmd) {
156	case '?':
157		usage();
158		break;
159
160	case 'q':
161		close(s);
162		exit(0);
163
164	case 's':
165		if ((sscanf(line, "%d", &n) != 1) || (n < 1)) {
166			printf("-1\n");
167			break;
168		}
169		sleep(n);
170		printf("ok\n");
171		break;
172
173	case 'j':
174	case 'l':
175		sscanf(line, "%s %s", str1, str2);
176		if (((imr.imr_multiaddr.s_addr = inet_addr(str1)) ==
177		    INADDR_NONE) ||
178		    ((imr.imr_interface.s_addr = inet_addr(str2)) ==
179		    INADDR_NONE)) {
180			printf("-1\n");
181			break;
182		}
183		opt = (*cmd == 'j') ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP;
184		if (setsockopt( s, IPPROTO_IP, opt, &imr,
185		    sizeof(imr)) != 0)
186			warn("setsockopt IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP");
187		else
188			printf("ok\n");
189		break;
190
191	case 'a':
192	case 'd': {
193		struct sockaddr_dl	*dlp;
194		struct ether_addr	*ep;
195
196		memset(&ifr, 0, sizeof(struct ifreq));
197		dlp = (struct sockaddr_dl *)&ifr.ifr_addr;
198		dlp->sdl_len = sizeof(struct sockaddr_dl);
199		dlp->sdl_family = AF_LINK;
200		dlp->sdl_index = 0;
201		dlp->sdl_nlen = 0;
202		dlp->sdl_alen = ETHER_ADDR_LEN;
203		dlp->sdl_slen = 0;
204		if (sscanf(line, "%s %s", str1, str2) != 2) {
205			warnc(EINVAL, "sscanf");
206			break;
207		}
208		ep = ether_aton(str2);
209		if (ep == NULL) {
210			warnc(EINVAL, "ether_aton");
211			break;
212		}
213		strlcpy(ifr.ifr_name, str1, IF_NAMESIZE);
214		memcpy(LLADDR(dlp), ep, ETHER_ADDR_LEN);
215		if (ioctl(s, (*cmd == 'a') ? SIOCADDMULTI : SIOCDELMULTI,
216		    &ifr) == -1)
217			warn("ioctl SIOCADDMULTI/SIOCDELMULTI");
218		else
219			printf("ok\n");
220		break;
221	}
222
223	case 'm':
224		printf("warning: IFF_ALLMULTI cannot be set from userland "
225		    "in FreeBSD; command ignored.\n");
226		break;
227	case 'p':
228		if (sscanf(line, "%s %u", ifr.ifr_name, &f) != 2) {
229			printf("-1\n");
230			break;
231		}
232		if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) {
233			warn("ioctl SIOCGIFFLAGS");
234			break;
235		}
236		flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16);
237		opt = IFF_PPROMISC;
238		if (f == 0) {
239			flags &= ~opt;
240		} else {
241			flags |= opt;
242		}
243		ifr.ifr_flags = flags & 0xffff;
244		ifr.ifr_flagshigh = flags >> 16;
245		if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1)
246			warn("ioctl SIOCGIFFLAGS");
247		else
248			printf( "changed to 0x%08x\n", flags );
249		break;
250
251#ifdef WITH_IGMPV3
252	/*
253	 * Set the socket to include or exclude filter mode, and
254	 * add some sources to the filterlist, using the full-state,
255	 * or advanced api.
256	 */
257	case 'i':
258	case 'e':
259		if ((sscanf(line, "%s %s %d", str1, str2, &n)) != 3) {
260			printf("-1\n");
261			break;
262		}
263		imsfp = (struct ip_msfilter *)filtbuf;
264		if (((imsfp->imsf_multiaddr.s_addr = inet_addr(str1)) ==
265		    INADDR_NONE) ||
266		    ((imsfp->imsf_interface.s_addr = inet_addr(str2)) ==
267		    INADDR_NONE) || (n > MAX_ADDRS)) {
268			printf("-1\n");
269			break;
270		}
271		imsfp->imsf_fmode = (*cmd == 'i') ? MCAST_INCLUDE :
272		    MCAST_EXCLUDE;
273		imsfp->imsf_numsrc = n;
274		for (i = 0; i < n; i++) {
275			fgets(str1, sizeof(str1), fp);
276			if ((imsfp->imsf_slist[i].s_addr = inet_addr(str1)) ==
277			    INADDR_NONE) {
278				printf("-1\n");
279				return;
280			}
281		}
282		if (ioctl(s, SIOCSIPMSFILTER, imsfp) != 0)
283			warn("setsockopt SIOCSIPMSFILTER");
284		else
285			printf("ok\n");
286		break;
287
288	/*
289	 * Allow or block traffic from a source, using the
290	 * delta based api.
291	 */
292	case 't':
293	case 'b':
294		sscanf(line, "%s %s %s", str1, str2, str3);
295		if (((imrs.imr_multiaddr.s_addr = inet_addr(str1)) ==
296		    INADDR_NONE) ||
297			((imrs.imr_interface.s_addr = inet_addr(str2)) ==
298		    INADDR_NONE) ||
299			((imrs.imr_sourceaddr.s_addr = inet_addr(str3)) ==
300		    INADDR_NONE)) {
301			printf("-1\n");
302			break;
303		}
304
305		/* First determine out current filter mode. */
306		imsfp = (struct ip_msfilter *)filtbuf;
307		imsfp->imsf_multiaddr.s_addr = imrs.imr_multiaddr.s_addr;
308		imsfp->imsf_interface.s_addr = imrs.imr_interface.s_addr;
309		imsfp->imsf_numsrc = 5;
310		if (ioctl(s, SIOCSIPMSFILTER, imsfp) != 0) {
311			/* It's only okay for 't' to fail */
312			if (*cmd != 't') {
313				warn("ioctl SIOCSIPMSFILTER");
314				break;
315			} else {
316				imsfp->imsf_fmode = MCAST_INCLUDE;
317			}
318		}
319		if (imsfp->imsf_fmode == MCAST_EXCLUDE) {
320			/* Any source */
321			opt = (*cmd == 't') ? IP_UNBLOCK_SOURCE :
322			    IP_BLOCK_SOURCE;
323		} else {
324			/* Controlled source */
325			opt = (*cmd == 't') ? IP_ADD_SOURCE_MEMBERSHIP :
326			    IP_DROP_SOURCE_MEMBERSHIP;
327		}
328		if (setsockopt(s, IPPROTO_IP, opt, &imrs, sizeof(imrs)) == -1)
329			warn("ioctl IP_ADD_SOURCE_MEMBERSHIP/IP_DROP_SOURCE_MEMBERSHIP/IP_UNBLOCK_SOURCE/IP_BLOCK_SOURCE");
330		else
331			printf("ok\n");
332		break;
333
334	case 'g':
335		if ((sscanf(line, "%s %s %d", str1, str2, &n)) != 3) {
336			printf("-1\n");
337			break;
338		}
339		imsfp = (struct ip_msfilter *)filtbuf;
340		if (((imsfp->imsf_multiaddr.s_addr = inet_addr(str1)) ==
341		    INADDR_NONE) ||
342		    ((imsfp->imsf_interface.s_addr = inet_addr(str2)) ==
343		    INADDR_NONE) || (n < 0 || n > MAX_ADDRS)) {
344			printf("-1\n");
345			break;
346		}
347		imsfp->imsf_numsrc = n;
348		if (ioctl(s, SIOCSIPMSFILTER, imsfp) != 0) {
349			warn("setsockopt SIOCSIPMSFILTER");
350			break;
351		}
352		printf("%s\n", (imsfp->imsf_fmode == MCAST_INCLUDE) ?
353		    "include" : "exclude");
354		printf("%d\n", imsfp->imsf_numsrc);
355		if (n >= imsfp->imsf_numsrc) {
356			n = imsfp->imsf_numsrc;
357			qsort(imsfp->imsf_slist, n, sizeof(struct in_addr),
358			    &inaddr_cmp);
359			for (i = 0; i < n; i++)
360				printf("%s\n", inet_ntoa(imsfp->imsf_slist[i]));
361		}
362		break;
363#else	/* !WITH_IGMPV3 */
364	case 'i':
365	case 'e':
366	case 't':
367	case 'b':
368	case 'g':
369		printf("warning: IGMPv3 is not supported by this version "
370		    "of FreeBSD; command ignored.\n");
371		break;
372#endif	/* WITH_IGMPV3 */
373
374	case '\n':
375		break;
376	default:
377		printf("invalid command\n");
378		break;
379	}
380}
381
382static void
383usage(void)
384{
385
386	printf("j g.g.g.g i.i.i.i          - join IP multicast group\n");
387	printf("l g.g.g.g i.i.i.i          - leave IP multicast group\n");
388	printf("a ifname e.e.e.e.e.e       - add ether multicast address\n");
389	printf("d ifname e.e.e.e.e.e       - delete ether multicast address\n");
390	printf("m ifname 1/0               - set/clear ether allmulti flag\n");
391	printf("p ifname 1/0               - set/clear ether promisc flag\n");
392	printf("i g.g.g.g i.i.i.i n        - set n include mode src filter\n");
393	printf("e g.g.g.g i.i.i.i n        - set n exclude mode src filter\n");
394	printf("t g.g.g.g i.i.i.i s.s.s.s  - allow traffic from src\n");
395	printf("b g.g.g.g i.i.i.i s.s.s.s  - block traffic from src\n");
396	printf("g g.g.g.g i.i.i.i n        - get and show n src filters\n");
397	printf("f filename                 - read command(s) from file\n");
398	printf("s seconds                  - sleep for some time\n");
399	printf("q                          - quit\n");
400}
401
402#ifdef WITH_IGMPV3
403static int
404inaddr_cmp(const void *a, const void *b)
405{
406	return((int)((const struct in_addr *)a)->s_addr -
407	    ((const struct in_addr *)b)->s_addr);
408}
409#endif
410