diff --git a/Makefile b/Makefile index 501830d..257d89e 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ CFLAGS += -Wall -Wextra -pedantic all: passt qrap -passt: passt.c passt.h arp.c arp.h dhcp.c dhcp.h util.c util.h - $(CC) $(CFLAGS) passt.c arp.c dhcp.c util.c -o passt +passt: passt.c passt.h arp.c arp.h dhcp.c dhcp.h ndp.c ndp.h util.c util.h + $(CC) $(CFLAGS) passt.c arp.c dhcp.c ndp.c util.c -o passt qrap: qrap.c passt.h $(CC) $(CFLAGS) qrap.c -o qrap diff --git a/arp.c b/arp.c index 4646792..3837a04 100644 --- a/arp.c +++ b/arp.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/dhcp.c b/dhcp.c index 44a7cfe..abb76f6 100644 --- a/dhcp.c +++ b/dhcp.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/ndp.c b/ndp.c new file mode 100644 index 0000000..a15ecc3 --- /dev/null +++ b/ndp.c @@ -0,0 +1,133 @@ +/* PASST - Plug A Simple Socket Transport + * + * ndp.c - NDP support for PASST + * + * Author: Stefano Brivio + * License: GPLv2 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "passt.h" +#include "util.h" + +#define RS 133 +#define RA 134 +#define NS 135 +#define NA 136 + +/** + * ndp() - Check for NDP solicitations, reply as needed + * @c: Execution context + * @len: Total L2 packet length + * @eh: Packet buffer, Ethernet header + * + * Return: 0 if not handled here, 1 if handled, -1 on failure + */ +int ndp(struct ctx *c, unsigned len, struct ethhdr *eh) +{ + struct ethhdr *ehr; + struct ipv6hdr *ip6h = (struct ipv6hdr *)(eh + 1), *ip6hr; + struct icmp6hdr *ih, *ihr; + char buf[BUFSIZ] = { 0 }; + uint8_t proto, *p; + + ih = (struct icmp6hdr *)ipv6_l4hdr(ip6h, &proto); + if (!ih) + return -1; + + if (proto != IPPROTO_ICMPV6 || + ih->icmp6_type < RS || ih->icmp6_type > NA) + return 0; + + ehr = (struct ethhdr *)buf; + ip6hr = (struct ipv6hdr *)(ehr + 1); + ihr = (struct icmp6hdr *)(ip6hr + 1); + + if (ih->icmp6_type == NS) { + fprintf(stderr, "NDP: received NS, sending NA\n"); + ihr->icmp6_type = NA; + ihr->icmp6_code = 0; + ihr->icmp6_router = 1; + ihr->icmp6_solicited = 1; + ihr->icmp6_override = 1; + + p = (unsigned char *)(ihr + 1); + memcpy(p, &c->gw6, sizeof(c->gw6)); /* target address */ + p += 16; + *p++ = 2; /* target ll */ + *p++ = 1; /* length */ + memcpy(p, c->mac, ETH_ALEN); + p += 6; + } else if (ih->icmp6_type == RS) { + fprintf(stderr, "NDP: received RS, sending RA\n"); + ihr->icmp6_type = RA; + ihr->icmp6_code = 0; + ihr->icmp6_rt_lifetime = htons(3600); + + p = (unsigned char *)(ihr + 1); + p += 8; /* reachable, retrans time */ + *p++ = 3; /* prefix */ + *p++ = 4; /* length */ + *p++ = 64; /* prefix length */ + *p++ = 0xc0; /* flags: L, A */ + *(uint32_t *)p = htonl(3600); /* lifetime */ + p += 4; + *(uint32_t *)p = htonl(3600); /* preferred lifetime */ + p += 8; + memcpy(p, &c->addr6, 8); /* prefix */ + p += 16; + + *p++ = 25; /* RDNS */ + *p++ = 3; /* length */ + p += 2; + *(uint32_t *)p = htonl(60); /* lifetime */ + p += 4; + memcpy(p, &c->dns6, 16); /* address */ + p += 16; + + *p++ = 1; /* source ll */ + *p++ = 1; /* length */ + memcpy(p, c->mac, ETH_ALEN); + p += 6; + } else { + return 1; + } + + len = (uintptr_t)p - (uintptr_t)ihr - sizeof(*ihr); + + ip6hr->daddr = ip6h->saddr; + ip6hr->saddr = c->gw6; + ip6hr->payload_len = htons(sizeof(*ihr) + len); + ip6hr->hop_limit = IPPROTO_ICMPV6; + ihr->icmp6_cksum = 0; + ihr->icmp6_cksum = csum_ip4(ip6hr, sizeof(*ip6hr) + + sizeof(*ihr) + len); + + ip6hr->version = 6; + ip6hr->nexthdr = IPPROTO_ICMPV6; + ip6hr->hop_limit = 255; + + len += sizeof(*ehr) + sizeof(*ip6hr) + sizeof(*ihr); + memcpy(ehr->h_dest, eh->h_source, ETH_ALEN); + memcpy(ehr->h_source, c->mac, ETH_ALEN); + ehr->h_proto = htons(ETH_P_IPV6); + + if (send(c->fd_unix, ehr, len, 0) < 0) + perror("NDP: send"); + + return 1; +} diff --git a/ndp.h b/ndp.h new file mode 100644 index 0000000..2c59713 --- /dev/null +++ b/ndp.h @@ -0,0 +1 @@ +int ndp(struct ctx *c, unsigned len, struct ethhdr *eh); diff --git a/passt.c b/passt.c index f5d88d9..57759e4 100644 --- a/passt.c +++ b/passt.c @@ -5,22 +5,22 @@ * Author: Stefano Brivio * License: GPLv2 * - * Grab Ethernet frames via AF_UNIX socket, build AF_INET sockets for each - * 5-tuple from ICMP, TCP, UDP packets, perform connection tracking and forward - * them with destination address NAT. Forward packets received on sockets back - * to the UNIX domain socket (typically, a tap file descriptor from qemu). + * Grab Ethernet frames via AF_UNIX socket, build AF_INET/AF_INET6 sockets for + * each 5-tuple from ICMP, TCP, UDP packets, perform connection tracking and + * forward them with destination address NAT. Forward packets received on + * sockets back to the UNIX domain socket (typically, a tap file descriptor from + * qemu). * * TODO: - * - steal packets from AF_INET sockets (using eBPF/XDP, or a new socket - * option): currently, incoming packets are also handled by in-kernel protocol - * handlers, so every incoming untracked TCP packet gets a RST. Workaround: + * - steal packets from AF_INET/AF_INET6 sockets (using eBPF/XDP, or a new + * socket option): currently, incoming packets are also handled by in-kernel + * protocol handlers, so every incoming untracked TCP packet gets a RST. + * Workaround: * iptables -A OUTPUT -m state --state INVALID,NEW,ESTABLISHED \ * -p tcp --tcp-flags RST RST -j DROP + * ip6tables -A OUTPUT -m state --state INVALID,NEW,ESTABLISHED \ + * -p tcp --tcp-flags RST RST -j DROP * - and use XDP sockmap on top of that to improve performance - * - add IPv6 support. Current workaround on the namespace or machine on the - * tap side: - * echo 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6 - * - reserve and translate ports * - aging and timeout/RST bookkeeping for connection tracking entries */ @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,7 @@ #include "passt.h" #include "arp.h" #include "dhcp.h" +#include "ndp.h" #include "util.h" #define EPOLL_EVENTS 10 @@ -112,11 +114,13 @@ static void get_routes(struct ctx *c) struct sockaddr_nl addr = { .nl_family = AF_NETLINK, }; - int s, n, na, found = 0; struct nlmsghdr *nlh; struct rtattr *rta; struct rtmsg *rtm; char buf[BUFSIZ]; + int s, n, na; + + c->v6 = -1; s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (s < 0) { @@ -129,6 +133,7 @@ static void get_routes(struct ctx *c) goto out; } +v6: if (send(s, &req, sizeof(req), 0) < 0) { perror("netlink send"); goto out; @@ -141,35 +146,50 @@ static void get_routes(struct ctx *c) } nlh = (struct nlmsghdr *)buf; - if (nlh->nlmsg_type == NLMSG_DONE) - goto out; - - for ( ; NLMSG_OK(nlh, n) && found < 2; NLMSG_NEXT(nlh, n)) { + for ( ; NLMSG_OK(nlh, n); nlh = NLMSG_NEXT(nlh, n)) { rtm = (struct rtmsg *)NLMSG_DATA(nlh); - if (rtm->rtm_dst_len) + if (rtm->rtm_dst_len || + (rtm->rtm_family != AF_INET && rtm->rtm_family != AF_INET6)) continue; rta = (struct rtattr *)RTM_RTA(rtm); na = RTM_PAYLOAD(nlh); - for ( ; RTA_OK(rta, na) && found < 2; rta = RTA_NEXT(rta, na)) { - if (rta->rta_type == RTA_GATEWAY) { + for ( ; RTA_OK(rta, na); rta = RTA_NEXT(rta, na)) { + if (rta->rta_type == RTA_GATEWAY && + rtm->rtm_family == AF_INET && !c->v4) { memcpy(&c->gw4, RTA_DATA(rta), sizeof(c->gw4)); - found++; + c->v4 = 1; } - if (rta->rta_type == RTA_OIF) { + if (rta->rta_type == RTA_GATEWAY && + rtm->rtm_family == AF_INET6 && !c->v6) { + memcpy(&c->gw6, RTA_DATA(rta), sizeof(c->gw6)); + c->v6 = 1; + } + + if (rta->rta_type == RTA_OIF && !*c->ifn) { if_indextoname(*(unsigned *)RTA_DATA(rta), c->ifn); - found++; } } + + if (nlh->nlmsg_type == NLMSG_DONE) + break; + } + + if (c->v6 == -1) { + c->v6 = 0; + req.rtm.rtm_family = AF_INET6; + req.nlh.nlmsg_seq++; + recv(s, &buf, sizeof(buf), 0); + goto v6; } out: close(s); - if (found < 2) { + if (!(c->v4 || c->v6) || !*c->ifn) { fprintf(stderr, "No routing information\n"); exit(EXIT_FAILURE); } @@ -185,15 +205,16 @@ static void get_addrs(struct ctx *c) .ifr_addr.sa_family = AF_INET, }; struct ifaddrs *ifaddr, *ifa; - int s; + int s, v4 = 0, v6 = 0; if (getifaddrs(&ifaddr) == -1) { perror("getifaddrs"); goto out; } - for (ifa = ifaddr; ifa && !c->addr4; ifa = ifa->ifa_next) { + for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { struct sockaddr_in *in_addr; + struct sockaddr_in6 *in6_addr; if (strcmp(ifa->ifa_name, c->ifn)) continue; @@ -201,17 +222,28 @@ static void get_addrs(struct ctx *c) if (!ifa->ifa_addr) continue; - if (ifa->ifa_addr->sa_family != AF_INET) - continue; + if (ifa->ifa_addr->sa_family == AF_INET && !v4) { + in_addr = (struct sockaddr_in *)ifa->ifa_addr; + c->addr4 = in_addr->sin_addr.s_addr; + in_addr = (struct sockaddr_in *)ifa->ifa_netmask; + c->mask4 = in_addr->sin_addr.s_addr; + v4 = 1; + } else if (ifa->ifa_addr->sa_family == AF_INET6 && !v6) { + in6_addr = (struct sockaddr_in6 *)ifa->ifa_addr; + memcpy(&c->addr6, &in6_addr->sin6_addr, + sizeof(c->addr6)); + v6 = 1; + } - in_addr = (struct sockaddr_in *)ifa->ifa_addr; - c->addr4 = in_addr->sin_addr.s_addr; - in_addr = (struct sockaddr_in *)ifa->ifa_netmask; - c->mask4 = in_addr->sin_addr.s_addr; + if (v4 == c->v4 && v6 == c->v6) + break; } freeifaddrs(ifaddr); + if (v4 != c->v4 || v6 != c->v6) + goto out; + s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket SIOCGIFHWADDR"); @@ -239,55 +271,72 @@ out: */ static void get_dns(struct ctx *c) { - char buf[BUFSIZ], *p, *nl; - int dns4 = 0; + char buf[BUFSIZ], *p, *end; + int dns4 = 0, dns6 = 0; FILE *r; r = fopen("/etc/resolv.conf", "r"); - while (fgets(buf, BUFSIZ, r) && !dns4) { + while (fgets(buf, BUFSIZ, r) && !(dns4 && dns6)) { if (!strstr(buf, "nameserver ")) continue; p = strrchr(buf, ' '); - nl = strchr(buf, '\n'); - if (nl) - *nl = 0; + end = strpbrk(buf, "%\n"); + if (end) + *end = 0; if (p && inet_pton(AF_INET, p + 1, &c->dns4)) dns4 = 1; + if (p && inet_pton(AF_INET6, p + 1, &c->dns6)) + dns6 = 1; } fclose(r); - if (dns4) + if (dns4 || dns6) return; - fprintf(stderr, "Couldn't get IPv4 nameserver address\n"); + fprintf(stderr, "Couldn't get any nameserver address\n"); exit(EXIT_FAILURE); } /** - * sock4_l4() - Create and bind AF_INET socket for given L4, add to epoll list + * sock_l4() - Create and bind socket for given L4, add to epoll list * @c: Execution context + * @v: IP protocol, 4 or 6 * @proto: Protocol number, network order * @port: L4 port, network order * * Return: newly created socket, -1 on error */ -static int sock4_l4(struct ctx *c, uint16_t proto, uint16_t port) +static int sock_l4(struct ctx *c, int v, uint16_t proto, uint16_t port) { - struct sockaddr_in addr = { + struct sockaddr_in addr4 = { .sin_family = AF_INET, .sin_port = port, .sin_addr = { .s_addr = c->addr4 }, }; + struct sockaddr_in6 addr6 = { + .sin6_family = AF_INET6, + .sin6_port = port, + .sin6_addr = c->addr6, + }; struct epoll_event ev = { 0 }; - int fd; + const struct sockaddr *sa; + int fd, sl; - fd = socket(AF_INET, SOCK_RAW, proto); + fd = socket(v == 4 ? AF_INET : AF_INET6, SOCK_RAW, proto); if (fd < 0) { perror("L4 socket"); return -1; } - if (bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { + if (v == 4) { + sa = (const struct sockaddr *)&addr4; + sl = sizeof(addr4); + } else { + sa = (const struct sockaddr *)&addr6; + sl = sizeof(addr6); + } + + if (bind(fd, sa, sl) < 0) { perror("L4 bind"); close(fd); return -1; @@ -304,7 +353,7 @@ static int sock4_l4(struct ctx *c, uint16_t proto, uint16_t port) } /** - * lookup4() - Look up socket entry from tap-sourced packet, create if missing + * lookup4() - Look up entry from tap-sourced IPv4 packet, create if missing * @c: Execution context * @eh: Packet buffer, Ethernet header * @@ -337,17 +386,21 @@ static int lookup4(struct ctx *c, const struct ethhdr *eh) } for (i = 0; i < CT_SIZE && ct[i].p; i++) { - if (iph->protocol == IPPROTO_ICMP) + if (iph->protocol == IPPROTO_ICMP && ct[i].p == IPPROTO_ICMP) one_icmp_fd = ct[i].fd; } if (i == CT_SIZE) { fprintf(stderr, "\nToo many sockets, aborting "); } else { - if (iph->protocol == IPPROTO_ICMP && one_icmp_fd) - ct[i].fd = one_icmp_fd; - else - ct[i].fd = sock4_l4(c, iph->protocol, th->source); + if (iph->protocol == IPPROTO_ICMP) { + if (one_icmp_fd) + ct[i].fd = one_icmp_fd; + else + ct[i].fd = sock_l4(c, 4, iph->protocol, 0); + } else { + ct[i].fd = sock_l4(c, 4, iph->protocol, th->source); + } fprintf(stderr, "\n(socket %i) New ", ct[i].fd); ct[i].p = iph->protocol; @@ -378,7 +431,103 @@ static int lookup4(struct ctx *c, const struct ethhdr *eh) } /** - * lookup4_r4() - Reverse look up connection tracking entry from incoming packet + * lookup6() - Look up entry from tap-sourced IPv6 packet, create if missing + * @c: Execution context + * @eh: Packet buffer, Ethernet header + * + * Return: -1 for unsupported or too many sockets, matching socket otherwise + */ +static int lookup6(struct ctx *c, const struct ethhdr *eh) +{ + struct ipv6hdr *ip6h = (struct ipv6hdr *)(eh + 1); + char buf_s[BUFSIZ], buf_d[BUFSIZ]; + struct ct6 *ct = c->map6; + int i, one_icmp_fd = 0; + struct tcphdr *th; + uint8_t proto; + + th = (struct tcphdr *)ipv6_l4hdr(ip6h, &proto); + if (!th) + return -1; + + if (proto != IPPROTO_ICMPV6 && proto != IPPROTO_TCP && + proto != IPPROTO_UDP) + return -1; + + for (i = 0; i < CT_SIZE; i++) { + if (ct[i].p != proto) + continue; + + if (memcmp(ct[i].hd, eh->h_dest, ETH_ALEN) || + memcmp(ct[i].hs, eh->h_source, ETH_ALEN) || + memcmp(&ct[i].sa, &ip6h->saddr, sizeof(ct[i].sa))) + continue; + + if (ct[i].p != IPPROTO_ICMPV6 && + ct[i].sp != th->source) + continue; + + if (ct[i].p == IPPROTO_ICMPV6 && + memcmp(&ct[i].da, &ip6h->daddr, sizeof(ct[i].da))) + continue; + + if (ct[i].p != IPPROTO_ICMPV6) { + memcpy(&ct[i].da, &ip6h->daddr, sizeof(ct[i].da)); + ct[i].dp = th->dest; + } + + return ct[i].fd; + } + + for (i = 0; i < CT_SIZE && ct[i].p; i++) { + if (proto == IPPROTO_ICMPV6 && ct[i].p == IPPROTO_ICMPV6) + one_icmp_fd = ct[i].fd; + } + + if (i == CT_SIZE) { + fprintf(stderr, "\nToo many sockets, aborting "); + } else { + if (proto == IPPROTO_ICMPV6) { + if (one_icmp_fd) + ct[i].fd = one_icmp_fd; + else + ct[i].fd = sock_l4(c, 6, proto, 0); + } else { + ct[i].fd = sock_l4(c, 6, proto, th->source); + } + + fprintf(stderr, "\n(socket %i) New ", ct[i].fd); + ct[i].p = proto; + memcpy(&ct[i].sa, &ip6h->saddr, sizeof(ct[i].sa)); + memcpy(&ct[i].da, &ip6h->daddr, sizeof(ct[i].da)); + if (ct[i].p != IPPROTO_ICMPV6) { + ct[i].sp = th->source; + ct[i].dp = th->dest; + } + memcpy(&ct[i].hd, eh->h_dest, ETH_ALEN); + memcpy(&ct[i].hs, eh->h_source, ETH_ALEN); + } + + if (proto == IPPROTO_ICMPV6) { + fprintf(stderr, "icmpv6 connection\n\tfrom %s\n" + "\tto %s\n\n", + inet_ntop(AF_INET6, &ct[i].sa, buf_s, sizeof(buf_s)), + inet_ntop(AF_INET6, &ct[i].da, buf_d, sizeof(buf_d))); + } else { + fprintf(stderr, "%s connection\n\tfrom [%s]:%i\n" + "\tto [%s]:%i\n\n", + getprotobynumber(proto)->p_name, + inet_ntop(AF_INET6, &ct[i].sa, buf_s, sizeof(buf_s)), + ntohs(th->source), + inet_ntop(AF_INET6, &ct[i].da, buf_d, sizeof(buf_d)), + ntohs(th->dest)); + } + + return (i == CT_SIZE) ? -1 : ct[i].fd; +} + +/** + * lookup_r4() - Reverse look up connection tracking entry for IPv4 packet * @ct: Connection tracking table * @fd: File descriptor that received the packet * @iph: Packet buffer, IP header @@ -403,13 +552,26 @@ struct ct4 *lookup_r4(struct ct4 *ct, int fd, struct iphdr *iph) } /** - * nat4_out() - Perform outgoing IPv4 address translation - * @addr: Source address to be used - * @iph: IP header + * lookup_r6() - Reverse look up connection tracking entry for IPv6 packet + * @ct: Connection tracking table + * @fd: File descriptor that received the packet + * + * Return: matching entry if any, NULL otherwise */ -static void nat4_out(unsigned long addr, struct iphdr *iph) +struct ct6 *lookup_r6(struct ct6 *ct, int fd, struct tcphdr *th) { - iph->saddr = addr; + int i; + + for (i = 0; i < CT_SIZE; i++) { + if (ct[i].fd != fd) + continue; + + if (ct[i].p == IPPROTO_ICMPV6 || + (ct[i].dp == th->source && ct[i].sp == th->dest)) + return &ct[i]; + } + + return NULL; } /** @@ -454,7 +616,7 @@ static void csum_tcp4(struct iphdr *iph) } /** - * tap4_handler() - Packet handler for tap file descriptor + * tap4_handler() - IPv4 packet handler for tap file descriptor * @c: Execution context * @len: Total L2 packet length * @in: Packet buffer, L2 headers @@ -502,8 +664,6 @@ static void tap4_handler(struct ctx *c, int len, char *in) else if (iph->protocol != IPPROTO_ICMP) return; - nat4_out(c->addr4, iph); - if (sendto(fd, (void *)th, len - sizeof(*eh) - iph->ihl * 4, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) perror("sendto"); @@ -511,7 +671,103 @@ static void tap4_handler(struct ctx *c, int len, char *in) } /** - * ext4_handler() - Packet handler for external routable interface + * tap6_handler() - IPv6 packet handler for tap file descriptor + * @c: Execution context + * @len: Total L2 packet length + * @in: Packet buffer, L2 headers + */ +static void tap6_handler(struct ctx *c, int len, char *in) +{ + struct ethhdr *eh = (struct ethhdr *)in; + struct ipv6hdr *ip6h = (struct ipv6hdr *)(eh + 1); + struct tcphdr *th; + struct udphdr *uh; + struct icmp6hdr *ih; + struct sockaddr_in6 addr = { + .sin6_family = AF_INET6, + .sin6_addr = ip6h->daddr, + }; + char buf_s[BUFSIZ], buf_d[BUFSIZ]; + uint8_t proto; + int fd; + + if (ndp(c, len, eh)) + return; + + fd = lookup6(c, eh); + if (fd == -1) + return; + + th = (struct tcphdr *)ipv6_l4hdr(ip6h, &proto); + uh = (struct udphdr *)th; + ih = (struct icmp6hdr *)th; + + if (proto == IPPROTO_ICMPV6) { + fprintf(stderr, "icmpv6 from tap: %s ->\n\t%s (socket %i)\n", + inet_ntop(AF_INET6, &ip6h->saddr, buf_s, sizeof(buf_s)), + inet_ntop(AF_INET6, &ip6h->daddr, buf_d, sizeof(buf_d)), + fd); + } else { + fprintf(stderr, "%s from tap: [%s]:%i\n" + "\t-> [%s]:%i (socket %i)\n", + getprotobynumber(proto)->p_name, + inet_ntop(AF_INET6, &ip6h->saddr, buf_s, sizeof(buf_s)), + ntohs(th->source), + inet_ntop(AF_INET6, &ip6h->daddr, buf_d, sizeof(buf_d)), + ntohs(th->dest), + fd); + } + + if (proto != IPPROTO_TCP && proto != IPPROTO_UDP && + proto != IPPROTO_ICMPV6) + return; + + ip6h->saddr = c->addr6; + + ip6h->hop_limit = proto; + ip6h->version = 0; + ip6h->nexthdr = 0; + memset(ip6h->flow_lbl, 0, 3); + + if (proto == IPPROTO_TCP) { + th->check = 0; + th->check = csum_ip4(ip6h, + len - ((intptr_t)th - (intptr_t)eh) + + sizeof(*ip6h)); + } else if (proto == IPPROTO_UDP) { + uh->check = 0; + uh->check = csum_ip4(ip6h, + len - ((intptr_t)uh - (intptr_t)eh) + + sizeof(*ip6h)); + } else if (proto == IPPROTO_ICMPV6) { + ih->icmp6_cksum = 0; + ih->icmp6_cksum = csum_ip4(ip6h, + len - ((intptr_t)ih - (intptr_t)eh) + + sizeof(*ip6h)); + } + + ip6h->version = 6; + ip6h->nexthdr = proto; + ip6h->hop_limit = 255; + + if (sendto(fd, (void *)th, len - ((intptr_t)th - (intptr_t)eh), 0, + (struct sockaddr *)&addr, sizeof(addr)) < 0) + perror("sendto"); + +} + +static void tap_handler(struct ctx *c, int len, char *in) +{ + struct ethhdr *eh = (struct ethhdr *)in; + + if (eh->h_proto == ntohs(ETH_P_IP) || eh->h_proto == ntohs(ETH_P_ARP)) + tap4_handler(c, len, in); + else if (eh->h_proto == ntohs(ETH_P_IPV6)) + tap6_handler(c, len, in); +} + +/** + * ext4_handler() - IPv4 packet handler for external routable interface * @c: Execution context * @fd: File descriptor that received the packet * @len: Total L3 packet length @@ -565,6 +821,85 @@ static void ext4_handler(struct ctx *c, int fd, int len, char *in) perror("send"); } +/** + * ext6_handler() - IPv6 packet handler for external routable interface + * @c: Execution context + * @fd: File descriptor that received the packet + * @len: Total L4 packet length + * @in: Packet buffer, L4 headers + */ +static int ext6_handler(struct ctx *c, int fd, int len, char *in) +{ + struct tcphdr *th = (struct tcphdr *)in; + struct udphdr *uh; + struct icmp6hdr *ih; + char buf_s[BUFSIZ], buf_d[BUFSIZ], buf[ETH_MAX_MTU] = { 0 }; + struct ethhdr *eh = (struct ethhdr *)buf; + struct ipv6hdr *ip6h = (struct ipv6hdr *)(eh + 1); + struct ct6 *entry; + + entry = lookup_r6(c->map6, fd, th); + if (!entry) + return 0; + + ip6h->daddr = entry->sa; + ip6h->saddr = entry->da; + memcpy(ip6h + 1, in, len); + ip6h->payload_len = htons(len); + + th = (struct tcphdr *)(ip6h + 1); + uh = (struct udphdr *)th; + ih = (struct icmp6hdr *)th; + ip6h->hop_limit = entry->p; + + if (entry->p == IPPROTO_TCP) { + th->check = 0; + th->check = csum_ip4(ip6h, len + sizeof(*ip6h)); + } else if (entry->p == IPPROTO_UDP) { + uh->check = 0; + uh->check = csum_ip4(ip6h, len + sizeof(*ip6h)); + } else if (entry->p == IPPROTO_ICMPV6) { + ih->icmp6_cksum = 0; + ih->icmp6_cksum = csum_ip4(ip6h, len + sizeof(*ip6h)); + } + + ip6h->version = 6; + ip6h->nexthdr = entry->p; + ip6h->hop_limit = 255; + + memcpy(eh->h_dest, entry->hs, ETH_ALEN); + memcpy(eh->h_source, entry->hd, ETH_ALEN); + eh->h_proto = ntohs(ETH_P_IPV6); + + if (entry->p == IPPROTO_ICMPV6) { + fprintf(stderr, "icmpv6 (socket %i) to tap: %s\n\t-> %s\n", + entry->fd, + inet_ntop(AF_INET6, &ip6h->saddr, buf_s, sizeof(buf_s)), + inet_ntop(AF_INET6, &ip6h->daddr, buf_d, + sizeof(buf_d))); + } else { + fprintf(stderr, "%s (socket %i) to tap: [%s]:%i\n" + "\t-> [%s]:%i\n", + getprotobynumber(entry->p)->p_name, + entry->fd, + inet_ntop(AF_INET6, &ip6h->saddr, buf_s, sizeof(buf_s)), + ntohs(th->source), + inet_ntop(AF_INET6, &ip6h->daddr, buf_d, sizeof(buf_d)), + ntohs(th->dest)); + } + + if (send(c->fd_unix, buf, len + sizeof(*ip6h) + sizeof(*eh), 0) < 0) + perror("send"); + + return 1; +} + +static void ext_handler(struct ctx *c, int fd, int len, char *in) +{ + if (!ext6_handler(c, fd, len, in)) + ext4_handler(c, fd, len, in); +} + /** * usage() - Print usage and exit * @name: Executable name @@ -585,8 +920,9 @@ void usage(const char *name) */ int main(int argc, char **argv) { - struct epoll_event events[EPOLL_EVENTS]; + char buf6[3][sizeof("0123:4567:89ab:cdef:0123:4567:89ab:cdef")]; char buf4[4][sizeof("255.255.255.255")]; + struct epoll_event events[EPOLL_EVENTS]; struct epoll_event ev = { 0 }; char buf[ETH_MAX_MTU]; struct ctx c = { 0 }; @@ -600,16 +936,27 @@ int main(int argc, char **argv) get_addrs(&c); get_dns(&c); - fprintf(stderr, "ARP:\n"); - fprintf(stderr, "\taddress: %02x:%02x:%02x:%02x:%02x:%02x from %s\n", - c.mac[0], c.mac[1], c.mac[2], c.mac[3], c.mac[4], c.mac[5], - c.ifn); - fprintf(stderr, "DHCP:\n"); - fprintf(stderr, "\tassign: %s, mask: %s, router: %s, DNS: %s\n\n", - inet_ntop(AF_INET, &c.addr4, buf4[0], sizeof(buf4[0])), - inet_ntop(AF_INET, &c.mask4, buf4[1], sizeof(buf4[1])), - inet_ntop(AF_INET, &c.gw4, buf4[2], sizeof(buf4[2])), - inet_ntop(AF_INET, &c.dns4, buf4[3], sizeof(buf4[3]))); + if (c.v4) { + fprintf(stderr, "ARP:\n"); + fprintf(stderr, "\taddress: %02x:%02x:%02x:%02x:%02x:%02x " + "from %s\n", c.mac[0], c.mac[1], c.mac[2], + c.mac[3], c.mac[4], c.mac[5], c.ifn); + fprintf(stderr, "DHCP:\n"); + fprintf(stderr, "\tassign:\t%s\n\tnmask:\t%s\n" + "\trouter:\t%s\n\tDNS:\t%s\n", + inet_ntop(AF_INET, &c.addr4, buf4[0], sizeof(buf4[0])), + inet_ntop(AF_INET, &c.mask4, buf4[1], sizeof(buf4[1])), + inet_ntop(AF_INET, &c.gw4, buf4[2], sizeof(buf4[2])), + inet_ntop(AF_INET, &c.dns4, buf4[3], sizeof(buf4[3]))); + } + if (c.v6) { + fprintf(stderr, "NDP:\n"); + fprintf(stderr, "\tassign:\t%s\n\trouter:\t%s\n\tDNS:\t%s\n", + inet_ntop(AF_INET6, &c.addr6, buf6[0], sizeof(buf6[0])), + inet_ntop(AF_INET6, &c.gw6, buf6[1], sizeof(buf6[1])), + inet_ntop(AF_INET6, &c.dns6, buf6[2], sizeof(buf6[2]))); + } + fprintf(stderr, "\n"); c.epollfd = epoll_create1(0); if (c.epollfd == -1) { @@ -620,13 +967,14 @@ int main(int argc, char **argv) fd_unix = sock_unix(); listen: listen(fd_unix, 1); + fprintf(stderr, + "You can now start qrap:\n\t" + "./qrap 42 kvm ... -net tap,fd=42 -net nic,model=virtio\n\n"); + c.fd_unix = accept(fd_unix, NULL, NULL); ev.events = EPOLLIN; ev.data.fd = c.fd_unix; epoll_ctl(c.epollfd, EPOLL_CTL_ADD, c.fd_unix, &ev); - fprintf(stderr, - "You can now start qrap:\n\t" - "./qrap 42 kvm ... -net tap,fd=42 -net nic,model=virtio\n\n"); loop: nfds = epoll_wait(c.epollfd, events, EPOLL_EVENTS, -1); @@ -654,9 +1002,9 @@ loop: } if (events[i].data.fd == c.fd_unix) - tap4_handler(&c, len, buf); + tap_handler(&c, len, buf); else - ext4_handler(&c, events[i].data.fd, len, buf); + ext_handler(&c, events[i].data.fd, len, buf); } goto loop; diff --git a/passt.h b/passt.h index 402c95d..904b42f 100644 --- a/passt.h +++ b/passt.h @@ -23,25 +23,61 @@ struct ct4 { int fd; }; +/** + * struct ct6 - IPv6 connection tracking entry + * @p: IANA protocol number + * @sa: Source address (as seen from tap interface) + * @da: Destination address + * @sp: Source port, network order + * @dp: Destination port, network order + * @hd: Destination MAC address + * @hs: Source MAC address + * @fd: File descriptor for corresponding AF_INET6 socket + */ +struct ct6 { + uint8_t p; + struct in6_addr sa; + struct in6_addr da; + uint16_t sp; + uint16_t dp; + unsigned char hd[ETH_ALEN]; + unsigned char hs[ETH_ALEN]; + int fd; +}; + /** * struct ctx - Execution context * @epollfd: file descriptor for epoll instance * @fd_unix: AF_UNIX socket for tap file descriptor * @map4: Connection tracking table + * @v4: Enable IPv4 transport * @addr4: IPv4 address for external, routable interface * @mask4: IPv4 netmask, network order * @gw4: Default IPv4 gateway, network order * @dns4: IPv4 DNS address, network order + * @v6: Enable IPv6 transport + * @addr6: IPv6 address for external, routable interface + * @gw6: Default IPv6 gateway + * @dns4: IPv6 DNS address * @ifn: Name of routable interface */ struct ctx { int epollfd; int fd_unix; struct ct4 map4[CT_SIZE]; + struct ct6 map6[CT_SIZE]; unsigned char mac[ETH_ALEN]; + + int v4; unsigned long addr4; unsigned long mask4; unsigned long gw4; unsigned long dns4; + + int v6; + struct in6_addr addr6; + struct in6_addr gw6; + struct in6_addr dns6; + char ifn[IF_NAMESIZE]; }; diff --git a/qrap.c b/qrap.c index 06dc5c4..a3a04d3 100644 --- a/qrap.c +++ b/qrap.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "passt.h" diff --git a/util.c b/util.c index aee41a0..7dd0db1 100644 --- a/util.c +++ b/util.c @@ -7,8 +7,11 @@ * */ +#include #include #include +#include +#include /** * csum_fold() - Fold long sum for IP and TCP checksum @@ -46,3 +49,39 @@ uint16_t csum_ip4(void *buf, size_t len) return ~csum_fold(sum); } + +unsigned char *ipv6_l4hdr(struct ipv6hdr *ip6h, uint8_t *proto) +{ + int offset, len, hdrlen; + struct ipv6_opt_hdr *o; + uint8_t nh; + + len = ntohs(ip6h->payload_len); + offset = 0; + + while (offset < len) { + if (!offset) { + nh = ip6h->nexthdr; + hdrlen = sizeof(struct ipv6hdr); + } else { + nh = o->nexthdr; + hdrlen = (o->hdrlen + 1) * 8; + } + + if (nh == 59) + return NULL; + + if (nh == 0 || nh == 43 || nh == 44 || nh == 50 || + nh == 51 || nh == 60 || nh == 135 || nh == 139 || + nh == 140 || nh == 253 || nh == 254) { + offset += hdrlen; + o = (struct ipv6_opt_hdr *)(unsigned char *)ip6h + + offset; + } else { + *proto = nh; + return (unsigned char *)(ip6h + 1) + offset; + } + } + + return NULL; +} diff --git a/util.h b/util.h index 282820c..8298d22 100644 --- a/util.h +++ b/util.h @@ -1,2 +1,3 @@ uint16_t csum_fold(uint32_t sum); uint16_t csum_ip4(void *buf, size_t len); +unsigned char *ipv6_l4hdr(struct ipv6hdr *ip6h, uint8_t *proto);