From 8199dcaaef1dbfac25ef4f7a417d67c1c0a94ff4 Mon Sep 17 00:00:00 2001 From: vgross Date: Tue, 16 Aug 2016 22:25:08 +0000 Subject: [PATCH] Add regression tests for IP_SENDSRCADDR. --- regress/sys/netinet/sendsrcaddr/Makefile | 30 ++ regress/sys/netinet/sendsrcaddr/runtest.c | 402 ++++++++++++++++++++++ 2 files changed, 432 insertions(+) create mode 100644 regress/sys/netinet/sendsrcaddr/Makefile create mode 100644 regress/sys/netinet/sendsrcaddr/runtest.c diff --git a/regress/sys/netinet/sendsrcaddr/Makefile b/regress/sys/netinet/sendsrcaddr/Makefile new file mode 100644 index 00000000000..f5a8cf5aa9b --- /dev/null +++ b/regress/sys/netinet/sendsrcaddr/Makefile @@ -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 diff --git a/regress/sys/netinet/sendsrcaddr/runtest.c b/regress/sys/netinet/sendsrcaddr/runtest.c new file mode 100644 index 00000000000..8329d96227a --- /dev/null +++ b/regress/sys/netinet/sendsrcaddr/runtest.c @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2016 Vincent Gross + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#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 =,"); + + 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; +} -- 2.20.1