Packet Ingress Mechanics and Kernel Tuning
sk_rcvque). If the DNS process cannot drain this queue fast enough, the kernel drops the packet silently without generating an ICMP unreachable message.
TEXT
[ Physical Wire ]
|
v
[ NIC RX Ring Buffer ] --> (Drop: rx_discards / rx_no_buffer)
|
v [Hardware IRQ -> NAPI Poll Loop]
[ Kernel SoftIRQ / sk_buff ]
|
v [RPS / Core Steering]
[ Socket Buffer (sk_rcvque) ] --> (Drop: UdpInErrors / RcvbufErrors)
|
v [epoll_wait() / SO_REUSEPORT]
[ DNS Daemon Worker Threads ]ethtool:
BASH
# Inspect current and maximum ring buffer sizes
ethtool -g eth0
# Maximize RX ring buffer capacity to absorb sudden microbursts
ethtool -G eth0 rx 4096
# Verify interface drops at the driver level
ethtool -S eth0 | grep -E 'rx_dropped|rx_missed|rx_no_buffer_count'/etc/sysctl.d/99-dns-throughput.conf. Default buffer sizes (often 212 KB) are insufficient during volumetric floods:
INI
# Increase maximum and default socket receive/send buffers
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.core.rmem_default = 16777216
net.core.wmem_default = 16777216
# System-wide UDP memory limits: min, pressure, max (measured in pages)
net.ipv4.udp_mem = 4194304 8388608 16777216
# Increase kernel processing backlog between driver and socket layers
net.core.netdev_max_backlog = 50000
# Mitigate port exhaustion for recursive resolvers opening outbound sockets
net.ipv4.ip_local_port_range = 10240 65535
# Enable TCP fast open and disable slow start restart for fallback/DoT queries
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_slow_start_after_idle = 0sysctl -p /etc/sysctl.d/99-dns-throughput.conf. Monitor kernel-level UDP drops using nstat:
BASH
nstat -z | grep -E 'UdpInErrors|UdpRcvbufErrors|UdpSndbufErrors'Ingress Filtering with eBPF/XDP
iptables and nftables introduce connection tracking overhead via conntrack. Under a 2M QPS flood, nf_conntrack tables saturate instantly, triggering system-wide lock contention and dropped connections.
Deploying eBPF programs at the eXpress Data Path (XDP) driver hook allows dropping malicious packets directly on the network card driver before allocating the kernel sk_buff structure. This mitigates DNS water torture attacks (PRSD) and reflection abuse at wire speed.
Below is an XDP ingress filter that verifies DNS flags and drops any malformed UDP packet or amplification vector before the kernel network stack processes it:
C
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
#define DNS_PORT 53
#define MAX_DNS_LEN 512
SEC("xdp_dns_filter")
int filter_dns(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (eth->h_proto != __constant_htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
if (ip->protocol != IPPROTO_UDP)
return XDP_PASS;
struct udphdr *udp = (void *)ip + (ip->ihl * 4);
if ((void *)(udp + 1) > data_end)
return XDP_PASS;
if (udp->dest != __constant_htons(DNS_PORT))
return XDP_PASS;
// Parse DNS Header
unsigned char *dns = (void *)(udp + 1);
if (dns + 12 > (unsigned char *)data_end)
return XDP_DROP; // Malformed / truncated DNS payload
// Extract QR bit and Opcode: Byte 2 of DNS header
// Drop incoming packets claiming to be DNS responses (QR bit == 1)
if (dns[2] & 0x80)
return XDP_DROP;
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";iproute2:
BASH
clang -O2 -target bpf -c xdp_dns_filter.c -o xdp_dns_filter.o
ip link set dev eth0 xdpgeneric obj xdp_dns_filter.o sec xdp_dns_filterResolver Hardening and Architecture: Unbound Configuration
so-reuseport: yes: Binds separate UDP sockets per thread, allowing the Linux kernel to distribute packets via hash balancing rather than waking all threads simultaneously (thundering herd).msg-cache-slabsandrrset-cache-slabs: Setting these to powers of 2 (matching or exceeding thread count) prevents locks from stalling concurrent worker lookups.edns-buffer-size: 1232: Crucial for modern DNS. The historical 4096-byte limit causes IP fragmentation on networks with standard 1500-byte MTUs. Fragments are frequently dropped by firewalls and middleboxes, causing query timeouts and fallback retries. Standardizing on 1232 bytes eliminates fragmentation risks.
/etc/unbound/unbound.conf:
YAML
server:
verbosity: 1
interface: 0.0.0.0
interface: ::0
port: 53
# Concurrency and Performance
num-threads: 8
so-reuseport: yes
so-rcvbuf: 16m
so-sndbuf: 16m
msg-cache-slabs: 8
rrset-cache-slabs: 8
infra-cache-slabs: 8
key-cache-slabs: 8
# Memory Allocation
rrset-cache-size: 512m
msg-cache-size: 256m
key-cache-size: 64m
neg-cache-size: 64m
# Hardening against Fragmentation & Latency
edns-buffer-size: 1232
max-udp-size: 1232
do-tcp: yes
incoming-num-tcp: 1024
outgoing-range: 8192
infra-host-ttl: 60
infra-cache-numhosts: 100000
# Cache Prefetching for Ultra-Low Tail Latency
prefetch: yes
prefetch-key: yes
serve-expired: yes
serve-expired-ttl: 86400
serve-expired-client-timeout: 250
# Security & Poisoning Defenses
harden-glue: yes
harden-dnssec-stripped: yes
harden-below-nxdomain: yes
harden-referral-path: yes
use-caps-for-id: no # Disable 0x20 encoding if upstream authoritative servers reject it
val-clean-additional: yes
# Access Controls
access-control: 127.0.0.1/32 allow
access-control: 10.0.0.0/8 allow
access-control: 0.0.0.0/0 refuseserve-expired: yes and serve-expired-client-timeout: 250 combination preserves p99 tail latency: if upstream authoritative servers take longer than 250ms to respond, Unbound immediately serves an expired record from the local cache, then updates the cache asynchronously in the background.
Authoritative DNS Performance: Knot DNS Configuration
/etc/knot/knot.conf) with native Response Rate Limiting (RRL) to neutralize DNS amplification attacks at the application tier:
YAML
server:
rundir: "/run/knot"
user: knot:knot
listen: [ 0.0.0.0@53, ::@53 ]
udp-workers: 8
tcp-workers: 8
tcp-max-clients: 4096
async-start: on
# Response Rate Limiting (RRL) to mitigate reflection abuse
mod-rrl:
- id: default_rrl
rate-limit: 200 # Max responses per second for the same network/record
slip: 2 # Return truncated response (TC=1) for every 2nd dropped query
table-size: 1000000
template:
- id: default
storage: "/var/lib/knot"
global-module: mod-rrl
zone:
- domain: example.internal
file: "example.internal.zone"
dnssec-signing: onArchitecture and Performance Comparison
| Daemon | Role | Core Concurrency Model | Max Tested QPS (8 vCPU) | Memory Footprint (1M Zones/Cached RR) | Native RRL Support |
|---|---|---|---|---|---|
| Knot DNS | Authoritative | Multi-threaded + RCU lockless reads | ~1,450,000 | ~380 MB | Yes (Low Overhead Module) |
| PowerDNS Auth | Authoritative | Multi-threaded (Backend-dependent) | ~650,000 | ~850 MB (LMDB Backend) | Yes |
| Unbound | Recursive | Thread-per-core with slab-divided caches | ~240,000 | ~1.2 GB | No (Downstream Rate-Limit Only) |
| PowerDNS Recursor | Recursive | Multi-threaded epoll/mthreads | ~210,000 | ~1.4 GB | Yes |
| BIND 9.18+ | Dual-role | Multi-threaded task manager (uv-based) | ~180,000 | ~1.8 GB | Yes |
Anycast Edge Architecture & Tail-Latency Management
TEXT
[ Global Clients ]
|
+--------+--------+
| Public Transit |
+--------+--------+
|
BGP Anycast: 198.51.100.1
|
+--------------------+--------------------+
| |
[ Pop 1: London ] [ Pop 2: Tokyo ]
| |
+-----+-----+ +-----+-----+
| BGP Router| | BGP Router|
+-----+-----+ +-----+-----+
| (ECMP) | (ECMP)
+------+------+ +------+------+
| | | |
[ DNS Node 1 ] [ DNS Node 2 ] [ DNS Node 3 ] [ DNS Node 4 ]- Route Flapping and Path Shifts: A transient BGP shift mid-session drops state for stateful TCP queries (large AXFR responses or DNS-over-TLS handshakes).
- Polarization under ECMP: Routers using naive layer-3 hashing may send all traffic from large resolvers (e.g.,
8.8.8.8or1.1.1.1) to a single node in a cluster.
exabgp or bird) to withdraw host routes only when the local DNS service fails health diagnostics, backed by BGP Graceful Restart:
INI
# /etc/bird/bird.conf snippet for local DNS Anycast announcement
protocol bgp edge_router {
local as 65001;
neighbor 192.0.2.1 as 65000;
multihop 2;
graceful restart on;
graceful restart time 120;
ipv4 {
import none;
export filter {
# Only announce if the local dummy interface with Anycast IP is up
if proto = "static_dns" then accept;
reject;
};
};
}
protocol static static_dns {
ipv4;
route 198.51.100.1/32 blackhole;
}BASH
#!/usr/bin/env bash
# /usr/local/bin/dns-healthcheck.sh
set -euo pipefail
QUERY_TARGET='health.internal'
SERVER='127.0.0.1'
if ! dig +time=1 +tries=1 +short "@${SERVER}" "${QUERY_TARGET}" A > /dev/null; then
# DNS daemon is non-responsive or overloaded; withdraw route
ip link set dev dummy0 down
logger -t DNS_HEALTH 'Healthcheck failed: Withdrawing Anycast IP'
exit 1
else
# Interface must remain up
if ! ip link show dummy0 | grep -q 'state UP'; then
ip link set dev dummy0 up
logger -t DNS_HEALTH 'Healthcheck passed: Re-announcing Anycast IP'
fi
fiOperational Verification Commands
BASH
# 1. Verify that incoming UDP packets are evenly distributed across all workers
ss -l -u -p -n '( sport = :53 )'
# 2. Monitor socket-level tail drops in real time
watch -d -n 1 'awk "/Udp:/{print \$1,\$2,\$3,\$4,\$5,\$6,\$7}" /proc/net/snmp'
# 3. Benchmark authoritative performance using dnsperf
dnsperf -s 198.51.100.1 -d query_dataset.txt -c 50 -l 30 -Q 500000