Add regression tests for IP_SENDSRCADDR.
authorvgross <vgross@openbsd.org>
Tue, 16 Aug 2016 22:25:08 +0000 (22:25 +0000)
committervgross <vgross@openbsd.org>
Tue, 16 Aug 2016 22:25:08 +0000 (22:25 +0000)
regress/sys/netinet/sendsrcaddr/Makefile [new file with mode: 0644]
regress/sys/netinet/sendsrcaddr/runtest.c [new file with mode: 0644]

diff --git a/regress/sys/netinet/sendsrcaddr/Makefile b/regress/sys/netinet/sendsrcaddr/Makefile
new file mode 100644 (file)
index 0000000..f5a8cf5
--- /dev/null
@@ -0,0 +1,30 @@
+# $OpenBSD: Makefile,v 1.1 2016/08/16 22:25:08 vgross Exp $
+
+PROG      = runtest
+DESTADDR   = www.google.com
+TESTIFACE  = vether12
+TESTNET   != jot -r -s '.' 2 0 255
+RESV_ADDR  = 10.${TESTNET}.1
+BIND_ADDR  = 10.${TESTNET}.2
+CMSG_ADDR  = 10.${TESTNET}.3
+NONE_ADDR  = 10.${TESTNET}.4
+
+run-regress-runtest: ${PROG}
+       ${SUDO} ifconfig ${TESTIFACE} create
+       ${SUDO} ifconfig ${TESTIFACE} inet ${RESV_ADDR}/24 up
+       ${SUDO} ifconfig ${TESTIFACE} inet ${BIND_ADDR}/24 alias
+       ${SUDO} ifconfig ${TESTIFACE} inet ${CMSG_ADDR}/24 alias
+       sleep 1
+       ${SUDO} ifconfig ${TESTIFACE}
+       ${.OBJDIR}/${PROG}  0 ${DESTADDR}=destination ${RESV_ADDR}=reserved_saddr ${BIND_ADDR}=bind_saddr
+       ${.OBJDIR}/${PROG}  0 ${DESTADDR}=destination ${RESV_ADDR}=reserved_saddr ${BIND_ADDR}=bind_saddr ${CMSG_ADDR}=cmsg_saddr,wire_saddr
+       ${.OBJDIR}/${PROG}  0 ${DESTADDR}=destination ${RESV_ADDR}=reserved_saddr ${BIND_ADDR}=bind_saddr ${BIND_ADDR}=cmsg_saddr,wire_saddr
+       ${.OBJDIR}/${PROG} 49 ${DESTADDR}=destination ${RESV_ADDR}=reserved_saddr ${BIND_ADDR}=bind_saddr ${NONE_ADDR}=cmsg_saddr
+       ${.OBJDIR}/${PROG} 48 ${DESTADDR}=destination ${RESV_ADDR}=reserved_saddr ${BIND_ADDR}=bind_saddr ${RESV_ADDR}=cmsg_saddr
+       ${.OBJDIR}/${PROG}  0 ${DESTADDR}=destination ${RESV_ADDR}=reserved_saddr 0.0.0.0=bind_saddr ${BIND_ADDR}=cmsg_saddr,wire_saddr
+       ${.OBJDIR}/${PROG}  0 ${DESTADDR}=destination ${RESV_ADDR}=reserved_saddr 0.0.0.0=bind_saddr ${RESV_ADDR}=cmsg_saddr,wire_saddr
+       ${.OBJDIR}/${PROG}  0 ${DESTADDR}=destination ${RESV_ADDR}=reserved_saddr ${BIND_ADDR}=bind_saddr 0.0.0.0=cmsg_saddr
+       ${.OBJDIR}/${PROG} 22 ${DESTADDR}=destination ${RESV_ADDR}=reserved_saddr ${BIND_ADDR}=bind_saddr ${CMSG_ADDR}=cmsg_saddr fuzz
+       ${SUDO} ifconfig ${TESTIFACE} destroy
+
+.include <bsd.regress.mk>
diff --git a/regress/sys/netinet/sendsrcaddr/runtest.c b/regress/sys/netinet/sendsrcaddr/runtest.c
new file mode 100644 (file)
index 0000000..8329d96
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2016 Vincent Gross <vincent.gross@kilob.yt>
+ *
+ * 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 <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <net/bpf.h>
+#include <net/if.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/if_ether.h>
+
+#include <arpa/inet.h>
+
+#define PORTNUM "23000"
+#define PAYLOAD "payload"
+
+char cmd_tmpl[] = "route get %s | awk '/interface:/ { printf($2) }'";
+#define CMD_TMPL_SZ sizeof(cmd_tmpl)
+
+int fuzzit;
+
+void check_packet_tx(int);
+
+int
+main(int argc, char *argv[])
+{
+       int                       i;
+       char                     *argp, *addr, *flag;
+
+       struct addrinfo           hints;
+       struct addrinfo          *inai;
+
+       struct sockaddr_in       *dst_sin = NULL;
+       struct sockaddr_in       *reserved_sin = NULL;
+       struct sockaddr_in       *bind_sin = NULL;
+       struct sockaddr_in       *cmsg_sin = NULL;
+       struct sockaddr_in       *wire_sin = NULL;
+
+       int                       ch, rc, wstat, expected = -1;
+       int                       first_sock;
+       pid_t                     pid;
+
+       const char               *numerr;
+       char                      adrbuf[40];
+       const char               *adrp;
+
+       char                     *dst_str = NULL;
+       char                      cmd[CMD_TMPL_SZ + INET_ADDRSTRLEN];
+       FILE                     *outif_pipe;
+       char                      ifname_buf[IF_NAMESIZE];
+       size_t                    ifname_len;
+
+       int                       bpf_fd;
+
+
+       bzero(&hints, sizeof(hints));
+       hints.ai_family = AF_INET;
+       hints.ai_socktype = SOCK_DGRAM;
+
+       expected = strtonum(argv[1], 0, 255, &numerr);
+       if (numerr != NULL)
+               errx(2, "strtonum(%s): %s", optarg, numerr);
+
+       for (i = 2; i < argc; i++) {
+               argp = argv[i];
+               if (strcmp("fuzz",argp) == 0) {
+                       fuzzit = 1;
+                       continue;
+               }
+               addr = strsep(&argp, "=");
+               rc = getaddrinfo(addr, PORTNUM, &hints, &inai);
+               if (rc)
+                       errx(2, "getaddrinfo(%s) = %d: %s",
+                           argv[0], rc, gai_strerror(rc));
+               if (argp == NULL)
+                       errx(2, "arg must be of form <addr>=<flag>,<flag>");
+
+               for (; (flag = strsep(&argp,",")) != NULL;) {
+                       if (strcmp("destination",flag) == 0 && dst_sin == NULL) {
+                               dst_sin = (struct sockaddr_in *)inai->ai_addr;
+                               /* get output interface */
+                               snprintf(cmd, sizeof(cmd), cmd_tmpl, addr);
+                               outif_pipe = popen(cmd, "re");
+                               if (outif_pipe == NULL)
+                                       err(2, "popen(route get)");
+                               if (fgets(ifname_buf, IF_NAMESIZE, outif_pipe) == NULL)
+                                       err(2, "fgets()");
+                               pclose(outif_pipe);
+                               if (strlen(ifname_buf) == 0)
+                                       err(2, "strlen(ifname_buf) == 0");
+                       }
+                       if (strcmp("reserved_saddr",flag) == 0 && reserved_sin == NULL)
+                               reserved_sin = (struct sockaddr_in *)inai->ai_addr;
+                       if (strcmp("bind_saddr",flag) == 0 && bind_sin == NULL)
+                               bind_sin = (struct sockaddr_in *)inai->ai_addr;
+                       if (strcmp("cmsg_saddr",flag) == 0 && cmsg_sin == NULL)
+                               cmsg_sin = (struct sockaddr_in *)inai->ai_addr;
+                       if (strcmp("wire_saddr",flag) == 0 && wire_sin == NULL)
+                               wire_sin = (struct sockaddr_in *)inai->ai_addr;
+               }
+       }
+
+       if (reserved_sin == NULL)
+               errx(2, "reserved_sin == NULL");
+
+       if (bind_sin == NULL)
+               errx(2, "bind_sin == NULL");
+
+       if (dst_sin == NULL)
+               errx(2, "dst_sin == NULL");
+
+       if (expected < 0)
+               errx(2, "need expected");
+
+
+       if (wire_sin != NULL)
+               bpf_fd = setup_bpf(ifname_buf, wire_sin, dst_sin);
+
+       first_sock = udp_first(reserved_sin);
+
+       pid = fork();
+       if (pid == 0) {
+               return udp_override(dst_sin, bind_sin, cmsg_sin);
+       }
+       (void)wait(&wstat);
+       close(first_sock);
+
+       if (!WIFEXITED(wstat))
+               errx(2, "error setting up override");
+
+       if (WEXITSTATUS(wstat) != expected)
+               errx(2, "expected %d, got %d", expected, WEXITSTATUS(wstat));
+
+       if (wire_sin != NULL)
+               check_packet_tx(bpf_fd);
+
+       return EXIT_SUCCESS;
+}
+
+
+struct bpf_insn outgoing_bpf_filter[] = {
+       /* ethertype = IP */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 9),
+
+       /* Make sure it's a UDP packet. */
+       BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 7),
+
+       /* Fragments are handled as errors */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
+       BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 5, 0),
+
+       /* Make sure it's from the right address */
+       BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 26),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0, 0, 3), /* Need to patch this */
+
+       /* Make sure it's to the right address */
+       BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 30),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0, 0, 1), /* Need to patch this */
+#if 0
+       /* Get the IP header length. */
+       BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
+
+       /* Make sure it's to the right port. */
+       BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0, 0, 1),
+#endif
+       /* If we passed all the tests, ask for the whole packet. */
+       BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
+
+       /* Otherwise, drop it. */
+       BPF_STMT(BPF_RET+BPF_K, 0),
+};
+
+int outgoing_bpf_filter_len = sizeof(outgoing_bpf_filter)/sizeof(struct bpf_insn);
+
+int
+setup_bpf(char *ifname, struct sockaddr_in *from, struct sockaddr_in *to)
+{
+       int fd;
+       struct ifreq ifr;
+       u_int flag;
+       struct bpf_version vers;
+       struct bpf_program prog;
+
+       fd = open("/dev/bpf", O_RDWR | O_CLOEXEC);
+       if (fd == -1)
+               err(2, "open(/dev/bpf)");
+
+       if (ioctl(fd, BIOCVERSION, &vers) < 0)
+               err(2, "ioctl(BIOCVERSION)");
+
+       if (vers.bv_major != BPF_MAJOR_VERSION ||
+           vers.bv_minor < BPF_MINOR_VERSION)
+               errx(2, "bpf version mismatch, expected %d.%d, got %d.%d",
+                   BPF_MAJOR_VERSION, BPF_MINOR_VERSION,
+                   vers.bv_major, vers.bv_minor);
+
+       strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
+       if (ioctl(fd, BIOCSETIF, &ifr) < 0)
+               err(2, "ioctl(BIOCSETIF)");
+
+       flag = 1;
+       if (ioctl(fd, BIOCIMMEDIATE, &flag) < 0)
+               err(2, "ioctl(BIOCIMMEDIATE)");
+
+       flag = BPF_DIRECTION_IN;
+       if (ioctl(fd, BIOCSDIRFILT, &flag) < 0)
+               err(2, "ioctl(BIOCDIRFILT)");
+
+       outgoing_bpf_filter[7].k = ntohl(from->sin_addr.s_addr) ;
+       outgoing_bpf_filter[9].k = ntohl(to->sin_addr.s_addr) ;
+#if 0
+       outgoing_bpf_filter[12].k = (u_int32_t)ntohs(to->sin_port) ;
+#endif
+
+       prog.bf_len = outgoing_bpf_filter_len;
+       prog.bf_insns = outgoing_bpf_filter;
+       if (ioctl(fd, BIOCSETF, &prog) < 0)
+               err(2, "ioctl(BIOCSETF)");
+
+       return fd;
+}
+
+void
+check_packet_tx(int fd)
+{
+       u_int            buf_max;
+       size_t           len;
+       struct pollfd    pfd;
+       int              pollrc;
+       char            *buf, *payload;
+       struct bpf_hdr  *hdr;
+       struct ip       *ip;
+
+       if (ioctl(fd, BIOCGBLEN, &buf_max) < 0)
+               err(2, "ioctl(BIOCGBLEN)");
+
+       if (buf_max <= 0)
+               errx(2, "buf_max = %d <= 0", buf_max);
+
+       buf = malloc(buf_max);
+       if (!buf)
+               err(2, "malloc(buf_max)");
+
+       pfd.fd = fd;
+       pfd.events = POLLIN;
+       pollrc = poll(&pfd, 1, 5000);
+       if (pollrc == -1)
+               err(2, "poll()");
+       if (pollrc == 0)
+               errx(2, "poll() timeout");
+
+       len = read(fd, buf, buf_max);
+       if (len <= 0)
+               err(2, "read(/dev/bpf)");
+       len = BPF_WORDALIGN(len);
+
+       if (len < sizeof(hdr))
+               errx(2, "short read, len < sizeof(bpf_hdr)");
+
+       hdr = (struct bpf_hdr *)buf;
+       if (hdr->bh_hdrlen + hdr->bh_caplen > len)
+               errx(2, "buffer too small for the whole capture");
+
+       /* XXX we could try again if enough space in the buffer */
+       if (hdr->bh_caplen != hdr->bh_datalen)
+               errx(2, "partial capture");
+
+       ip = (struct ip *)(buf + hdr->bh_hdrlen + ETHER_HDR_LEN);
+       payload = ((char *)ip + ip->ip_hl*4 + 8);
+
+       if (strcmp(PAYLOAD,payload) != 0)
+               errx(2, "payload corrupted");
+
+       return;
+}
+
+int
+udp_first(struct sockaddr_in *src)
+{
+       int s_con;
+
+       s_con = socket(src->sin_family, SOCK_DGRAM, 0);
+       if (s_con == -1)
+               err(2, "udp_bind: socket()");
+
+       if (bind(s_con, (struct sockaddr *)src, src->sin_len))
+               err(2, "udp_bind: bind()");
+
+       return s_con;
+}
+
+
+int
+udp_override(struct sockaddr_in *dst, struct sockaddr_in *src_bind,
+    struct sockaddr_in *src_sendmsg)
+{
+       int                      s, optval, error, saved_errno;
+       ssize_t                  send_rc;
+       struct msghdr            msg;
+       struct iovec             iov;
+       struct cmsghdr          *cmsg;
+       struct in_addr          *sendopt;
+       int                     *hopopt;
+#define CMSGSP_SADDR   CMSG_SPACE(sizeof(u_int32_t))
+#define CMSGSP_HOPLIM  CMSG_SPACE(sizeof(int))
+#define CMSGSP_BOGUS   CMSG_SPACE(12)
+#define CMSGBUF_SP     CMSGSP_SADDR + CMSGSP_HOPLIM + CMSGSP_BOGUS + 3
+       unsigned char            cmsgbuf[CMSGBUF_SP];
+
+       bzero(&msg, sizeof(msg));
+       bzero(&cmsgbuf, sizeof(cmsgbuf));
+
+       s = socket(src_bind->sin_family, SOCK_DGRAM, 0);
+       if (s == -1) {
+               warn("udp_override: socket()");
+               kill(getpid(), SIGTERM);
+       }
+
+       optval = 1;
+       if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int))) {
+               warn("udp_override: setsockopt(SO_REUSEADDR)");
+               kill(getpid(), SIGTERM);
+       }
+
+       if (bind(s, (struct sockaddr *)src_bind, src_bind->sin_len)) {
+               warn("udp_override: bind()");
+               kill(getpid(), SIGTERM);
+       }
+
+       iov.iov_base = PAYLOAD;
+       iov.iov_len = strlen(PAYLOAD) + 1;
+       msg.msg_name = dst;
+       msg.msg_namelen = dst->sin_len;
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+
+       if (src_sendmsg) {
+               msg.msg_control = &cmsgbuf;
+               msg.msg_controllen = CMSGSP_SADDR;
+               cmsg = CMSG_FIRSTHDR(&msg);
+               cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
+               cmsg->cmsg_level = IPPROTO_IP;
+               cmsg->cmsg_type = IP_SENDSRCADDR;
+               sendopt = (struct in_addr *)CMSG_DATA(cmsg);
+               memcpy(sendopt, &src_sendmsg->sin_addr, sizeof(*sendopt));
+               if (fuzzit) {
+                       msg.msg_controllen = CMSGBUF_SP;
+                       cmsg = CMSG_NXTHDR(&msg, cmsg);
+                       cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+                       cmsg->cmsg_level = IPPROTO_IPV6;
+                       cmsg->cmsg_type = IPV6_UNICAST_HOPS;
+                       hopopt = (int *)CMSG_DATA(cmsg);
+                       *hopopt = 8;
+
+                       cmsg = CMSG_NXTHDR(&msg, cmsg);
+                       cmsg->cmsg_len = CMSG_LEN(sizeof(int)) + 15;
+                       cmsg->cmsg_level = IPPROTO_IPV6;
+                       cmsg->cmsg_type = IPV6_UNICAST_HOPS;
+               }
+       }
+
+       send_rc = sendmsg(s, &msg, 0);
+       saved_errno = errno;
+
+       close(s);
+
+       if (send_rc == iov.iov_len)
+               return 0;
+       return saved_errno;
+}