summaryrefslogtreecommitdiff
path: root/regress
diff options
context:
space:
mode:
authorAlexander Bluhm <bluhm@cvs.openbsd.org>2019-09-02 22:17:29 +0000
committerAlexander Bluhm <bluhm@cvs.openbsd.org>2019-09-02 22:17:29 +0000
commit5efde60372dda3cc805bc95da86e250260ee393d (patch)
treef4eec6fb17c97a288d0219cad1d6308c189ac6fe /regress
parent9fcc8ca8c07c0e59c75e90a6c871dc60db991769 (diff)
Test multicast sender, receiver, router. Minimal mcroute implmentation
does not care about IGMP, it simply adds static multicast routes. Mutlicast routing needs at least two test machines. Otherwise only send and receive on localhost is tested.
Diffstat (limited to 'regress')
-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;
+}