| comments | tags: categories:tech series:english
Naming and service discovery on a routed IPv6 network
I tend to eschew the normal practice of bridging WiFi and Ethernet networks in my private setups, instead running them on separate subnets and routing between them. This helps keep the amount of multicast traffic on the WiFi down, which is good. But it makes some things break, most notably DNS service discovery (AKA Zeroconf; this is the thing that makes printers and airplay devices pop up automatically).
Another issue I’ve been having is how to find my devices on an IPv6 network, where addresses are dynamic and devices pick them autonomously. In an IPv4 world, the DHCP server knows the addresses of all devices on the network (and Dnsmasq has excellent support for putting that into DNS), but this is not the case for IPv6. Dnsmasq does have support for guessing IPv6 addresses when handing out IPv4 addresses, but this doesn’t work if the client uses something other than SLAAC (such as RFC7217 addresses), and it won’t work in tomorrow’s IPv6-only world. Besides, I want the ability to put the global IPv6 addresses into public DNS, secured with DNSSEC.
In this post I’ll explain how I solve both of the above problems using the Unbound resolver running on my gateway router, the ohybridproxy mDNS proxy and Nsregd, a tool I wrote myself for registering addresses via signed DNS updates.
Making DNS service discovery work
DNS service discovery works by sending out Multicast DNS (mDNS) queries on the local link to discover services. Since the multicast discovery packets stay in the same layer 2 domain, when the WiFi network is not bridged to the LAN, a client on the WiFi can’t find a service on the LAN (and vice versa). So my roommate’s Apple laptop can’t find the printer that is connected to the wired LAN.
Fortunately, there exists a
specification for hybrid discovery
via unicast DNS queries, which we can use to re-enable service discovery
across the routed subnets. The
ohybridproxy implementation of
the proxy is packaged for OpenWrt and is straight forward to set up.
Simply install the ohybridproxy
package, and configure it in
/etc/config/ohybridproxy
:
config main main
option host '::1'
option port '5533'
config interface
option interface lan
option domain lan.example.org
config interface
option interface wifi
option domain wifi.example.org
This will cause the proxy server to listen for unicast DNS queries on
localhost port 5533 and translate queries for lan.example.org
into
mDNS queries on the lan
interface, and queries for wifi.example.org
into mDNS queries on the wifi
interface. Make sure both the
ohybridproxy
and the mdnsd
service are running.
The local unbound resolver can then be configured to forward queries for these domains to the hybrid proxy:
server:
local-zone: "lan.example.org." nodefault
local-zone: "wifi.example.org." nodefault
local-zone: "example.org." typetransparent
local-data: "b._dns-sd._udp.example.org. IN PTR lan.example.org."
local-data: "b._dns-sd._udp.example.org. IN PTR wifi.example.org."
local-data: "db._dns-sd._udp.example.org. IN PTR lan.example.org."
local-data: "lb._dns-sd._udp.example.org. IN PTR lan.example.org."
# These subnets are part of the guest network
access-control-view: "10.1.1.64/27" "guest"
access-control-view: "2001:db8:1::/64" "guest"
view:
# Disallow browsing for guests
name: "guest"
local-zone: "lan.example.org." refuse
local-zone: "wifi.example.org." refuse
stub-zone:
name: "lan.example.org."
stub-addr: ::1@5533
stub-zone:
name: "wifi.example.org."
stub-addr: ::1@5533
The view
part is for the case where a separate guest network is also
defined, which should not have access to the service discovery domains.
The local-data lines specify the PTR records that clients implementing
hybrid discovery will query for to discover in which domains to look for
services (see
section 5 of the draft
for details). The example.org
domain needs to be specified in the DHCP
config so clients receive it along with the DNS server to use.
And presto, laptops on the WiFi can print again!
Finding devices on the network
The other problem I was trying to solve was how to find devices on my
network. I.e., I want to be able to issue ssh mymachine.example.org
to
connect to a machine on my network, via IPv6, from anywhere. And I want
the address assignment to be secured with DNSSEC, but I don’t want to
manually maintain the zone file.
A mechanism for dynamically updating DNS zones has been around for 20 years in the form of DNS UPDATE queries. And signed updates are possible. However, using this means I would have to provision keys on all the devices that I want to register themselves, which is a pain.
To avoid this, I decided to write some code. What I came up with is Nsregd which is a DNS UPDATE proxy daemon that will allow clients on a network to issue signed updates for their hostname, authorising the updates on a Trust On First Use (TOFU) basis, similar to how SSH works the first time you connect to a host. What this means is that the first device to register a given name gets to “own” it, and subsequent updates for that names will only be allowed with the same key that initially registered it.
Nsregd will perform sanity checking on the registered names (only A and
AAAA records are allowed, and only for immediate subdomains of the
configured zone), then update the global DNS on the client’s behalf. It
can also optionally synthesise reverse PTR records for the names being
registered. Clients are expected to maintain their registration, and if
this is not done, nsregd
assumes the client has gone away and removes
the records from the DNS (but keeps the key so the client can re-claim
the name when it appears on the network again).
The repository linked above also contains a client (nsregc
) which will
try to discover where to find an nsregd
server for the current network
and register any addresses on the local machine with it, and maintain
the registration. Discovery is done by leveraging the same PTR magic
that the hybrid service discovery described above uses. On the unbound
server, I simply add the following config (for the 2001:db8:1::
subnet):
server:
local-data: "r._dns-sd._udp.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. IN PTR example.org"
This specifies that a client on that subnet can register itself in the
example.org
zone. The client will then try to find an nsregd
server
in that domain by issues an SRV query for _nsreg._tcp.example.org
(which must be added to global DNS). If this returns a record, the
client will attempt registration with that server (which can be located
anywhere, as long as it is reachable from the subnet we want to
configure).
In my case, I run nsregd
on a separate server (nsregd.example.org
in
this example), which is configured like this:
listen-addr: nsregd.example.org
listen-port: 5333
data-dir: /var/lib/nsregd
zones:
example.org:
reserved-names:
- localhost
- guardian
- ns
allowed-nets:
- 10.0.0.1/27
- 2001:db8:1::/64
max-key-ttl: 4320h
max-addr-ttl: 1h
allow-any-addr: false
upstreams:
-
type: nsupdate
hostname: ns.example.org
port: 53
tcp: true
timeout: 1s
zone: example.org
reverse-zones:
- 1.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa
record-ttl: 60s
keep-records: false
tsig-name: nsregd.example.org.
tsig-secret: "verysecretpassword"
exclude-nets:
- 10.0.0.0/8
- fd00::/8
-
type: unbound
hostname: guardian.example.org
port: 8953
client-cert: /etc/nsregd/unbound-example_client.crt
client-key: /etc/nsregd/unbound-example_client.key
server-cert: /etc/nsregd/unbound-example_server.crt
server-name: unbound
zone-type: typetransparent
timeout: 1s
record-ttl: 60s
keep-records: false
reverse-zones:
- 0.0.10.in-addr.arpa.
exclude-nets:
- 2000::/4
- fd00::/8
This config specifies that nsregd
will allow devices in the
10.0.0.1/27
and 2001:db8:1::/64
subnets to register new names, and
will update ns.example.org
using TSIG-signed DNS updates, and an
unbound instance on guardian.example.org
(the gateway device). Only
globally routable addresses will be inserted into the global DNS, and
only local addresses will be added to unbound, and reverse records will
be created corresponding to each forward record the client adds. The
upstream DNS server is configured to automatically sign records added
via DNS UPDATE, so the global DNS view will have correctly signed
records.
With this setup, all I need to do is run the nsregc
client on every
device I want to be able to find. The client will auto-discover the
configuration on the network and register the device; and keep that
registration up-to-date with changing IP addresses. This works quite
well; I am currently using it for three different networks, using the
same Nsregd instance.
Closing remarks
As outlined above, hybrid DNS-SD discovery can be used to make Zeroconf discovery work on a routed network, and is quite straight-forward to setup on an OpenWrt router. And using the Nsregd daemon and its corresponding client, devices can register their own names on the network, and the registration will go into public DNS, making it possible to find the device from anywhere.
I first discovered both of these mechanisms in the IETF homenet working group, and Nsregd is inspired by the naming architecture document currently being fleshed out in the group.