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
test_u_char(int optname,const char * optstring,u_char defaultv,u_char modifiedv,u_char fakev,const char * socktype,int sock,int flags)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
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)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
test_ttl(int raw_sock,int tcp_sock,int udp_sock)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
test_loop(int raw_sock,int tcp_sock,int udp_sock)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
test_if(int raw_sock,int tcp_sock,int udp_sock)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
test_add_multi(int sock,const char * socktype,struct ip_mreq imr,int flags)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
test_drop_multi(int sock,const char * socktype,struct ip_mreq imr,int flags)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
test_addr(int raw_sock,int tcp_sock,int udp_sock)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
test_udp(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
main(int argc,char * argv[])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