package inventory import ( "errors" "strings" "testing" ) // novox/hq ADR 0105: the mesh adopts the predecessor's tunnel in place. The controller reads the // hub's address and range from the adopted tunnel, assigns an enrolling node the address its key // already had, and refuses to hand out an address the tunnel already holds. const ( tunnelKey = "TUNNEL-KEY-the-found-interfaces-public-key=" peerTwo = "PEER-KEY-two=============================" peerThree = "PEER-KEY-three===========================" ) // theFoundTunnel is what a hub presents at enrolment: the predecessor's interface on a // documentation range, with two peers each routed one address. func theFoundTunnel() Tunnel { return Tunnel{ Interface: "wg0", Unit: "wg-quick@wg0", Config: "/etc/wireguard/wg0.conf", Port: 51900, Address: "192.0.2.1/24", Range: "192.0.2.0/24", PublicKey: tunnelKey, Peers: []TunnelPeer{{PublicKey: peerTwo, Address: "192.0.2.2/32"}, {PublicKey: peerThree, Address: "192.0.2.3"}}, } } // anAdoptedHub is an adopted node that enrolled with the found tunnel's key and presented the // tunnel, then was placed as the hub — the order genesis does it in. func anAdoptedHub(t *testing.T, inv *Inventory) Node { t.Helper() hub, err := inv.AddNodeAs(t.Context(), "anchor", true) if err != nil { t.Fatal(err) } if err := inv.RecordOverlayKey(t.Context(), hub.ID, tunnelKey); err != nil { t.Fatal(err) } if err := inv.RecordTunnel(t.Context(), hub.ID, theFoundTunnel()); err != nil { t.Fatal(err) } if err := inv.SetPlace(t.Context(), "anchor", "anchor.example:51900", "hosting", true, ""); err != nil { t.Fatal(err) } return hub } func TestTheHubsAddressAndRangeComeFromTheAdoptedTunnel(t *testing.T) { inv := fresh(t) hub := anAdoptedHub(t, inv) tunnel, name, adopted, err := inv.AdoptedTunnel(t.Context()) if err != nil { t.Fatal(err) } if !adopted || name != "anchor" || tunnel.Range != "192.0.2.0/24" || tunnel.Port != 51900 { t.Fatalf("the adopted tunnel did not read back: adopted=%t on %s, %+v", adopted, name, tunnel) } if len(tunnel.Peers) != 2 || tunnel.Peers[0].Address != "192.0.2.2" || tunnel.Peers[1].Address != "192.0.2.3" { t.Fatalf("the peers did not read back as one host address each: %+v", tunnel.Peers) } // Whatever range the caller would allocate from, the hub is at the tunnel's own address. address, err := inv.AssignAddress(t.Context(), hub.ID, "10.42.0.0/16") if err != nil { t.Fatal(err) } if address != "192.0.2.1" { t.Fatalf("the hub was given %s, not the address the tunnel it took over had", address) } } func TestAnEnrollingNodeKeepsTheAddressTheTunnelHadForItsKey(t *testing.T) { inv := fresh(t) anAdoptedHub(t, inv) // A predecessor machine enrols: its host took its found interface's key as its overlay key, // which is the key the hub's tunnel already routes to. peer, err := inv.AddNodeAs(t.Context(), "home-server", true) if err != nil { t.Fatal(err) } if err := inv.RecordOverlayKey(t.Context(), peer.ID, peerThree); err != nil { t.Fatal(err) } address, err := inv.AssignAddress(t.Context(), peer.ID, "192.0.2.0/24") if err != nil { t.Fatal(err) } if address != "192.0.2.3" { t.Fatalf("the enrolling peer was given %s, not the 192.0.2.3 the tunnel had for its key", address) } carried, err := inv.CarriedPeers(t.Context()) if err != nil { t.Fatal(err) } byKey := map[string]CarriedPeer{} for _, c := range carried { byKey[c.PublicKey] = c } if byKey[peerThree].EnrolledAs != "home-server" || byKey[peerTwo].EnrolledAs != "" { t.Fatalf("the registry cannot say which peer is a node now: %+v", carried) } } func TestAFreshNodeIsNeverGivenAnAddressTheTunnelHolds(t *testing.T) { inv := fresh(t) anAdoptedHub(t, inv) // .1 is the hub, .2 and .3 are peers of the tunnel that have not enrolled: a new machine with // a key of its own gets the next one, from the same range. fresh, err := inv.AddNode(t.Context(), "laptop") if err != nil { t.Fatal(err) } if err := inv.RecordOverlayKey(t.Context(), fresh.ID, "A-KEY-OF-ITS-OWN========================"); err != nil { t.Fatal(err) } address, err := inv.AssignAddress(t.Context(), fresh.ID, "192.0.2.0/24") if err != nil { t.Fatal(err) } if address != "192.0.2.4" { t.Fatalf("a fresh node was given %s; 192.0.2.2 and .3 are the tunnel's peers and .1 its hub", address) } } func TestATunnelUnderAnotherKeyIsNotAdopted(t *testing.T) { // A hub whose overlay key is not the found tunnel's would drop every peer's packets on the // found port (ADR 0105, option 2). Such a tunnel is recorded and not adopted: the mesh keeps // its own range, and ADR 0100's non-overlap rule stands for it. inv := fresh(t) hub, err := inv.AddNodeAs(t.Context(), "anchor", true) if err != nil { t.Fatal(err) } if err := inv.RecordOverlayKey(t.Context(), hub.ID, "THE-MESHS-OWN-KEY======================="); err != nil { t.Fatal(err) } if err := inv.RecordTunnel(t.Context(), hub.ID, theFoundTunnel()); err != nil { t.Fatal(err) } if err := inv.SetPlace(t.Context(), "anchor", "anchor.example:51820", "hosting", true, ""); err != nil { t.Fatal(err) } if _, _, adopted, err := inv.AdoptedTunnel(t.Context()); err != nil || adopted { t.Fatalf("a tunnel under another key was adopted (err %v)", err) } if carried, err := inv.CarriedPeers(t.Context()); err != nil || len(carried) != 0 { t.Fatalf("peers of a tunnel that was not adopted are carried: %+v (err %v)", carried, err) } if address, err := inv.AssignAddress(t.Context(), hub.ID, "10.42.0.0/16"); err != nil || address != "10.42.0.1" { t.Fatalf("the hub was given %s (err %v); it should allocate from the mesh's own range", address, err) } } func TestAPeerRoutedARangeIsNotCarried(t *testing.T) { // A peer routed a whole range is a spoke's view of its hub, never a machine with an address // the mesh could carry: skipped, and the single-address peers beside it kept. inv := fresh(t) hub, err := inv.AddNodeAs(t.Context(), "anchor", true) if err != nil { t.Fatal(err) } found := theFoundTunnel() found.Peers = append(found.Peers, TunnelPeer{PublicKey: "WIDE", Address: "192.0.2.0/24"}) if err := inv.RecordTunnel(t.Context(), hub.ID, found); err != nil { t.Fatal(err) } got, err := inv.TunnelOf(t.Context(), "anchor") if err != nil || len(got.Peers) != 2 { t.Fatalf("the range-routed peer was carried, or the others dropped: %+v %v", got.Peers, err) } // A single address outside the tunnel's range is still refused: it is not a peer this tunnel // routes to. found.Peers = []TunnelPeer{{PublicKey: "ELSEWHERE", Address: "198.51.100.7/32"}} if err := inv.RecordTunnel(t.Context(), hub.ID, found); err == nil || !strings.Contains(err.Error(), "outside") { t.Fatalf("a peer outside the range was recorded: %v", err) } } // A predecessor spoke's tunnel has one peer — the hub — routed the whole range. Its enrolment must // not fail on it: only the hub's peers are ever carried, so a range-routed peer is skipped. func TestASpokesTunnelEnrolsWithItsHubPeerSkipped(t *testing.T) { inv := fresh(t) anAdoptedHub(t, inv) spoke, err := inv.AddNodeAs(t.Context(), "home-server", true) if err != nil { t.Fatal(err) } if err := inv.RecordOverlayKey(t.Context(), spoke.ID, peerThree); err != nil { t.Fatal(err) } if err := inv.RecordTunnel(t.Context(), spoke.ID, Tunnel{ Interface: "wg0", Unit: "wg-quick@wg0", Config: "/etc/wireguard/wg0.conf", Port: 51900, Address: "192.0.2.3/24", Range: "192.0.2.0/24", PublicKey: peerThree, Peers: []TunnelPeer{{PublicKey: tunnelKey, Address: "192.0.2.0/24"}}, }); err != nil { t.Fatalf("a spoke-shaped tunnel was refused: %v", err) } got, err := inv.TunnelOf(t.Context(), "home-server") if err != nil || len(got.Peers) != 0 { t.Fatalf("the spoke's hub was recorded as a peer to carry: %+v %v", got.Peers, err) } // Nothing about the hub's carried peers changed: still two, one now enrolled. carried, err := inv.CarriedPeers(t.Context()) if err != nil || len(carried) != 2 { t.Fatalf("carried peers: %+v %v", carried, err) } if address, err := inv.AssignAddress(t.Context(), spoke.ID, "192.0.2.0/24"); err != nil || address != "192.0.2.3" { t.Fatalf("the spoke did not keep its address: %s %v", address, err) } } // Converging the hub flips its mode and nothing else: the range stays the tunnel's and the peers // stay carried, or the mesh would renumber itself and drop the peers still reaching it. func TestConvergingTheHubKeepsTheRangeAndTheCarriedPeers(t *testing.T) { inv := fresh(t) hub := anAdoptedHub(t, inv) if _, err := inv.AssignAddress(t.Context(), hub.ID, "192.0.2.0/24"); err != nil { t.Fatal(err) } if _, err := inv.Converge(t.Context(), "anchor"); err != nil { t.Fatal(err) } tunnel, _, adopted, err := inv.AdoptedTunnel(t.Context()) if err != nil || !adopted || tunnel.Range != "192.0.2.0/24" { t.Fatalf("converging renumbered the mesh: adopted=%t %+v %v", adopted, tunnel, err) } if carried, err := inv.CarriedPeers(t.Context()); err != nil || len(carried) != 2 { t.Fatalf("converging dropped the carried peers: %+v %v", carried, err) } found, err := inv.Tunnels(t.Context()) if err != nil || found["anchor"].NodeAdopted { t.Fatalf("a converged hub still reads as adopted for the takeover: %+v %v", found, err) } } // A hub that enrolled with a key of its own takes the tunnel over afterwards by rekeying: the key // and the tunnel are recorded, the hub moves to the tunnel's address, and the same rekey applied // again is stale. func TestARekeyTakesTheTunnelOverAfterEnrolment(t *testing.T) { inv := fresh(t) hub, err := inv.AddNodeAs(t.Context(), "anchor", true) if err != nil { t.Fatal(err) } const own = "THE-MESHS-OWN-KEY=======================" if err := inv.RecordOverlayKey(t.Context(), hub.ID, own); err != nil { t.Fatal(err) } if err := inv.SetPlace(t.Context(), "anchor", "anchor.example:51900", "hosting", true, ""); err != nil { t.Fatal(err) } if address, err := inv.AssignAddress(t.Context(), hub.ID, "10.42.0.0/16"); err != nil || address != "10.42.0.1" { t.Fatalf("before the rekey the hub is on the mesh's own range: %s %v", address, err) } if err := inv.Rekey(t.Context(), hub.ID, own, tunnelKey, theFoundTunnel()); err != nil { t.Fatal(err) } _, _, adopted, err := inv.AdoptedTunnel(t.Context()) if err != nil || !adopted { t.Fatalf("the tunnel is not adopted after the rekey (%v)", err) } placed, err := inv.Overlays(t.Context()) if err != nil || len(placed) != 1 || placed[0].Address != "192.0.2.1" || placed[0].Key != tunnelKey { t.Fatalf("the hub did not move to the tunnel's address under the tunnel's key: %+v %v", placed, err) } if err := inv.Rekey(t.Context(), hub.ID, own, tunnelKey, theFoundTunnel()); !errors.Is(err, ErrStaleRekey) { t.Fatalf("the same rekey applied again was not refused as stale: %v", err) } if err := inv.Rekey(t.Context(), hub.ID, tunnelKey, "ANOTHER-KEY=============================", theFoundTunnel()); err == nil { t.Fatal("a rekey to a key that is not the tunnel's was accepted") } }