Design 50: the own resolver's file waits longer than its pass over a silent mesh resolver (ADR 0678, issue 1748) #587
@@ -0,0 +1,59 @@
|
||||
---
|
||||
layer: to-be
|
||||
design: 50
|
||||
milestone: resolver-wait
|
||||
updated: 2026-11-12
|
||||
code: [mesh-catalog modules/systemd-resolved, mesh-host internal/compose/resolution]
|
||||
decisions:
|
||||
- "ADR 0678"
|
||||
- "ADR 0247"
|
||||
- "ADR 0223"
|
||||
---
|
||||
|
||||
# Design 50, milestone resolver-wait — the own resolver's file waits longer than its pass
|
||||
|
||||
**What it builds.** On a machine where a module holds the node seat `node-resolver`, the file `/etc/resolv.conf`
|
||||
gives its programs a wait longer than the own resolver's pass over one silent mesh resolver to the next, so a lookup
|
||||
behind a silent first mesh resolver answers late instead of failing (ADR 0678 §1). The value is the main file's,
|
||||
[§2](../50-split-dns-on-a-machine-with-a-vpn-client.md#2-the-file-and-the-resolver):
|
||||
`options timeout:3 attempts:2 edns0`. This part holds every line the milestone builds, cited as "design 50 resolver-wait §n". It is built under
|
||||
issue 1748.
|
||||
|
||||
**Where.** Two repositories: mesh-catalog (the module `systemd-resolved`, holder of `node-resolver`: §1) and `mesh-host`
|
||||
(the node-engine's names check and its test: §3). One milestone, its two pull requests one delivery group with one
|
||||
check. No ordering edge joins them: once §3's test reads the options from the catalog's template, each repository's
|
||||
check passes against the other's main, whichever merges first.
|
||||
|
||||
## Lines
|
||||
|
||||
1. **The own resolver's template carries the longer options.** The module `systemd-resolved` renders the file twice
|
||||
from one template, its facts `resolvers` (the live file) and `kept` (the kept copy beside resolved's
|
||||
configuration, which the guard puts back); both carry the options line of design 50 §2, `options timeout:3
|
||||
attempts:2 edns0`, in place of `options timeout:1 attempts:2 edns0`, which goes from the template. The one
|
||||
`nameserver` line, the machine's private address, is unchanged. The node-engine writes both when it renders the
|
||||
module, the kept copy first (design 50, *As built*), so the guard never compares a new live file with an old copy.
|
||||
Read by `<node>/node-reads.read` of the file and by the uplink seat's `resolvers` verb, as today. Checked by the
|
||||
module's manifest test, which asserts the one server and this options line in both facts; the guard's own test,
|
||||
which holds a literal of the file, carries the new line.
|
||||
2. **Nothing else of the resolver changes** (ADR 0678 §2). resolved's drop-in stays as it is: no cache, the mesh's
|
||||
resolvers as its servers in the order of the module's roster (ADR 0223 §9), `~.`, no fallback, its extra listener
|
||||
on the private address. The uplink modules' files (`networkmanager`, `systemd-networkd`) keep both mesh resolvers
|
||||
with `timeout:1 attempts:2`, held by the existing test over every uplink's resolver file. The captured copies of
|
||||
the catalog in other repositories' test data are left as they are: no check reads them.
|
||||
3. **The names check judges the file with the wait its programs give it.** The node-engine's names check already
|
||||
bounds each ask by the `timeout:` of the file it judges, never under 1 s; that stays, so on a machine holding
|
||||
`node-resolver` each ask's bound becomes 3 s. Its test that composes the catalog's own resolver on a machine
|
||||
reads the expected options line from the catalog's `systemd-resolved` template beside it, in place of the literal
|
||||
`timeout:1 attempts:2 edns0` it asserts today (ADR 0678, *How it is checked*). The reason: a test literal of
|
||||
another repository's template fails one repository's check on whichever merges first, the kind issue 1748's note
|
||||
of 2026-11-10 met.
|
||||
4. **The value stays 3 s per try until the pass can be read.** design 50 §2's `timeout:3 attempts:2` stays ADR
|
||||
0678's starting value until a read of resolved's own server switches exists. A restart of resolved and lookups
|
||||
at the own resolver with its first mesh resolver silent answered in 44 to 46 ms (the lead's reading of
|
||||
2026-11-12 18:05 CET, issue 1748), so a restart and a timed ask do not catch the pass. After the delivery, on the
|
||||
workstation first and then the laptop, `<node>/node-reads.read` of `/etc/resolv.conf` shows the one server and
|
||||
§1's line; the proof that the wait outlasts the pass is §5's.
|
||||
5. **The live proof.** At the next silence of the first mesh resolver, `mesh-build-go.build_go_steps` lists no Go
|
||||
build step on a machine holding `node-resolver` failed with `i/o timeout` at that machine's own address. Issue
|
||||
1748 is resolved once §4's read of the file holds, naming this line as where the proof will show, and reopened
|
||||
if it fails.
|
||||
@@ -6,11 +6,13 @@ code:
|
||||
- mesh-controller internal/catalogue/seats.go, declaration.go, resolve.go
|
||||
- mesh-catalog modules/systemd-resolved (the resolver, its guard and its verbs)
|
||||
- mesh-catalog modules/forticlient (the VPN client's adapter)
|
||||
updated: 2026-10-07
|
||||
- mesh-host internal/compose/resolution, internal/network (the names check of the file, milestone resolver-wait)
|
||||
updated: 2026-11-12
|
||||
decisions:
|
||||
- "ADR 0247"
|
||||
- "ADR 0223"
|
||||
- "ADR 0241"
|
||||
- "ADR 0678"
|
||||
---
|
||||
|
||||
# 50 — Split DNS on a machine with a VPN client
|
||||
@@ -66,8 +68,14 @@ not share a machine.
|
||||
|
||||
## 2. The file and the resolver
|
||||
|
||||
- **The file** names the machine's private address alone, with ADR 0223's options, and begins as every
|
||||
mesh resolver file does, so the uplink's `resolvers` verb and the node-engine read it as the mesh's.
|
||||
- **The file** names the machine's private address alone and begins as every mesh resolver file does, so
|
||||
the uplink's `resolvers` verb and the node-engine read it as the mesh's.
|
||||
- **The file's options wait longer than resolved's pass** over one silent mesh resolver to the next
|
||||
(ADR 0678 §1): `options timeout:3 attempts:2 edns0`, a total wait of 6 s, the timeout times the attempts, where
|
||||
ADR 0223's `timeout:1 attempts:2` gave about 2 s. Its program asks only the own resolver, so it waits on that
|
||||
pass; a file that lists the mesh's resolvers keeps `timeout:1 attempts:2`, because its programs try the second
|
||||
after one second (ADR 0678 §2, design 08 §2). The value stays ADR 0678's 3 s per try until a read of resolved's
|
||||
own server switches exists, since no read today catches the pass (milestone resolver-wait §4).
|
||||
- **The uplink's holder steps back** from that path on that machine, by the controller's rule. It does not
|
||||
write the file and lose a race; it is never given the file to write.
|
||||
- **resolved** is given every mesh resolver as its global servers, with `~.` so they answer every name no
|
||||
@@ -122,12 +130,24 @@ resolver's hold, and is therefore raised. A VPN that tells resolved its link's D
|
||||
## 6. What it costs and what it does not do
|
||||
|
||||
- The client's search domains route full names. They do not complete short ones.
|
||||
- A name no mesh resolver answers takes the file's total wait to fail on that machine: 6 s, about 4 s longer
|
||||
than before (ADR 0678).
|
||||
- resolved down is no names on that machine. Its `unit` health, and the node-engine's names check against
|
||||
the address the file lists, say so.
|
||||
- resolved should take no servers from the resolver file, because its own are configured. The live test
|
||||
confirms this while a write stands.
|
||||
- A container started before the resolver keeps the mesh's two resolvers until it restarts.
|
||||
|
||||
## How it is checked
|
||||
|
||||
| Check | Defends |
|
||||
|---|---|
|
||||
| the own resolver module's manifest test: its file names one server, the machine's private address, with §2's options | ADR 0247 §6, ADR 0678 §1 |
|
||||
| the node-engine's test that composes the catalog's own resolver on a machine reads the options from the catalog's template, not a literal, and its names check bounds each ask by the file's `timeout:` | ADR 0678 §1 |
|
||||
| the test over every uplink's resolver file: both mesh resolvers, `timeout:1 attempts:2` | ADR 0678 §2, design 08 §2 |
|
||||
| live, on a machine holding `node-resolver`: at the next silence of its first mesh resolver no Go build step there fails on `i/o timeout` (milestone resolver-wait §5); a timed lookup waits for a read of resolved's own server switches (§4) | ADR 0678 §1 |
|
||||
| each phase's *Done when* below | ADR 0247 |
|
||||
|
||||
## Phases
|
||||
|
||||
| Phase | Repository | Delivers | Done when |
|
||||
@@ -135,6 +155,7 @@ resolver's hold, and is therefore raised. A VPN that tells resolved its link's D
|
||||
| A — the seat | mesh-controller | `node-resolver` and its verbs; the uplink stepping back | its tests pass; the controller is running on the mesh |
|
||||
| B — the resolver | mesh-catalog `systemd-resolved` | the module, its guard and verbs | its tests pass; assigned to every machine that runs the VPN client's module, each one's file names its own address, `routes` answers |
|
||||
| C — the adapter | mesh-catalog `forticlient` | `split-dns` required; the adapter | its tests pass; on the laptop, connected, a company name and a mesh name both resolve and no rewrite stays raised |
|
||||
| resolver-wait | mesh-catalog `systemd-resolved`, `mesh-host` | design 50 §2's options (ADR 0678), under issue 1748; its part is [`50-milestones/resolver-wait.md`](50-milestones/resolver-wait.md) | its tests pass; *How it is checked*'s live row holds |
|
||||
|
||||
## As built
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ document is written and this one's status becomes `implemented`.
|
||||
| [`46-the-conversation-with-the-operator.md`](46-the-conversation-with-the-operator.md) | **Designed.** The mesh tells and asks its operator over channels that are holders of two kinded benches, `channel` and `intake`, declaring capabilities from a fixed vocabulary; the router orders them by work context and never lowers the bar; an answer that performs an action is checked and performed by the controller, on a TOTP code or a verified Telegram sender, never on a desk click alone; operator messages addressed or answered by the mesh's responder, untrusted input kept as data, references for what words may not carry, and a recoverable factor; Telegram first, in six phases | ADR 0234, ADR 0227 |
|
||||
| [`47-delivery-from-commit-to-delivered.md`](47-delivery-from-commit-to-delivered.md) | **Designed.** A delivery is one commit in one repository, from its pull request's head to every machine; a delivery group is deliveries sharing a branch name, ordered and checked as one future state; both owned by the `mesh-delivery` module with one state table, its state on the bus, every transition said, noted on the commit and shown on the pull request; the controller keeps the planner, the gate, sending and the rollout, and the core's own updates never wait for the module | ADR 0239, ADR 0238, ADR 0236 |
|
||||
| [`49-the-mesh-in-domains.md`](49-the-mesh-in-domains.md) | **In progress.** Every concept belongs to one of eleven domains, which owns its one word; the glossary is organised by them and is the authority, a retired word is named on its replacement's line with its scope, and two checks hold this repository's documents and the catalog's tool descriptions to it | ADR 0244, ADR 0006, ADR 0008, ADR 0302 |
|
||||
| [`50-split-dns-on-a-machine-with-a-vpn-client.md`](50-split-dns-on-a-machine-with-a-vpn-client.md) | **In progress.** A machine whose VPN client pushes servers of its own runs a resolver of its own, on a node seat, that routes the VPN's domains over its link and every other name to the mesh's resolvers; the VPN client's module carries the adapter | ADR 0247, ADR 0223 |
|
||||
| [`50-split-dns-on-a-machine-with-a-vpn-client.md`](50-split-dns-on-a-machine-with-a-vpn-client.md) | **In progress.** A machine whose VPN client pushes servers of its own runs a resolver of its own, on a node seat, that routes the VPN's domains over its link and every other name to the mesh's resolvers; the VPN client's module carries the adapter | ADR 0247, ADR 0223, ADR 0678 |
|
||||
| [`51-the-registries-keep-what-is-named.md`](51-the-registries-keep-what-is-named.md) | **In progress.** The artifact store, the package registry and each machine's container runtime get tools that say what they hold, what names each thing and what could go; every removal is a dry run unless a person asks and says why, and what decides is always a record | ADR 0251, ADR 0189 |
|
||||
| [`52-shares-and-mounts.md`](52-shares-and-mounts.md) | **In progress.** A node shares one folder at a time through `nfs-server`, which provides it as a provision to the private network only, mapping every client to the folder's owner; a node that asks mounts it through `mounts`, a mount unit and an automount per share, so nothing mounts or fails at boot; a device that comes and goes is an occasional source whose absence is a state | ADR 0263, ADR 0051, ADR 0092 |
|
||||
| [`53-the-issue-tracker.md`](53-the-issue-tracker.md) | **Designed.** The mesh keeps its issues in one module, `issues`, on the control-node: every number from one counter advanced by compare-and-set, the cycle's issue rules refused at the write, an event per change; an issue lives whole in the tracker, hq keeping no issue content (ADR 0291) | ADR 0275, ADR 0291 |
|
||||
|
||||
Reference in New Issue
Block a user