Skip to content

DNS Guard Plugin (dns) ​

The DNS Guard Plugin provides protocol-aware Layer 4 inspection for DNS traffic over both UDP and TCP (RFC 1035). It acts as an inline datagram firewall for DNS resolvers and authoritatives, blocking malicious domains, unauthorized zone transfers (AXFR), ANY query amplification vectors, and DNS rebinding attacks before queries reach your upstream DNS server.


Capabilities & Threat Defense ​

Threat / Attack VectorDefense MechanismAction Taken
DNS Amplification (EDNS0 / ANY)Filters query types against blocked_qtypes and caps max_packet_sizeQueries dropped silently or refused without amplification response
Malicious Domain Lookups (C2 / Phishing)Domain pattern matching (blocked_domains / allowed_domains) with wildcard supportReturns immediate synthetic NXDOMAIN / REFUSED response
DNS Rebinding Attacksblock_private_ips scans upstream response records for RFC1918 / loopback rangesResponse stripped or replaced to prevent intranet takeover
Zone Transfer ExfiltrationBlocks AXFR (query type 252) and IXFR (type 251) queriesDropped or rejected, shielding internal zone topology
Cache Poisoning / Thrashingmin_ttl enforces minimum time-to-live values on upstream repliesOverwrites low TTLs before forwarding to clients
Resolver Memory Exhaustionudp.max_sessions and udp.session_timeout reap idle client mapping tablesPrevents NAT session exhaustion and state table depletion

Installation ​

Install the DNS plugin using the CLI or declaratively:

# Install using short nametcp-warden plugins install dns# Or install via Git repository URLtcp-warden plugins install https://github.com/routewarden/plugins/dns

Configuration Reference ​

Configure a dual-transport DNS service in tcp-warden.yaml:

services:  dns_firewall:    listen: ":53"    upstream: "1.1.1.1:53"    transport: "both"         # Bind listeners on both UDP and TCP    protocol: "dns"    rate_limit:      connections_per_minute: 1200      burst: 200    udp:      session_timeout: "15s"      max_sessions: 20000      read_buffer_size: 4096    plugin_config:      blocked_domains:        - "*.badware.test"        - "c2.threat.org"        - "tracking.ads.*"      allowed_domains: []     # Optional allowlist: if populated, only matching domains pass      blocked_qtypes:        - "ANY"               # Stop ANY query reflection amplification floods        - "AXFR"              # Block unauthorized DNS zone transfers      block_private_ips: true # Mitigate DNS rebinding (blocks 127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16 in answers)      min_ttl: 60             # Enforce minimum TTL in upstream responses to prevent cache thrashing      max_packet_size: 4096   # Cap EDNS0 buffer size to mitigate large UDP reflection

Configuration Options ​

FieldTypeDefaultDescription
listenstring":53"Listen address and port.
upstreamstring"1.1.1.1:53"Upstream resolver or recursive DNS backend.
transportstring"both"Network transport: "udp", "tcp", or "both" (binds both sockets simultaneously).
protocolstring"dns"Must be set to "dns".
plugin_config.blocked_domains[]string[]List of blocked domain patterns. Supports leading/trailing wildcards (e.g. .ads., c2.threat.org).
plugin_config.allowed_domains[]string[]List of allowed domain patterns. If specified and non-empty, only matching domains are allowed through.
plugin_config.blocked_qtypes[]string[]DNS record types to reject (e.g., ["ANY", "AXFR", "TXT"]).
plugin_config.allowed_qtypes[]string[]Optional allowlist of DNS record types (e.g., ["A", "AAAA", "CNAME", "PTR"]).
plugin_config.block_private_ipsboolfalseWhen true, upstream responses resolving to private/internal IPs (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) are blocked to prevent DNS rebinding.
plugin_config.min_ttlint0Minimum TTL in seconds. Responses with TTLs lower than this value have their TTL rewritten to min_ttl.
plugin_config.max_packet_sizeint4096Maximum allowed UDP DNS packet size in bytes. Drops oversized datagrams used in reflection attacks.

Network & Deployment Architecture ​

Option A: Kernel Firewall Redirection (nftables / iptables) ​

Redirect incoming external port 53 traffic (both UDP and TCP) to RouteWarden port 5353:

# 1. Redirect external DNS (port 53) UDP and TCP to RouteWarden port 5353sudo nft add rule ip routewarden_nat prerouting iifname "eth0" udp dport 53 redirect to :5353sudo nft add rule ip routewarden_nat prerouting iifname "eth0" tcp dport 53 redirect to :5353

Option B: Docker Compose with CoreDNS Backend ​

Run RouteWarden on the host network to guard an internal CoreDNS or Unbound instance:

services:  routewarden:    image: ghcr.io/routewarden/tcp-warden:latest    network_mode: host      # Host networking allows direct binding to port 53 UDP & TCP    volumes:      - ./tcp-warden.yaml:/etc/routewarden/tcp-warden.yaml:ro      - routewarden-data:/var/lib/routewarden    depends_on:      - coredns  coredns:    image: coredns/coredns:latest    command: -conf /etc/coredns/Corefile    volumes:      - ./Corefile:/etc/coredns/Corefile:ro    # Listen only on localhost 127.0.0.1:5353; RouteWarden guards the public interface    ports:      - "127.0.0.1:5353:53/udp"      - "127.0.0.1:5353:53/tcp"volumes:  routewarden-data:

Testing & Verification ​

Verify standard resolution, forced TCP queries, and blocked domain defense:

# Standard query over UDP port 53dig @127.0.0.1 -p 53 example.com A

Released under the MIT License.