ADR 0244 sorts the mesh's concepts into ten domains (ADR 0006's contexts carry over as domains), keeps machine and node as distinct words, and makes the glossary the authority with every retired word on its replacement's Not: line. To-be 49 draws the domains; the glossary is reorganised by them, its two contradictions removed and the missing words added. words.py now fails on a retired word in running prose and on a word defined twice, so the rule is enforced rather than believed; the 63 documents it failed on are reworded here, and research 034 is kept as the record of the words it studied.
114 lines
7.4 KiB
Markdown
114 lines
7.4 KiB
Markdown
# 02 — Options
|
||
|
||
What any answer must give, from the [evidence](01-evidence.md) 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](../../04-ISSUES/262-an-alpine-container-could-not-find-a-machine-by-its-mesh-name/00-report.md),
|
||
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 node-engine 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 `nameserver` and `search` lines, give them to
|
||
the tunnel link with `resolvectl dns` and `resolvectl 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 `.internal`
|
||
question 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 | — |
|