package overlay import ( "errors" "strings" "testing" ) const cidr = "10.42.0.0/16" func at(name, site, address, endpoint string, hub bool) Node { return Node{Name: name, Key: "key-" + name, Site: site, Address: address, Endpoint: endpoint, Hub: hub} } func peersOf(t *testing.T, g Graph, node string) map[string]Peer { t.Helper() out := map[string]Peer{} for _, p := range g[node] { out[p.Name] = p } return out } func TestEveryNodeReachesTheHub(t *testing.T) { // The property that makes this a network at all. Without it a node has no route to anything // it does not share a site with. g, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "198.51.100.1:51820", true), at("laptop", "", "10.42.0.2", "", false), at("home", "house", "10.42.0.3", "", false), }, cidr) if err != nil { t.Fatal(err) } for _, node := range []string{"laptop", "home"} { hub, ok := peersOf(t, g, node)["anchor"] if !ok { t.Fatalf("%s has no route to the hub", node) } if hub.Allowed != cidr { t.Errorf("%s routes %s through the hub; it must be the whole overlay or the hub is "+ "not a route of last resort", node, hub.Allowed) } } } func TestNodesAtOneSitePeerDirectly(t *testing.T) { // Machines that share a site have a path that does not need the hub, and using it keeps their // traffic off a link that may be on another continent. g, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "198.51.100.1:51820", true), at("desk", "house", "10.42.0.2", "192.0.2.2:51820", false), at("server", "house", "10.42.0.3", "192.0.2.3:51820", false), }, cidr) if err != nil { t.Fatal(err) } direct, ok := peersOf(t, g, "desk")["server"] if !ok { t.Fatal("two machines at one site do not peer directly") } if direct.Allowed != "10.42.0.3/32" { t.Errorf("the direct peer allows %s; a single address is what makes it win on "+ "specificity over the hub's whole-overlay route", direct.Allowed) } } func TestARoamingNodeGetsExactlyOnePath(t *testing.T) { // Hub-only, and not as a simplification. WireGuard has no failover: a more specific route to // a dead endpoint blackholes rather than falling back, so two paths would mean one of them // silently swallowing traffic. g, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "198.51.100.1:51820", true), at("laptop", "", "10.42.0.2", "", false), at("other", "", "10.42.0.3", "", false), }, cidr) if err != nil { t.Fatal(err) } if got := len(g["laptop"]); got != 1 { t.Fatalf("a roaming node has %d peers; it gets exactly one path", got) } if g["laptop"][0].Name != "anchor" { t.Errorf("a roaming node's single path is %s, not the hub", g["laptop"][0].Name) } } func TestTwoRoamingNodesDoNotPeerWithEachOther(t *testing.T) { // An empty site is not a site. Nodes that roam have no shared location, and treating "" as // one would have every roaming machine try to dial every other, none of which can be dialled. g, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "198.51.100.1:51820", true), at("laptop", "", "10.42.0.2", "", false), at("phone", "", "10.42.0.3", "", false), }, cidr) if err != nil { t.Fatal(err) } if _, ok := peersOf(t, g, "laptop")["phone"]; ok { t.Error("two nodes with no site peered as though they shared one") } } func TestOnlyTheSideThatCannotBeDialledKeepsThePathOpen(t *testing.T) { // Keepalive matters exactly once: on the node behind NAT. Without it the peer's first packet // arrives at a mapping that has already expired. On the reachable side it is pointless // traffic for ever. g, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "198.51.100.1:51820", true), at("laptop", "", "10.42.0.2", "", false), }, cidr) if err != nil { t.Fatal(err) } if !peersOf(t, g, "laptop")["anchor"].Keepalive { t.Error("a node that cannot be dialled does not keep its path open") } if peersOf(t, g, "anchor")["laptop"].Keepalive { t.Error("a reachable node sends keepalives it does not need") } // And between two direct peers at one site, where whether to keep the path open depends on // which end you are. Checked here because the hub's own peer list happens not to set the // field at all, so asserting only against the hub tests an absence rather than the rule. same, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "198.51.100.1:51820", true), at("desk", "house", "10.42.0.2", "192.0.2.2:51820", false), at("server", "house", "10.42.0.3", "", false), }, cidr) if err != nil { t.Fatal(err) } if !peersOf(t, same, "server")["desk"].Keepalive { t.Error("the peer that cannot be dialled does not keep the path open") } if peersOf(t, same, "desk")["server"].Keepalive { t.Error("the peer that can be dialled sends keepalives it does not need") } } func TestTheHubKnowsEveryoneItRoutesFor(t *testing.T) { // The replies have to get back. A hub that does not hold a peer cannot answer it. g, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "198.51.100.1:51820", true), at("laptop", "", "10.42.0.2", "", false), at("home", "house", "10.42.0.3", "", false), }, cidr) if err != nil { t.Fatal(err) } hub := peersOf(t, g, "anchor") for _, want := range []string{"laptop", "home"} { if _, ok := hub[want]; !ok { t.Errorf("the hub does not hold %s, so replies to it have nowhere to go", want) } } } func TestAMeshWithNoHubIsRefused(t *testing.T) { // Not an empty graph. A mesh with no hub has no path between sites, and answering "no peers" // would look like a working network in which nothing can reach anything — which is how the // old arrangement failed, silently, when nobody knew the address convention. _, err := Compute([]Node{ at("a", "", "10.42.0.1", "", false), at("b", "", "10.42.0.2", "", false), }, cidr) if !errors.Is(err, ErrNoHub) { t.Fatalf("a mesh with no hub gave %v", err) } } func TestAnUnreachableHubIsRefused(t *testing.T) { // The hub is the one node that must be dialable from wherever the others are. Left // unchecked, every node would be given a peer it can never reach and the mesh would look // configured and be silent. _, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "", true), at("laptop", "", "10.42.0.2", "", false), }, cidr) if err == nil { t.Fatal("a hub with no endpoint was accepted") } if !strings.Contains(err.Error(), "must be reachable") { t.Errorf("the refusal does not say why: %v", err) } } func TestANodeWithNoPlaceYetIsSkippedRatherThanFatal(t *testing.T) { // A node that has enrolled and not yet been given an address is an ordinary in-between state. // Failing the whole graph over it would mean no node gets a network because one is half done. g, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "198.51.100.1:51820", true), at("laptop", "", "10.42.0.2", "", false), {Name: "newcomer", Key: "", Address: ""}, }, cidr) if err != nil { t.Fatal(err) } if _, ok := g["newcomer"]; ok { t.Error("a node with no key or address was given a peer list") } if _, ok := peersOf(t, g, "laptop")["newcomer"]; ok { t.Error("a node with no key was put in somebody's peer list") } } func TestNobodyPeersWithThemselves(t *testing.T) { g, err := Compute([]Node{ at("anchor", "house", "10.42.0.1", "198.51.100.1:51820", true), at("desk", "house", "10.42.0.2", "192.0.2.2:51820", false), }, cidr) if err != nil { t.Fatal(err) } for node, peers := range g { for _, p := range peers { if p.Name == node { t.Errorf("%s peers with itself", node) } } } } func TestAHubAtYourOwnSiteAppearsOnceNotTwice(t *testing.T) { // WireGuard takes one entry per public key. A hub that shares a site with a spoke was being // emitted twice — once as a direct peer and once as the route of last resort — producing a // configuration the interface refuses, from a mesh that thought it had succeeded. // // Found in the lab on the first two machines that shared a site with their hub, which is the // ordinary case for a small mesh and was in none of the tests above. g, err := Compute([]Node{ at("anchor", "lab", "10.42.0.1", "192.0.2.10:51820", true), at("laptop", "lab", "10.42.0.2", "", false), }, cidr) if err != nil { t.Fatal(err) } seen := map[string]int{} for _, p := range g["laptop"] { seen[p.Key]++ } for key, count := range seen { if count > 1 { t.Errorf("the key %s appears %d times; WireGuard takes one entry per key", key, count) } } if len(g["laptop"]) != 1 { t.Fatalf("laptop has %d peers; the hub at its own site is one peer, not two", len(g["laptop"])) } // And that single entry has to carry everything, or the node keeps a direct path to the hub // and loses its route to everywhere else. if got := g["laptop"][0].Allowed; got != cidr { t.Errorf("the single entry allows %s; it is both the direct path and the route of last "+ "resort, so it carries the whole overlay", got) } } func TestTwoUnreachableNodesAtOneSiteDoNotPeerDirectly(t *testing.T) { // Nobody would open the path, and the direct route is more specific than the hub's — so it // wins and blackholes. Both nodes would reach the hub perfectly and be unable to reach each // other, which is the worst arrangement available: it looks configured and is not. // // This is the design's own warning arriving in its implementation, and the lab found it with // two machines at one site behind no reachable address — the ordinary shape of a house. g, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "198.51.100.1:51820", true), at("desk", "house", "10.42.0.2", "", false), at("server", "house", "10.42.0.3", "", false), }, cidr) if err != nil { t.Fatal(err) } if _, ok := peersOf(t, g, "desk")["server"]; ok { t.Error("two nodes that neither can be dialled were peered directly; neither could open " + "the path and the route is more specific than the hub's, so it blackholes") } // And they must still be able to reach each other — through the hub. if hub := peersOf(t, g, "desk")["anchor"]; hub.Allowed != cidr { t.Errorf("desk's route to everything else allows %s", hub.Allowed) } } func TestOneReachableNodeIsEnoughToPeerDirectly(t *testing.T) { // The other side of it: if either end can be dialled, the path can be opened, and using it // keeps their traffic off a hub that may be on another continent. g, err := Compute([]Node{ at("anchor", "datacentre", "10.42.0.1", "198.51.100.1:51820", true), at("desk", "house", "10.42.0.2", "192.0.2.2:51820", false), at("server", "house", "10.42.0.3", "", false), }, cidr) if err != nil { t.Fatal(err) } if _, ok := peersOf(t, g, "server")["desk"]; !ok { t.Error("a node did not peer with a reachable neighbour at its own site") } if _, ok := peersOf(t, g, "desk")["server"]; !ok { t.Error("the reachable node did not hold its unreachable neighbour, so replies have " + "nowhere to go") } }