/*
* Copyright (c) 2016 Vincent Gross <vincent.gross@kilob.yt>
+ * Copyright (c) 2017 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
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
#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);
+void __dead usage(const char *);
+int udp_bind(struct sockaddr_in *);
+int udp_send(int, struct sockaddr_in *, struct sockaddr_in *);
+struct sockaddr_in * udp_recv(int s, struct sockaddr_in *);
+
+void __dead
+usage(const char *msg)
+{
+ if (msg != NULL)
+ fprintf(stderr, "%s\n", msg);
+ fprintf(stderr, "runtest [-f] -D destination -B bind [-C cmesg] "
+ "[-E error] -R reserved -W wire\n");
+ exit(1);
+}
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;
-
+ int ch, error, errexpect, bind_sock, dest_sock, resv_sock;
+ char addr[16];
+ const char *errstr;
+ struct addrinfo hints, *res;
+ struct sockaddr_in *bind_sin, *cmsg_sin, *dest_sin, *resv_sin,
+ *wire_sin, *from_sin;
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) {
+ bind_sin = cmsg_sin = dest_sin = resv_sin = wire_sin = NULL;
+ errexpect = 0;
+
+ while ((ch = getopt(argc, argv, "B:C:D:E:fR:W:")) != -1) {
+ switch (ch) {
+ case 'B':
+ error = getaddrinfo(optarg, NULL, &hints, &res);
+ if (error)
+ errx(1, "-B: %s", gai_strerror(error));
+ bind_sin = (struct sockaddr_in *)res->ai_addr;
+ break;
+ case 'C':
+ error = getaddrinfo(optarg, NULL, &hints, &res);
+ if (error)
+ errx(1, "-C: %s", gai_strerror(error));
+ cmsg_sin = (struct sockaddr_in *)res->ai_addr;
+ break;
+ case 'D':
+ error = getaddrinfo(optarg, NULL, &hints, &res);
+ if (error)
+ errx(1, "-D: %s", gai_strerror(error));
+ dest_sin = (struct sockaddr_in *)res->ai_addr;
+ break;
+ case 'E':
+ errexpect = strtonum(optarg, 1, 255, &errstr);
+ if (errstr != NULL)
+ errx(1, "error number is %s: %s",
+ errstr, optarg);
+ break;
+ case 'f':
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;
+ break;
+ case 'R':
+ error = getaddrinfo(optarg, NULL, &hints, &res);
+ if (error)
+ errx(1, "-R: %s", gai_strerror(error));
+ resv_sin = (struct sockaddr_in *)res->ai_addr;
+ break;
+ case 'W':
+ error = getaddrinfo(optarg, NULL, &hints, &res);
+ if (error)
+ errx(1, "-W: %s", gai_strerror(error));
+ wire_sin = (struct sockaddr_in *)res->ai_addr;
+ break;
+ default:
+ usage(NULL);
}
}
+ argc -= optind;
+ argv += optind;
- if (reserved_sin == NULL)
- errx(2, "reserved_sin == NULL");
+ if (argc > 0)
+ usage("too many arguments");
if (bind_sin == NULL)
- errx(2, "bind_sin == NULL");
+ usage("no bind addr");
- if (dst_sin == NULL)
- errx(2, "dst_sin == NULL");
+ if (dest_sin == NULL)
+ usage("no destination addr");
- if (expected < 0)
- errx(2, "need expected");
+ if (resv_sin == NULL)
+ usage("no reserved addr");
+ /* bind on address that cannot be used */
+ resv_sock = udp_bind(resv_sin);
- if (wire_sin != NULL)
- bpf_fd = setup_bpf(ifname_buf, wire_sin, dst_sin);
+ /* bind socket that should receive the packet */
+ dest_sock = udp_bind(dest_sin);
- first_sock = udp_first(reserved_sin);
+ /* bind socket that is used to send the packet */
+ bind_sin->sin_port = resv_sin->sin_port;
+ bind_sock = udp_bind(bind_sin);
+ error = udp_send(bind_sock, cmsg_sin, dest_sin);
- pid = fork();
- if (pid == 0) {
- return udp_override(dst_sin, bind_sin, cmsg_sin);
+ if (errexpect && !error) {
+ errno = errexpect;
+ err(2, "udp send succeeded, but expected error");
+ }
+ if (!errexpect && error) {
+ errno = error;
+ err(2, "no error expected, but udp send failed");
+ }
+ if (errexpect != error) {
+ errno = error;
+ err(2, "expected error %d, but udp send failed", errexpect);
}
- (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;
-}
+ if (wire_sin != NULL) {
+ from_sin = udp_recv(dest_sock, dest_sin);
+ if (from_sin == NULL)
+ errx(2, "receive timeout");
+ inet_ntop(from_sin->sin_family, &from_sin->sin_addr,
+ addr, sizeof(addr));
+ if (from_sin->sin_addr.s_addr != wire_sin->sin_addr.s_addr)
+ errx(2, "receive addr %s", addr);
+ if (from_sin->sin_port != bind_sin->sin_port)
+ errx(2, "receive port %d", ntohs(from_sin->sin_port));
+ }
-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;
+ return 0;
}
int
-udp_first(struct sockaddr_in *src)
+udp_bind(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 s, reuse, salen;
+ char addr[16];
+
+ inet_ntop(src->sin_family, &src->sin_addr, addr, sizeof(addr));
+
+ if ((s = socket(src->sin_family, SOCK_DGRAM, 0)) == -1)
+ err(1, "socket %s", addr);
+ reuse = ntohl(src->sin_addr.s_addr) == INADDR_ANY ? 1 : 0;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))
+ == -1)
+ err(1, "setsockopt %s", addr);
+ if (bind(s, (struct sockaddr *)src, src->sin_len) == -1)
+ err(1, "bind %s", addr);
+ /* fill out port */
+ salen = sizeof(*src);
+ if (getsockname(s, (struct sockaddr *)src, &salen))
+ err(1, "getsockname %s", addr);
+
+ return s;
}
-
int
-udp_override(struct sockaddr_in *dst, struct sockaddr_in *src_bind,
- struct sockaddr_in *src_sendmsg)
+udp_send(int s, struct sockaddr_in *src, struct sockaddr_in *dst)
{
- int s, optval, error, saved_errno;
- ssize_t send_rc;
struct msghdr msg;
struct iovec iov;
struct cmsghdr *cmsg;
#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;
+ bzero(&msg, sizeof(msg));
msg.msg_name = dst;
msg.msg_namelen = dst->sin_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
- if (src_sendmsg) {
+ if (src) {
+ bzero(&cmsgbuf, sizeof(cmsgbuf));
msg.msg_control = &cmsgbuf;
msg.msg_controllen = CMSGSP_SADDR;
cmsg = CMSG_FIRSTHDR(&msg);
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));
+ memcpy(sendopt, &src->sin_addr, sizeof(*sendopt));
if (fuzzit) {
msg.msg_controllen = CMSGBUF_SP;
cmsg = CMSG_NXTHDR(&msg, cmsg);
}
}
- send_rc = sendmsg(s, &msg, 0);
- saved_errno = errno;
+ if (sendmsg(s, &msg, 0) == -1)
+ return errno;
- close(s);
+ return 0;
+}
- if (send_rc == iov.iov_len)
- return 0;
- return saved_errno;
+struct sockaddr_in *
+udp_recv(int s, struct sockaddr_in *dst)
+{
+ struct sockaddr_in *src;
+ struct pollfd pfd[1];
+ char addr[16], buf[256];
+ int nready, len, salen;
+
+ inet_ntop(dst->sin_family, &dst->sin_addr, addr, sizeof(addr));
+
+ pfd[0].fd = s;
+ pfd[0].events = POLLIN;
+ nready = poll(pfd, 1, 2 * 1000);
+ if (nready == -1)
+ err(1, "poll");
+ if (nready == 0)
+ return NULL;
+ if ((pfd[0].revents & POLLIN) == 0)
+ errx(1, "event %d %s", pfd[0].revents, addr);
+
+ if ((src = malloc(sizeof(*src))) == NULL)
+ err(1, "malloc");
+ salen = sizeof(*src);
+ if ((len = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)src,
+ &salen)) == -1)
+ err(1, "recvfrom %s", addr);
+
+ if (len != strlen(PAYLOAD) + 1)
+ errx(1, "recvfrom %s len %d", addr, len);
+ if (strcmp(buf, PAYLOAD) != 0)
+ errx(1, "recvfrom %s payload", addr);
+
+ return src;
}