summaryrefslogtreecommitdiffstats
path: root/src/libknot/xdp/bpf-kernel.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libknot/xdp/bpf-kernel.c')
-rw-r--r--src/libknot/xdp/bpf-kernel.c105
1 files changed, 69 insertions, 36 deletions
diff --git a/src/libknot/xdp/bpf-kernel.c b/src/libknot/xdp/bpf-kernel.c
index 97192bc9f..949e60ff7 100644
--- a/src/libknot/xdp/bpf-kernel.c
+++ b/src/libknot/xdp/bpf-kernel.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2025 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -35,6 +35,12 @@
/* Define maximum reasonable number of NIC queues supported. */
#define QUEUE_MAX 256
+/* Define maximum number of allowed IPv6 headers. */
+#define IPV6_HDR_MAX 3
+
+/* DNS header size. */
+#define DNS_HDR_SIZE 12
+
/* A map of configuration options. */
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
@@ -51,11 +57,6 @@ struct {
__uint(value_size, sizeof(int));
} xsks_map SEC(".maps");
-struct ipv6_frag_hdr {
- unsigned char nexthdr;
- unsigned char whatever[7];
-} __attribute__((packed));
-
SEC("xdp")
int xdp_redirect_dns_func(struct xdp_md *ctx)
{
@@ -89,10 +90,10 @@ int xdp_redirect_dns_func(struct xdp_md *ctx)
const void *ip_hdr;
const struct iphdr *ip4;
const struct ipv6hdr *ip6;
- const void *l4_hdr;
+ __u16 ip_len;
__u8 ipv4;
__u8 ip_proto;
- __u8 fragmented = 0;
+ __u8 fragmented = 0; /* Fragmented or IPv6 packet with a header extension. */
__u16 eth_type; /* In big endian. */
/* Parse Ethernet header. */
@@ -102,7 +103,7 @@ int xdp_redirect_dns_func(struct xdp_md *ctx)
data += sizeof(*eth_hdr);
/* Parse possible VLAN (802.1Q) header. */
- if (eth_hdr->h_proto == __constant_htons(ETH_P_8021Q)) {
+ if (eth_hdr->h_proto == bpf_htons(ETH_P_8021Q)) {
if (data + sizeof(__u16) + sizeof(eth_type) > data_end) {
return XDP_DROP;
} else if (meta == 0) { /* VLAN not supported. */
@@ -113,13 +114,12 @@ int xdp_redirect_dns_func(struct xdp_md *ctx)
} else {
eth_type = eth_hdr->h_proto;
}
-
ip_hdr = data;
/* Parse IPv4 or IPv6 header. */
switch (eth_type) {
- case __constant_htons(ETH_P_IP):
- ip4 = ip_hdr;
+ case bpf_htons(ETH_P_IP):
+ ip4 = data;
if ((void *)ip4 + sizeof(*ip4) > data_end) {
return XDP_DROP;
}
@@ -129,20 +129,21 @@ int xdp_redirect_dns_func(struct xdp_md *ctx)
/* Check the IP length. Cannot use strict equality due to
* Ethernet padding applied to frames shorter than 64 octects. */
- if (data_end - data < __bpf_ntohs(ip4->tot_len)) {
+ ip_len = bpf_ntohs(ip4->tot_len);
+ if (data_end - data < ip_len) {
return XDP_DROP;
}
if (ip4->frag_off != 0 &&
- ip4->frag_off != __constant_htons(IP_DF)) {
+ ip4->frag_off != bpf_htons(IP_DF)) {
fragmented = 1;
}
ip_proto = ip4->protocol;
- l4_hdr = data + ip4->ihl * 4;
+ data += ip4->ihl * 4;
ipv4 = 1;
break;
- case __constant_htons(ETH_P_IPV6):
- ip6 = ip_hdr;
+ case bpf_htons(ETH_P_IPV6):
+ ip6 = data;
if ((void *)ip6 + sizeof(*ip6) > data_end) {
return XDP_DROP;
}
@@ -152,22 +153,50 @@ int xdp_redirect_dns_func(struct xdp_md *ctx)
/* Check the IP length. Cannot use strict equality due to
* Ethernet padding applied to frames shorter than 64 octects. */
- if (data_end - data < __bpf_ntohs(ip6->payload_len) + sizeof(*ip6)) {
+ ip_len = sizeof(*ip6) + bpf_ntohs(ip6->payload_len);
+ if (data_end - data < ip_len) {
return XDP_DROP;
}
- ip_proto = ip6->nexthdr;
- data += sizeof(*ip6);
- if (ip_proto == IPPROTO_FRAGMENT) {
- fragmented = 1;
- const struct ipv6_frag_hdr *frag = data;
- if ((void *)frag + sizeof(*frag) > data_end) {
+ for (int i = 0; i <= IPV6_HDR_MAX; i++) {
+ if (i == IPV6_HDR_MAX) {
return XDP_DROP;
}
- ip_proto = frag->nexthdr;
- data += sizeof(*frag);
+ const struct ipv6_opt_hdr *ext_hdr = (void *)&ip6->nexthdr;
+ if ((void *)ext_hdr + sizeof(*ext_hdr) > data_end) {
+ return XDP_DROP;
+ }
+ ip_proto = ext_hdr->nexthdr;
+
+ switch (ip_proto) {
+ case IPPROTO_HOPOPTS:
+ case IPPROTO_ROUTING:
+ case IPPROTO_DSTOPTS:
+ case IPPROTO_MH:
+ case 139: /* HIP */
+ case 140: /* Shim6 */
+ fragmented = 1;
+ data += (ext_hdr->hdrlen + 1) * 8;
+ break;
+ case IPPROTO_AH:
+ fragmented = 1;
+ data += (ext_hdr->hdrlen + 2) * 4;
+ break;
+ case IPPROTO_FRAGMENT:
+ fragmented = 1;
+ data += 8;
+ break;
+ case IPPROTO_NONE:
+ case 253: /* Reserved */
+ case 254: /* Reserved */
+ return XDP_DROP;
+ case IPPROTO_ESP: /* IPsec, different header format, stop. */
+ default: /* Not an IPv6 extension header, stop. */
+ i = IPV6_HDR_MAX + 1;
+ }
+ ip6 = data;
}
- l4_hdr = data;
+ data += sizeof(*ip6);
ipv4 = 0;
break;
default:
@@ -184,12 +213,12 @@ int xdp_redirect_dns_func(struct xdp_md *ctx)
switch (ip_proto) {
case IPPROTO_TCP:
/* Parse TCP header. */
- tcp = l4_hdr;
- if (l4_hdr + sizeof(*tcp) > data_end) {
+ tcp = data;
+ if ((void *)tcp + sizeof(*tcp) > data_end) {
return XDP_DROP;
}
- port_dest = __bpf_ntohs(tcp->dest);
+ port_dest = bpf_ntohs(tcp->dest);
if ((opts.flags & KNOT_XDP_FILTER_TCP) &&
(port_dest == opts.udp_port ||
@@ -200,22 +229,26 @@ int xdp_redirect_dns_func(struct xdp_md *ctx)
break;
case IPPROTO_UDP:
/* Parse UDP header. */
- udp = l4_hdr;
- if (l4_hdr + sizeof(*udp) > data_end) {
+ udp = data;
+ if ((void *)udp + sizeof(*udp) > data_end) {
return XDP_DROP;
}
- /* Check the UDP length. */
- if (data_end - (void *)udp < __bpf_ntohs(udp->len)) {
+ /* Check if the UDP length matches the IP payload length. */
+ if ((void *)udp - ip_hdr != ip_len - bpf_ntohs(udp->len)) {
return XDP_DROP;
}
- port_dest = __bpf_ntohs(udp->dest);
+ port_dest = bpf_ntohs(udp->dest);
if ((opts.flags & KNOT_XDP_FILTER_UDP) &&
(port_dest == opts.udp_port ||
((opts.flags & (KNOT_XDP_FILTER_PASS | KNOT_XDP_FILTER_DROP)) &&
port_dest >= opts.udp_port))) {
+ /* Check for minimum DNS message content. */
+ if (bpf_ntohs(udp->len) - sizeof(*udp) < DNS_HDR_SIZE) {
+ return XDP_DROP;
+ }
match = 1;
} else if ((opts.flags & KNOT_XDP_FILTER_QUIC) &&
(port_dest == opts.quic_port ||
@@ -236,7 +269,7 @@ int xdp_redirect_dns_func(struct xdp_md *ctx)
/* Drop matching packet if requested. */
return XDP_DROP;
} else if (fragmented) {
- /* Drop fragmented packet. */
+ /* Drop fragmented/extended DNS-related (UDP, TCP, QUIC) packet. */
return XDP_DROP;
}