Skip to content

DNS Architecture

DNS in this estate is split into five deliberately separate planes: an external authoritative service, an internal authoritative + resolver plane, a split-horizon view resolver, a public recursive resolver run as a community service, and a fixed upstream policy. Each plane fails independently of the others.

flowchart TD
    subgraph external["External / authoritative"]
        bind["BIND 9.20\nnameserver ns — LB .53\npublic 31.3.128.59"]
        dnsdist["dnsdist on BPI-R4\nnative IPv6 frontend\nPROXYv2 → BIND"]
        hap["haproxy on BPI-R4\npublic v4 TCP :53\nsend-proxy-v2 → BIND"]
    end
    subgraph internal["Internal"]
        tech["Technitium — LB .54\ninternal zones + DHCP"]
        unb["unbound split-horizon\nLB 192.168.1.1\ninternal views + recursion"]
    end
    opennic["OpenNIC tier-2\nrecursive — LB .44"]
    nextdns["NextDNS\n(the only recursion upstream)"]

    v6c["IPv6 clients"] --> dnsdist --> bind
    v4t["IPv4 TCP clients"] --> hap --> bind
    v4u["IPv4 UDP clients"] -->|DNAT| bind
    lan["LAN clients"] --> unb --> tech
    tech -.->|"DoT upstream"| nextdns
    unb -.->|"failover only"| nextdns
    lan --> tech
    world["Public resolver users"] --> opennic

External authoritative — BIND

ns.mdapi.ch (BIND 9.20 in the nameserver namespace, LoadBalancer 192.168.1.53, public 31.3.128.59) is authoritative for the public zones, with DNSSEC and external secondaries (zone transfers restricted to them). Dynamic updates arrive TSIG-signed (cert-manager DNS-01, external-dns).

What makes the front end unusual is client-address preservation through the PROXY protocol on both address families:

  • IPv6 terminates natively at dnsdist on the router — no NAT64 — and reaches BIND over IPv4 carrying a PROXYv2 header per UDP datagram. DNS is the one protocol ecosystem where PROXY-over-UDP exists on both ends (dnsdist sends it, BIND ≥ 9.20 accepts it on a dedicated proxy plain listener).
  • IPv4 TCP is fronted by haproxy on the router with send-proxy-v2 into the same listener; plain UDP is a straight DNAT.

The result: BIND sees every real client address on every transport, so rate-limiting and ACLs operate per-client rather than per-translator.

Internal — Technitium and split-horizon unbound

Technitium (LB .54) is the internal authoritative server and DHCP for the LAN — internal zones, per-client control, and an API that the automation plane drives (zone diffing, health probes, query-log shipping run as Windmill probes). It also serves as unbound's default recursive upstream (DoT), so it resolves external names on the LAN's behalf as well as the internal zones.

unbound (LB 192.168.1.1 — deliberately the address every DHCP client already trusts) serves the split-horizon view: internal names resolve to internal addresses, everything else recurses. Public names of self-hosted services resolve to their LAN addresses here, so internal traffic never hairpins through the edge. Non-override queries forward to Technitium first; NextDNS is a failover path only, used if the Technitium LoadBalancer itself goes unreachable.

OpenNIC tier-2

A public recursive resolver (LB .44, IPv6 via the NAT64 prefix) run as an OpenNIC community tier-2 — one of the estate's public-service contributions, alongside the NTP pool membership.

Upstream policy

Recursion is internal-first: unbound forwards non-override queries to Technitium, which is the actual recursive resolver (DoT upstream). NextDNS sits behind both as the sole external fallback — one place for filtering policy, no fan-out to ad-hoc public resolvers, and no outage cascades from a single upstream being unreachable. This is a standing invariant across the router, unbound, and every appliance.