jarl encrypted-dns setup (and the Encrypted DNS page) finds or obtains a certificate for the host name, checks that nothing else listens on the DoT and DoH ports at the chosen addresses (offering a free DoH port), and stages named's tls statement and listeners as one change, keeping port 53 where it was when there was no listen-on, plus the host name's records in the private views (local to the view in a linked zone). Status shows what to give Android, Linux, Firefox, Chrome and Windows clients. A tls statement with missing files is refused (tls_file_missing), and a certificate named uses can't be deleted. docs/encrypted-dns.md, e2e with real DoT and DoH queries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
132 lines
6.4 KiB
Markdown
132 lines
6.4 KiB
Markdown
# DNS over TLS and HTTPS
|
|
|
|
named (BIND 9.20) can answer over encrypted connections itself: DNS over
|
|
TLS (DoT, port 853) and DNS over HTTPS (DoH, at `/dns-query`). Your
|
|
phones, laptops and browsers then ask this server without anyone on the
|
|
network seeing or changing their queries, and they get exactly what plain
|
|
DNS gives them: the same views, the same records and the same blocking.
|
|
|
|
Jarl sets it up in one step: a certificate for the host name clients
|
|
connect to (obtained and renewed by Jarl, [certificates.md](certificates.md)),
|
|
named's `tls` statement and listeners on the addresses you choose, and the
|
|
host name's address records in your private views.
|
|
|
|
In the web UI: **Encrypted DNS** (administrators). In the CLI: `jarl
|
|
encrypted-dns`. In the API: `/api/v1/encrypted-dns`.
|
|
|
|
## Setting it up
|
|
|
|
```sh
|
|
jarl encrypted-dns setup --hostname dns.adbraun.lol --address 10.0.10.5 --address 2a02:8013:7f3d:10::5
|
|
```
|
|
|
|
This:
|
|
|
|
1. **finds or obtains the certificate**: one Jarl manages whose names cover
|
|
the host name (`--certificate NAME` to pick one), or a new one named
|
|
after the host name's first label, which Jarl obtains from the CA (the
|
|
command waits). The certificate gets the `dns` use, so it is written
|
|
where named can read it, and named rereads it on every renewal.
|
|
2. **checks the ports**: nothing else may listen on the chosen addresses
|
|
on port 853 or the DoH port. Jarl looks at the host's listening sockets
|
|
(it binds nothing) and says what is in the way, e.g. `jarld (Jarl's web
|
|
UI and API) listens on 10.0.10.5 port 443`, and which port is free
|
|
instead (`--doh-port 8443`).
|
|
3. **stages named's configuration**, as one change you can review:
|
|
|
|
```
|
|
tls jarl-dns { cert-file ".../tls/dns/fullchain.pem"; key-file ".../tls/dns/privkey.pem"; protocols { TLSv1.2; TLSv1.3; }; };
|
|
options {
|
|
listen-on port 853 tls jarl-dns { 10.0.10.5; };
|
|
listen-on port 443 tls jarl-dns http default { 10.0.10.5; };
|
|
listen-on-v6 port 853 tls jarl-dns { 2a02:8013:7f3d:10::5; };
|
|
...
|
|
```
|
|
|
|
named listens on port 53 on every address only while there is no
|
|
`listen-on` statement at all. The first one, for DoT, would end that, so
|
|
when you have none the setup adds `listen-on { any; };` (and the same
|
|
for IPv6) to keep plain DNS where it was.
|
|
4. **adds the host name's A and AAAA records** with the chosen addresses,
|
|
in the private views that have a zone for it (`--view` to choose). In a
|
|
linked zone they are local to the view, so the name doesn't reach your
|
|
external view.
|
|
|
|
Running it again replaces the earlier setup (other addresses, ports, DoT
|
|
or DoH only with `--no-doh` / `--no-dot`). `jarl encrypted-dns disable`
|
|
takes the listeners and the `tls` statement out; the certificate and the
|
|
records stay.
|
|
|
|
| Option | Default | |
|
|
|--|--|--|
|
|
| `--hostname` | | The name clients connect to; the certificate is for it |
|
|
| `--address` | | This server's addresses to listen on (repeat). Choose your own networks' addresses: a listener on a public address answers anyone who reaches it, like port 53 there |
|
|
| `--certificate` | one for the host name, obtained if needed | A certificate Jarl manages |
|
|
| `--no-dot`, `--no-doh` | both on | |
|
|
| `--dot-port` | 853 | Clients expect 853 |
|
|
| `--doh-port` | 443 | Any free port; clients take it in the URL |
|
|
| `--view` | private views with the zone | Where the host name's records go |
|
|
|
|
In the web UI the same form checks the ports as you choose, offers the
|
|
free port, and obtains the certificate before setting up.
|
|
|
|
## Which view clients get
|
|
|
|
A DoT or DoH client gets the view its address matches, as over plain DNS:
|
|
your phone at home gets the internal view, with its records and blocking.
|
|
Choosing a view by key (`match-clients { key ...; }`) doesn't work over
|
|
DoT and DoH, as clients don't sign their queries.
|
|
|
|
## Clients
|
|
|
|
`jarl encrypted-dns` (and the page) shows what to give them:
|
|
|
|
```
|
|
For DoT clients: dns.adbraun.lol
|
|
For DoH clients: https://dns.adbraun.lol/dns-query
|
|
```
|
|
|
|
| Client | Where |
|
|
|--|--|
|
|
| Android 9+ | Settings → Network → Private DNS → Private DNS provider hostname: `dns.adbraun.lol` (DoT) |
|
|
| Linux (systemd-resolved) | `/etc/systemd/resolved.conf`: `DNS=10.0.10.5#dns.adbraun.lol` and `DNSOverTLS=yes` (or `opportunistic`, which falls back to plain DNS) |
|
|
| Firefox | Settings → Privacy & Security → DNS over HTTPS → Increased protection → Custom: the DoH URL |
|
|
| Chrome, Edge | Settings → Privacy and security → Security → Use secure DNS → custom: the DoH URL |
|
|
| Windows 11 | Settings → Network → your connection → DNS server assignment → Edit: the server's address, DNS over HTTPS on (manual template), the DoH URL |
|
|
|
|
Test from a machine on your network:
|
|
|
|
```sh
|
|
kdig +tls-ca +tls-host=dns.adbraun.lol @10.0.10.5 example.com
|
|
dig +https @10.0.10.5 example.com
|
|
curl -s -H 'accept: application/dns-json' 'https://dns.adbraun.lol/dns-query?name=example.com'
|
|
```
|
|
|
|
**Android away from home.** Private DNS in hostname mode applies on every
|
|
network. Away from home, your server can't be reached, and Android then
|
|
resolves nothing at all ("Private DNS server cannot be accessed"). Use a
|
|
VPN home (WireGuard) when you're out, or switch Private DNS to
|
|
*Automatic* when you leave. Firefox's *Increased protection* and
|
|
systemd-resolved's `opportunistic` fall back to normal DNS by themselves.
|
|
|
|
Clients check the certificate against the host name, so the name must
|
|
resolve to this server for them: the setup adds it to your private views.
|
|
Open the ports in the server's firewall for your own networks only.
|
|
|
|
## What Jarl checks
|
|
|
|
* A `tls` statement whose files don't exist is an error
|
|
(`tls_file_missing`): named won't load its configuration without them.
|
|
* A certificate named uses can't be deleted, nor lose its `dns` use: turn
|
|
encrypted DNS off first.
|
|
* Listeners you wrote yourself are left alone; the page notes them.
|
|
|
|
## API
|
|
|
|
| Method | Path | Body |
|
|
|--------|------|------|
|
|
| GET | `/encrypted-dns` | `enabled`, `certificate`, `hostname`, `dot` and `doh` (address and port), `dot_host`, `doh_url`, `listeners`; for the form: `addresses` (this server's, with `private`), `views` (with `public`, and `has_zone` for `?hostname=`), `certificates` |
|
|
| POST | `/encrypted-dns/check` | `{"addresses", "dot", "doh", "dot_port", "doh_port"}`: `conflicts` (address, port, what) and `free_doh_port` |
|
|
| POST | `/encrypted-dns` | `{"hostname", "certificate", "addresses", "dot", "doh", "dot_port", "doh_port", "views"}`: staged or applied like any change; 409 while the certificate hasn't been obtained |
|
|
| DELETE | `/encrypted-dns` | Turns it off |
|