Skip to content

DNS Architecture

Overview

Split-horizon DNS using PowerDNS on Bogart (10.10.10.10) as the authoritative server for internal scandora.net records.

graph LR
    subgraph "External"
        EXT_CLIENT[External Client]
        CF[Cloudflare DNS]
        EXT_CLIENT --> CF
        CF --> |Public IPs| EXT_CLIENT
    end

    subgraph "Internal"
        INT_CLIENT[Internal Client]
        GW[Gateway Unbound]
        PDNS[PowerDNS<br/>Bogart 10.10.10.10]
        INT_CLIENT --> GW
        GW --> |scandora.net| PDNS
        GW --> |other domains| CF
        PDNS --> |Private IPs| GW
    end

Components

PowerDNS Server (Bogart)

Attribute Value
Host bogart (10.10.10.10)
Version 4.4.1
Backend PostgreSQL
DNS Port 53
API Port 8081
API Credentials 1Password: "PowerDNS API - Bogart"

DNS Zones

Forward Zone

Zone Description
scandora.net All internal hostnames

Reverse Zones

Zone Network Description
7.10.in-addr.arpa 10.7.0.0/16 Owl LAN
15.10.in-addr.arpa 10.15.0.0/16 Blue LAN
0.10.in-addr.arpa 10.0.0.0/16 AWS
1.10.in-addr.arpa 10.1.0.0/16 GCE Dumbo
10.10.in-addr.arpa 10.10.0.0/16 GCE Bogart
2.10.in-addr.arpa 10.2.0.0/16 Meanservers
194.168.192.in-addr.arpa 192.168.194.0/24 ZeroTier

Gateway Configuration

Both Owl and Blue forward scandora.net queries to PowerDNS via Unbound.

OPNsense Forward Zone

Forward zones are configured in config.xml under <unboundplus><dots><dot>:

<dot uuid="...">
  <enabled>1</enabled>
  <type>forward</type>
  <domain>scandora.net</domain>
  <server>10.10.10.10</server>
  <forward_tcp_upstream>0</forward_tcp_upstream>
  <forward_first>0</forward_first>
  <description>Internal DNS - PowerDNS on Bogart</description>
</dot>

After editing config.xml:

sudo configctl unbound restart

Generated config appears in: /usr/local/etc/unbound.opnsense.d/dot.conf

Infrastructure Records

Primary A Records

Hostname IP Description
owl.scandora.net 10.7.0.1 Owl gateway (ZeroTier)
blue.scandora.net 10.15.0.1 Blue gateway (ZeroTier)
bogart.scandora.net 10.10.10.10 PowerDNS server
pluto.scandora.net 10.0.0.10 AWS instance
dumbo.scandora.net 10.1.0.110 GCE instance
rocky.scandora.net 10.2.0.1 Meanservers
ha.owl.scandora.net 10.7.1.99 Home Assistant
ns1.scandora.net 10.10.10.10 Nameserver

AAAA Records (ZeroTier IPv6)

Cloud instances accessible via ZeroTier also have IPv6 AAAA records:

Hostname IPv6 Address Description
dumbo.scandora.net fd6a:b565:387a:4b91:7799:93c1:af41:ee70 GCE via ZeroTier
bogart.scandora.net fd6a:b565:387a:4b91:7799:935a:fe44:b5f4 PowerDNS via ZeroTier
rocky.scandora.net fd6a:b565:387a:4b91:7799:933a:4a47:5ef8 Meanservers via ZeroTier

Direct Access Records ("d" suffix)

For emergency access when DNS or ZeroTier is unavailable, direct-IP records bypass the overlay network:

Hostname IP Description
owld.scandora.net 46.110.77.34 Owl public IPv4 (Metronet)
owld.scandora.net 2001:470:c09c:1::1 Owl HE tunnel IPv6
plutod.scandora.net 52.32.80.62 Pluto AWS EIP
dumbod.scandora.net 34.44.33.3 Dumbo GCE static IP
dumbod.scandora.net 2600:1900:4000:5bfa:0:27:: Dumbo GCE IPv6
bogartd.scandora.net 35.209.219.216 Bogart GCE static IP
bogartd.scandora.net 2600:1900:4001:dca:0:3:: Bogart GCE IPv6
blued.scandora.net (dynamic IPv6) Blue Starlink IPv6 (via DDNS)

When to use 'd' suffix records

