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.
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
// 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
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;
}
--- /dev/null
+#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, ð_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;
+}
--- /dev/null
+#!/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)])