mesh/merge-gate fail: builds new: modules/systemd-resolved, sent nowhere; no bus step; a manifest the change touches fails the module check: modules/system…
mesh/repo-check fail: its merge-check.sh failed: long-running resources without health: 70
mesh/delivery superseded: a newer head of the same pull request
Holds node-resolver and provides split-dns at the machine's reach, for a machine whose VPN client pushes resolvers of its own. It writes the resolver file naming the machine's private address, gives resolved the mesh's resolvers as the default route, and serves routes, route and unroute on the mesh and, over a root-only socket, on the machine. Its guard keeps an outside write of the file for the module that handles it and puts the module's file back: at once when taken, after 90 s otherwise, so a write nothing declared to handle is still raised by the node-engine.
78 lines
5.2 KiB
Markdown
78 lines
5.2 KiB
Markdown
# systemd-resolved
|
|
|
|
A machine's own resolver (novox/hq ADR 0247): systemd-resolved, holding the node seat `node-resolver` and
|
|
providing `split-dns` at the machine's reach. It is assigned only where something on the machine requires
|
|
`split-dns` — today a VPN client whose company domains must go to the VPN's own servers while every other
|
|
name still goes to the mesh's two resolvers. Every other machine has none, and lists the mesh's resolvers
|
|
in its resolver file as ADR 0223 says.
|
|
|
|
**It knows nothing about any VPN.** It routes a set of domains to a set of servers over one link, lists
|
|
what is routed, and takes a route away. A module wrapping a VPN client that writes `/etc/resolv.conf`
|
|
itself carries its own adapter, which calls these verbs. A VPN that tells systemd-resolved its link's DNS
|
|
itself (NetworkManager's VPNs, WireGuard set up by networkd, Tailscale) needs none.
|
|
|
|
## What it writes
|
|
|
|
| file | what |
|
|
|---|---|
|
|
| `/etc/resolv.conf` | this machine's own private address alone, with ADR 0223's options. The uplink's holder steps back from this file where this module is assigned (the controller's rule, ADR 0247), so it has one writer |
|
|
| `/etc/node-resolver/resolv.conf` | the same file, kept beside it for the guard to compare with and put back |
|
|
| `/etc/node-resolver/suffix` | the mesh's own domain, which a route may never take |
|
|
| `/etc/systemd/resolved.conf.d/50-mesh.conf` | every mesh resolver as the default route for names (`DNS=`, `Domains=~.`), no public fallback, the stub on loopback and on the private address, no cache, no LLMNR or mDNS |
|
|
|
|
**On the private address, so containers reach it.** A container copies its machine's resolver file and
|
|
cannot reach the machine's loopback, so the file names the address the machine has on the private
|
|
network, where resolved also listens (`DNSStubListenerExtra`). The packet filter admits a machine's own
|
|
guests and nobody else, so no other machine can ask it. The port is declared `from: machine` and fixed:
|
|
a fixed port is a claim on the machine, given to one module, and the mesh's own resolver (`dnsmasq`, on
|
|
the anchor and the home server) claims the same one. The two are not meant to share a machine.
|
|
|
|
**No cache.** Nothing on the machine keeps a copy of a mesh name or of a "no such name". Each question is
|
|
asked again, as it was before this resolver.
|
|
|
|
**Its package is systemd's.** resolved ships with systemd, whose package belongs to the `systemd` module
|
|
holding `node-service-manager` (ADR 0207). This module declares the service, running and enabled,
|
|
restarted when its drop-in changes, and nothing to install.
|
|
|
|
## The guard
|
|
|
|
One long-running process, as root: `resolver-tools guard`.
|
|
|
|
- **It keeps the resolver file the module's own.** Twice a second it compares `/etc/resolv.conf` with the
|
|
kept copy. Another program's write is *displaced*: the guard keeps what it wrote in
|
|
`/run/node-resolver/displaced/`, readable by root alone and gone at the next boot, and names its writer
|
|
from the file's own header.
|
|
- **A write a module takes is put back at once.** A module on the machine that reads the write and routes
|
|
what it needed says it *took* it, and the module's own file stands again within a second.
|
|
- **A write nobody takes stands for 90 seconds**, then is put back. 90 seconds is longer than the
|
|
node-engine needs to see the rewrite twice, so ADR 0241's `machine.<m>.systemd-resolved.rewritten` is
|
|
still raised, naming the writer, for a write nothing on the machine declared to handle.
|
|
- **Only the mesh's resolvers answer every name.** Every five seconds, a link that a network manager gave
|
|
servers of its own is told it is not a default route for names. Its own domains stay its own.
|
|
- **What it says elsewhere** — the `routes` verb's `outside_writes`, from `/run/node-resolver/history.json`
|
|
— is when, the writer's name and what became of each write. It never includes a server or a domain.
|
|
|
|
## Its verbs
|
|
|
|
On the mesh, through the node's runtime as the operator account (writes escalate with `sudo -n`):
|
|
|
|
| verb | does |
|
|
|---|---|
|
|
| `<node>/node-resolver.routes` (r) | the mesh's resolvers, each link given servers of its own with its routed domains and whether it is a default route, and the resolver file's outside writes |
|
|
| `<node>/node-resolver.route` (a) | `{link, domains, servers}`: these domains, and every name under them, to these servers over this link, and only them. The mesh's own domain and the root are refused |
|
|
| `<node>/node-resolver.unroute` (a) | `{link}`: that link's domains go to the mesh's resolvers again |
|
|
|
|
**On the machine, to its own root processes**, over `/run/node-resolver/verbs.sock` (mode 0600). The path
|
|
is the seat's, so a caller does not need to know which holder answers. The protocol is one JSON line
|
|
`{"verb": …, "args": {…}}` and one JSON line back, `{"result": …}` or `{"error": "…"}`. The verbs are the
|
|
same three, plus:
|
|
|
|
- `displaced`: the write standing now, with what it held, or null;
|
|
- `route` with `takes` (the write's `id`) and `by` (the module's name): route, then take that write, so
|
|
the module's file is put back.
|
|
|
|
A VPN's servers and domains go over this socket and never over the bus, so they never leave the machine.
|
|
|
|
resolved holds the routes itself, per link, and forgets a link's route when the link goes. Nothing here
|
|
keeps a table of its own that could disagree with it.
|