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