Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dnsdist: Added XDP middleware for dropped/redirected queries logging #11020

Merged
merged 2 commits into from Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)])