]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
xdp: support for ipv6
authorLibor Peltan <libor.peltan@nic.cz>
Mon, 23 Dec 2019 15:50:34 +0000 (16:50 +0100)
committerDaniel Salzman <daniel.salzman@nic.cz>
Tue, 21 Apr 2020 16:43:10 +0000 (18:43 +0200)
src/knot/server/udp-handler.c
src/libknot/xdp/af_xdp.c
src/libknot/xdp/af_xdp.h
src/libknot/xdp/bpf-kernel.c
src/libknot/xdp/bpf-user.h

index b8ac182d1df60782d2c9a19db39557a06117adc6..9465fdec685cb9ec81a8b33f26b1cbb433ff89b4 100644 (file)
@@ -406,7 +406,7 @@ static int xdp_recvmmsg_handle(udp_context_t *ctx, void *d, void *xdp_sock)
                struct iovec *rx = &rq->msgs_rx[i].payload;
                struct iovec *tx = &rq->msgs_tx[i].payload;
 
-               *tx = knot_xsk_alloc_frame(xdp_sock);
+               *tx = knot_xsk_alloc_frame(xdp_sock, rq->msgs_rx[i].ip_to.ss_family == AF_INET6);
                if (tx->iov_base == NULL) {
                        return KNOT_ERROR;
                }
index f48eae2478c8b0ae5c3645007198f58d130733c1..46e30042d559ff86969b47e249a836cf9cf8206f 100644 (file)
@@ -27,7 +27,6 @@
 
 #include <assert.h>
 #include <errno.h>
