Resolve devices with DNS
Out of the box you address simulated devices by their 10.42.x.x management IPs.
With the CoreDNS sidecar you can resolve them by name instead, with forward <device-name>.nl6.local records and reverse PTRs.
The records update automatically as devices come and go.
nl6 runs an authoritative DNS server as a hidden primary; a stock CoreDNS runs as a secondary, transferring the zones via AXFR and refreshing on NOTIFY. No custom CoreDNS plugin.
dig @localhost ──► CoreDNS :53 (secondary) ──AXFR/NOTIFY──► nl6 :5353 (primary)
derived from the
live device set
- Forward
<device-name>.nl6.local→ device management IP - Reverse
<ip>(PTR) →ip4.mgmt.<device-name>.nl6.local, which resolves forward to the same IP (round-trips)
Prerequisites
- Docker with Compose v2 (
docker compose). The sidecar stack runs nl6 in a container, so you don't need host TUN/netns setup. See Docker. dig(or any DNS client) andcurlon the host to verify.
Run the stack
The repo ships a ready-to-run stack at examples/coredns-sidecar/:
git clone https://github.com/labmonkeys-space/nl6
cd nl6/examples/coredns-sidecar
docker compose up -d
It auto-starts a 10-device demo fleet (10.42.0.1 to 10.42.0.10) with the DNS subsystem enabled.
On every device create/delete nl6 bumps the zone serial (debounced ~1s) and NOTIFYs CoreDNS, which re-transfers.
The nl6 service runs with these DNS flags (see the CLI flags for the full set):
command:
- -dns-enable
- -dns-domain=nl6.local
- -dns-reverse-zone=42.10.in-addr.arpa
- -dns-notify=coredns:53
…and the CoreDNS Corefile is a stock secondary block per zone:
nl6.local:53 {
secondary {
transfer from nl6:5353
}
log
errors
}
42.10.in-addr.arpa:53 {
secondary {
transfer from nl6:5353
}
log
errors
}
Verify name resolution
# List the running fleet to pick a real name/IP (names are random per run):
curl -s localhost:8080/api/v1/devices | jq -r '.data[].ip'
dig @localhost -p 5353 nl6.local AXFR | grep ' A ' # the forward names + IPs
# Forward + reverse against CoreDNS (host :53):
dig @localhost core-rtr-01.nl6.local +short # device name -> 10.42.0.x
dig @localhost -x 10.42.0.5 +short # 10.42.0.5 -> ip4.mgmt.<name>.nl6.local.
# Publish + NOTIFY counters:
curl -s localhost:8080/api/v1/dns/status | jq
Names are synthesised. nl6 generates each device's sysName randomly, so the exact forward names change per run.
Read them from the AXFR (dig @localhost -p 5353 nl6.local AXFR) or from /api/v1/devices.
Duplicate names are disambiguated deterministically (lowest IP keeps the bare label).
How it stays current
Creating or deleting a device marks the zones dirty.
A debounced worker waits for a quiescence window (-dns-debounce, default 1s), then bumps the SOA serial once and NOTIFYs each secondary.
A large batch therefore coalesces into a single transfer rather than one per device.
Try it:
# Create a device, then watch the serial advance and CoreDNS pick it up.
curl -s -XPOST localhost:8080/api/v1/devices \
-H 'content-type: application/json' \
-d '{"device_count":1,"start_ip":"10.42.0.50"}'
curl -s localhost:8080/api/v1/dns/status | jq '.zones, .zone_bumps'
Adjust the configuration
- Different domain / subnets. Change
-dns-domainand-dns-reverse-zoneon thenl6service, and add/rename the matchingsecondaryblocks in theCorefile. A CoreDNS secondary needs one block per zone. Device IPs outside every configured reverse zone resolve forward-only (no PTR). - No NOTIFY. Drop
-dns-notify; CoreDNS still picks up changes on its SOA refresh interval, but less promptly.
Next
- DNS service discovery reference covers the full naming scheme, flags, zone boundaries, and operational caveats.