Use hostnamed.scandora.net when:

  • ZeroTier is down or unreachable
  • Debugging network routing issues
  • Need direct public IP access
  • Testing from outside the ZeroTier network

These records are internal only (PowerDNS) and not published to Cloudflare.

DHCP Integration

DHCP clients are automatically registered in PowerDNS via the pdns-dhcp-watcher daemon.

Deployment

Gateway Script Config Status
Blue /usr/local/bin/pdns-dhcp-watcher.py /usr/local/etc/pdns-dhcp-watcher.conf ✅ Running
Owl /usr/local/bin/pdns-dhcp-watcher.py /usr/local/etc/pdns-dhcp-watcher.conf ✅ Running

Naming Convention

DHCP clients use: {hostname}.{site}.scandora.net

  • Blue DHCP clients: devicename.blue.scandora.net
  • Owl DHCP clients: devicename.owl.scandora.net

How It Works

  1. Watches /var/dhcpd/var/db/dhcpd.leases for changes
  2. Creates A record in scandora.net zone
  3. Creates PTR record in appropriate reverse zone
  4. Records auto-expire when lease expires

Service Control

sudo service pdns_dhcp_watcher start|stop|status

Dynamic DNS (DDNS)

Cloud Instance Registration

Cloud instances maintain their DNS records via cf-ddns.sh on cron, updating Cloudflare for public records.

Instance Internal IP Cron Schedule
bogart 10.10.10.10 */5 * * * *
pluto 10.0.0.10 1-59/5 * * * *
dumbo 10.1.0.110 2-59/5 * * * *

Gateway DDNS

The Blue gateway uses an enhanced DDNS script that updates both Cloudflare (public) and PowerDNS (internal):

Gateway Cloudflare Record PowerDNS Record Protocol
Blue blue.scandora.net (IPv6) blued.scandora.net (IPv6) AAAA only
Owl owl.scandora.net (IPv4) N/A (static) A only

Blue DDNS Configuration (/usr/local/etc/cf-ddns.conf):

  • Updates blue.scandora.net AAAA record in Cloudflare (public)
  • Updates blued.scandora.net AAAA record in PowerDNS (internal/private)
  • IPv4 updates disabled (Starlink CGNAT has no public IPv4)

Why Blue needs PowerDNS DDNS

Blue's Starlink connection uses CGNAT for IPv4 (no public IP) but provides native IPv6 via DHCPv6-PD. The IPv6 prefix can change when the connection resets. The blued.scandora.net record provides a private, internal-only direct-access hostname that tracks the current IPv6 address.

Owl doesn't need PowerDNS DDNS because its public IPv4 (46.110.77.34) and HE tunnel IPv6 are both static.

API Usage

Authentication

# Get API key from 1Password
KEY=$(op item get "PowerDNS API - Bogart" --fields credential --reveal)

List Zones

curl -H "X-API-Key: $KEY" http://10.10.10.10:8081/api/v1/servers/localhost/zones

Add/Update Record

curl -X PATCH "http://10.10.10.10:8081/api/v1/servers/localhost/zones/scandora.net." \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rrsets": [{
      "name": "newhost.scandora.net.",
      "type": "A",
      "ttl": 3600,
      "changetype": "REPLACE",
      "records": [{"content": "10.7.1.100", "disabled": false}]
    }]
  }'

Testing

Verify Split-Horizon

# External resolution (public IP)
dig @1.1.1.1 owl.scandora.net

# Internal resolution (private IP)
dig @10.10.10.10 owl.scandora.net

Forward Lookup

dig @10.10.10.10 owl.scandora.net

Reverse Lookup

dig @10.10.10.10 -x 10.7.0.1

Utilities

Generate Hosts File

# Output to stdout
./dns/scripts/gen-hosts.sh

# Write to file
./dns/scripts/gen-hosts.sh -o scandora.net.hosts.txt

Sync Static DHCP

# Sync all gateways
./dns/scripts/sync-static-dhcp.sh

# Sync specific gateway
./dns/scripts/sync-static-dhcp.sh --gateway blue

# Preview without changes
./dns/scripts/sync-static-dhcp.sh --dry-run

Performance Note

Bogart (GCE e2-micro) is under-powered for PowerDNS workloads. SSH and API calls are noticeably slow. Consider upgrading instance type or optimizing PostgreSQL/PowerDNS configuration.