xref: /freebsd/usr.sbin/mtest/mtest.c (revision f0a75d274af375d15b97b830966b99a02b7db911)
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 static void	process_file(char *, int);
59 static void	process_cmd(char*, int, FILE *fp);
60 static void	usage(void);
61 #ifdef WITH_IGMPV3
62 static 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 
69 int
70 main(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 
108 static void
109 process_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 
133 static void
134 process_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 
382 static void
383 usage(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
403 static int
404 inaddr_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