The laptop's VPN client replaces the mesh's resolver file on every connect and the node-engine writes it back minutes later, so either the mesh's names or the corporate ones fail for whole sessions. Measure how the client sets DNS and weigh how names can be routed by domain without breaking ADR 0223's one-answer rule.
7.4 KiB
02 — Options
What any answer must give, from the evidence and ADR 0223:
- R1 mesh names resolve on the machine and in its containers while the VPN is up;
- R2 the corporate domains resolve through the VPN's servers while it is up;
- R3 public names resolve, VPN up or down;
- R4 no listed set of servers can give two different answers to one name — musl asks every listed server at once and takes the first reply, glibc takes the first server's "no such name" as final (issue 262, ADR 0223);
- R5 the VPN client keeps working unmodified — it is the employer's software, configured by the employer's gateway; the mesh cannot change what it pushes or how it writes;
- R6 no change on a machine that runs no VPN.
A constraint every option meets: the VPN client always writes /etc/resolv.conf itself, even with
systemd-resolved running. No option makes it stop; each decides what happens next.
1. systemd-resolved with per-link DNS and routing domains
resolved runs on the machine. The mesh0 link gets the two mesh resolvers with the routing domain
~internal and the default route (~.); the tunnel link gets the VPN's two servers and its eight
domains as routing domains. resolved sends each question to the link whose domain matches longest, the
rest to the default route.
- R1–R3 met, by routing rather than by listing: one answer per name.
- Containers cannot reach resolved's stub on
127.0.0.53. A container on the host network can; one on a bridge cannot — it reads the machine's file and gets an address that is its own loopback. resolved can listen on further addresses (DNSStubListenerExtra=): on the machine's private mesh address, which ADR 0223 already relies on being reachable from containers on a holder. The machine's file then lists that one address. R4 met: one server listed. - Who gives the tunnel link its servers? Not the VPN client (it never sets per-link DNS) and not
NetworkManager (the tunnel is not its device). Something must read what the VPN pushed — and the VPN
client writes exactly that, in its file, within a second of connecting. A path watch on the resolver
file, declared by the uplink's holder, can read the file's
nameserverandsearchlines, give them to the tunnel link withresolvectl dnsandresolvectl domain, and write the mesh's file back at once — not at the next reconcile. When the tunnel goes, resolved forgets its link's configuration by itself. - The VPN client's restore at disconnect puts back the mesh's file (or an older one: the node-engine corrects the content, as today). Nothing points at a link that is gone.
- Cost: a daemon on the machine (already installed; part of systemd), its configuration, the watch.
- For ADR 0223: it is the local forwarder 0223 rejected (its option 2), on fewer machines and with
its three objections met: not on every machine — only on one that declares a VPN client; reachable
from containers — on the private address; and it holds no copy of the mesh's names — every
.internalquestion still goes to the two resolvers, so nothing can disagree with the truth. - R6 met if it is scoped to machines that declare a VPN client.
2. The uplink's holder adds the VPN's servers to the mesh's file
When the tunnel appears, the holder writes the mesh's two resolvers and the VPN's two servers, and the VPN's search domains.
- R4 broken, by construction. The VPN's servers answer "no such name" for
.internal; the mesh's resolvers forward a corporate name to the public internet, which answers "no such name" or a public record. glibc stops at the first listed server's answer, musl takes whichever is fastest. Whatever order is chosen, one kind of name fails — the exact fault ADR 0223 removed the public resolver for. - Rejected. It only works with something that routes by domain in front — which is option 1 or 5.
3. The mesh's resolvers forward the corporate domains while the VPN is up
- They cannot reach the VPN's servers: those sit behind the laptop's tunnel, routed only on the laptop. Forwarding through the laptop would make the mesh's resolvers depend on one laptop's session.
- It would also put an employer's zones into the mesh's shared configuration, answered for every machine.
- Rejected.
4. Stand back: detect the VPN and defer
While the resolver file carries the VPN client's marker and its backup holds the mesh's file, the node-engine does not correct it, and the machine's health says deferred to a VPN client rather than rewritten by another program. On disconnect the client restores the mesh's file.
- R2, R3 met; R1 broken for the whole session: no mesh name resolves on the laptop, nor in its containers. The node-engine reaches the bus by name; agents and tools on the laptop lose the mesh.
- Cheap: no daemon, one rule in the node-engine (and the content check on restore stays).
- Today's behaviour is the opposite trade — R1 met after a few minutes, R2 broken for the rest of the session — and the evidence shows the operator living with it by reconnecting.
- Acceptable only as a stated, chosen degradation for a machine where the owner's work comes first; better than the fight, worse than routing. Useful as the fallback of option 1 while it is not built, if the operator prefers work names to mesh names.
5. A local forwarder of the mesh's own (dnsmasq) on the VPN machine
As option 1, with the catalogue's resolver program in place of resolved: server=/<corporate domain>/…
lines for the VPN's domains, everything else to the two mesh resolvers, listening on the private address.
- Meets R1–R4 like option 1.
- Costs more than option 1: a package to declare, and a configuration file to rewrite and a process to reload on every connect and disconnect, where resolved takes per-link settings live over its own interface and drops them with the link. dnsmasq is already the mesh's resolver module, which makes "a second copy of the resolver on this machine" easy to misread as a third mesh resolver.
- Kept as the fallback for a machine without systemd.
6. Replace the VPN client
openfortivpn, through NetworkManager's plugin, hands the VPN's DNS to NetworkManager instead of writing
the file; NetworkManager with dns=systemd-resolved then gives resolved per-link servers and domains —
option 1 with no watch at all.
- Not the mesh's to decide (R5): the employer's gateway may require its own client (posture checks,
single sign-on through the vendor's flow). It would also hand the file back to NetworkManager, against
ADR 0117's
dns=none. - Recorded so the option is not rediscovered; not proposed.
Summary
| R1 mesh | R2 corporate | R3 public | R4 one answer | R6 others untouched | New on the machine | |
|---|---|---|---|---|---|---|
| 1 resolved, per link | yes | yes | yes | yes | yes, if scoped | resolved, a path watch |
| 2 add the VPN's servers | some | some | yes | no | yes | nothing |
| 3 forward from the mesh | yes | no | yes | yes | no | nothing |
| 4 defer | no | yes | yes | yes | yes | one rule |
| 5 dnsmasq locally | yes | yes | yes | yes | yes, if scoped | a package, a reload per connect |
| 6 replace the client | yes | yes | yes | yes | yes | not the mesh's call |
| today (the fight) | after minutes | no, after minutes | mostly | yes | yes | — |