1 /*- 2 * Copyright (c) 2005 Robert N. M. Watson 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 #include <sys/types.h> 28 #include <sys/socket.h> 29 30 #include <netinet/in.h> 31 32 #include <arpa/inet.h> 33 34 #include <err.h> 35 #include <errno.h> 36 #include <fcntl.h> 37 #include <stdio.h> 38 #include <string.h> 39 #include <unistd.h> 40 41 /* 42 * Regression test for multicast sockets and options: 43 * 44 * - Check the defaults for ttl, if, and loopback. Make sure they can be set 45 * and then read. 46 * 47 * - Check that adding and removing multicast addresses seems to work. 48 * 49 * - Send a test message over loop back multicast and make sure it arrives. 50 * 51 * NB: 52 * 53 * Would be nice to use BPF or if_tap to actually check packet contents and 54 * layout, make sure that the ttl is set right, etc. 55 * 56 * Would be nice if attempts to use multicast options on TCP sockets returned 57 * an error, as the docs suggest it might. 58 */ 59 60 #ifdef WARN_TCP 61 #define WARN_SUCCESS 0x00000001 /* Set for TCP to warn on success. */ 62 #else 63 #define WARN_SUCCESS 0x00000000 64 #endif 65 66 /* 67 * Multicast test address, picked arbitrarily. Will be used with the 68 * loopback interface. 69 */ 70 #define TEST_MADDR "224.100.100.100" 71 72 /* 73 * Test that a given IP socket option (optname) has a default value of 74 * 'defaultv', that we can set it to 'modifiedv', and use 'fakev' as a dummy 75 * value that shouldn't be returned at any point during the tests. Perform 76 * the tests on the raw socket, tcp socket, and upd socket passed. 77 * 'optstring' is used in printing warnings and errors as needed. 78 */ 79 static void 80 test_u_char(int optname, const char *optstring, u_char defaultv, 81 u_char modifiedv, u_char fakev, const char *socktype, int sock, 82 int flags) 83 { 84 socklen_t socklen; 85 u_char uc; 86 int ret; 87 88 /* 89 * Check that we read back the expected default. 90 */ 91 uc = fakev; 92 socklen = sizeof(uc); 93 94 ret = getsockopt(sock, IPPROTO_IP, optname, &uc, &socklen); 95 if (ret < 0) 96 err(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s)", 97 socktype, optstring); 98 if (ret == 0 && (flags & WARN_SUCCESS)) 99 warnx("WARN: getsockopt(%s, IPPROTO_IP, %s) returned 0", 100 socktype, optstring); 101 if (uc != defaultv) 102 errx(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s) default is " 103 "%d not %d", socktype, optstring, uc, defaultv); 104 105 /* 106 * Set to a modifiedv value, read it back and make sure it got there. 107 */ 108 uc = modifiedv; 109 ret = setsockopt(sock, IPPROTO_IP, optname, &uc, sizeof(uc)); 110 if (ret == -1) 111 err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, %s)", 112 socktype, optstring); 113 if (ret == 0 && (flags & WARN_SUCCESS)) 114 warnx("WARN: setsockopt(%s, IPPROTO_IP, %s) returned 0", 115 socktype, optstring); 116 117 uc = fakev; 118 socklen = sizeof(uc); 119 ret = getsockopt(sock, IPPROTO_IP, optname, &uc, &socklen); 120 if (ret < 0) 121 err(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s)", 122 socktype, optstring); 123 if (ret == 0 && (flags & WARN_SUCCESS)) 124 warnx("WARN: getsockopt(%s, IPPROTO_IP, %s) returned 0", 125 socktype, optstring); 126 if (uc != modifiedv) 127 errx(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s) set value is " 128 "%d not %d", socktype, optstring, uc, modifiedv); 129 } 130 131 /* 132 * test_in_addr() is like test_u_char(), only it runs on a struct in_addr 133 * (surprise). 134 */ 135 static void 136 test_in_addr(int optname, const char *optstring, struct in_addr defaultv, 137 struct in_addr modifiedv, struct in_addr fakev, const char *socktype, 138 int sock, int flags) 139 { 140 socklen_t socklen; 141 struct in_addr ia; 142 int ret; 143 144 /* 145 * Check that we read back the expected default. 146 */ 147 ia = fakev; 148 socklen = sizeof(ia); 149 150 ret = getsockopt(sock, IPPROTO_IP, optname, &ia, &socklen); 151 if (ret < 0) 152 err(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s)", 153 socktype, optstring); 154 if (ret == 0 && (flags & WARN_SUCCESS)) 155 warnx("WARN: getsockopt(%s, IPPROTO_IP, %s) returned 0", 156 socktype, optstring); 157 if (memcmp(&ia, &defaultv, sizeof(struct in_addr))) 158 errx(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s) default is " 159 "%s not %s", socktype, optstring, inet_ntoa(ia), 160 inet_ntoa(defaultv)); 161 162 /* 163 * Set to a modifiedv value, read it back and make sure it got there. 164 */ 165 ia = modifiedv; 166 ret = setsockopt(sock, IPPROTO_IP, optname, &ia, sizeof(ia)); 167 if (ret == -1) 168 err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, %s)", 169 socktype, optstring); 170 if (ret == 0 && (flags & WARN_SUCCESS)) 171 warnx("WARN: setsockopt(%s, IPPROTO_IP, %s) returned 0", 172 socktype, optstring); 173 174 ia = fakev; 175 socklen = sizeof(ia); 176 ret = getsockopt(sock, IPPROTO_IP, optname, &ia, &socklen); 177 if (ret < 0) 178 err(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s)", 179 socktype, optstring); 180 if (ret == 0 && (flags & WARN_SUCCESS)) 181 warnx("WARN: getsockopt(%s, IPPROTO_IP, %s) returned 0", 182 socktype, optstring); 183 if (memcmp(&ia, &modifiedv, sizeof(struct in_addr))) 184 errx(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s) set value is " 185 "%s not %s", socktype, optstring, inet_ntoa(ia), 186 inet_ntoa(modifiedv)); 187 } 188 189 static void 190 test_ttl(int raw_sock, int tcp_sock, int udp_sock) 191 { 192 193 test_u_char(IP_MULTICAST_TTL, "IP_MULTICAST_TTL", 1, 2, 243, 194 "raw_sock", raw_sock, 0); 195 test_u_char(IP_MULTICAST_TTL, "IP_MULTICAST_TTL", 1, 2, 243, 196 "tcp_sock", tcp_sock, WARN_SUCCESS); 197 test_u_char(IP_MULTICAST_TTL, "IP_MULTICAST_TTL", 1, 2, 243, 198 "udp_sock", udp_sock, 0); 199 } 200 201 static void 202 test_loop(int raw_sock, int tcp_sock, int udp_sock) 203 { 204 205 test_u_char(IP_MULTICAST_LOOP, "IP_MULTICAST_LOOP", 1, 0, 243, 206 "raw_sock", raw_sock, 0); 207 test_u_char(IP_MULTICAST_LOOP, "IP_MULTICAST_LOOP", 1, 0, 243, 208 "tcp_sock", tcp_sock, WARN_SUCCESS); 209 test_u_char(IP_MULTICAST_LOOP, "IP_MULTICAST_LOOP", 1, 0, 243, 210 "udp_sock", udp_sock, 0); 211 } 212 213 static void 214 test_if(int raw_sock, int tcp_sock, int udp_sock) 215 { 216 struct in_addr defaultv, modifiedv, fakev; 217 218 defaultv.s_addr = inet_addr("0.0.0.0"); 219 220 /* Should be valid on all hosts. */ 221 modifiedv.s_addr = inet_addr("127.0.0.1"); 222 223 /* Should not happen. */ 224 fakev.s_addr = inet_addr("255.255.255.255"); 225 226 test_in_addr(IP_MULTICAST_IF, "IP_MULTICAST_IF", defaultv, modifiedv, 227 fakev, "raw_sock", raw_sock, 0); 228 test_in_addr(IP_MULTICAST_IF, "IP_MULTICAST_IF", defaultv, modifiedv, 229 fakev, "tcp_sock", tcp_sock, WARN_SUCCESS); 230 test_in_addr(IP_MULTICAST_IF, "IP_MULTICAST_IF", defaultv, modifiedv, 231 fakev, "udp_sock", udp_sock, 0); 232 } 233 234 /* 235 * Add a multicast address to an interface. Warn if appropriate. No query 236 * interface so can't check if it's there directly; instead we have to try 237 * to add it a second time and make sure we get back EADDRINUSE. 238 */ 239 static void 240 test_add_multi(int sock, const char *socktype, struct ip_mreq imr, 241 int flags) 242 { 243 char buf[128]; 244 int ret; 245 246 ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, 247 sizeof(imr)); 248 if (ret < 0) { 249 strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); 250 err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_ADD_MEMBERSHIP " 251 "%s, %s)", socktype, buf, inet_ntoa(imr.imr_interface)); 252 } 253 if (ret == 0 && (flags & WARN_SUCCESS)) { 254 strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); 255 warnx("WARN: setsockopt(%s, IPPROTO_IP, IP_ADD_MEMBERSHIP " 256 "%s, %s) returned 0", socktype, buf, 257 inet_ntoa(imr.imr_interface)); 258 } 259 260 /* Try to add a second time to make sure it got there. */ 261 ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, 262 sizeof(imr)); 263 if (ret == 0) { 264 strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); 265 err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_ADD_MEMBERSHIP " 266 "%s, %s) dup returned 0", socktype, buf, 267 inet_ntoa(imr.imr_interface)); 268 } 269 if (ret < 0 && errno != EADDRINUSE) { 270 strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); 271 err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_ADD_MEMBERSHIP " 272 "%s, %s)", socktype, buf, inet_ntoa(imr.imr_interface)); 273 } 274 } 275 276 /* 277 * Drop a multicast address from an interface. Warn if appropriate. No 278 * query interface so can't check if it's gone directly; instead we have to 279 * try to drop it a second time and make sure we get back EADDRNOTAVAIL. 280 */ 281 static void 282 test_drop_multi(int sock, const char *socktype, struct ip_mreq imr, 283 int flags) 284 { 285 char buf[128]; 286 int ret; 287 288 ret = setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, 289 sizeof(imr)); 290 if (ret < 0) { 291 strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); 292 err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_DROP_MEMBERSHIP " 293 "%s, %s)", socktype, buf, inet_ntoa(imr.imr_interface)); 294 } 295 if (ret == 0 && (flags & WARN_SUCCESS)) { 296 strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); 297 warnx("WARN: setsockopt(%s, IPPROTO_IP, IP_DROP_MEMBERSHIP " 298 "%s, %s) returned 0", socktype, buf, 299 inet_ntoa(imr.imr_interface)); 300 } 301 302 /* Try a second time to make sure it's gone. */ 303 ret = setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, 304 sizeof(imr)); 305 if (ret == 0) { 306 strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); 307 err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_DROP_MEMBERSHIP " 308 "%s, %s) returned 0", socktype, buf, 309 inet_ntoa(imr.imr_interface)); 310 } 311 if (ret < 0 && errno != EADDRNOTAVAIL) { 312 strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); 313 err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_DROP_MEMBERSHIP " 314 "%s, %s)", socktype, buf, inet_ntoa(imr.imr_interface)); 315 } 316 } 317 318 /* 319 * Should really also test trying to add an invalid address, delete one 320 * that's not there, etc. 321 */ 322 static void 323 test_addr(int raw_sock, int tcp_sock, int udp_sock) 324 { 325 struct ip_mreq imr; 326 327 /* Arbitrary. */ 328 imr.imr_multiaddr.s_addr = inet_addr(TEST_MADDR); 329 330 /* Localhost should be OK. */ 331 imr.imr_interface.s_addr = inet_addr("127.0.0.1"); 332 333 test_add_multi(raw_sock, "raw_sock", imr, 0); 334 test_drop_multi(raw_sock, "raw_sock", imr, 0); 335 336 test_add_multi(tcp_sock, "raw_sock", imr, WARN_SUCCESS); 337 test_drop_multi(tcp_sock, "raw_sock", imr, WARN_SUCCESS); 338 339 test_add_multi(udp_sock, "raw_sock", imr, 0); 340 test_drop_multi(udp_sock, "raw_sock", imr, 0); 341 } 342 343 /* 344 * Test an actual simple UDP message - send a single byte to an address we're 345 * subscribed to, and hope to get it back. We create a new UDP socket for 346 * this purpose because we will need to bind it. 347 */ 348 #define UDP_PORT 5012 349 static void 350 test_udp(void) 351 { 352 struct sockaddr_in sin; 353 struct ip_mreq imr; 354 struct in_addr if_addr; 355 char message; 356 ssize_t len; 357 int sock; 358 359 sock = socket(PF_INET, SOCK_DGRAM, 0); 360 if (sock < 0) 361 err(-1, "FAIL: test_udp: socket(PF_INET, SOCK_DGRAM)"); 362 363 if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) 364 err(-1, "FAIL: test_udp: fcntl(F_SETFL, O_NONBLOCK)"); 365 366 bzero(&sin, sizeof(sin)); 367 sin.sin_len = sizeof(sin); 368 sin.sin_family = AF_INET; 369 sin.sin_port = htons(UDP_PORT); 370 sin.sin_addr.s_addr = inet_addr(TEST_MADDR); 371 372 if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) 373 err(-1, "FAIL: test_udp: bind(udp_sock, 127.0.0.1:%d", 374 UDP_PORT); 375 376 /* Arbitrary. */ 377 imr.imr_multiaddr.s_addr = inet_addr(TEST_MADDR); 378 379 /* Localhost should be OK. */ 380 imr.imr_interface.s_addr = inet_addr("127.0.0.1"); 381 382 /* 383 * Tell socket what interface to send on -- use localhost. 384 */ 385 if_addr.s_addr = inet_addr("127.0.0.1"); 386 if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &if_addr, 387 sizeof(if_addr)) < 0) 388 err(-1, "test_udp: setsockopt(IPPROTO_IP, IP_MULTICAST_IF)"); 389 390 test_add_multi(sock, "udp_sock", imr, 0); 391 392 bzero(&sin, sizeof(sin)); 393 sin.sin_len = sizeof(sin); 394 sin.sin_family = AF_INET; 395 sin.sin_port = htons(UDP_PORT); 396 sin.sin_addr.s_addr = inet_addr(TEST_MADDR); 397 398 message = 'A'; 399 len = sizeof(message); 400 len = sendto(sock, &message, len, 0, (struct sockaddr *)&sin, 401 sizeof(sin)); 402 if (len < 0) 403 err(-1, "test_udp: sendto"); 404 405 if (len != sizeof(message)) 406 errx(-1, "test_udp: sendto: expected to send %d, instead %d", 407 sizeof(message), len); 408 409 message = 'B'; 410 len = sizeof(sin); 411 len = recvfrom(sock, &message, sizeof(message), 0, 412 (struct sockaddr *)&sin, &len); 413 if (len < 0) 414 err(-1, "test_udp: recvfrom"); 415 416 if (len != sizeof(message)) 417 errx(-1, "test_udp: recvfrom: len %d != message len %d", 418 len, sizeof(message)); 419 420 if (message != 'A') 421 errx(-1, "test_udp: recvfrom: expected 'A', got '%c'", 422 message); 423 424 test_drop_multi(sock, "udp_sock", imr, 0); 425 426 close(sock); 427 } 428 #undef UDP_PORT 429 430 int 431 main(int argc, char *argv[]) 432 { 433 int raw_sock, tcp_sock, udp_sock; 434 435 if (geteuid() != 0) 436 errx(-1, "FAIL: root privilege required"); 437 438 raw_sock = socket(PF_INET, SOCK_RAW, 0); 439 if (raw_sock == -1) 440 err(-1, "FAIL: socket(PF_INET, SOCK_RAW)"); 441 442 tcp_sock = socket(PF_INET, SOCK_STREAM, 0); 443 if (raw_sock == -1) 444 err(-1, "FAIL: socket(PF_INET, SOCK_STREAM)"); 445 446 udp_sock = socket(PF_INET, SOCK_DGRAM, 0); 447 if (raw_sock == -1) 448 err(-1, "FAIL: socket(PF_INET, SOCK_DGRAM)"); 449 450 test_ttl(raw_sock, tcp_sock, udp_sock); 451 test_loop(raw_sock, tcp_sock, udp_sock); 452 test_if(raw_sock, tcp_sock, udp_sock); 453 test_addr(raw_sock, tcp_sock, udp_sock); 454 455 close(udp_sock); 456 close(tcp_sock); 457 close(raw_sock); 458 459 test_udp(); 460 461 return (0); 462 } 463