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.
6.5 KiB
01 — The evidence
Gathered read-only on the laptop, 2026-10-07, with the VPN disconnected: the VPN client's installed files, its own log (kept since its install, about five weeks), the strings of its binaries, the node-engine's journal (kept for eleven days), and the machine's network state. Nothing was changed and nothing restarted. The corporate network's domains and addresses are not reproduced here.
The machine
- Uplink: NetworkManager, held by the mesh's
networkmanagermodule, which tells itdns=noneand leaves the private network's interface (mesh0) unmanaged. The resolver file is the module's own, declared from one template: the two mesh resolvers,options timeout:1 attempts:2 edns0. - systemd-resolved is installed, as part of systemd, but disabled and not running. The name service
order is
files dns … resolve [!UNAVAIL=return], so with resolved stopped the resolver file is the only DNS path. - The mesh's names are not in
/etc/hostsany more (ADR 0148 moved every name to the resolvers): with the resolver file replaced, no mesh name resolves on the machine itself, nor in any container. - Five containers: two on the node-engine network (they read the machine's file as it is, at each lookup), three on bridges (they read it, or the runtime's embedded resolver copies its servers, when they start).
The VPN client
FortiClient 7.4, the vendor's own Linux client (not openfortivpn), its scheduler running as a system
service; the tunnel is set up on demand by its vpn process.
What it does at connect, from its log, identical in all eleven sessions recorded:
Inherit local DNS: NoandDNS service resetting interval: 0— settings pushed with the profile. The first means the machine's own resolvers are not kept beside the corporate ones; the second that it never re-asserts its file during a session.- It configures the tunnel device with
ip("fall back to using ip command") — so NetworkManager sees the tunnel as an external device it does not manage. Moving /etc/resolv.conf to /etc/resolv.conf.forticlient.backup— a rename, so the mesh's file, inode and all, becomes the backup.- It writes a new
/etc/resolv.conf: two nameservers (in a private RFC 1918 range, reached through the tunnel) and eight search domains (the corporate network's internal and cloud zones). Its binary carries the header it writes:# Dynamic resolv.conf(5) file for glibc resolver(3) generated by forticlient, and a line saying the original is backed up and restored after the VPN disconnects. - Split tunnel: 119 routes through the tunnel — 110 host routes, mostly to a CDN's addresses, and
nine networks including the whole of
10.0.0.0/8and172.16.0.0/12. The mesh's private /24 sits inside the first, and survives only because a more specific route onmesh0wins.
At disconnect: Moving /etc/resolv.conf.forticlient.backup to /etc/resolv.conf — the backup taken
at connect is put back as it was then, whatever has happened to the file since. With no backup present
it logs "No DNS backup file was found. Skip."
What it does not do on this machine. Its binary knows systemd-resolved (if running, it reads the
resolvers from resolved's own file and flushes resolved's cache) and NetworkManager (it can allocate the
tunnel through nmcli, set a connection's DNS search domains, and drop a configuration file into
NetworkManager's conf.d, keeping its own backups beside the resolver file). Here it took neither path.
In no path found in the binary does it give systemd-resolved a per-link server or routing domain; it
always writes /etc/resolv.conf itself. Public reports agree: with systemd-resolved running, the client
replaces the stub symlink with its static file.
The collision
The node-engine corrects a declared file that was "changed on the machine since this host last wrote it" on its next apply or reconcile. Matching its journal to the VPN client's log:
| Session | VPN wrote its file | Node-engine wrote the mesh's back | Gap |
|---|---|---|---|
| 1 | 21:20:18 | 21:21:38 | 1 min 20 s |
| 2 | 11:51:33 | 11:54:48 | 3 min 15 s |
| 3 | 12:59:48 | 13:00:59 | 1 min 11 s |
| 4 | 12:26:30 | 12:29:55 | 3 min 25 s |
| 5 | 12:31:36 | 12:34:54 | 3 min 18 s |
| 6 | 12:42:58 | 12:44:54 | 1 min 56 s |
| 7 | 13:53:22 | 13:54:06 | 44 s |
| 8 | 14:58:37 | 15:00:35 | 1 min 58 s |
| 9 | 15:46:13 | 15:46:14 | 1 s |
- Nine of nine sessions since the journal begins were overwritten by the mesh, within 1 s to 3.5 min (median about 1 min 56 s). Two earlier sessions predate the journal.
- Each session then ran on the mesh's file — sessions lasted from five minutes to fourteen hours — so for all but the first minutes no corporate name resolved, while the tunnel and its routes stood. The VPN client never noticed: its resetting interval is 0.
- Sessions 4–6 were three connects within sixteen minutes, two ended by hand after five and seven minutes — the shape of a person reconnecting because something stopped working.
- The VPN client's restore is itself an outside write. On one night the uplink's holder changed (the resolver file moved from one module's template to another's) while a session was up; when the session ended at 02:43 the client put back its backup — the old module's file — and the node-engine corrected it two minutes later. A restore puts back a mesh file, not necessarily the current one.
- In the minutes before each correction, the machine had only the corporate servers: no mesh name
resolved (the agents' lookups failed), and public names depended on the corporate servers. The
operator reported agents seeing
ENOTFOUNDfor the AI API's public name in such a window; it is not in any journal kept on the machine, so how often public names failed is not measured.
What the other machines have
No VPN client runs on the anchor, the home server or the workstation. The two mesh resolvers have no route to the corporate network's servers: they sit behind the laptop's tunnel, in ranges the laptop routes only to itself.
Two related faults, noted
- One mesh resolver briefly slow under load (issue 277): any design that adds a hop must keep the two-resolver answer and its short timeouts.
- The self-check asks the resolvers only from the control node (to-be 45 §4, D2). A machine whose own resolver file is wrong is invisible to it — the gap ADR 0241 (proposed) closes from the machine's side.