-#include <stdbool.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 
 /** The memory layout of each umem frame. */
 struct umem_frame {
-       union { uint8_t bytes[FRAME_SIZE]; struct {
+       union { uint8_t bytes[FRAME_SIZE]; union {
 
        struct udpv4 udpv4;
+       struct udpv6 udpv6;
 
        }; };
 };
 
-static const size_t FRAME_PAYLOAD_OFFSET = offsetof(struct udpv4, data) + offsetof(struct umem_frame, udpv4);
+static const size_t FRAME_PAYLOAD_OFFSET4 = offsetof(struct udpv4, data) + offsetof(struct umem_frame, udpv4);
+static const size_t FRAME_PAYLOAD_OFFSET6 = offsetof(struct udpv6, data) + offsetof(struct umem_frame, udpv6);
 
 static const struct xsk_umem_config global_umem_config = {
        .fill_size = XSK_RING_PROD__DEFAULT_NUM_DESCS,
@@ -128,14 +129,15 @@ static struct umem_frame *xsk_alloc_umem_frame(struct xsk_umem_info *umem)
 }
 
 _public_
-struct iovec knot_xsk_alloc_frame(struct knot_xsk_socket *socket)
+struct iovec knot_xsk_alloc_frame(struct knot_xsk_socket *socket, bool ipv6)
 {
        struct iovec res = { 0 };
+       size_t ofs = ipv6 ? FRAME_PAYLOAD_OFFSET6 : FRAME_PAYLOAD_OFFSET4;
 
        struct umem_frame *uframe = xsk_alloc_umem_frame(socket->umem);
        if (uframe != NULL) {
-               res.iov_len = MIN(UINT16_MAX, FRAME_SIZE - FRAME_PAYLOAD_OFFSET - 4/*eth CRC*/);
-               res.iov_base = uframe->udpv4.data;
+               res.iov_len = MIN(UINT16_MAX, FRAME_SIZE - ofs - 4/*eth CRC*/);
+               res.iov_base = ipv6 ? uframe->udpv6.data : uframe->udpv4.data;
        }
        return res;
 }
@@ -271,6 +273,26 @@ static __be16 pkt_ipv4_checksum_2(const struct iphdr *h)
        return ~BS16(from32to16(sum32));
 }
 
+static void udp_checksum1(size_t *result, const void *_data, size_t _data_len)
+{
+       assert(!(_data_len & 1));
+       const uint16_t *data = _data;
+       size_t len = _data_len / 2;
+       while (len-- > 0) {
+               *result += *data++;
+       }
+}
+
+static void udp_checksum_finish(size_t *result)
+{
+       while (*result > 0xffff) {
+               *result = (*result & 0xffff) + (*result >> 16);
+       }
+       if (*result != 0xffff) {
+               *result = ~*result;
+       }
+}
+
 static int pkt_send(struct knot_xsk_socket *xsk, uint64_t addr, uint32_t len)
 {
        uint32_t tx_idx;
@@ -304,8 +326,7 @@ static uint8_t *msg_uframe_p(struct knot_xsk_socket *socket, const knot_xsk_msg_
        return uframe_p;
 }
 
-_public_
-int knot_xsk_sendmsg(struct knot_xsk_socket *socket, const knot_xsk_msg_t *msg)
+int xsk_sendmsg_ipv4(struct knot_xsk_socket *socket, const knot_xsk_msg_t *msg)
 {
        uint8_t *uframe_p = msg_uframe_p(socket, msg);
        if (uframe_p == NULL) {
@@ -347,11 +368,76 @@ int knot_xsk_sendmsg(struct knot_xsk_socket *socket, const knot_xsk_msg_t *msg)
        memcpy(h->eth.h_source, msg->eth_from, sizeof(msg->eth_from));
        h->eth.h_proto = BS16(ETH_P_IP);
 
-       uint32_t eth_len = FRAME_PAYLOAD_OFFSET + msg->payload.iov_len + 4/*CRC*/;
+       uint32_t eth_len = FRAME_PAYLOAD_OFFSET4 + msg->payload.iov_len + 4/*CRC*/;
+       // TODO compute eth CRC (also for IPv6)
+
+       return pkt_send(socket, h->bytes - socket->umem->frames->bytes, eth_len);
+}
+
+int xsk_sendmsg_ipv6(struct knot_xsk_socket *socket, const knot_xsk_msg_t *msg)
+{
+       uint8_t *uframe_p = msg_uframe_p(socket, msg);
+       if (uframe_p == NULL) {
+               return KNOT_EINVAL;
+       }
+
+       struct umem_frame *uframe = (struct umem_frame *)uframe_p;
+       struct udpv6 *h = &uframe->udpv6;
+
+       // sockaddr* contents is already in network byte order
+       const struct sockaddr_in6 *src_v6 = (const struct sockaddr_in6 *)&msg->ip_from;
+       const struct sockaddr_in6 *dst_v6 = (const struct sockaddr_in6 *)&msg->ip_to;
+
+       const uint16_t udp_len = sizeof(h->udp) + msg->payload.iov_len;
+       h->udp.len = BS16(udp_len);
+       h->udp.source = src_v6->sin6_port;
+       h->udp.dest   = dst_v6->sin6_port;
+       h->udp.check  = 0;
+
+       h->ipv6.version = 6;
+       h->ipv6.payload_len = BS16(udp_len);
+       memset(h->ipv6.flow_lbl, 0, sizeof(h->ipv6.flow_lbl));
+       h->ipv6.hop_limit = IPDEFTTL;
+       h->ipv6.nexthdr = 17; // UDP
+       h->ipv6.priority = 0;
+
+       memcpy(&h->ipv6.saddr, &src_v6->sin6_addr, sizeof(src_v6->sin6_addr));
+       memcpy(&h->ipv6.daddr, &dst_v6->sin6_addr, sizeof(dst_v6->sin6_addr));
+
+       // UDP checksum is mandatory for IPv6, in contrast to IPv4
+       size_t chk = 0;
+       udp_checksum1(&chk, &h->ipv6.saddr, sizeof(h->ipv6.saddr));
+       udp_checksum1(&chk, &h->ipv6.daddr, sizeof(h->ipv6.daddr));
+       udp_checksum1(&chk, &h->udp.len, sizeof(h->udp.len));
+       __be16 version = BS16(h->ipv6.nexthdr);
+       udp_checksum1(&chk, &version, sizeof(version));
+       udp_checksum1(&chk, &h->udp, sizeof(h->udp));
+       udp_checksum1(&chk, msg->payload.iov_base, msg->payload.iov_len);
+       udp_checksum_finish(&chk);
+       h->udp.check = chk; // TODO check for mistakes ?
+
+       memcpy(h->eth.h_dest, msg->eth_to, sizeof(msg->eth_to));
+       memcpy(h->eth.h_source, msg->eth_from, sizeof(msg->eth_from));
+       h->eth.h_proto = BS16(ETH_P_IPV6);
+
+       uint32_t eth_len = FRAME_PAYLOAD_OFFSET6 + msg->payload.iov_len + 4/*CRC*/;
 
        return pkt_send(socket, h->bytes - socket->umem->frames->bytes, eth_len);
 }
 
+_public_
+int knot_xsk_sendmsg(struct knot_xsk_socket *socket, const knot_xsk_msg_t *msg)
+{
+       switch (msg->ip_from.ss_family) {
+       case AF_INET:
+               return xsk_sendmsg_ipv4(socket, msg);
+       case AF_INET6:
+               return xsk_sendmsg_ipv6(socket, msg);
+       default:
+               return KNOT_EINVAL;
+       }
+}
+
 _public_
 int knot_xsk_sendmmsg(struct knot_xsk_socket *socket, const knot_xsk_msg_t msgs[], uint32_t count)
 {
@@ -431,9 +517,12 @@ static int rx_desc(struct knot_xsk_socket *xsi, const struct xdp_desc *desc,
                udp = (struct udphdr *)(uframe_p + sizeof(struct ethhdr) + ipv4->ihl * 4);
 
        } else if (eth->h_proto == BS16(ETH_P_IPV6)) {
-               (void)ipv6;
-               ret = KNOT_ENOTSUP; // FIXME later
-               goto free_frame;
+               ipv6 = (struct ipv6hdr *)(uframe_p + sizeof(struct ethhdr));
+               if (ipv6->version != 6) {
+                       ret = KNOT_EFEWDATA;
+                       goto free_frame;
+               }
+               udp = (struct udphdr *)(uframe_p + sizeof(struct ethhdr) + sizeof(struct ipv6hdr)); // TODO ???
        } else {
                ret = KNOT_ENOTSUP;
                goto free_frame;
@@ -450,13 +539,27 @@ static int rx_desc(struct knot_xsk_socket *xsi, const struct xdp_desc *desc,
        // process the packet; ownership is passed on, but beware of holding frames
        // LATER: filter the address-port combinations that we listen on?
 
-       assert(ipv4);
-       struct sockaddr_in *src_v4 = (struct sockaddr_in *)&msg->ip_from;
-       struct sockaddr_in *dst_v4 = (struct sockaddr_in *)&msg->ip_to;
-       memcpy(&src_v4->sin_addr, &ipv4->saddr, sizeof(src_v4->sin_addr));
-       memcpy(&dst_v4->sin_addr, &ipv4->daddr, sizeof(dst_v4->sin_addr));
-       src_v4->sin_port = udp->source;
-       dst_v4->sin_port = udp->dest;
+       if (ipv4) {
+               struct sockaddr_in *src_v4 = (struct sockaddr_in *)&msg->ip_from;
+               struct sockaddr_in *dst_v4 = (struct sockaddr_in *)&msg->ip_to;
+               memcpy(&src_v4->sin_addr, &ipv4->saddr, sizeof(src_v4->sin_addr));
+               memcpy(&dst_v4->sin_addr, &ipv4->daddr, sizeof(dst_v4->sin_addr));
+               src_v4->sin_port = udp->source;
+               dst_v4->sin_port = udp->dest;
+               src_v4->sin_family = AF_INET;
+               dst_v4->sin_family = AF_INET;
+       } else {
+               assert(ipv6);
+               struct sockaddr_in6 *src_v6 = (struct sockaddr_in6 *)&msg->ip_from;
+               struct sockaddr_in6 *dst_v6 = (struct sockaddr_in6 *)&msg->ip_to;
+               memcpy(&src_v6->sin6_addr, &ipv6->saddr, sizeof(src_v6->sin6_addr));
+               memcpy(&dst_v6->sin6_addr, &ipv6->daddr, sizeof(dst_v6->sin6_addr));
+               src_v6->sin6_port = udp->source;
+               dst_v6->sin6_port = udp->dest;
+               src_v6->sin6_family = AF_INET6;
+               dst_v6->sin6_family = AF_INET6;
+               // TODO shall we anyhow handle flow info ?
+       }
 
        return KNOT_EOK;
 
index 5455147c8ea442f340740fc7d0071bbe6b7e0def..e2a9be3dc6dd16083f9fd17ea6bafc94d051fa6a 100644 (file)
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <stdbool.h>
 #include <stdint.h>
 
 #include <sys/socket.h>
@@ -35,7 +36,7 @@ int knot_xsk_init(struct knot_xsk_socket **socket, const char *ifname, int if_qu
 
 void knot_xsk_deinit(struct knot_xsk_socket *socket);
 
-struct iovec knot_xsk_alloc_frame(struct knot_xsk_socket *socket);
+struct iovec knot_xsk_alloc_frame(struct knot_xsk_socket *socket, bool ipv6);
 
 int knot_xsk_sendmsg(struct knot_xsk_socket *socket, const knot_xsk_msg_t *msg); // msg->payload MUST have been allocated by knot_xsk_alloc_frame()
 
index 31007a43b2e1f439c72bd7450093e70b1528e299..7894ab129097ce19132ca7bd2b7ea3a7f4ce011e 100644 (file)
@@ -31,7 +31,7 @@ int xdp_redirect_udp_func(struct xdp_md *ctx)
 {
        struct ethhdr *eth;
        struct iphdr *iphdr;
-       //struct ipv6hdr *ipv6hdr;
+       struct ipv6hdr *ipv6hdr;
        struct udphdr *udphdr;
 
        void *data_end = (void *)(long)ctx->data_end;
@@ -42,11 +42,9 @@ int xdp_redirect_udp_func(struct xdp_md *ctx)
                case ETH_P_IP:
                        ip_type = parse_iphdr(&nh, data_end, &iphdr);
                        break;
-               /*
                case ETH_P_IPV6:
                        ip_type = parse_ip6hdr(&nh, data_end, &ipv6hdr);
                        break;
-               */
                default:
                        return XDP_PASS;
        }
index e7f30b8a79f77f077a0a83a752e54557130b5bf3..ccce985f5e9042739abb6f77717005c54fd50660 100644 (file)
@@ -25,8 +25,6 @@
 #include <linux/ipv6.h>
 #include <linux/udp.h>
 
-//#include <uv.h> // LATER: split kresd-specific stuff
-
 struct udpv4 {
        union { uint8_t bytes[1]; struct {
 
@@ -38,6 +36,17 @@ struct udpv4 {
        } __attribute__((packed)); };
 };
 
+struct udpv6 {
+       union { uint8_t bytes[1]; struct {
+
+       struct ethhdr eth; // no VLAN support; CRC at the "end" of .data!
+       struct ipv6hdr ipv6;
+       struct udphdr udp;
+       uint8_t data[];
+
+       } __attribute__((packed)); };
+};
+
 /** Data around one network interface. */
 struct kxsk_iface {
        const char *ifname;