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