I am a XDP newbie and not much of a C guy, this program will be called from Golang using https://github.com/cilium/ebpf.What i want is to use XDP to send DNS resolution packets to a specified resolver_addr and receive a response. This wil be a high thoroughput program.
Reading and reviewing docs shows it's not going to be easy to send using XDP, so alternatively i was thinking of trying out io_uring to send and receive with AF_XDP.
Couldn't get this to work as sendto() isn't allow at that kernel level.
#include <linux/bpf.h> // BPF helper functions and macros#include <bpf/bpf_helpers.h>#include <linux/if_ether.h> // Ethernet header struct#include <linux/ip.h> // IP header struct#include <linux/udp.h> // UDP header struct#include <string.h>#include <stdlib.h>#include <stdio.h>#include <netinet/in.h>#include <unistd.h>#define ETH_HLEN 14 // Ethernet header length// DNS header structstruct dns_hdr { __u16 id; __u16 flags; __u16 qdcount; __u16 ancount; __u16 nscount; __u16 arcount;};// DNS query structstruct query { __u16 qtype; __u16 qclass;};// Key struct for perf event mapstruct dns_key { __u32 saddr; __u32 daddr; __u16 sport; __u16 dport;};// Define perf event mapstruct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(max_entries, 10240);} dns_results SEC(".maps");// Function to perform DNS resolution__u32 resolve_dns(char *domain, char *resolver_addr) { // Open a raw socket for sending DNS queries int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); if (sockfd < 0) { perror("socket"); return 0; } // Set up the destination address for the resolver server struct sockaddr_in dest_addr; dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(53); // DNS port inet_pton(AF_INET, resolver_addr, &dest_addr.sin_addr); // Create DNS query packet size_t query_len = strlen(domain) + 2; // domain length + NULL byte + 1 byte for query type __u8 *dns_query = (__u8 *)malloc(sizeof(struct dns_hdr) + query_len + sizeof(struct query)); if (!dns_query) { perror("malloc"); close(sockfd); return 0; } // Construct DNS query header struct dns_hdr *header = (struct dns_hdr *)dns_query; memset(header, 0, sizeof(struct dns_hdr)); header->id = htons(1234); // Random query ID header->flags = htons(0x0100); // Standard query header->qdcount = htons(1); // One question __u8 *query = dns_query + sizeof(struct dns_hdr); // Copy domain name into query strcpy((char *)query, domain); // Add query type (A record) query += strlen(domain) + 1; // Move pointer past domain name *(__u16 *)query = htons(1); // A record type *(__u16 *)(query + 2) = htons(1); // IN class // Send DNS query packet if (sendto(sockfd, dns_query, sizeof(struct dns_hdr) + query_len + sizeof(struct query), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) { perror("sendto"); free(dns_query); close(sockfd); return 0; } // Receive DNS response __u8 dns_response[2048]; // Adjust the buffer size as needed ssize_t num_bytes = recv(sockfd, dns_response, sizeof(dns_response), 0); if (num_bytes < 0) { perror("recv"); free(dns_query); close(sockfd); return 0; } // Parse DNS response and extract IP address struct dns_hdr *resp_header = (struct dns_hdr *)dns_response; if (ntohs(resp_header->ancount) < 1) { free(dns_query); close(sockfd); return 0; } __u8 *answer_ptr = dns_response + sizeof(struct dns_hdr) + query_len + sizeof(struct query); answer_ptr += 10; // Skip the name and type/class fields __u32 ip_addr = 0; memcpy(&ip_addr, answer_ptr, sizeof(ip_addr)); // Close socket and free memory free(dns_query); close(sockfd); return ip_addr;}// XDP programSEC("xdp_dns")int dns_resolver(struct xdp_md *ctx, char *resolver_addr) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; // Ethernet header struct iphdr *ip = data + sizeof(struct ethhdr); // IP header struct udphdr *udp = (void *)ip + sizeof(struct iphdr); // UDP header struct dns_hdr *dns = (void *)udp + sizeof(struct udphdr); // DNS header struct query *q = (void *)dns + sizeof(struct dns_hdr); // DNS query // Check packet boundaries if ((void *)ip + sizeof(struct iphdr) > data_end || (void *)udp + sizeof(struct udphdr) > data_end || (void *)dns + sizeof(struct dns_hdr) > data_end) { return XDP_PASS; } // Check if UDP and DNS packet if (ip->protocol != IPPROTO_UDP || ntohs(udp->dest) != 53) { return XDP_PASS; } // Parse DNS query if (ntohs(dns->qdcount) != 1) { return XDP_PASS; } // Extract domain name from DNS query char domain[256]; __u8 *ptr = (__u8 *)q; int len = 0; while (*ptr != 0 && (void *)ptr < data_end) { int label_len = *ptr; ptr++; for (int i = 0; i < label_len && (void *)ptr < data_end; i++) { domain[len++] = *ptr++; } domain[len++] = '.'; } domain[len - 1] = '\0'; // Perform DNS resolution (pseudo code) __u32 ip_addr = resolve_dns(domain, resolver_addr); // Send result back to user space struct dns_key key = { .saddr = ip->saddr, .daddr = ip->daddr, .sport = udp->source, .dport = udp->dest, }; int ret = bpf_map_update_elem(&dns_results, &key, &ip_addr, BPF_ANY); if (ret) { // Error handling return XDP_ABORTED; // Or another appropriate action } bpf_perf_event_output(ctx, &dns_results, BPF_F_CURRENT_CPU, &ip_addr, sizeof(ip_addr)); return XDP_PASS;}// Licensechar _license[] SEC("license") = "MIT";