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
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
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
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
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
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
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
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
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
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
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