]> CyberLeo.Net >> Repos - FreeBSD/releng/7.2.git/blob - usr.sbin/mtest/mtest.c
Create releng/7.2 from stable/7 in preparation for 7.2-RELEASE.
[FreeBSD/releng/7.2.git] / usr.sbin / mtest / mtest.c
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