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