aboutgitcodebugslistschat
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--arp.c1
-rw-r--r--dhcp.c1
-rw-r--r--ndp.c133
-rw-r--r--ndp.h1
-rw-r--r--passt.c502
-rw-r--r--passt.h36
-rw-r--r--qrap.c1
-rw-r--r--util.c39
-rw-r--r--util.h1
10 files changed, 640 insertions, 79 deletions
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 <string.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
+#include <linux/ipv6.h>
#include <linux/udp.h>
#include <net/if.h>
#include <net/if_arp.h>
diff --git a/dhcp.c b/dhcp.c
index 44a7cfe..abb76f6 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -14,6 +14,7 @@
#include <string.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
+#include <linux/ipv6.h>
#include <linux/udp.h>
#include <net/if.h>
#include <arpa/inet.h>
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 <sbrivio@redhat.com>
+ * License: GPLv2
+ *
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/icmpv6.h>
+#include <linux/udp.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <arpa/inet.h>
+
+#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 <sbrivio@redhat.com>
* 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 <linux/ipv6.h>
#include <linux/tcp.h>
#include <linux/udp.h>
+#include <linux/icmpv6.h>
#include <linux/if_link.h>
#include <net/ethernet.h>
#include <stdlib.h>
@@ -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_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) {
+ 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
@@ -566,6 +822,85 @@ static void ext4_handler(struct ctx *c, int fd, int len, char *in)
}
/**
+ * 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
@@ -24,24 +24,60 @@ struct ct4 {
};
/**
+ * 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 <errno.h>
#include <limits.h>
#include <linux/if_ether.h>
+#include <linux/ipv6.h>
#include <net/if.h>
#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 <stdio.h>
#include <stdint.h>
#include <stddef.h>
+#include <linux/ipv6.h>
+#include <arpa/inet.h>
/**
* 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);