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