Bali, Indonesia
Insights

Hardening and Latency Mitigation in Modern DNS Infrastructure & Resiliency

October 13, 2026
0:00
Operating distributed Domain Name System (DNS) infrastructure under hostile network conditions requires moving past default daemon configurations. In high-volume environments, DNS serves as the primary attack surface: its reliance on stateless UDP exposes edges to amplification abuse, Pseudo-Random Subdomain (PRSD) floods, and packet queue exhaustion. At the same time, recursive resolvers must handle DNSSEC validation and aggressive upstream timeouts without blowing out p99 tail latency. Achieving sub-millisecond local resolution and sustaining multi-gigabit query rates demands optimization across the entire path: network interface ring buffers, kernel socket queues, eBPF drop filters, and daemon-level thread architectures.
When a high-frequency DNS query storm hits an interface, packet loss rarely originates inside the DNS daemon itself. It occurs earlier in the kernel ingress path. UDP packets move from the physical NIC ring buffer via Direct Memory Access (DMA), trigger a hardware interrupt, enter the driver NAPI poll loop, and are placed into the socket receive queue (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 ]
To prevent ingress drops before packets reach user space, inspect the interface drops and adjust the ring buffers using 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'
Next, tune the Linux network stack parameters in /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 = 0
Apply these sysctls with sysctl -p /etc/sysctl.d/99-dns-throughput.conf. Monitor kernel-level UDP drops using nstat:
BASH
nstat -z | grep -E 'UdpInErrors|UdpRcvbufErrors|UdpSndbufErrors'
Standard software firewalls like 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";
Attach the compiled program to your NIC driver using 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_filter
Recursive resolvers require rigorous isolation to prevent cache poisoning (Kaminsky attacks) and cache exhaustion. Unbound provides clean concurrency through its thread-slab architecture, which minimizes mutex contention across independent CPU cores. Key parameters for high-throughput recursive deployments include:
  • 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-slabs and rrset-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.
Here is an optimized /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 refuse
The serve-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.
For authoritative workloads, Knot DNS outperforms legacy BIND servers because of its read-copy-update (RCU) architecture and native lock-free transaction model. Knot can handle tens of millions of queries per second over Anycast topologies. Configure Knot DNS (/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: on
Selecting the right daemon depending on the network tier directly dictates system resilience and throughput under load:
DaemonRoleCore Concurrency ModelMax Tested QPS (8 vCPU)Memory Footprint (1M Zones/Cached RR)Native RRL Support
Knot DNSAuthoritativeMulti-threaded + RCU lockless reads~1,450,000~380 MBYes (Low Overhead Module)
PowerDNS AuthAuthoritativeMulti-threaded (Backend-dependent)~650,000~850 MB (LMDB Backend)Yes
UnboundRecursiveThread-per-core with slab-divided caches~240,000~1.2 GBNo (Downstream Rate-Limit Only)
PowerDNS RecursorRecursiveMulti-threaded epoll/mthreads~210,000~1.4 GBYes
BIND 9.18+Dual-roleMulti-threaded task manager (uv-based)~180,000~1.8 GBYes
For production DNS topologies spanning multiple datacenters, Anycast routing via BGP (Border Gateway Protocol) remains standard practice. Multiple DNS physical servers announce the same IP prefix to upstream transit routers using BIRD or FRRouting (FRR).
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 ]
Under Anycast deployments, two challenges frequently occur:
  1. 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).
  2. Polarization under ECMP: Routers using naive layer-3 hashing may send all traffic from large resolvers (e.g., 8.8.8.8 or 1.1.1.1) to a single node in a cluster.
To harden BGP announcements against flapped links, configure health-checking daemons (like 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;
}
Run an active local health checker via systemd timer to drop the static dummy interface route if the DNS daemon stops responding to valid query probes:
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
fi
Validate your deployment using these lower-level profiling tools:
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
A resilient DNS infrastructure requires defense across multiple layers of the networking stack. Hardening the Linux kernel socket buffers, leveraging eBPF/XDP to discard malformed queries before kernel allocation, configuring slab-separated caches, standardizing on a 1232-byte EDNS payload, and backing Anycast announcements with rigorous health-checking ensures consistent query throughput and low tail latencies even under severe attack traffic.