Build log · MikroTik RB5009 · DoH + stable RDNSS

Encrypted DNS with a stable resolver address on RouterOS

Cloudflare DoH upstream and a resolver address clients never have to relearn — a locally assigned ULA over RA RDNSS. No VLANs, no IPv6 uplink. The DNS companion to the CGNAT build log.

Overview

This is a small, self-contained companion to the CGNAT build log. It does one thing: make a RouterOS v7 box resolve upstream over encrypted DNS while handing clients a resolver address that never changes.

It deliberately depends on almost nothing. There is no VLAN segmentation here, no WireGuard, and — the part worth stating plainly — no IPv6 uplink required. The stable resolver address is a Unique Local Address (RFC 4193): it is generated by you, on the LAN, and exists whether or not the ISP delegates a single bit of IPv6. The encrypted upstream is DoH over port 443, which leaves the house on whatever default route exists — plain IPv4 is fine. The whole thing works on a flat, single-subnet, IPv4-only-internet LAN; it simply also survives the prefix churn you get once real IPv6 shows up.

Every numbered section is paste-ready against a defconf RouterOS v7 box. The italic notes are the rationale — the trade-off being made and why.

1. What you need first

A RouterOS v7 box with a working WAN and a LAN interface clients sit on. That is the whole list. Concretely it does not require:

  • VLAN segmentation. One flat LAN bridge is fine. If you do have VLANs, the same three steps apply per RA interface.
  • An IPv6 uplink. The ULA is internal and needs no delegation; DoH egresses over IPv4. A house with zero IPv6 internet still gets encrypted upstream resolution and a stable resolver address.
  • WireGuard, a VPS, or the rest of the CGNAT build. Those recover global IPv6; none of them are involved in resolving names or in advertising a local resolver.

The snippets below assume the defconf bridge on 192.168.88.0/24. Rename the interface and subnet to match your box; nothing else changes.

2. Conventions and placeholders

PlaceholderMeaning
<ULA_PREFIX>Your RFC 4193 ULA /48, e.g. fd7a:1b2c:3d4e. Generate one randomly; do not reuse the example.
bridgeThe interface your LAN clients are on (defconf bridge here).
192.168.88.0/24The LAN's IPv4 subnet (defconf here).

A ULA is fd00::/8 plus 40 random bits. Pick the 40 bits randomly once and keep them — the whole value of a ULA is that it is stable and unique to your network. fd7a:1b2c:3d4e::/48 is an illustrative value, not one to copy.

3. Encrypted upstream — DoH with bootstrap pins

The router becomes the LAN resolver and forwards every query upstream over Cloudflare DoH. The static records pin cloudflare-dns.com so the very first query has somewhere to go before DoH itself can resolve anything.

DoH, not DoT or plain 53: DNS-over-HTTPS is indistinguishable from any other port-443 HTTPS flow, so no carrier NAT44 or captive middlebox can single it out and it traverses residential CGNAT with no special handling — DoT (853) and plain 53 are both trivially blockable and observable. Endpoints stay simple: they keep a local resolver and never speak DoH themselves; all the encryption is one hop upstream, on the router.

DoH resolver + bootstrap pins

bash

1/tool/fetch url=https://curl.se/ca/cacert.pem dst-path=cacert.pem 2/certificate/import file-name=cacert.pem passphrase="" 3 4/ip/dns set allow-remote-requests=yes max-concurrent-queries=200 \ 5 use-doh-server=https://cloudflare-dns.com/dns-query verify-doh-cert=yes 6 7/ip/dns/static 8# A-only on purpose — see rationale below. 9add address=104.16.248.249 name=cloudflare-dns.com comment="DoH bootstrap" 10add address=104.16.249.249 name=cloudflare-dns.com comment="DoH bootstrap"

The DoH bootstrap is A-only on purpose. IPv4 is up the moment the WAN is up; an IPv6 path, if it exists at all, comes up later. Pinning AAAA records here would push the first DoH query onto a half-warm — or entirely absent — IPv6 path while the IPv4 route is direct to a nearby Cloudflare PoP. Once DoH is up, regular client queries still resolve and use AAAA records normally.

4. A ULA on the LAN — the stable resolver address

Add a ULA /64 to the LAN interface and publish the router itself as a name on it. This address is reachable on-link with no IPv6 internet whatsoever — it is a locally assigned RFC 4193 prefix, not anything the ISP hands you.

LAN ULA + router.lan record

bash

1/ipv6/address add interface=bridge address=<ULA_PREFIX>:1::1/64 \ 2 advertise=yes comment="LAN ULA" 3 4/ip/dns/static 5# Reachable as the FQDN `router.lan` from any client whose resolver is the 6# router (the default once §5/§6 are applied). No search-domain magic; type 7# the dot-lan suffix. 8add address=<ULA_PREFIX>:1::1 name=router.lan type=AAAA comment="LAN ULA"

The resolver identity is on the ULA, so SLAAC prefix churn — or never having a global prefix at all — never changes the DNS server the OS has memorized. A GUA would track the ISP delegation and move out from under every client that cached it.

5. Advertise the resolver — RA RDNSS

Router Advertisements carry the resolver to clients (RFC 8106). This needs IPv6 enabled on the LAN — it does not need IPv6 to the internet.

RA RDNSS — advertise self

bash

1/ipv6/nd add interface=bridge advertise-dns=self \ 2 managed-address-configuration=no other-configuration=no

advertise-dns=self advertises whatever address the router currently holds on the interface rather than a literal dns=<ULA_PREFIX>:1::1. The RDNSS can never point at a stale or wrong address, and it survives a renumber with nothing to keep in sync. Set it on every RA interface, not just one.

6. Stop handing out a DHCPv4 resolver

With the resolver advertised over RDNSS, the DHCPv4 server should stop handing out a DNS server at all, so resolution is uniformly the ND RDNSS path.

dns-none on every scope

bash

1# Defconf scope; repeat for every DHCPv4 network you serve. 2/ip/dhcp-server/network set [find address=192.168.88.0/24] dns-none=yes

No scope hands out a DHCPv4 resolver anymore, so DNS is uniformly the ND RDNSS and the router resolves upstream over DoH. The trade-off: a client with no RFC 8106 RDNSS support gets no DNS at all — acceptable when every LAN carries a ULA and the device population supports it, but it makes IPv6 a hard dependency for name resolution. This is the same posture the CGNAT build's trusted LAN runs; that post's ULA-only VLAN section points back here for the why.

7. Verification

Smoke tests

bash

1# On the router: upstream really is DoH, not plain 53. 2/ip/dns/cache/print # entries present 3/log/print where message~"doh" # DoH server in use 4 5# From a client: resolver is the ULA, and it answers. 6dig @<ULA_PREFIX>:1::1 cloudflare.com 7scutil --dns | grep -i 'nameserver\[' # macOS: expect the ULA 8nslookup router.lan # resolves to the ULA 9 10# No DHCPv4 resolver leaked. 11ipconfig /all # Windows: no IPv4 DNS server

A client with the ULA as its only nameserver, resolving names while the WAN shows IPv4-only, is the whole proof: upstream is sealed and the resolver address is one that prefix churn can never move.

References

Share

Comments

Comments are powered by GitHub Discussions and require a free GitHub account to post.