The tunnel found at adoption and the names of the hub's peers are kept in the placement; whether the hub took it over and who enrolled under a peer's key are read from placement and records, as the controller reads them (issue 1126, ADR 0555)

This commit is contained in:
2026-10-26 18:26:11 +01:00
parent a431746c0e
commit 5b1e990eb4
5 changed files with 72 additions and 53 deletions
+42 -13
View File
@@ -11,8 +11,9 @@
// indexer's state `placement`, one key per node (ADR 0549 §2);
// - what a person decided of each node (its endpoint, site, hub, address on the private network, public domain and
// accounts): the same placement key, Placement.Decided;
// - what each node observes of itself (its tunnel key, capabilities, keys, networks, the tunnel it found, and on the
// hub the adopted tunnel and its carried peers): the node-engine's state `records`;
// - what each node observes of itself (its tunnel key, capabilities, keys, certificate, links and networks): the
// node-engine's state `records`; the tunnel found at a node's adoption and the names of the hub's peers are decided,
// in its placement, and whether the hub took the tunnel over and who enrolled under a peer's key are read from both;
// - every module's manifest: the indexer's `modules.<module>` and its manifests by digest, or the digest the
// composing node's want names where it runs another build (Scope.Kept);
// - each module's config values on a node: mesh-config's resolved state, the composing node's slice alone unless the
@@ -28,6 +29,7 @@ import (
"context"
"encoding/json"
"fmt"
"net/netip"
"sort"
"strings"
@@ -168,22 +170,24 @@ func Input(ctx context.Context, r Reader, mesh Mesh, s Scope) (compose.Input, er
}
in.Nodes = append(in.Nodes, n)
in.Holdings = append(in.Holdings, p.Held()...)
if rec.Tunnel != nil {
if in.Tunnels == nil {
in.Tunnels = map[string]compose.FoundTunnel{}
// A tunnel found at adoption is the node's private network's when the node's tunnel key is the tunnel's (the
// controller's inv.Tunnels); the hub's found tunnel is the mesh's adopted one whether or not the hub took it over
// (inv.AdoptedTunnel), and its peers are carried once it did (inv.CarriedPeers).
if t := p.Decided.Tunnel; t != nil {
if rec.TunnelKey == t.PublicKey {
if in.Tunnels == nil {
in.Tunnels = map[string]compose.FoundTunnel{}
}
in.Tunnels[node] = compose.FoundTunnel{Tunnel: tunnelOf(*t), NodeAdopted: p.Decided.Adopted}
}
in.Tunnels[node] = compose.FoundTunnel{Tunnel: tunnelOf(*rec.Tunnel), NodeAdopted: p.Decided.Adopted}
if p.Decided.Hub {
in.Adopted = &compose.AdoptedTunnel{Tunnel: tunnelOf(*rec.Tunnel), Hub: node, Adopted: rec.HubAdopted}
}
}
if p.Decided.Hub {
for _, c := range rec.Carried {
in.CarriedPeers = append(in.CarriedPeers, compose.CarriedPeer{PublicKey: c.PublicKey, Address: c.Address,
EnrolledAs: c.EnrolledAs, Named: c.Named})
in.Adopted = &compose.AdoptedTunnel{Tunnel: tunnelOf(*t), Hub: node, Adopted: rec.TunnelKey == t.PublicKey}
}
}
}
if in.Adopted != nil && in.Adopted.Adopted {
in.CarriedPeers = carriedPeers(in.Adopted.Tunnel, placements[in.Adopted.Hub].Decided.PeerNames, records)
}
if len(missing) > 0 {
return compose.Input{}, fmt.Errorf("frombus: modules are placed on %s, whose agent has written no record yet",
strings.Join(missing, ", "))
@@ -237,6 +241,31 @@ func tunnelOf(t delivery.RecordTunnel) compose.Tunnel {
return out
}
// carriedPeers is every peer of the hub's adopted tunnel, by address, each with the node that enrolled under its key
// where one has (a node whose tunnel key is the peer's) and the name a person gave it (inv.CarriedPeers).
func carriedPeers(t compose.Tunnel, names map[string]string, records map[string]delivery.NodeRecord) []compose.CarriedPeer {
byKey := map[string]string{}
for node, rec := range records {
if rec.TunnelKey != "" {
byKey[rec.TunnelKey] = node
}
}
var out []compose.CarriedPeer
for _, p := range t.Peers {
address, _, _ := strings.Cut(p.Address, "/")
out = append(out, compose.CarriedPeer{PublicKey: p.PublicKey, Address: address, EnrolledAs: byKey[p.PublicKey], Named: names[p.PublicKey]})
}
sort.SliceStable(out, func(i, j int) bool {
a, errA := netip.ParseAddr(out[i].Address)
b, errB := netip.ParseAddr(out[j].Address)
if errA != nil || errB != nil {
return out[i].Address < out[j].Address
}
return a.Less(b)
})
return out
}
// nodesOf is every node named by a placement or a record, by name: the order the controller reads them in.
func nodesOf(placements map[string]delivery.Placement, records map[string]delivery.NodeRecord) []string {
seen := map[string]bool{}
+11 -9
View File
@@ -67,11 +67,12 @@ func TestInput(t *testing.T) {
keys := map[string]string{"anchor": publicKey(t), "laptop": publicKey(t)}
caps := []string{"container-runtime", "systemd", "wireguard"}
tunnel := &delivery.RecordTunnel{Interface: "wg0", PublicKey: keys["anchor"], Address: "10.77.0.1/24",
Peers: []delivery.RecordTunnelPeer{{PublicKey: "peer", Address: "10.77.0.9/32"}}}
Peers: []delivery.RecordTunnelPeer{{PublicKey: "peer", Address: "10.77.0.9/32"}, {PublicKey: keys["laptop"], Address: "10.77.0.2/32"}}}
mk := func() *bus {
return &bus{
placements: map[string]delivery.Placement{
"anchor": {Node: "anchor", Decided: delivery.NodeDecided{Endpoint: "anchor.example:51820", Site: "here", Hub: true, Address: "10.77.0.1"},
"anchor": {Node: "anchor", Decided: delivery.NodeDecided{Endpoint: "anchor.example:51820", Site: "here", Hub: true, Address: "10.77.0.1",
Tunnel: tunnel, PeerNames: map[string]string{"peer": "phone"}},
Modules: []delivery.PlacedModule{{Module: overlay.Name}},
Holds: []delivery.Holding{{Claim: "mesh-terminal", Scope: "mesh", Module: "xterm"}}},
"laptop": {Node: "laptop", Decided: delivery.NodeDecided{Endpoint: "laptop.example:51820", Site: "here", Address: "10.77.0.2"},
@@ -79,8 +80,7 @@ func TestInput(t *testing.T) {
{Module: "xterm", HasLayer: true, Ports: map[string]int{"80": 8080}}}},
},
records: map[string]delivery.NodeRecord{
"anchor": {Node: "anchor", TunnelKey: keys["anchor"], Capabilities: caps, Tunnel: tunnel, Contract: 2,
Carried: []delivery.RecordCarriedPeer{{PublicKey: "peer", Address: "10.77.0.9", EnrolledAs: "phone"}}},
"anchor": {Node: "anchor", TunnelKey: keys["anchor"], Capabilities: caps, Contract: 2},
"laptop": {Node: "laptop", TunnelKey: keys["laptop"], Capabilities: caps},
},
modules: map[string]delivery.Module{"xterm": {Manifest: digest}, "unread": {}},
@@ -101,7 +101,7 @@ func TestInput(t *testing.T) {
}
capsSet := map[string]bool{"container-runtime": true, "systemd": true, "wireguard": true}
tun := compose.Tunnel{Interface: "wg0", PublicKey: keys["anchor"], Address: "10.77.0.1/24",
Peers: []compose.TunnelPeer{{PublicKey: "peer", Address: "10.77.0.9/32"}}}
Peers: []compose.TunnelPeer{{PublicKey: "peer", Address: "10.77.0.9/32"}, {PublicKey: keys["laptop"], Address: "10.77.0.2/32"}}}
want := compose.Input{
Catalogue: map[string]catalogue.Manifest{overlay.Name: private, "xterm": parsed},
Nodes: []compose.Node{
@@ -112,10 +112,12 @@ func TestInput(t *testing.T) {
Ports: map[string]map[int]int{"xterm": {80: 8080}},
Settings: map[string][]catalogue.Layer{"xterm": {{From: "laptop", Values: map[string]any{"font": "mono"}}}}},
},
Holdings: []catalogue.Held{{Claim: "mesh-terminal", Scope: "mesh", Node: "anchor", Module: "xterm"}},
Tunnels: map[string]compose.FoundTunnel{"anchor": {Tunnel: tun}},
Adopted: &compose.AdoptedTunnel{Tunnel: tun, Hub: "anchor"},
CarriedPeers: []compose.CarriedPeer{{PublicKey: "peer", Address: "10.77.0.9", EnrolledAs: "phone"}},
Holdings: []catalogue.Held{{Claim: "mesh-terminal", Scope: "mesh", Node: "anchor", Module: "xterm"}},
Tunnels: map[string]compose.FoundTunnel{"anchor": {Tunnel: tun}},
Adopted: &compose.AdoptedTunnel{Tunnel: tun, Hub: "anchor", Adopted: true},
// The controller's order is by address; laptop enrolled under its peer's key, and a person named the other.
CarriedPeers: []compose.CarriedPeer{{PublicKey: keys["laptop"], Address: "10.77.0.2", EnrolledAs: "laptop"},
{PublicKey: "peer", Address: "10.77.0.9", Named: "phone"}},
}
b := mk()