From c132b35c624a87d5765bf92bbd380844a7bb81ed Mon Sep 17 00:00:00 2001 From: Vincent Li Date: Tue, 1 Oct 2024 17:46:13 +0000 Subject: [PATCH] xdp-dns: add bpf ring buf and user space program add bpf ring buff and user space to log the DNS query to system log for DNS query monitoring use clang-format to format the code clang-format -i xdp-dns/xdp_dns.bpf.c to format the code Signed-off-by: Vincent Li --- xdp-dns/Makefile | 2 +- xdp-dns/xdp_dns.bpf.c | 264 ++++++++++++++++++++++++------------------ xdp-dns/xdp_dns.c | 151 ++++++++++++------------ xdp-dns/xdp_dns_log.c | 104 +++++++++++++++++ 4 files changed, 335 insertions(+), 186 deletions(-) create mode 100644 xdp-dns/xdp_dns_log.c diff --git a/xdp-dns/Makefile b/xdp-dns/Makefile index d5f3042d..d4fdc8d1 100644 --- a/xdp-dns/Makefile +++ b/xdp-dns/Makefile @@ -2,7 +2,7 @@ XDP_TARGETS := xdp_dns.bpf BPF_SKEL_TARGETS := $(XDP_TARGETS) -USER_TARGETS := xdp_dns +USER_TARGETS := xdp_dns xdp_dns_log LIB_DIR = ../lib diff --git a/xdp-dns/xdp_dns.bpf.c b/xdp-dns/xdp_dns.bpf.c index 8c43d8e3..725a7a59 100644 --- a/xdp-dns/xdp_dns.bpf.c +++ b/xdp-dns/xdp_dns.bpf.c @@ -35,8 +35,8 @@ #include "xdp_dns.h" /* with vmlinux.h, define here to avoid the undefined error */ -#define ETH_P_8021Q 0x8100 /* 802.1Q VLAN Extended Header */ -#define ETH_P_8021AD 0x88A8 /* 802.1ad Service VLAN */ +#define ETH_P_8021Q 0x8100 /* 802.1Q VLAN Extended Header */ +#define ETH_P_8021AD 0x88A8 /* 802.1ad Service VLAN */ // do not use libc includes because this causes clang // to include 32bit headers on 64bit ( only ) systems. @@ -52,19 +52,30 @@ struct meta_data { /* Define the LPM Trie Map for domain names */ struct domain_key { - struct bpf_lpm_trie_key lpm_key; - char data[MAX_DOMAIN_SIZE + 1]; + struct bpf_lpm_trie_key lpm_key; + char data[MAX_DOMAIN_SIZE + 1]; }; struct { - __uint(type, BPF_MAP_TYPE_LPM_TRIE); - __type(key, struct domain_key); - __type(value, __u8); - __uint(max_entries, 10000); - __uint(pinning, LIBBPF_PIN_BY_NAME); - __uint(map_flags, BPF_F_NO_PREALLOC); + __uint(type, BPF_MAP_TYPE_LPM_TRIE); + __type(key, struct domain_key); + __type(value, __u8); + __uint(max_entries, 10000); + __uint(pinning, LIBBPF_PIN_BY_NAME); + __uint(map_flags, BPF_F_NO_PREALLOC); } domain_denylist SEC(".maps"); +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 1 << 12); // 4KB buffer + __uint(pinning, LIBBPF_PIN_BY_NAME); +} dns_ringbuf SEC(".maps"); + +struct qname_event { + __u8 len; + __u32 src_ip; // Store IPv4 address + char qname[MAX_DOMAIN_SIZE + 1]; +}; /* * Store the VLAN header @@ -82,23 +93,21 @@ struct cursor { void *end; }; -static __always_inline -void cursor_init(struct cursor *c, struct xdp_md *ctx) +static __always_inline void cursor_init(struct cursor *c, struct xdp_md *ctx) { c->end = (void *)(long)ctx->data_end; c->pos = (void *)(long)ctx->data; } -#define PARSE_FUNC_DECLARATION(STRUCT) \ -static __always_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; \ -} +#define PARSE_FUNC_DECLARATION(STRUCT) \ + static __always_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) @@ -106,25 +115,25 @@ PARSE_FUNC_DECLARATION(iphdr) PARSE_FUNC_DECLARATION(udphdr) PARSE_FUNC_DECLARATION(dnshdr) -static __always_inline -struct ethhdr *parse_eth(struct cursor *c, __u16 *eth_proto) +static __always_inline struct ethhdr *parse_eth(struct cursor *c, + __u16 *eth_proto) { - struct ethhdr *eth; + 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)) { + 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 (*eth_proto == __bpf_htons(ETH_P_8021Q) || + *eth_proto == __bpf_htons(ETH_P_8021AD)) { if (!(vlan = parse_vlanhdr(c))) return 0; @@ -134,94 +143,101 @@ struct ethhdr *parse_eth(struct cursor *c, __u16 *eth_proto) return eth; } -static __always_inline char *parse_dname(struct cursor *c) { - __u8 *dname = c->pos; - __u8 i; +static __always_inline char *parse_dname(struct cursor *c) +{ + __u8 *dname = c->pos; + __u8 i; - for (i = 0; i < 128; i++) { /* Maximum 128 labels */ - __u8 o; + for (i = 0; i < 128; i++) { /* Maximum 128 labels */ + __u8 o; - // Check bounds before accessing the next byte - if (c->pos + 1 > c->end) - return 0; + // Check bounds before accessing the next byte + if (c->pos + 1 > c->end) + return 0; - o = *(__u8 *)c->pos; + o = *(__u8 *)c->pos; - // Check for DNS name compression - if ((o & 0xC0) == 0xC0) { - // If the current label is compressed, skip the next 2 bytes - if (c->pos + 2 > c->end) // Ensure we have 2 bytes to skip - return 0; + // Check for DNS name compression + if ((o & 0xC0) == 0xC0) { + // If the current label is compressed, skip the next 2 bytes + if (c->pos + 2 > + c->end) // Ensure we have 2 bytes to skip + return 0; - c->pos += 2; - return (char *)dname; // Return the parsed domain name - } else if (o > 63 || c->pos + o + 1 > c->end) { - // Label is invalid or out of bounds - return 0; - } + c->pos += 2; + return (char *)dname; // Return the parsed domain name + } else if (o > 63 || c->pos + o + 1 > c->end) { + // Label is invalid or out of bounds + return 0; + } - // Move the cursor by label length and its leading length byte - c->pos += o + 1; + // Move the cursor by label length and its leading length byte + c->pos += o + 1; - // End of domain name (null label length) - if (o == 0) - return (char *)dname; - } + // End of domain name (null label length) + if (o == 0) + return (char *)dname; + } - // If we exit the loop without finding a terminating label, return NULL - return 0; + // If we exit the loop without finding a terminating label, return NULL + return 0; } -static __always_inline void *custom_memcpy(void *dest, const void *src, __u8 len) { - __u8 i; +static __always_inline void *custom_memcpy(void *dest, const void *src, + __u8 len) +{ + __u8 i; - // Perform the copy byte-by-byte to satisfy the BPF verifier - for (i = 0; i < len; i++) { - *((__u8 *)dest + i) = *((__u8 *)src + i); - } + // Perform the copy byte-by-byte to satisfy the BPF verifier + for (i = 0; i < len; i++) { + *((__u8 *)dest + i) = *((__u8 *)src + i); + } - return dest; + return dest; } // Custom strlen function for BPF -static __always_inline __u8 custom_strlen(const char *str, struct cursor *c) { - __u8 len = 0; - - // Loop through the string, ensuring not to exceed MAX_STRING_LEN - #pragma unroll - for (int i = 0; i < MAX_DOMAIN_SIZE; i++) { - if (str + i >= c->end) // Check if we are at or beyond the end of the packet - break; - if (str[i] == '\0') - break; - len++; - } - - return len; +static __always_inline __u8 custom_strlen(const char *str, struct cursor *c) +{ + __u8 len = 0; + +// Loop through the string, ensuring not to exceed MAX_STRING_LEN +#pragma unroll + for (int i = 0; i < MAX_DOMAIN_SIZE; i++) { + if (str + i >= + c->end) // Check if we are at or beyond the end of the packet + break; + if (str[i] == '\0') + break; + len++; + } + + return len; } -static __always_inline void reverse_string(char *str, __u8 len) { - for (int i = 0; i < (len - 1) / 2; i++) { - char temp = str[i]; - str[i] = str[len - 1 - i]; - str[len - 1 - i] = temp; - } +static __always_inline void reverse_string(char *str, __u8 len) +{ + for (int i = 0; i < (len - 1) / 2; i++) { + char temp = str[i]; + str[i] = str[len - 1 - i]; + str[len - 1 - i] = temp; + } } SEC("xdp") int xdp_dns_denylist(struct xdp_md *ctx) { struct meta_data *md = (void *)(long)ctx->data_meta; - struct cursor c; - struct ethhdr *eth; - struct iphdr *ipv4; - struct udphdr *udp; - struct dnshdr *dns; - char *qname; + struct cursor c; + struct ethhdr *eth; + struct iphdr *ipv4; + struct udphdr *udp; + struct dnshdr *dns; + char *qname; //__u8 value = 1; __u8 len = 0; - struct domain_key dkey = {0}; // LPM trie key + struct domain_key dkey = { 0 }; // LPM trie key if (bpf_xdp_adjust_meta(ctx, -(int)sizeof(struct meta_data))) return XDP_PASS; @@ -240,15 +256,14 @@ int xdp_dns_denylist(struct xdp_md *ctx) return XDP_PASS; /* Not IPv4 */ switch (ipv4->protocol) { case IPPROTO_UDP: - if (!(udp = parse_udphdr(&c)) - || !(udp->dest == __bpf_htons(DNS_PORT)) - || !(dns = parse_dnshdr(&c))) + if (!(udp = parse_udphdr(&c)) || + !(udp->dest == __bpf_htons(DNS_PORT)) || + !(dns = parse_dnshdr(&c))) return XDP_PASS; /* Not DNS */ - if (dns->flags.as_bits_and_pieces.qr - || dns->qdcount != __bpf_htons(1) - || dns->ancount || dns->nscount - || dns->arcount > __bpf_htons(2)) + if (dns->flags.as_bits_and_pieces.qr || + dns->qdcount != __bpf_htons(1) || dns->ancount || + dns->nscount || dns->arcount > __bpf_htons(2)) return XDP_ABORTED; // Return FORMERR? qname = parse_dname(&c); @@ -256,29 +271,53 @@ int xdp_dns_denylist(struct xdp_md *ctx) return XDP_ABORTED; // Return FORMERR? } - len = custom_strlen(qname, &c); - bpf_printk("qname %s len is %d from %pI4", qname, len, &ipv4->saddr); + len = custom_strlen(qname, &c); + bpf_printk("qname %s len is %d from %pI4", qname, len, + &ipv4->saddr); - //avoid R2 offset is outside of the packet error - if (qname + len > c.end) + //avoid R2 offset is outside of the packet error + if (qname + len > c.end) return XDP_ABORTED; // Return FORMERR? - int copy_len = len < MAX_DOMAIN_SIZE ? len : MAX_DOMAIN_SIZE; + int copy_len = len < MAX_DOMAIN_SIZE ? len : + MAX_DOMAIN_SIZE; + + // Allocate a buffer from the ring buffer + struct qname_event *event = bpf_ringbuf_reserve( + &dns_ringbuf, sizeof(*event), 0); + if (!event) + return XDP_PASS; // Drop if no space + + // Set event fields + event->len = copy_len; + event->src_ip = + ipv4->saddr; // Extract source IP address + custom_memcpy(event->qname, qname, copy_len); + event->qname[copy_len] = + '\0'; // Ensure null termination + + // Submit the event + bpf_ringbuf_submit(event, 0); + custom_memcpy(dkey.data, qname, copy_len); - dkey.data[MAX_DOMAIN_SIZE] = '\0'; // Ensure null-termination + dkey.data[MAX_DOMAIN_SIZE] = + '\0'; // Ensure null-termination reverse_string(dkey.data, copy_len); - // Set the LPM key prefix length (the length of the domain name string) - dkey.lpm_key.prefixlen = copy_len * 8; // Prefix length in bits + // Set the LPM key prefix length (the length of the domain name string) + dkey.lpm_key.prefixlen = + copy_len * 8; // Prefix length in bits - //bpf_printk("domain_key %s copy_len is %d from %pI4", dkey.data, copy_len, &ipv4->saddr); + //bpf_printk("domain_key %s copy_len is %d from %pI4", dkey.data, copy_len, &ipv4->saddr); - if (bpf_map_lookup_elem(&domain_denylist, &dkey)) { - bpf_printk("Domain %s found in denylist, dropping packet\n", dkey.data); - return XDP_DROP; - } + if (bpf_map_lookup_elem(&domain_denylist, &dkey)) { + bpf_printk( + "Domain %s found in denylist, dropping packet\n", + dkey.data); + return XDP_DROP; + } -/* + /* if (bpf_map_update_elem(&domain_denylist, &dkey, &value, BPF_ANY) < 0) { bpf_printk("Domain %s not updated in denylist\n", dkey.data); } else { @@ -288,7 +327,6 @@ int xdp_dns_denylist(struct xdp_md *ctx) break; } - } return XDP_PASS; } diff --git a/xdp-dns/xdp_dns.c b/xdp-dns/xdp_dns.c index 4c7dfd7a..13047cde 100644 --- a/xdp-dns/xdp_dns.c +++ b/xdp-dns/xdp_dns.c @@ -22,92 +22,99 @@ #include #include -#define MAX_DOMAIN_SIZE 128 // Increased size to handle larger domains +#define MAX_DOMAIN_SIZE 128 // Increased size to handle larger domains struct domain_key { - struct bpf_lpm_trie_key lpm_key; - char data[MAX_DOMAIN_SIZE + 1]; + struct bpf_lpm_trie_key lpm_key; + char data[MAX_DOMAIN_SIZE + 1]; }; // Function to encode a domain name with label lengths -static void encode_domain(const char *domain, char *encoded) { - const char *ptr = domain; - char *enc_ptr = encoded; - size_t label_len; +static void encode_domain(const char *domain, char *encoded) +{ + const char *ptr = domain; + char *enc_ptr = encoded; + size_t label_len; - while (*ptr) { - // Find the length of the current label - label_len = strcspn(ptr, "."); - if (label_len > 0) { - // Set the length of the label - *enc_ptr++ = (char)label_len; - // Copy the label itself - memcpy(enc_ptr, ptr, label_len); - enc_ptr += label_len; - } - // Move to the next label - ptr += label_len; - if (*ptr == '.') { - ptr++; // Skip the dot - } - } - // Append a zero-length label to mark the end of the domain name - *enc_ptr++ = 0; + while (*ptr) { + // Find the length of the current label + label_len = strcspn(ptr, "."); + if (label_len > 0) { + // Set the length of the label + *enc_ptr++ = (char)label_len; + // Copy the label itself + memcpy(enc_ptr, ptr, label_len); + enc_ptr += label_len; + } + // Move to the next label + ptr += label_len; + if (*ptr == '.') { + ptr++; // Skip the dot + } + } + // Append a zero-length label to mark the end of the domain name + *enc_ptr++ = 0; } -static void reverse_string(char *str) { - int len = strlen(str); - for (int i = 0; i < len / 2; i++) { - char temp = str[i]; - str[i] = str[len - i - 1]; - str[len - i - 1] = temp; - } +static void reverse_string(char *str) +{ + int len = strlen(str); + for (int i = 0; i < len / 2; i++) { + char temp = str[i]; + str[i] = str[len - i - 1]; + str[len - i - 1] = temp; + } } -int main(int argc, char *argv[]) { - int map_fd; - struct domain_key dkey = {0}; - __u8 value = 1; +int main(int argc, char *argv[]) +{ + int map_fd; + struct domain_key dkey = { 0 }; + __u8 value = 1; - // Check for proper number of arguments - if (argc != 3) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; - } + // Check for proper number of arguments + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } - // Encode the domain name with label lengths - encode_domain(argv[2], dkey.data); - reverse_string(dkey.data); + // Encode the domain name with label lengths + encode_domain(argv[2], dkey.data); + reverse_string(dkey.data); - // Set the LPM trie key prefix length - dkey.lpm_key.prefixlen = strlen(dkey.data) * 8; + // Set the LPM trie key prefix length + dkey.lpm_key.prefixlen = strlen(dkey.data) * 8; - // Open the BPF map - map_fd = bpf_obj_get("/sys/fs/bpf/xdp-dns/domain_denylist"); - if (map_fd < 0) { - fprintf(stderr, "Failed to open map: %s\n", strerror(errno)); - return 1; - } + // Open the BPF map + map_fd = bpf_obj_get("/sys/fs/bpf/xdp-dns/domain_denylist"); + if (map_fd < 0) { + fprintf(stderr, "Failed to open map: %s\n", strerror(errno)); + return 1; + } - // Add or delete the domain based on the first argument - if (strcmp(argv[1], "add") == 0) { - // Update the map with the encoded domain name - if (bpf_map_update_elem(map_fd, &dkey, &value, BPF_ANY) != 0) { - fprintf(stderr, "Failed to add domain to map: %s\n", strerror(errno)); - return 1; - } - printf("Domain %s added to denylist\n", argv[2]); - } else if (strcmp(argv[1], "delete") == 0) { - // Remove the domain from the map - if (bpf_map_delete_elem(map_fd, &dkey) != 0) { - fprintf(stderr, "Failed to remove domain from map: %s\n", strerror(errno)); - return 1; - } - printf("Domain %s removed from denylist\n", argv[2]); - } else { - fprintf(stderr, "Invalid command: %s. Use 'add' or 'delete'.\n", argv[1]); - return 1; - } + // Add or delete the domain based on the first argument + if (strcmp(argv[1], "add") == 0) { + // Update the map with the encoded domain name + if (bpf_map_update_elem(map_fd, &dkey, &value, BPF_ANY) != 0) { + fprintf(stderr, "Failed to add domain to map: %s\n", + strerror(errno)); + return 1; + } + printf("Domain %s added to denylist\n", argv[2]); + } else if (strcmp(argv[1], "delete") == 0) { + // Remove the domain from the map + if (bpf_map_delete_elem(map_fd, &dkey) != 0) { + fprintf(stderr, + "Failed to remove domain from map: %s\n", + strerror(errno)); + return 1; + } + printf("Domain %s removed from denylist\n", argv[2]); + } else { + fprintf(stderr, "Invalid command: %s. Use 'add' or 'delete'.\n", + argv[1]); + return 1; + } - return 0; + return 0; } diff --git a/xdp-dns/xdp_dns_log.c b/xdp-dns/xdp_dns_log.c new file mode 100644 index 00000000..84e98ed5 --- /dev/null +++ b/xdp-dns/xdp_dns_log.c @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024, BPFire. All rights reserved. + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#define MAX_DOMAIN_SIZE 128 + +struct qname_event { + __u8 len; + __u32 src_ip; // IPv4 address + char qname[MAX_DOMAIN_SIZE + 1]; +}; + +// Helper function to convert DNS label to a standard domain format +void dns_label_to_dot_notation(char *dns_name, char *output, size_t len) +{ + size_t pos = 0, out_pos = 0; + + while (pos < len) { + __u8 label_len = dns_name[pos]; + if (label_len == 0) + break; // End of domain name + + if (out_pos != 0) { + output[out_pos++] = '.'; // Add a dot between labels + } + + // Copy the label + for (int i = 1; i <= label_len; i++) { + output[out_pos++] = dns_name[pos + i]; + } + + pos += label_len + 1; // Move to the next label + } + + output[out_pos] = '\0'; // Null-terminate the result +} + +// Corrected handle_event function to match the signature expected by ring_buffer__new +int handle_event(void *ctx __attribute__((unused)), void *data, + size_t data_sz __attribute__((unused))) +{ + struct qname_event *event = (struct qname_event *)data; + + char src_ip_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &event->src_ip, src_ip_str, sizeof(src_ip_str)); + + char domain_str[MAX_DOMAIN_SIZE] = { 0 }; + dns_label_to_dot_notation(event->qname, domain_str, event->len); + + syslog(LOG_INFO, "Received qname: %s from source IP: %s", domain_str, + src_ip_str); + + return 0; // Return 0 to indicate success +} + +int main() +{ + struct ring_buffer *rb; + int ringbuf_fd; + + openlog("qname_logger", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1); + + // Open the ring buffer + ringbuf_fd = bpf_obj_get("/sys/fs/bpf/xdp-dns/dns_ringbuf"); + if (ringbuf_fd < 0) { + perror("Failed to open ring buffer"); + return 1; + } + + // Set up ring buffer polling with the corrected function signature + rb = ring_buffer__new(ringbuf_fd, handle_event, NULL, NULL); + if (!rb) { + perror("Failed to create ring buffer"); + return 1; + } + + // Poll the ring buffer + while (1) { + ring_buffer__poll(rb, -1); // Block indefinitely + } + + ring_buffer__free(rb); + closelog(); + return 0; +}