Skip to content

Commit

Permalink
Merge pull request #11020 from MiniPierre/xdp-logging
Browse files Browse the repository at this point in the history
dnsdist: Added XDP middleware for dropped/redirected queries logging
  • Loading branch information
rgacogne committed Dec 1, 2022
2 parents b8acdc3 + 7e4692d commit 9d86b2b
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 2 deletions.
15 changes: 13 additions & 2 deletions contrib/xdp-filter.ebpf.src
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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;
}
252 changes: 252 additions & 0 deletions contrib/xdp-logging-middleware.ebpf.src
@@ -0,0 +1,252 @@
#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;
}
74 changes: 74 additions & 0 deletions contrib/xdp-logging.py
@@ -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)])

0 comments on commit 9d86b2b

Please sign in to comment.