/* * 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; }