Home | History | Annotate | Line # | Download | only in mcast
mcast.c revision 1.4
      1 /*	$NetBSD: mcast.c,v 1.4 2017/02/28 09:23:23 ozaki-r Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2014 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Christos Zoulas.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 #include <sys/cdefs.h>
     32 #ifdef __RCSID
     33 __RCSID("$NetBSD: mcast.c,v 1.4 2017/02/28 09:23:23 ozaki-r Exp $");
     34 #else
     35 extern const char *__progname;
     36 #define getprogname() __progname
     37 #endif
     38 
     39 #include <sys/types.h>
     40 #include <sys/socket.h>
     41 #include <sys/wait.h>
     42 #include <sys/time.h>
     43 #include <netinet/in.h>
     44 
     45 #include <assert.h>
     46 #include <netdb.h>
     47 #include <time.h>
     48 #include <signal.h>
     49 #include <stdio.h>
     50 #include <string.h>
     51 #include <stdlib.h>
     52 #include <unistd.h>
     53 #include <err.h>
     54 #include <errno.h>
     55 #include <poll.h>
     56 #include <stdbool.h>
     57 
     58 #ifdef ATF
     59 #include <atf-c.h>
     60 
     61 #define ERRX(ev, msg, ...)	ATF_REQUIRE_MSG(0, msg, __VA_ARGS__)
     62 #define ERRX0(ev, msg)		ATF_REQUIRE_MSG(0, msg)
     63 
     64 #define SKIPX(ev, msg, ...)	do {			\
     65 	atf_tc_skip(msg, __VA_ARGS__);			\
     66 	return;						\
     67 } while(/*CONSTCOND*/0)
     68 
     69 #else
     70 #define ERRX(ev, msg, ...)	errx(ev, msg, __VA_ARGS__)
     71 #define ERRX0(ev, msg)		errx(ev, msg)
     72 #define SKIPX(ev, msg, ...)	errx(ev, msg, __VA_ARGS__)
     73 #endif
     74 
     75 static int debug;
     76 static int nsleep;
     77 
     78 #define TOTAL 10
     79 #define PORT_V4MAPPED "6666"
     80 #define HOST_V4MAPPED "::FFFF:239.1.1.1"
     81 #define PORT_V4 "6666"
     82 #define HOST_V4 "239.1.1.1"
     83 #define PORT_V6 "6666"
     84 #define HOST_V6 "FF05:1:0:0:0:0:0:1"
     85 
     86 struct message {
     87 	size_t seq;
     88 	struct timespec ts;
     89 };
     90 
     91 static int
     92 addmc(int s, struct addrinfo *ai, bool bug)
     93 {
     94 	struct ip_mreq m4;
     95 	struct ipv6_mreq m6;
     96 	struct sockaddr_in *s4;
     97 	struct sockaddr_in6 *s6;
     98 	unsigned int ifc;
     99 
    100 	switch (ai->ai_family) {
    101 	case AF_INET:
    102 		s4 = (void *)ai->ai_addr;
    103 		assert(sizeof(*s4) == ai->ai_addrlen);
    104 		m4.imr_multiaddr = s4->sin_addr;
    105 		m4.imr_interface.s_addr = htonl(INADDR_ANY);
    106 		return setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
    107 		    &m4, sizeof(m4));
    108 	case AF_INET6:
    109 		s6 = (void *)ai->ai_addr;
    110 		/*
    111 		 * Linux:	Does not support the v6 ioctls on v4 mapped
    112 		 *		sockets but it does support the v4 ones and
    113 		 *		it works.
    114 		 * MacOS/X:	Supports the v6 ioctls on v4 mapped sockets,
    115 		 *		but does not work and also does not support
    116 		 *		the v4 ioctls. So no way to make multicasting
    117 		 *		work with mapped addresses.
    118 		 * NetBSD:	Supports both and works for both.
    119 		 */
    120 		if (bug && IN6_IS_ADDR_V4MAPPED(&s6->sin6_addr)) {
    121 			memcpy(&m4.imr_multiaddr, &s6->sin6_addr.s6_addr[12],
    122 			    sizeof(m4.imr_multiaddr));
    123 			m4.imr_interface.s_addr = htonl(INADDR_ANY);
    124 			return setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
    125 			    &m4, sizeof(m4));
    126 		}
    127 		assert(sizeof(*s6) == ai->ai_addrlen);
    128 		memset(&m6, 0, sizeof(m6));
    129 #if 0
    130 		ifc = 1;
    131 		if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
    132 		    &ifc, sizeof(ifc)) == -1)
    133 			return -1;
    134 		ifc = 224;
    135 		if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
    136 		    &ifc, sizeof(ifc)) == -1)
    137 			return -1;
    138 		ifc = 1; /* XXX should pick a proper interface */
    139 		if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifc,
    140 		    sizeof(ifc)) == -1)
    141 			return -1;
    142 #else
    143 		ifc = 0; /* Let pick an appropriate interface */
    144 #endif
    145 		m6.ipv6mr_interface = ifc;
    146 		m6.ipv6mr_multiaddr = s6->sin6_addr;
    147 		return setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP,
    148 		    &m6, sizeof(m6));
    149 	default:
    150 		errno = EOPNOTSUPP;
    151 		return -1;
    152 	}
    153 }
    154 
    155 static int
    156 allowv4mapped(int s, struct addrinfo *ai)
    157 {
    158 	struct sockaddr_in6 *s6;
    159 	int zero = 0;
    160 
    161 	if (ai->ai_family != AF_INET6)
    162 		return 0;
    163 
    164 	s6 = (void *)ai->ai_addr;
    165 
    166 	if (!IN6_IS_ADDR_V4MAPPED(&s6->sin6_addr))
    167 		return 0;
    168 	return setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero));
    169 }
    170 
    171 static struct sockaddr_storage ss;
    172 static int
    173 connector(int fd, const struct sockaddr *sa, socklen_t slen)
    174 {
    175 	assert(sizeof(ss) > slen);
    176 	memcpy(&ss, sa, slen);
    177 	return 0;
    178 }
    179 
    180 static void
    181 show(const char *prefix, const struct message *msg)
    182 {
    183 	printf("%10.10s: %zu [%jd.%ld]\n", prefix, msg->seq, (intmax_t)
    184 	    msg->ts.tv_sec, msg->ts.tv_nsec);
    185 }
    186 
    187 static int
    188 getsocket(const char *host, const char *port,
    189     int (*f)(int, const struct sockaddr *, socklen_t), socklen_t *slen,
    190     bool bug)
    191 {
    192 	int e, s, lasterrno = 0;
    193 	struct addrinfo hints, *ai0, *ai;
    194 	const char *cause = "?";
    195 
    196 	memset(&hints, 0, sizeof(hints));
    197 	hints.ai_family = AF_UNSPEC;
    198 	hints.ai_socktype = SOCK_DGRAM;
    199 	e = getaddrinfo(host, port, &hints, &ai0);
    200 	if (e)
    201 		ERRX(EXIT_FAILURE, "Can't resolve %s:%s (%s)", host, port,
    202 		    gai_strerror(e));
    203 
    204 	s = -1;
    205 	for (ai = ai0; ai; ai = ai->ai_next) {
    206 		s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
    207 		if (s == -1) {
    208 			lasterrno = errno;
    209 			cause = "socket";
    210 			continue;
    211 		}
    212 		if (allowv4mapped(s, ai) == -1) {
    213 			cause = "allow v4 mapped";
    214 			goto out;
    215 		}
    216 		if ((*f)(s, ai->ai_addr, ai->ai_addrlen) == -1) {
    217 			cause = f == bind ? "bind" : "connect";
    218 			goto out;
    219 		}
    220 		if ((f == bind || f == connector) && addmc(s, ai, bug) == -1) {
    221 			cause = "join group";
    222 			goto out;
    223 		}
    224 		*slen = ai->ai_addrlen;
    225 		break;
    226 out:
    227 		lasterrno = errno;
    228 		close(s);
    229 		s = -1;
    230 		continue;
    231 	}
    232 	freeaddrinfo(ai0);
    233 	if (s == -1)
    234 		ERRX(EXIT_FAILURE, "%s (%s)", cause, strerror(lasterrno));
    235 	return s;
    236 }
    237 
    238 static int
    239 synchronize(const int fd, bool waiter)
    240 {
    241 	int syncmsg = 0;
    242 	int r;
    243 	struct pollfd pfd;
    244 
    245 	if (waiter) {
    246 		pfd.fd = fd;
    247 		pfd.events = POLLIN;
    248 
    249 		/* We use poll to avoid lock up when the peer died unexpectedly */
    250 		r = poll(&pfd, 1, 10000);
    251 		if (r == -1)
    252 			ERRX(EXIT_FAILURE, "poll (%s)", strerror(errno));
    253 		if (r == 0)
    254 			/* Timed out */
    255 			return -1;
    256 
    257 		if (read(fd, &syncmsg, sizeof(syncmsg)) == -1)
    258 			ERRX(EXIT_FAILURE, "read (%s)", strerror(errno));
    259 	} else {
    260 		if (write(fd, &syncmsg, sizeof(syncmsg)) == -1)
    261 			ERRX(EXIT_FAILURE, "write (%s)", strerror(errno));
    262 	}
    263 
    264 	return 0;
    265 }
    266 
    267 static int
    268 sender(const int fd, const char *host, const char *port, size_t n, bool conn,
    269     bool bug)
    270 {
    271 	int s;
    272 	ssize_t l;
    273 	struct message msg;
    274 
    275 	socklen_t slen;
    276 
    277 	s = getsocket(host, port, conn ? connect : connector, &slen, bug);
    278 
    279 	/* Wait until receiver gets ready. */
    280 	if (synchronize(fd, true) == -1)
    281 		return -1;
    282 
    283 	for (msg.seq = 0; msg.seq < n; msg.seq++) {
    284 #ifdef CLOCK_MONOTONIC
    285 		if (clock_gettime(CLOCK_MONOTONIC, &msg.ts) == -1)
    286 			ERRX(EXIT_FAILURE, "clock (%s)", strerror(errno));
    287 #else
    288 		struct timeval tv;
    289 		if (gettimeofday(&tv, NULL) == -1)
    290 			ERRX(EXIT_FAILURE, "clock (%s)", strerror(errno));
    291 		msg.ts.tv_sec = tv.tv_sec;
    292 		msg.ts.tv_nsec = tv.tv_usec * 1000;
    293 #endif
    294 		if (debug)
    295 			show("sending", &msg);
    296 		l = conn ? send(s, &msg, sizeof(msg), 0) :
    297 		    sendto(s, &msg, sizeof(msg), 0, (void *)&ss, slen);
    298 		if (l == -1)
    299 			ERRX(EXIT_FAILURE, "send (%s)", strerror(errno));
    300 		usleep(100);
    301 	}
    302 
    303 	/* Wait until receiver finishes its work. */
    304 	if (synchronize(fd, true) == -1)
    305 		return -1;
    306 
    307 	return 0;
    308 }
    309 
    310 static void
    311 receiver(const int fd, const char *host, const char *port, size_t n, bool conn,
    312     bool bug)
    313 {
    314 	int s;
    315 	ssize_t l;
    316 	size_t seq;
    317 	struct message msg;
    318 	struct pollfd pfd;
    319 	socklen_t slen;
    320 
    321 	s = getsocket(host, port, bind, &slen, bug);
    322 	pfd.fd = s;
    323 	pfd.events = POLLIN;
    324 
    325 	/* Tell I'm ready */
    326 	synchronize(fd, false);
    327 
    328 	for (seq = 0; seq < n; seq++) {
    329 		if (poll(&pfd, 1, 10000) == -1)
    330 			ERRX(EXIT_FAILURE, "poll (%s)", strerror(errno));
    331 		l = conn ? recv(s, &msg, sizeof(msg), 0) :
    332 		    recvfrom(s, &msg, sizeof(msg), 0, (void *)&ss, &slen);
    333 		if (l == -1)
    334 			ERRX(EXIT_FAILURE, "recv (%s)", strerror(errno));
    335 		if (debug)
    336 			show("got", &msg);
    337 		if (seq != msg.seq)
    338 			ERRX(EXIT_FAILURE, "seq: expect=%zu actual=%zu",
    339 			    seq, msg.seq);
    340 	}
    341 
    342 	if (nsleep)
    343 		sleep(nsleep);
    344 	/* Tell I'm finished */
    345 	synchronize(fd, false);
    346 }
    347 
    348 static void
    349 run(const char *host, const char *port, size_t n, bool conn, bool bug)
    350 {
    351 	pid_t pid;
    352 	int status;
    353 	int syncfds[2];
    354 	int error;
    355 
    356 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, syncfds) == -1)
    357 		ERRX(EXIT_FAILURE, "socketpair (%s)", strerror(errno));
    358 
    359 	switch ((pid = fork())) {
    360 	case 0:
    361 		receiver(syncfds[0], host, port, n, conn, bug);
    362 		return;
    363 	case -1:
    364 		ERRX(EXIT_FAILURE, "fork (%s)", strerror(errno));
    365 	default:
    366 		error = sender(syncfds[1], host, port, n, conn, bug);
    367 	again:
    368 		switch (waitpid(pid, &status, WNOHANG)) {
    369 		case -1:
    370 			ERRX(EXIT_FAILURE, "wait (%s)", strerror(errno));
    371 		case 0:
    372 			if (error == 0)
    373 				/*
    374 				 * Receiver is still alive, but we know
    375 				 * it will exit soon.
    376 				 */
    377 				goto again;
    378 
    379 			if (kill(pid, SIGTERM) == -1)
    380 				ERRX(EXIT_FAILURE, "kill (%s)",
    381 				    strerror(errno));
    382 			goto again;
    383 		default:
    384 			if (WIFSIGNALED(status)) {
    385 				if (WTERMSIG(status) == SIGTERM)
    386 					ERRX0(EXIT_FAILURE,
    387 					    "receiver failed and was killed" \
    388 					    "by sender");
    389 				else
    390 					ERRX(EXIT_FAILURE,
    391 					    "receiver got signaled (%s)",
    392 					    strsignal(WTERMSIG(status)));
    393 			} else if (WIFEXITED(status)) {
    394 				if (WEXITSTATUS(status) != 0)
    395 					ERRX(EXIT_FAILURE,
    396 					    "receiver exited with status %d",
    397 					    WEXITSTATUS(status));
    398 			} else {
    399 				ERRX(EXIT_FAILURE,
    400 				    "receiver exited with unexpected status %d",
    401 				    status);
    402 			}
    403 			break;
    404 		}
    405 		return;
    406 	}
    407 }
    408 
    409 #ifndef ATF
    410 int
    411 main(int argc, char *argv[])
    412 {
    413 	const char *host, *port;
    414 	int c;
    415 	size_t n;
    416 	bool conn, bug;
    417 
    418 	host = HOST_V4;
    419 	port = PORT_V4;
    420 	n = TOTAL;
    421 	bug = conn = false;
    422 
    423 	while ((c = getopt(argc, argv, "46bcdmn:s:")) != -1)
    424 		switch (c) {
    425 		case '4':
    426 			host = HOST_V4;
    427 			port = PORT_V4;
    428 			break;
    429 		case '6':
    430 			host = HOST_V6;
    431 			port = PORT_V6;
    432 			break;
    433 		case 'b':
    434 			bug = true;
    435 			break;
    436 		case 'c':
    437 			conn = true;
    438 			break;
    439 		case 'd':
    440 			debug++;
    441 			break;
    442 		case 'm':
    443 			host = HOST_V4MAPPED;
    444 			port = PORT_V4MAPPED;
    445 			break;
    446 		case 'n':
    447 			n = atoi(optarg);
    448 			break;
    449 		case 's':
    450 			nsleep = atoi(optarg);
    451 			break;
    452 		default:
    453 			fprintf(stderr, "Usage: %s [-cdm46] [-n <tot>]"
    454 			    " [-s <sleep>]",
    455 			    getprogname());
    456 			return 1;
    457 		}
    458 
    459 	run(host, port, n, conn, bug);
    460 	return 0;
    461 }
    462 #else
    463 
    464 ATF_TC(conninet4);
    465 ATF_TC_HEAD(conninet4, tc)
    466 {
    467 	atf_tc_set_md_var(tc, "descr", "Checks connected multicast for ipv4");
    468 }
    469 
    470 ATF_TC_BODY(conninet4, tc)
    471 {
    472 	run(HOST_V4, PORT_V4, TOTAL, true, false);
    473 }
    474 
    475 ATF_TC(connmappedinet4);
    476 ATF_TC_HEAD(connmappedinet4, tc)
    477 {
    478 	atf_tc_set_md_var(tc, "descr", "Checks connected multicast for mapped ipv4");
    479 }
    480 
    481 ATF_TC_BODY(connmappedinet4, tc)
    482 {
    483 	run(HOST_V4MAPPED, PORT_V4MAPPED, TOTAL, true, false);
    484 }
    485 
    486 ATF_TC(connmappedbuginet4);
    487 ATF_TC_HEAD(connmappedbuginet4, tc)
    488 {
    489 	atf_tc_set_md_var(tc, "descr", "Checks connected multicast for mapped ipv4 using the v4 ioctls");
    490 }
    491 
    492 ATF_TC_BODY(connmappedbuginet4, tc)
    493 {
    494 	run(HOST_V4MAPPED, PORT_V4MAPPED, TOTAL, true, true);
    495 }
    496 
    497 ATF_TC(conninet6);
    498 ATF_TC_HEAD(conninet6, tc)
    499 {
    500 	atf_tc_set_md_var(tc, "descr", "Checks connected multicast for ipv6");
    501 }
    502 
    503 ATF_TC_BODY(conninet6, tc)
    504 {
    505 	run(HOST_V6, PORT_V6, TOTAL, true, false);
    506 }
    507 
    508 ATF_TC(unconninet4);
    509 ATF_TC_HEAD(unconninet4, tc)
    510 {
    511 	atf_tc_set_md_var(tc, "descr", "Checks unconnected multicast for ipv4");
    512 }
    513 
    514 ATF_TC_BODY(unconninet4, tc)
    515 {
    516 	run(HOST_V4, PORT_V4, TOTAL, false, false);
    517 }
    518 
    519 ATF_TC(unconnmappedinet4);
    520 ATF_TC_HEAD(unconnmappedinet4, tc)
    521 {
    522 	atf_tc_set_md_var(tc, "descr", "Checks unconnected multicast for mapped ipv4");
    523 }
    524 
    525 ATF_TC_BODY(unconnmappedinet4, tc)
    526 {
    527 	run(HOST_V4MAPPED, PORT_V4MAPPED, TOTAL, false, false);
    528 }
    529 
    530 ATF_TC(unconnmappedbuginet4);
    531 ATF_TC_HEAD(unconnmappedbuginet4, tc)
    532 {
    533 	atf_tc_set_md_var(tc, "descr", "Checks unconnected multicast for mapped ipv4 using the v4 ioctls");
    534 }
    535 
    536 ATF_TC_BODY(unconnmappedbuginet4, tc)
    537 {
    538 	run(HOST_V4MAPPED, PORT_V4MAPPED, TOTAL, false, true);
    539 }
    540 
    541 ATF_TC(unconninet6);
    542 ATF_TC_HEAD(unconninet6, tc)
    543 {
    544 	atf_tc_set_md_var(tc, "descr", "Checks unconnected multicast for ipv6");
    545 }
    546 
    547 ATF_TC_BODY(unconninet6, tc)
    548 {
    549 	run(HOST_V6, PORT_V6, TOTAL, false, false);
    550 }
    551 
    552 ATF_TP_ADD_TCS(tp)
    553 {
    554 	debug++;
    555 	ATF_TP_ADD_TC(tp, conninet4);
    556 	ATF_TP_ADD_TC(tp, connmappedinet4);
    557 	ATF_TP_ADD_TC(tp, connmappedbuginet4);
    558 	ATF_TP_ADD_TC(tp, conninet6);
    559 	ATF_TP_ADD_TC(tp, unconninet4);
    560 	ATF_TP_ADD_TC(tp, unconnmappedinet4);
    561 	ATF_TP_ADD_TC(tp, unconnmappedbuginet4);
    562 	ATF_TP_ADD_TC(tp, unconninet6);
    563 
    564 	return atf_no_error();
    565 }
    566 #endif
    567