]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Added XDP middleware for dropped/redirected queries logging
authorPierre GriƩ <pierre.grie@nameshield.net>
Mon, 22 Nov 2021 16:56:12 +0000 (17:56 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 30 Nov 2022 15:37:01 +0000 (16:37 +0100)
contrib/xdp-filter.ebpf.src
contrib/xdp-logging-middleware.ebpf.src [new file with mode: 0644]
contrib/xdp-logging.py [new file with mode: 0644]

index 5e4d9d13b896053fa4426ede295d674c595aaa60..879baee3a48910558976c58edb6c7a4824e63d88 100644 (file)
@@ -117,6 +117,7 @@ struct map_value
 BPF_TABLE_PINNED("hash", uint32_t, struct map_value, v4filter, 1024, "/sys/fs/bpf/dnsdist/addr-v4");
 BPF_TABLE_PINNED("hash", struct in6_addr, struct map_value, v6filter, 1024, "/sys/fs/bpf/dnsdist/addr-v6");
 BPF_TABLE_PINNED("hash", struct dns_qname, struct map_value, qnamefilter, 1024, "/sys/fs/bpf/dnsdist/qnames");
+BPF_TABLE_PINNED("prog", int, int, progsarray, 2, "/sys/fs/bpf/dnsdist/progs");
 
 /*
  * bcc has added BPF_TABLE_PINNED7 to the latest commit of the master branch, but it has not yet been released.
@@ -430,7 +431,11 @@ int xdp_dns_filter(struct xdp_md* ctx)
       key.addr = bpf_htonl(ipv4->saddr);
       // if TC bit must not be set, apply the action
       if ((r = udp_dns_reply_v4(&c, &key)) != TC) {
-        return r == DROP ? XDP_DROP : XDP_PASS;
+        if (r == DROP) {
+          progsarray.call(ctx, 0);
+          return XDP_DROP;
+        }
+        return XDP_PASS;
       }
 
       // swap src/dest IP addresses
@@ -448,7 +453,11 @@ int xdp_dns_filter(struct xdp_md* ctx)
 
       // if TC bit must not be set, apply the action
       if ((r = udp_dns_reply_v6(&c, &key)) != TC) {
-        return r == DROP ? XDP_DROP : XDP_PASS;
+        if (r == DROP) {
+          progsarray.call(ctx, 0);
+          return XDP_DROP;
+        }
+        return XDP_PASS;
       }
 
       // swap src/dest IP addresses
@@ -471,6 +480,8 @@ int xdp_dns_filter(struct xdp_md* ctx)
   memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
   memcpy(eth->h_source, swap_eth, ETH_ALEN);
 
+  progsarray.call(ctx, 1);
+
   // bounce the request
   return XDP_TX;
 }
diff --git a/contrib/xdp-logging-middleware.ebpf.src b/contrib/xdp-logging-middleware.ebpf.src
new file mode 100644 (file)
index 0000000..fee5729
--- /dev/null
@@ -0,0 +1,274 @@
+#include <net/sock.h>
+#include <uapi/linux/udp.h>
+#include <uapi/linux/ip.h>
+#include <uapi/linux/ipv6.h>
+
+#define DNS_PORT      53
+
+// do not use libc includes because this causes clang
+// to include 32bit headers on 64bit ( only ) systems.
+typedef __u8  uint8_t;
+typedef __u16 uint16_t;
+typedef __u32 uint32_t;
+typedef __u64 uint64_t;
+#define memcpy __builtin_memcpy
+
+/*
+ * Helper pointer to parse the incoming packets
+ * Copyright 2020, NLnet Labs, All rights reserved.
+ */
+struct cursor {
+  void *pos;
+  void *end;
+};
+
+/*
+ * Store the VLAN header
+ * Copyright 2020, NLnet Labs, All rights reserved.
+ */
+struct vlanhdr {
+  uint16_t tci;
+  uint16_t encap_proto;
+};
+
+/*
+ * Store the DNS header
+ * Copyright 2020, NLnet Labs, All rights reserved.
+ */
+struct dnshdr {
+  uint16_t id;
+  union {
+       struct {
+#if BYTE_ORDER == LITTLE_ENDIAN
+               uint8_t  rd     : 1;
+               uint8_t  tc     : 1;
+               uint8_t  aa     : 1;
+               uint8_t  opcode : 4;
+               uint8_t  qr     : 1;
+
+               uint8_t  rcode  : 4;
+               uint8_t  cd     : 1;
+               uint8_t  ad     : 1;
+               uint8_t  z      : 1;
+               uint8_t  ra     : 1;
+#elif BYTE_ORDER == BIG_ENDIAN || BYTE_ORDER == PDP_ENDIAN
+               uint8_t  qr     : 1;
+               uint8_t  opcode : 4;
+               uint8_t  aa     : 1;
+               uint8_t  tc     : 1;
+               uint8_t  rd     : 1;
+
+               uint8_t  ra     : 1;
+               uint8_t  z      : 1;
+               uint8_t  ad     : 1;
+               uint8_t  cd     : 1;
+               uint8_t  rcode  : 4;
+#endif
+       }        as_bits_and_pieces;
+       uint16_t as_value;
+  } flags;
+  uint16_t qdcount;
+  uint16_t ancount;
+  uint16_t nscount;
+  uint16_t arcount;
+};
+
+/*
+ * Store the qname and qtype
+ */
+struct dns_qname
+{
+  uint8_t qname[255];
+  uint16_t qtype;
+};
+
+/*
+ * The possible actions to perform on the packet
+ * PASS: XDP_PASS
+ * DROP: XDP_DROP
+ * TC: set TC bit and XDP_TX
+ */
+enum dns_action {
+  PASS = 0,
+  DROP = 1,
+  TC = 2
+};
+
+BPF_TABLE_PINNED("prog", int, int, progsarray, 2, "/sys/fs/bpf/dnsdist/progs");
+BPF_PERF_OUTPUT(events);
+
+/*
+ * Initializer of a cursor pointer
+ *  Copyright 2020, NLnet Labs, All rights reserved.
+ */
+static inline void cursor_init(struct cursor *c, struct xdp_md *ctx)
+{
+  c->end = (void *)(long)ctx->data_end;
+  c->pos = (void *)(long)ctx->data;
+}
+
+/*
+ * Header parser functions
+ * Copyright 2020, NLnet Labs, All rights reserved.
+ */
+#define PARSE_FUNC_DECLARATION(STRUCT)                            \
+static inline struct STRUCT *parse_ ## STRUCT (struct cursor *c)  \
+{                                                                 \
+  struct STRUCT *ret = c->pos;                                    \
+  if (c->pos + sizeof(struct STRUCT) > c->end)                    \
+       return 0;                                                 \
+  c->pos += sizeof(struct STRUCT);                                \
+  return ret;                                                     \
+}
+
+PARSE_FUNC_DECLARATION(ethhdr)
+PARSE_FUNC_DECLARATION(vlanhdr)
+PARSE_FUNC_DECLARATION(iphdr)
+PARSE_FUNC_DECLARATION(ipv6hdr)
+PARSE_FUNC_DECLARATION(udphdr)
+PARSE_FUNC_DECLARATION(dnshdr)
+
+/*
+ * Parse ethernet frame and fill the struct
+ * Copyright 2020, NLnet Labs, All rights reserved.
+ */
+static inline struct ethhdr *parse_eth(struct cursor *c, uint16_t *eth_proto)
+{
+  struct ethhdr  *eth;
+
+  if (!(eth = parse_ethhdr(c)))
+       return 0;
+
+  *eth_proto = eth->h_proto;
+  if (*eth_proto == bpf_htons(ETH_P_8021Q)
+  ||  *eth_proto == bpf_htons(ETH_P_8021AD)) {
+       struct vlanhdr *vlan;
+
+       if (!(vlan = parse_vlanhdr(c)))
+               return 0;
+
+       *eth_proto = vlan->encap_proto;
+       if (*eth_proto == bpf_htons(ETH_P_8021Q)
+       ||  *eth_proto == bpf_htons(ETH_P_8021AD)) {
+               if (!(vlan = parse_vlanhdr(c)))
+                       return 0;
+
+               *eth_proto = vlan->encap_proto;
+       }
+  }
+  return eth;
+}
+
+/*
+ * Parse DNS QName and fill the struct
+ */
+static inline void parse_qname(struct cursor *c, struct dns_qname *query)
+{
+  uint8_t qname_byte;
+  uint16_t qtype;
+  int length = 0;
+
+  for(int i = 0; i<255; i++) {
+      bpf_probe_read_kernel(&qname_byte, sizeof(qname_byte), c->pos);
+
+      c->pos += 1;
+      if (length == 0) {
+        if (qname_byte == 0 || qname_byte > 63 ) {
+            break;
+        }
+        length += qname_byte;
+      } else {
+        length--;
+      }
+      if (qname_byte >= 'A' && qname_byte <= 'Z') {
+              query->qname[i] = qname_byte + ('a' - 'A');
+      } else {
+              query->qname[i] = qname_byte;
+      }
+  }
+
+  bpf_probe_read_kernel(&(query->qtype), sizeof(query->qtype), c->pos);
+}
+
+/*
+ * Push data regarding the dropped/redirected packet to a perf buffer
+ */
+static inline void log_packet(struct xdp_md *ctx, enum dns_action action) {
+  // store variables
+  struct cursor   c;
+  struct ethhdr  *eth;
+  uint16_t        eth_proto;
+  struct iphdr   *ipv4;
+  struct ipv6hdr *ipv6;
+  struct udphdr  *udp;
+  struct dnshdr  *dns;
+  int            r = 0;
+
+  struct pktdata {
+    uint32_t         ipv4_src;
+    uint8_t          ipv6_src[16];
+    struct dns_qname query;
+  } packet_info = {0};
+
+  // initialise the cursor
+  cursor_init(&c, ctx);
+
+  if ((eth = parse_eth(&c, &eth_proto))) {
+       if (eth_proto == bpf_htons(ETH_P_IP))
+        {
+                if ((ipv4 = parse_iphdr(&c)))
+               {
+                       if (action == DROP)
+                       {
+                               memcpy(&(packet_info.ipv4_src), &(ipv4->saddr), sizeof(packet_info.ipv4_src));
+                       }
+                       else if (action == TC)
+                       {
+                               memcpy(&(packet_info.ipv4_src), &(ipv4->daddr), sizeof(packet_info.ipv4_src));
+                       }
+                       if ((udp = parse_udphdr(&c)))
+                       {
+                               if ((dns = parse_dnshdr(&c)))
+                               {
+                                       parse_qname(&c, &(packet_info.query));
+                               }
+                       }
+                }
+
+        }
+        else if (eth_proto == bpf_htons(ETH_P_IPV6))
+        {
+                if ((ipv6 = parse_ipv6hdr(&c)))
+               {
+                       if (action == DROP)
+                       {
+                               memcpy(&(packet_info.ipv6_src), &(ipv6->saddr.in6_u.u6_addr8), 16);
+                       }
+                       else if (action == TC)
+                       {
+                               memcpy(&(packet_info.ipv6_src), &(ipv6->daddr.in6_u.u6_addr8), 16);
+                       }
+                       if ((udp = parse_udphdr(&c)))
+                       {
+                               if ((dns = parse_dnshdr(&c)))
+                               {
+                                       parse_qname(&c, &(packet_info.query));
+                               }
+                       }
+                }
+        }
+  }
+  events.perf_submit(ctx, &packet_info, sizeof(packet_info));
+}
+
+int log_drop(struct xdp_md *ctx)
+{
+  log_packet(ctx, DROP);
+  return XDP_DROP;
+}
+
+int log_tc(struct xdp_md *ctx)
+{
+  log_packet(ctx, TC);
+  return XDP_TX;
+}
diff --git a/contrib/xdp-logging.py b/contrib/xdp-logging.py
new file mode 100644 (file)
index 0000000..1c0e012
--- /dev/null
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+
+from bcc import BPF
+import ctypes as ct
+import netaddr
+import socket
+
+class DNSQuery(ct.Structure):
+    _fields_ = [
+        ("qname", ct.c_uint8 * 255),
+        ("qtype", ct.c_uint16)
+    ]
+
+class PacketInfo(ct.Structure):
+    _fields_ = [
+        ("ipv4_src", ct.c_uint32),
+        ("ipv6_src", ct.c_uint8 * 16),
+        ("query", DNSQuery)
+    ]
+
+def decode_qname(qname_array):
+    qname = ""
+    length = 0
+    for qname_byte in qname_array:
+        if length == 0:
+            if int(qname_byte) == 0:
+                break
+            else:
+                length = int(qname_byte)
+                if qname != "":
+                    qname += '.'
+        else:
+            qname += chr(int(qname_byte))
+            length -= 1
+    return qname
+
+def print_event(cpu, data, size):
+    event = ct.cast(data, ct.POINTER(PacketInfo)).contents
+    if event.ipv4_src != 0:
+        src_ip = str(netaddr.IPAddress(socket.htonl(event.ipv4_src)))
+    else:
+        src_ip = str(netaddr.IPAddress(sum([byte << 8*(15-index) for index, byte in enumerate(event.ipv6_src)]), 6))
+    qtype = INV_QTYPES[socket.htons(event.query.qtype)]
+    qname = decode_qname(event.query.qname)
+    print(f"{src_ip}|{qtype}|{qname}")
+
+QTYPES = {'LOC': 29, '*': 255, 'IXFR': 251, 'UINFO': 100, 'NSEC3': 50, 'AAAA': 28, 'CNAME': 5, 'MINFO': 14, 'EID': 31, 'GPOS': 27, 'X25': 19, 'HINFO': 13, 'CAA': 257, 'NULL': 10, 'DNSKEY': 48, 'DS': 43, 'ISDN': 20, 'SOA': 6, 'RP': 17, 'UID': 101, 'TALINK': 58, 'TKEY': 249, 'PX': 26, 'NSAP-PTR': 23, 'TXT': 16, 'IPSECKEY': 45, 'DNAME': 39, 'MAILA': 254, 'AFSDB': 18, 'SSHFP': 44, 'NS': 2, 'PTR': 12, 'SPF': 99, 'TA': 32768, 'A': 1, 'NXT': 30, 'AXFR': 252, 'RKEY': 57, 'KEY': 25, 'NIMLOC': 32, 'A6': 38, 'TLSA': 52, 'MG': 8, 'HIP': 55, 'NSEC': 47, 'GID': 102, 'SRV': 33, 'DLV': 32769, 'NSEC3PARAM': 51, 'UNSPEC': 103, 'TSIG': 250, 'ATMA': 34, 'RRSIG': 46, 'OPT': 41, 'MD': 3, 'NAPTR': 35, 'MF': 4, 'MB': 7, 'DHCID': 49, 'MX': 15, 'MAILB': 253, 'CERT': 37, 'NINFO': 56, 'APL': 42, 'MR': 9, 'SIG': 24, 'WKS': 11, 'KX': 36, 'NSAP': 22, 'RT': 21, 'SINK': 40}
+INV_QTYPES = {v: k for k, v in QTYPES.items()}
+
+# Main
+xdp = BPF(src_file="xdp-logging-middleware.ebpf.src")
+
+fn_drop = xdp.load_func("log_drop", BPF.XDP)
+fn_tc = xdp.load_func("log_tc", BPF.XDP)
+
+progs = xdp.get_table("progsarray")
+events = xdp.get_table("events")
+
+progs[ct.c_int(0)] = ct.c_int(fn_drop.fd)
+progs[ct.c_int(1)] = ct.c_int(fn_tc.fd)
+
+events.open_perf_buffer(print_event)
+
+print("Filter is ready")
+while True:
+    try:
+        xdp.perf_buffer_poll()
+    except KeyboardInterrupt:
+        break
+
+if progs[ct.c_int(0)]:
+    del(progs[ct.c_int(0)])
+if progs[ct.c_int(1)]:
+    del(progs[ct.c_int(1)])