xref: /freebsd/tools/regression/netinet/msocket/msocket.c (revision d0b2dbfa0ecf2bbc9709efc5e20baf8e4b44bbbf)
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