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 */
81static void
82test_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 */
137static void
138test_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
191static void
192test_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
203static void
204test_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
215static void
216test_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 */
241static void
242test_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 */
283static void
284test_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 */
324static void
325test_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
351static void
352test_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
432int
433main(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