Laser's cool website :)

Simple Home DNS using dnscrypt-proxy

Making your hosts easily accessible on your home network is a something that can be tackled in a variety of ways. Each of these solutions come with their pros and cons regarding ease of use, adaptability and requirements for the devices on your network.

Let’s take mDNS for example: it’s easy to set up and works mostly fine with Linux hosts. However, even Android devices pose a problem, as they don’t support it. That means you can’t use it to resolve hosts on your phone or tablet.

Another approach is probably NetBIOS / winbind. I did not explore this as I’m happy when I can avoid Samba.

Nevertheless, these two come with a significant downside: lack of certificates. One might try to set up their own Certificate Authority, but then these would need to be deployed on devices that often aren’t managed at the required level (like phones without a Enterprise Mobility Management solution). So I went for a in my opinion easy approach: a local DNS cache that allows easy definition of my own domains.

The complete setup also consists of TLS with ACME and an nginx reverse proxy. These parts are synergetic with DNS, but not required. You can just use the parts of this guide to e.g. ssh into your local machine using “hostname.internal.network.tld” and call it a day. However, in my opinion, it only gets interesting and useful when the components work all together, but this would be too long for this post. As such, see the examples as teasers for more possibilities that this setup enables.

This is not a “definite guide” or anything, but rather a bit of documentation of how I tackled the problem and hopefully basis for a discussion. Feel free to comment on where I post this!

Requirements

For this to work, you need a domain name of your own that you control. Get one from a provider with API access as this is required for wildcard certificates.

This post does not go into how to set up ACME on NixOS.

Enter dnscrypt-proxy

dnscrpyt-proxy is

A flexible DNS proxy, with support for modern encrypted DNS protocols such as DNSCrypt v2, DNS-over-HTTPS, Anonymized DNSCrypt and ODoH (Oblivious DoH).

While these are nice features to get domain names from an upstream, we’re looking for what this service calls Cloaking. This allows to override specific domains with our own destinations, like

a HOSTS (or /etc/hosts) file on steroids

So really simple stuff, if you know how those files look.

Obviously, for this to work, you need to ensure that your devices always get the same IP on your home network. My router has an option to always assign the same address to a device for IPv4. For IPv6, I use Unique Local Addresses (ULAs).

An example NixOS configuration module

To enable this service with reasonable defaults on a device and open the firewall ports, this is sufficient:

 1  _:
 2
 3{
 4  services.dnscrypt-proxy2 = {
 5    enable = true;
 6    settings = {
 7      cloaking_rules = ./cloaking-rules.txt;
 8      listen_addresses = [
 9        "192.168.178.10:53"
10        "127.0.0.1:53"
11        "[::1]:53"
12        "[fdaa:66e:6af0:0:443a:53ff:fecb:99e5]:53"
13      ];
14    };
15  };
16  networking.firewall.allowedTCPPorts = [ 53 ];
17  networking.firewall.allowedUDPPorts = [ 53 ];
18}

These are the loopback addresses and the LAN addresses for the device. Since I’m managing those from my router, I can’t refer to config values here.

The cloaking rules file could look like this:

 1powerbox.internal.pc-hass.de 192.168.178.72
 2powerbox.internal.pc-hass.de fdaa:66e:6af0:0:3071:91ff:fed1:9e26
 3
 4odroid.internal.pc-hass.de 192.168.178.12
 5odroid.internal.pc-hass.de fdaa:66e:6af0:0:21e:6ff:fe45:59e3
 6
 7internal.pc-hass.de 192.168.178.10
 8internal.pc-hass.de fdaa:66e:6af0:0:443a:53ff:fecb:99e5
 9idm.pc-hass.de 192.168.178.10
10idm.pc-hass.de fdaa:66e:6af0:0:443a:53ff:fecb:99e5
11
12fritz.box 192.168.178.1
13fritz.box fdaa:66e:6af0::de39:6fff:fe6f:6046

This registers the addresses for my Odroid and Workstation hosts, both for IPv4 and IPv6. The lines starting with internal in my case refer to this device itself. You can’t use loopback addresses here for obvious reasons.

Anyhow, the way dnscrypt-proxy works is that it will resolve the same address for all subdomains if it matches a domain in this list. That means some-service.internal.pc-hass.de will resolve to 192.168.178.10. I use this mechanism to reverse proxy most services through Renegade. Identity Management (IDM) is a topic for a future post, but just to see that it’s working:

Screenshot of the kanidm login page on my local network with TLS enabled

An example of a service only reachable on the local network with a valid TLS certificate

Tying it all together

To finish it all off, we make the router announce our newly setup DNS. Obviously, this depends on your device, so I’ll just post examples here:

Screenshot of IPv4 DNS settings Screenshot of IPv6 DNS settings

Let’s verify that this does what we want with a dumb device, in this case, an Android phone that simply uses DHCP and has no special network configuration whatsoever:

Screenshot of the TLS information of the internal site on Android

Nice.

Additional considerations

Having a local DNS means also that additional care needs to be taken. For example, if your DNS goes down, internet access is also restricted to IP addresses for all devices not explicitly configured to use a different DNS. Also, when using ACME with DNS challenge for your home network, a different DNS server needs to be chosen because the ACME daemon will not register that the required DNS challenge has completed as dnscrypt-proxy will not relay that information, probably because that’s cloaked.

Possible changes

Since we’re using nix, we could make the cloaking rules file a nix module, where the hostname gets filled in according to something like config.networking.domain or whatever. However, I feel like this shouldn’t really change, especially when using an IDM. So this is currently static.

Summary

We set up a very simple DNS server, created some rules for our home network and had the DNS propagate through our consumer grade router. This is not a solution for bigger networks, but something that works reasonably well for me. In the end, the amount of required Nix configuration was minimal, but this is also due to already having done the ACME part somewhere else. Another topic for another day. I wanted to get this out first because I think the other topics have been explored elsewhere already.

#nix #nixos #linux