summaryrefslogtreecommitdiff
path: root/regress/sys/netinet
diff options
context:
space:
mode:
Diffstat (limited to 'regress/sys/netinet')
-rw-r--r--regress/sys/netinet/mcast/Makefile195
-rw-r--r--regress/sys/netinet/mcast/README31
-rw-r--r--regress/sys/netinet/mcast/mcrecv.c171
-rw-r--r--regress/sys/netinet/mcast/mcroute.c283
-rw-r--r--regress/sys/netinet/mcast/mcsend.c154
5 files changed, 834 insertions, 0 deletions
diff --git a/regress/sys/netinet/mcast/Makefile b/regress/sys/netinet/mcast/Makefile
new file mode 100644
index 00000000000..8937666eaf1
--- /dev/null
+++ b/regress/sys/netinet/mcast/Makefile
@@ -0,0 +1,195 @@
+# $OpenBSD: Makefile,v 1.1.1.1 2019/09/02 22:17:28 bluhm Exp $
+
+PROGS = mcsend mcrecv mcroute
+WARNINGS = Yes
+CLEANFILES = stamp-* *.log
+MSG !!= echo $$RANDOM
+
+REGRESS_SETUP_ONCE = setup-sudo
+setup-sudo:
+ ${SUDO} true
+.if ! empty(REMOTE_SSH)
+ ssh -t ${REMOTE_SSH} ${SUDO} true
+.endif
+.if ! empty(TARGET_SSH)
+ ssh -t ${TARGET_SSH} ${SUDO} true
+.endif
+
+REGRESS_TARGETS += run-localhost
+run-localhost:
+ @echo '\n======== $@ ========'
+ # send over localhost interface
+ ./mcrecv -f recv.log -i 127.0.0.1 -r 5 -- \
+ ./mcsend -f send.log -i 127.0.0.1 -m '${MSG}'
+ grep '> ${MSG}$$' send.log
+ grep '< ${MSG}$$' recv.log
+
+REGRESS_TARGETS += run-localhost-loop
+run-localhost-loop:
+ @echo '\n======== $@ ========'
+ # explicitly enable loop back on multicast interface
+ ./mcrecv -f recv.log -i 127.0.0.1 -r 5 -- \
+ ./mcsend -f send.log -i 127.0.0.1 -l 1 -m '${MSG}'
+ grep '> ${MSG}$$' send.log
+ grep '< ${MSG}$$' recv.log
+
+REGRESS_TARGETS += run-localhost-loop0
+run-localhost-loop0:
+ @echo '\n======== $@ ========'
+ # disable loop back on multicast interface, must fail
+ ./mcrecv -f recv.log -i 127.0.0.1 -n 1 -- \
+ ./mcsend -f send.log -i 127.0.0.1 -l 0 -m '${MSG}'
+ grep '> ${MSG}$$' send.log
+ ! grep '< ' recv.log
+
+REGRESS_TARGETS += run-localhost-ttl0
+run-localhost-ttl0:
+ @echo '\n======== $@ ========'
+ # send over localhost interface
+ ./mcrecv -f recv.log -i 127.0.0.1 -r 5 -- \
+ ./mcsend -f send.log -i 127.0.0.1 -m '${MSG}' -t 0
+ grep '> ${MSG}$$' send.log
+ grep '< ${MSG}$$' recv.log
+
+REGRESS_TARGETS += run-localaddr
+run-localaddr:
+ @echo '\n======== $@ ========'
+ # send over a local physical interface
+ ./mcrecv -f recv.log -i ${LOCAL_ADDR} -r 5 -- \
+ ./mcsend -f send.log -i ${LOCAL_ADDR} -m '${MSG}'
+ grep '> ${MSG}$$' send.log
+ grep '< ${MSG}$$' recv.log
+
+REGRESS_TARGETS += run-localaddr-loop0
+run-localaddr-loop0:
+ @echo '\n======== $@ ========'
+ # send over physical interface to loopback, ttl is 0
+ ./mcrecv -f recv.log -i ${LOCAL_ADDR} -n 1 -- \
+ ./mcsend -f send.log -i ${LOCAL_ADDR} -l 0 -m '${MSG}'
+ grep '> ${MSG}$$' send.log
+ ! grep '< ' recv.log
+
+REGRESS_TARGETS += run-localaddr-ttl0
+run-localaddr-ttl0:
+ @echo '\n======== $@ ========'
+ # send over physical interface to loopback, ttl is 0
+ ./mcrecv -f recv.log -i ${LOCAL_ADDR} -r 5 -- \
+ ./mcsend -f send.log -i ${LOCAL_ADDR} -m '${MSG}' -t 0
+ grep '> ${MSG}$$' send.log
+ grep '< ${MSG}$$' recv.log
+
+REGRESS_TARGETS += run-remoteaddr
+run-remoteaddr:
+ @echo '\n======== $@ ========'
+ # send over a local physical interface
+ ./mcrecv -f recv.log -i ${LOCAL_ADDR} -r 5 -- \
+ ssh ${REMOTE_SSH} ${.OBJDIR}/mcsend \
+ -f send.log -i ${REMOTE_ADDR} -m '${MSG}'
+ grep '< ${MSG}$$' recv.log
+
+REGRESS_TARGETS += run-remoteaddr-loop0
+run-remoteaddr-loop0:
+ @echo '\n======== $@ ========'
+ # send over a local physical interface
+ ./mcrecv -f recv.log -i ${LOCAL_ADDR} -r 5 -- \
+ ssh ${REMOTE_SSH} ${.OBJDIR}/mcsend \
+ -f send.log -i ${REMOTE_ADDR} -l 0 -m '${MSG}'
+ grep '< ${MSG}$$' recv.log
+
+REGRESS_TARGETS += run-remoteaddr-ttl0
+run-remoteaddr-ttl0:
+ @echo '\n======== $@ ========'
+ # send over a local physical interface
+ ./mcrecv -f recv.log -i ${LOCAL_ADDR} -n 2 -- \
+ ssh ${REMOTE_SSH} ${.OBJDIR}/mcsend -f send.log \
+ -i ${REMOTE_ADDR} -m '${MSG}' -t 0
+ ! grep '< ' recv.log
+
+REGRESS_TARGETS += run-forward
+run-forward:
+ @echo '\n======== $@ ========'
+ # start multicast router, start receiver, start sender
+ ssh ${REMOTE_SSH} ${SUDO} pkill mcroute || true
+ ssh ${REMOTE_SSH} ${SUDO} ${.OBJDIR}/mcroute -b -f route.log \
+ -g 224.0.1.123 -i ${OTHER_ADDR} -o ${REMOTE_ADDR} -r 5
+.if empty(TARGET_SSH)
+ ./mcrecv -f recv.log -g 224.0.1.123 -i ${LOCAL_ADDR} -r 5 -- \
+ ./mcsend -f send.log \
+ -g 224.0.1.123 -i ${TARGET_ADDR} -l 0 -m '${MSG}' -t 2
+ grep '> ${MSG}$$' send.log
+.else
+ ./mcrecv -f recv.log -g 224.0.1.123 -i ${LOCAL_ADDR} -r 5 -- \
+ ssh ${TARGET_SSH} ${.OBJDIR}/mcsend -f send.log \
+ -g 224.0.1.123 -i ${TARGET_ADDR} -l 0 -m '${MSG}' -t 2
+.endif
+ grep '< ${MSG}$$' recv.log
+
+REGRESS_TARGETS += run-forward-ttl1
+run-forward-ttl1:
+ @echo '\n======== $@ ========'
+ # try to get ttl 1 over multicast router, must fail
+ ssh ${REMOTE_SSH} ${SUDO} pkill mcroute || true
+ ssh ${REMOTE_SSH} ${SUDO} ${.OBJDIR}/mcroute -b -f route.log \
+ -g 224.0.1.123 -i ${OTHER_ADDR} -o ${REMOTE_ADDR} -n 3
+.if empty(TARGET_SSH)
+ ./mcrecv -f recv.log -g 224.0.1.123 -i ${LOCAL_ADDR} -n 2 -- \
+ ./mcsend -f send.log \
+ -g 224.0.1.123 -i ${TARGET_ADDR} -l 0 -m '${MSG}' -t 1
+ grep '> ${MSG}$$' send.log
+.else
+ ./mcrecv -f recv.log -g 224.0.1.123 -i ${LOCAL_ADDR} -n 2 -- \
+ ssh ${TARGET_SSH} ${.OBJDIR}/mcsend -f send.log \
+ -g 224.0.1.123 -i ${TARGET_ADDR} -l 0 -m '${MSG}' -t 1
+.endif
+ ! grep '< ' recv.log
+
+REGRESS_TARGETS += run-forward-local
+run-forward-local:
+ @echo '\n======== $@ ========'
+ # try to get local multicast group over router, must fail
+ ssh ${REMOTE_SSH} ${SUDO} pkill mcroute || true
+ ssh ${REMOTE_SSH} ${SUDO} ${.OBJDIR}/mcroute -b -f route.log \
+ -g 224.0.0.123 -i ${OTHER_ADDR} -o ${REMOTE_ADDR} -n 3
+.if empty(TARGET_SSH)
+ ./mcrecv -f recv.log -g 224.0.0.123 -i ${LOCAL_ADDR} -n 2 -- \
+ ./mcsend -f send.log \
+ -g 224.0.0.123 -i ${TARGET_ADDR} -l 0 -m '${MSG}' -t 2
+ grep '> ${MSG}$$' send.log
+.else
+ ./mcrecv -f recv.log -g 224.0.0.123 -i ${LOCAL_ADDR} -n 2 -- \
+ ssh ${TARGET_SSH} ${.OBJDIR}/mcsend -f send.log \
+ -g 224.0.0.123 -i ${TARGET_ADDR} -l 0 -m '${MSG}' -t 2
+.endif
+ ! grep '< ' recv.log
+
+stamp-remote-build:
+ ssh ${REMOTE_SSH} ${MAKE} -C ${.CURDIR} ${PROGS}
+ date >$@
+
+stamp-target-build:
+ ssh ${TARGET_SSH} ${MAKE} -C ${.CURDIR} ${PROGS}
+ date >$@
+
+${REGRESS_TARGETS}: ${PROGS}
+${REGRESS_TARGETS:M*-remoteaddr*}: stamp-remote-build
+${REGRESS_TARGETS:M*-forward*}: stamp-remote-build
+.if ! empty(TARGET_SSH)
+${REGRESS_TARGETS:M*-forward*}: stamp-target-build
+.endif
+
+.if empty(LOCAL_ADDR)
+REGRESS_SKIP_TARGETS += ${REGRESS_TARGETS:M*-localaddr*}
+.endif
+.if empty(REMOTE_ADDR) || empty(REMOTE_SSH)
+REGRESS_SKIP_TARGETS += ${REGRESS_TARGETS:M*-remoteaddr*}
+REGRESS_SKIP_TARGETS += ${REGRESS_TARGETS:M*-forward*}
+.endif
+
+check-setup:
+ ! ssh ${REMOTE_SSH} route -n get 224/4
+ ssh ${REMOTE_SSH} sysctl net.inet.ip.mforwarding | fgrep =1
+
+.include <bsd.regress.mk>
+
+stamp-remote-build: ${SRCS}
+stamp-target-build: ${SRCS}
diff --git a/regress/sys/netinet/mcast/README b/regress/sys/netinet/mcast/README
new file mode 100644
index 00000000000..4d4756f27c5
--- /dev/null
+++ b/regress/sys/netinet/mcast/README
@@ -0,0 +1,31 @@
+Test IPv4 multicast packets.
+
+Use two programs to send and receive multicast packets. After
+setting up the socket, the receiver forks and execs the sender to
+avoid races. If the test fails, the receiver runs into a timeout.
+
+The test programs are:
+mcsend - send one multicat UDP packet
+mcrecv - receive one multicast UDP packet
+mcroute - route one multicast UDP packet
+
+The options for mcsend and mcrecv and mcroute are:
+-b fork to background after setup
+-f file print message to log file, default stdout
+-g group multicast group, default 224.0.0.123
+-i ifaddr multicast interface address
+-l loop disable or enable loopback, 0 or 1
+-m message message in payload, maximum 255 characters, default foo
+-n timeout expect not to receive any message until timeout
+-p port destination port number, default 12345
+-o outaddr outgoing interface address
+-r timeout receive timeout in seconds
+-t ttl set multicast ttl
+mcsend ... after setting up receive, fork and exec send command
+
+With mcroute packets are sent over a multicast router. The kernel
+route is installed statically. The machines sender, router, receiver
+are involved. Receiver is on the local machine, route is on remote
+machine. The sender can share the local machine or be started on
+a target machine, depending on the setup. This is controlled via
+environment.
diff --git a/regress/sys/netinet/mcast/mcrecv.c b/regress/sys/netinet/mcast/mcrecv.c
new file mode 100644
index 00000000000..5ba2133928a
--- /dev/null
+++ b/regress/sys/netinet/mcast/mcrecv.c
@@ -0,0 +1,171 @@
+/* $OpenBSD: mcrecv.c,v 1.1.1.1 2019/09/02 22:17:28 bluhm Exp $ */
+/*
+ * Copyright (c) 2019 Alexander Bluhm <bluhm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+void __dead usage(void);
+void sigexit(int);
+
+void __dead
+usage(void)
+{
+ fprintf(stderr,
+"mcrecv [-f file] [-g group] [-i ifaddr] [-n timeout] [-p port] [-r timeout]\n"
+" [mcsend ...]\n"
+" -f file print message to log file, default stdout\n"
+" -g group multicast group, default 224.0.0.123\n"
+" -i ifaddr multicast interface address\n"
+" -n timeout expect not to receive any message until timeout\n"
+" -p port destination port number, default 12345\n"
+" -r timeout receive timeout in seconds\n"
+" mcsend ... after setting up receive, fork and exec send command\n");
+ exit(2);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_in sin;
+ struct ip_mreq mreq;
+ FILE *log;
+ const char *errstr, *file, *group, *ifaddr;
+ char msg[256];
+ ssize_t n;
+ int ch, s, norecv, port, status;
+ unsigned int timeout;
+ pid_t pid;
+
+ log = stdout;
+ file = NULL;
+ group = "224.0.0.123";
+ ifaddr = "0.0.0.0";
+ norecv = 0;
+ port = 12345;
+ timeout = 0;
+ while ((ch = getopt(argc, argv, "f:g:i:n:p:r:")) != -1) {
+ switch (ch) {
+ case 'f':
+ file = optarg;
+ break;
+ case 'g':
+ group = optarg;
+ break;
+ case 'i':
+ ifaddr = optarg;
+ break;
+ case 'n':
+ norecv = 1;
+ timeout = strtonum(optarg, 1, INT_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "no timeout is %s: %s", errstr, optarg);
+ break;
+ case 'p':
+ port = strtonum(optarg, 1, 0xffff, &errstr);
+ if (errstr != NULL)
+ errx(1, "port is %s: %s", errstr, optarg);
+ break;
+ case 'r':
+ timeout = strtonum(optarg, 1, INT_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "timeout is %s: %s", errstr, optarg);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (file != NULL) {
+ log = fopen(file, "w");
+ if (log == NULL)
+ err(1, "fopen %s", file);
+ }
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s == -1)
+ err(1, "socket");
+ if (inet_pton(AF_INET, group, &mreq.imr_multiaddr) == -1)
+ err(1, "inet_pton %s", group);
+ if (inet_pton(AF_INET, ifaddr, &mreq.imr_interface) == -1)
+ err(1, "inet_pton %s", ifaddr);
+ if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
+ sizeof(mreq)) == -1)
+ err(1, "setsockopt IP_ADD_MEMBERSHIP %s %s", group, ifaddr);
+
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port);
+ if (inet_pton(AF_INET, group, &sin.sin_addr) == -1)
+ err(1, "inet_pton %s", group);
+ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1)
+ err(1, "bind %s:%d", group, port);
+
+ if (argc) {
+ pid = fork();
+ switch (pid) {
+ case -1:
+ err(1, "fork");
+ case 0:
+ execvp(argv[0], argv);
+ err(1, "exec %s", argv[0]);
+ }
+ }
+ if (timeout) {
+ if (norecv) {
+ if (signal(SIGALRM, sigexit) == SIG_ERR)
+ err(1, "signal SIGALRM");
+ }
+ if (alarm(timeout) == (unsigned int)-1)
+ err(1, "alarm %u", timeout);
+ }
+ n = recv(s, msg, sizeof(msg) - 1, 0);
+ if (n == -1)
+ err(1, "recv");
+ msg[n] = '\0';
+ fprintf(log, "<<< %s\n", msg);
+ fflush(log);
+
+ if (norecv)
+ errx(1, "received %s", msg);
+
+ if (argc) {
+ if (waitpid(pid, &status, 0) == -1)
+ err(1, "waitpid %d", pid);
+ if (status)
+ errx(1, "%s %d", argv[0], status);
+ }
+
+ return 0;
+}
+
+void
+sigexit(int sig)
+{
+ _exit(0);
+}
diff --git a/regress/sys/netinet/mcast/mcroute.c b/regress/sys/netinet/mcast/mcroute.c
new file mode 100644
index 00000000000..2962610354f
--- /dev/null
+++ b/regress/sys/netinet/mcast/mcroute.c
@@ -0,0 +1,283 @@
+/* $OpenBSD: mcroute.c,v 1.1.1.1 2019/09/02 22:17:28 bluhm Exp $ */
+/*
+ * Copyright (c) 2019 Alexander Bluhm <bluhm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+/*
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/ip_mroute.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+void __dead usage(void);
+void sigexit(int);
+size_t get_sysctl(const int *mib, u_int mcnt, char **buf);
+
+void __dead
+usage(void)
+{
+ fprintf(stderr,
+"mcroute [-b] [-f file] [-g group] -i ifaddr [-n timeout] -o outaddr\n"
+" [-r timeout]\n"
+" -b fork to background after setup\n"
+" -f file print message to log file, default stdout\n"
+" -g group multicast group, default 224.0.0.123\n"
+" -i ifaddr multicast interface address\n"
+" -n timeout expect not to receive any message until timeout\n"
+" -o outaddr outgoing interface address\n"
+" -r timeout receive timeout in seconds\n");
+ exit(2);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct vifctl vif;
+ struct mfcctl mfc;
+ struct vifinfo *vinfo;
+ FILE *log;
+ const char *errstr, *file, *group, *ifaddr, *outaddr;
+ char *buf;
+ size_t needed;
+ unsigned long pktin, pktout;
+ int value, ch, s, fd, background, norecv;
+ unsigned int timeout;
+ pid_t pid;
+ int mib[] = { CTL_NET, PF_INET, IPPROTO_IP, IPCTL_MRTVIF };
+
+ background = 0;
+ log = stdout;
+ file = NULL;
+ group = "224.0.0.123";
+ ifaddr = NULL;
+ norecv = 0;
+ outaddr = NULL;
+ timeout = 0;
+ while ((ch = getopt(argc, argv, "bf:g:i:n:o:r:")) != -1) {
+ switch (ch) {
+ case 'b':
+ background = 1;
+ break;
+ case 'f':
+ file = optarg;
+ break;
+ case 'g':
+ group = optarg;
+ break;
+ case 'i':
+ ifaddr = optarg;
+ break;
+ case 'n':
+ norecv = 1;
+ timeout = strtonum(optarg, 1, INT_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "no timeout is %s: %s", errstr, optarg);
+ break;
+ case 'o':
+ outaddr = optarg;
+ break;
+ case 'r':
+ timeout = strtonum(optarg, 1, INT_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "timeout is %s: %s", errstr, optarg);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (ifaddr == NULL)
+ errx(2, "no ifaddr");
+ if (outaddr == NULL)
+ errx(2, "no outaddr");
+ if (argc)
+ usage();
+
+ if (file != NULL) {
+ log = fopen(file, "w");
+ if (log == NULL)
+ err(1, "fopen %s", file);
+ }
+
+ s = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
+ if (s == -1)
+ err(1, "socket");
+ value = 1;
+ if (setsockopt(s, IPPROTO_IP, MRT_INIT, &value, sizeof(value)) == -1)
+ err(1, "setsockopt MRT_INIT");
+
+ memset(&vif, 0, sizeof(vif));
+ vif.vifc_vifi = 0;
+ if (inet_pton(AF_INET, ifaddr, &vif.vifc_lcl_addr) == -1)
+ err(1, "inet_pton %s", ifaddr);
+ if (setsockopt(s, IPPROTO_IP, MRT_ADD_VIF, &vif, sizeof(vif)) == -1)
+ err(1, "setsockopt MRT_ADD_VIF %s", ifaddr);
+
+ memset(&vif, 0, sizeof(vif));
+ vif.vifc_vifi = 1;
+ if (inet_pton(AF_INET, outaddr, &vif.vifc_lcl_addr) == -1)
+ err(1, "inet_pton %s", outaddr);
+ if (setsockopt(s, IPPROTO_IP, MRT_ADD_VIF, &vif, sizeof(vif)) == -1)
+ err(1, "setsockopt MRT_ADD_VIF %s", outaddr);
+
+ memset(&mfc, 0, sizeof(mfc));
+ if (inet_pton(AF_INET, group, &mfc.mfcc_mcastgrp) == -1)
+ err(1, "inet_pton %s", group);
+ mfc.mfcc_parent = 0;
+ mfc.mfcc_ttls[1] = 1;
+
+ if (setsockopt(s, IPPROTO_IP, MRT_ADD_MFC, &mfc, sizeof(mfc)) == -1)
+ err(1, "setsockopt MRT_ADD_MFC %s", ifaddr);
+
+ if (background) {
+ pid = fork();
+ switch (pid) {
+ case -1:
+ err(1, "fork");
+ case 0:
+ fd = open("/dev/null", O_RDWR);
+ if (fd == -1)
+ err(1, "open /dev/null");
+ if (dup2(fd, 0) == -1)
+ err(1, "dup 0");
+ if (dup2(fd, 1) == -1)
+ err(1, "dup 1");
+ if (dup2(fd, 2) == -1)
+ err(1, "dup 2");
+ break;
+ default:
+ _exit(0);
+ }
+ }
+
+ if (timeout) {
+ if (norecv) {
+ if (signal(SIGALRM, sigexit) == SIG_ERR)
+ err(1, "signal SIGALRM");
+ }
+ if (alarm(timeout) == (unsigned int)-1)
+ err(1, "alarm %u", timeout);
+ }
+
+ buf = NULL;
+ pktin = pktout = 0;
+ do {
+ struct timespec sleeptime = { 0, 10000000 };
+
+ if (nanosleep(&sleeptime, NULL) == -1)
+ err(1, "nanosleep");
+ needed = get_sysctl(mib, sizeof(mib) / sizeof(mib[0]), &buf);
+ for (vinfo = (struct vifinfo *)buf;
+ (char *)vinfo < buf + needed;
+ vinfo++) {
+ switch (vinfo->v_vifi) {
+ case 0:
+ if (pktin != vinfo->v_pkt_in) {
+ fprintf(log, "<<< %lu\n",
+ vinfo->v_pkt_in);
+ fflush(log);
+ }
+ pktin = vinfo->v_pkt_in;
+ break;
+ case 1:
+ if (pktout != vinfo->v_pkt_out) {
+ fprintf(log, ">>> %lu\n",
+ vinfo->v_pkt_out);
+ fflush(log);
+ }
+ pktout = vinfo->v_pkt_out;
+ break;
+ }
+ }
+ } while (pktin == 0 || pktout == 0);
+ free(buf);
+
+ if (norecv)
+ errx(1, "pktin %lu, pktout %lu", pktin, pktout);
+
+ return 0;
+}
+
+void
+sigexit(int sig)
+{
+ _exit(0);
+}
+
+/* from netstat(8) */
+size_t
+get_sysctl(const int *mib, u_int mcnt, char **buf)
+{
+ size_t needed;
+
+ while (1) {
+ if (sysctl(mib, mcnt, NULL, &needed, NULL, 0) == -1)
+ err(1, "sysctl-estimate");
+ if (needed == 0)
+ break;
+ if ((*buf = realloc(*buf, needed)) == NULL)
+ err(1, NULL);
+ if (sysctl(mib, mcnt, *buf, &needed, NULL, 0) == -1) {
+ if (errno == ENOMEM)
+ continue;
+ err(1, "sysctl");
+ }
+ break;
+ }
+
+ return needed;
+}
diff --git a/regress/sys/netinet/mcast/mcsend.c b/regress/sys/netinet/mcast/mcsend.c
new file mode 100644
index 00000000000..4f38f616b8c
--- /dev/null
+++ b/regress/sys/netinet/mcast/mcsend.c
@@ -0,0 +1,154 @@
+/* $OpenBSD: mcsend.c,v 1.1.1.1 2019/09/02 22:17:28 bluhm Exp $ */
+/*
+ * Copyright (c) 2019 Alexander Bluhm <bluhm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+void __dead usage(void);
+
+void __dead
+usage(void)
+{
+ fprintf(stderr,
+"mcsend [-f file] [-g group] [-i ifaddr] [-m message] [-p port]\n"
+" -f file print message to log file, default stdout\n"
+" -g group multicast group, default 224.0.0.123\n"
+" -i ifaddr multicast interface address\n"
+" -m message message in payload, maximum 255 characters, default foo\n"
+" -l loop disable or enable loopback, 0 or 1\n"
+" -p port destination port number, default 12345\n"
+" -t ttl set multicast ttl\n");
+ exit(2);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_in sin;
+ struct in_addr addr;
+ FILE *log;
+ const char *errstr, *file, *group, *ifaddr, *msg;
+ size_t len;
+ ssize_t n;
+ int ch, s, loop, port, ttl;
+
+ log = stdout;
+ file = NULL;
+ group = "224.0.0.123";
+ ifaddr = NULL;
+ loop = -1;
+ msg = "foo";
+ port = 12345;
+ ttl = -1;
+ while ((ch = getopt(argc, argv, "f:g:i:l:m:p:t:")) != -1) {
+ switch (ch) {
+ case 'f':
+ file = optarg;
+ break;
+ case 'g':
+ group = optarg;
+ break;
+ case 'i':
+ ifaddr = optarg;
+ break;
+ case 'l':
+ loop = strtonum(optarg, 0, 1, &errstr);
+ if (errstr != NULL)
+ errx(1, "loop is %s: %s", errstr, optarg);
+ break;
+ case 'm':
+ msg = optarg;
+ break;
+ case 'p':
+ port = strtonum(optarg, 1, 0xffff, &errstr);
+ if (errstr != NULL)
+ errx(1, "port is %s: %s", errstr, optarg);
+ break;
+ case 't':
+ ttl = strtonum(optarg, 0, 255, &errstr);
+ if (errstr != NULL)
+ errx(1, "ttl is %s: %s", errstr, optarg);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc)
+ usage();
+
+ if (file != NULL) {
+ log = fopen(file, "w");
+ if (log == NULL)
+ err(1, "fopen %s", file);
+ }
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s == -1)
+ err(1, "socket");
+ if (ifaddr != NULL) {
+ if (inet_pton(AF_INET, ifaddr, &addr) == -1)
+ err(1, "inet_pton %s", ifaddr);
+ if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &addr,
+ sizeof(addr)) == -1)
+ err(1, "setsockopt IP_MULTICAST_IF %s", ifaddr);
+ }
+ if (loop != -1) {
+ unsigned char value = loop;
+
+ if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &value,
+ sizeof(value)) == -1)
+ err(1, "setsockopt loop %d", loop);
+ }
+ if (ttl != -1) {
+ unsigned char value = ttl;
+
+ if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &value,
+ sizeof(value)) == -1)
+ err(1, "setsockopt ttl %d", ttl);
+ }
+
+ sin.sin_len = sizeof(sin);
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port);
+ if (inet_pton(AF_INET, group, &sin.sin_addr) == -1)
+ err(1, "inet_pton %s", group);
+ if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == -1)
+ err(1, "connect %s:%d", group, port);
+
+ len = strlen(msg);
+ if (len >= 255)
+ err(1, "message too long %zu", len);
+ n = send(s, msg, len, 0);
+ if (n == -1)
+ err(1, "send");
+ if ((size_t)n != len)
+ errx(1, "send %zd", n);
+ fprintf(log, ">>> %s\n", msg);
+ fflush(log);
+
+ return 0;
+}