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:
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¶
- Watches
/var/dhcpd/var/db/dhcpd.leasesfor changes - Creates A record in
scandora.netzone - Creates PTR record in appropriate reverse zone
- Records auto-expire when lease expires
Service Control¶
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.netAAAA record in Cloudflare (public) - Updates
blued.scandora.netAAAA 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¶
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¶
Reverse Lookup¶
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.