diff --git a/internal/configgen/lighthouse_peers_test.go b/internal/configgen/lighthouse_peers_test.go new file mode 100644 index 0000000..d7feea8 --- /dev/null +++ b/internal/configgen/lighthouse_peers_test.go @@ -0,0 +1,50 @@ +package configgen + +import ( + "testing" + + "gopkg.in/yaml.v3" +) + +func TestGenerate_LighthousesCanReachEachOther(t *testing.T) { + lighthouses := []LighthouseInfo{ + {NebulaIPs: []string{"192.168.100.1", "fd00::1"}, PublicAddr: "203.0.113.10:4242"}, + {NebulaIPs: []string{"192.168.100.2", "fd00::2"}, PublicAddr: "203.0.113.20:4242"}, + } + for _, own := range lighthouses { + t.Run(own.NebulaIPs[0], func(t *testing.T) { + data, err := Generate(GeneratorInput{ + NebulaIPs: own.NebulaIPs, IsLighthouse: true, Lighthouses: lighthouses, + }) + if err != nil { + t.Fatal(err) + } + var parsed struct { + StaticHostMap map[string][]string `yaml:"static_host_map"` + Lighthouse struct { + AmLighthouse bool `yaml:"am_lighthouse"` + Hosts []string `yaml:"hosts"` + } `yaml:"lighthouse"` + } + if err := yaml.Unmarshal(data, &parsed); err != nil { + t.Fatal(err) + } + if !parsed.Lighthouse.AmLighthouse || len(parsed.Lighthouse.Hosts) != 0 { + t.Fatalf("lighthouses must remain independent: %+v", parsed.Lighthouse) + } + for _, peer := range lighthouses { + for _, ip := range peer.NebulaIPs { + if peer.PublicAddr == own.PublicAddr { + if _, ok := parsed.StaticHostMap[ip]; ok { + t.Errorf("lighthouse advertises itself as a remote: %s", ip) + } + continue + } + if got := parsed.StaticHostMap[ip]; len(got) != 1 || got[0] != peer.PublicAddr { + t.Errorf("cannot locate peer %s: got %v, want [%s]", ip, got, peer.PublicAddr) + } + } + } + }) + } +} diff --git a/internal/configgen/marshal.go b/internal/configgen/marshal.go index 176b297..38678a5 100644 --- a/internal/configgen/marshal.go +++ b/internal/configgen/marshal.go @@ -3,6 +3,7 @@ package configgen import ( "bytes" "fmt" + "slices" "strings" "unicode/utf8" @@ -270,17 +271,19 @@ func buildConfig(input GeneratorInput) nebulaConfig { } cfg.StaticHostMap = map[safeString][]safeString{} - if input.IsLighthouse { - cfg.Lighthouse = lighthouseSection{AmLighthouse: true} - } else { - var hosts []safeString - for _, lh := range input.Lighthouses { - for _, ip := range lh.NebulaIPs { - cfg.StaticHostMap[safeString(ip)] = []safeString{safeString(lh.PublicAddr)} - hosts = append(hosts, safeString(ip)) + cfg.Lighthouse = lighthouseSection{AmLighthouse: input.IsLighthouse} + // Lighthouses do not query one another, but still need static endpoints + // to establish tunnels to their peers, including for relayed traffic. + for _, lh := range input.Lighthouses { + for _, ip := range lh.NebulaIPs { + if input.IsLighthouse && slices.Contains(input.NebulaIPs, ip) { + continue + } + cfg.StaticHostMap[safeString(ip)] = []safeString{safeString(lh.PublicAddr)} + if !input.IsLighthouse { + cfg.Lighthouse.Hosts = append(cfg.Lighthouse.Hosts, safeString(ip)) } } - cfg.Lighthouse = lighthouseSection{AmLighthouse: false, Hosts: hosts} } listenHost := input.ListenHost