From 1c0c7e27517e1adf0d8353cb7896e8b4d95bd6a1 Mon Sep 17 00:00:00 2001 From: PizzaLovingNerd Date: Thu, 9 Jul 2026 20:01:01 -0700 Subject: [PATCH 1/4] Additional relay support. --- combined/cmd/config.go | 15 ++++-- combined/cmd/config_test.go | 100 +++++++++++++++++++++++++++++++++++ combined/config.yaml.example | 9 +++- 3 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 combined/cmd/config_test.go diff --git a/combined/cmd/config.go b/combined/cmd/config.go index fe350e52a21..591e351d356 100644 --- a/combined/cmd/config.go +++ b/combined/cmd/config.go @@ -29,7 +29,8 @@ import ( // Architecture: // - Management: Always runs locally (this IS the management server) // - Signal: Runs locally by default; disabled if server.signalUri is set -// - Relay: Runs locally by default; disabled if server.relays is set +// - Relay: Runs locally by default; server.additionalRelays adds more relay +// addresses, while server.relays disables the local relay and replaces them // - STUN: Runs locally on port 3478 by default; disabled if server.stuns is set // // All user-facing settings are under "server". The relay/signal/management @@ -59,6 +60,9 @@ type ServerConfig struct { AuthSecret string `yaml:"authSecret"` // Shared secret for relay authentication DataDir string `yaml:"dataDir"` // Data directory for all services + // Additional relay addresses advertised alongside the local relay + AdditionalRelays []string `yaml:"additionalRelays"` + // External service overrides (simplified mode) // When these are set, the corresponding local service is NOT started // and these values are used for client configuration instead @@ -273,6 +277,8 @@ func parseExposedAddress(exposedAddress string) (protocol, hostname, hostPort st // ApplySimplifiedDefaults populates internal relay/signal/management configs from server settings. // Management is always enabled. Signal, Relay, and STUN are enabled unless external // overrides are configured (server.signalUri, server.relays, server.stuns). +// server.additionalRelays does not override the local relay; its addresses are +// advertised to clients in addition to the auto-configured local relay address. func (c *CombinedConfig) ApplySimplifiedDefaults() { if !c.hasRequiredSettings() { return @@ -395,10 +401,9 @@ func (c *CombinedConfig) autoConfigureClientSettings(exposedProto, exposedHost, // Use external relay config from server c.Management.Relays = c.Server.Relays } else if len(c.Management.Relays.Addresses) == 0 { - // Auto-configure local relay - c.Management.Relays.Addresses = []string{ - fmt.Sprintf("%s://%s", relayProto, exposedHostPort), - } + // Auto-configure the local relay, then advertise any additional relays. + localRelay := fmt.Sprintf("%s://%s", relayProto, exposedHostPort) + c.Management.Relays.Addresses = append([]string{localRelay}, c.Server.AdditionalRelays...) } if c.Management.Relays.Secret == "" { c.Management.Relays.Secret = c.Server.AuthSecret diff --git a/combined/cmd/config_test.go b/combined/cmd/config_test.go new file mode 100644 index 00000000000..5a24edeb202 --- /dev/null +++ b/combined/cmd/config_test.go @@ -0,0 +1,100 @@ +package cmd + +import ( + "os" + "path/filepath" + "reflect" + "testing" +) + +func TestApplySimplifiedDefaultsWithAdditionalRelays(t *testing.T) { + cfg := DefaultConfig() + cfg.Server.ExposedAddress = "https://netbird.example.com:443" + cfg.Server.AuthSecret = "shared-relay-secret" + cfg.Server.AdditionalRelays = []string{ + "rels://relay-eu.example.com:443", + "rels://relay-us.example.com:443", + } + + cfg.ApplySimplifiedDefaults() + + if !cfg.Relay.Enabled { + t.Fatal("local relay should remain enabled when additional relays are configured") + } + if !cfg.Relay.Stun.Enabled { + t.Fatal("local STUN should remain enabled when additional relays are configured") + } + + wantAddresses := []string{ + "rels://netbird.example.com:443", + "rels://relay-eu.example.com:443", + "rels://relay-us.example.com:443", + } + if !reflect.DeepEqual(cfg.Management.Relays.Addresses, wantAddresses) { + t.Fatalf("relay addresses = %v, want %v", cfg.Management.Relays.Addresses, wantAddresses) + } + if cfg.Management.Relays.Secret != cfg.Server.AuthSecret { + t.Fatalf("relay secret = %q, want server auth secret", cfg.Management.Relays.Secret) + } +} + +func TestApplySimplifiedDefaultsWithRelayOverride(t *testing.T) { + cfg := DefaultConfig() + cfg.Server.ExposedAddress = "https://netbird.example.com:443" + cfg.Server.Relays = RelaysConfig{ + Addresses: []string{"rels://relay.example.com:443"}, + CredentialsTTL: "24h", + Secret: "external-relay-secret", + } + + cfg.ApplySimplifiedDefaults() + + if cfg.Relay.Enabled { + t.Fatal("local relay should remain disabled when server.relays overrides it") + } + if cfg.Relay.Stun.Enabled { + t.Fatal("local STUN should remain disabled with the relay override") + } + if !reflect.DeepEqual(cfg.Management.Relays, cfg.Server.Relays) { + t.Fatalf("management relay config = %+v, want %+v", cfg.Management.Relays, cfg.Server.Relays) + } +} + +func TestAdditionalRelaysStillRequireLocalRelaySecret(t *testing.T) { + cfg := DefaultConfig() + cfg.Server.ExposedAddress = "https://netbird.example.com:443" + cfg.Server.AuthSecret = "" + cfg.Server.AdditionalRelays = []string{"rels://relay.example.com:443"} + + if err := cfg.Validate(); err == nil { + t.Fatal("Validate() should require server.authSecret because the local relay is enabled") + } +} + +func TestLoadConfigParsesAdditionalRelays(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config.yaml") + configData := []byte(`server: + exposedAddress: "https://netbird.example.com:443" + authSecret: "shared-relay-secret" + additionalRelays: + - "rels://relay-eu.example.com:443" + - "rels://relay-us.example.com:443" +`) + if err := os.WriteFile(configPath, configData, 0o600); err != nil { + t.Fatalf("write test config: %v", err) + } + + cfg, err := LoadConfig(configPath) + if err != nil { + t.Fatalf("LoadConfig() error = %v", err) + } + + wantAddresses := []string{ + "rels://netbird.example.com:443", + "rels://relay-eu.example.com:443", + "rels://relay-us.example.com:443", + } + if !reflect.DeepEqual(cfg.Management.Relays.Addresses, wantAddresses) { + t.Fatalf("relay addresses = %v, want %v", cfg.Management.Relays.Addresses, wantAddresses) + } +} diff --git a/combined/config.yaml.example b/combined/config.yaml.example index 66bc7170304..c170558df00 100644 --- a/combined/config.yaml.example +++ b/combined/config.yaml.example @@ -8,7 +8,8 @@ # Architecture: # - Management: Always runs locally (this IS the management server) # - Signal: Local by default; set 'signalUri' to use external (disables local) -# - Relay: Local by default; set 'relays' to use external (disables local) +# - Relay: Local by default; set 'additionalRelays' to advertise more relays, +# or set 'relays' to replace the local relay with external relays # - STUN: Local on port 3478 by default; set 'stuns' to use external instead server: @@ -51,6 +52,12 @@ server: # Data directory for all services dataDir: "/var/lib/netbird/" + # Additional relay servers - keeps the local relay and advertises these too. + # The additional relay servers must use the same authSecret as this server. + # additionalRelays: + # - "rels://relay-eu.example.com:443" + # - "rels://relay-us.example.com:443" + # ============================================================================ # External Service Overrides (optional) # Use these to point to external Signal, Relay, or STUN servers instead of From 0e4684373b343d0814ced622337d8824ab99f797 Mon Sep 17 00:00:00 2001 From: PizzaLovingNerd Date: Fri, 10 Jul 2026 16:09:40 -0700 Subject: [PATCH 2/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- combined/config.yaml.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/combined/config.yaml.example b/combined/config.yaml.example index c170558df00..7f32da2192a 100644 --- a/combined/config.yaml.example +++ b/combined/config.yaml.example @@ -52,7 +52,7 @@ server: # Data directory for all services dataDir: "/var/lib/netbird/" - # Additional relay servers - keeps the local relay and advertises these too. + # Additional relay servers - keeps the local relay and advertises these too (ignored if relays.addresses is set). # The additional relay servers must use the same authSecret as this server. # additionalRelays: # - "rels://relay-eu.example.com:443" From 3a902b94b80e5d1f85b96fd5fa6df9037f7bba65 Mon Sep 17 00:00:00 2001 From: PizzaLovingNerd Date: Fri, 10 Jul 2026 17:51:02 -0700 Subject: [PATCH 3/4] Added coderabbit suggested test --- combined/cmd/config_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/combined/cmd/config_test.go b/combined/cmd/config_test.go index 5a24edeb202..3854e000ac7 100644 --- a/combined/cmd/config_test.go +++ b/combined/cmd/config_test.go @@ -60,6 +60,23 @@ func TestApplySimplifiedDefaultsWithRelayOverride(t *testing.T) { } } +func TestApplySimplifiedDefaultsWithRelayOverrideIgnoresAdditionalRelays(t *testing.T) { + cfg := DefaultConfig() + cfg.Server.ExposedAddress = "https://netbird.example.com:443" + cfg.Server.Relays = RelaysConfig{ + Addresses: []string{"rels://relay.example.com:443"}, + } + cfg.Server.AdditionalRelays = []string{ + "rels://additional-relay.example.com:443", + } + + cfg.ApplySimplifiedDefaults() + + if !reflect.DeepEqual(cfg.Management.Relays.Addresses, cfg.Server.Relays.Addresses) { + t.Fatalf("management relay addresses = %v, want %v", cfg.Management.Relays.Addresses, cfg.Server.Relays.Addresses) + } +} + func TestAdditionalRelaysStillRequireLocalRelaySecret(t *testing.T) { cfg := DefaultConfig() cfg.Server.ExposedAddress = "https://netbird.example.com:443" From ae33fc258958da60e5cacf980dfd78811c0c053e Mon Sep 17 00:00:00 2001 From: PizzaLovingNerd Date: Mon, 13 Jul 2026 19:46:28 -0700 Subject: [PATCH 4/4] switched to assert.ElementsMatch --- combined/cmd/config_test.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/combined/cmd/config_test.go b/combined/cmd/config_test.go index 3854e000ac7..6c01b1e2da4 100644 --- a/combined/cmd/config_test.go +++ b/combined/cmd/config_test.go @@ -5,6 +5,8 @@ import ( "path/filepath" "reflect" "testing" + + "github.com/stretchr/testify/assert" ) func TestApplySimplifiedDefaultsWithAdditionalRelays(t *testing.T) { @@ -30,9 +32,7 @@ func TestApplySimplifiedDefaultsWithAdditionalRelays(t *testing.T) { "rels://relay-eu.example.com:443", "rels://relay-us.example.com:443", } - if !reflect.DeepEqual(cfg.Management.Relays.Addresses, wantAddresses) { - t.Fatalf("relay addresses = %v, want %v", cfg.Management.Relays.Addresses, wantAddresses) - } + assert.ElementsMatch(t, wantAddresses, cfg.Management.Relays.Addresses) if cfg.Management.Relays.Secret != cfg.Server.AuthSecret { t.Fatalf("relay secret = %q, want server auth secret", cfg.Management.Relays.Secret) } @@ -72,9 +72,7 @@ func TestApplySimplifiedDefaultsWithRelayOverrideIgnoresAdditionalRelays(t *test cfg.ApplySimplifiedDefaults() - if !reflect.DeepEqual(cfg.Management.Relays.Addresses, cfg.Server.Relays.Addresses) { - t.Fatalf("management relay addresses = %v, want %v", cfg.Management.Relays.Addresses, cfg.Server.Relays.Addresses) - } + assert.ElementsMatch(t, cfg.Server.Relays.Addresses, cfg.Management.Relays.Addresses) } func TestAdditionalRelaysStillRequireLocalRelaySecret(t *testing.T) { @@ -111,7 +109,5 @@ func TestLoadConfigParsesAdditionalRelays(t *testing.T) { "rels://relay-eu.example.com:443", "rels://relay-us.example.com:443", } - if !reflect.DeepEqual(cfg.Management.Relays.Addresses, wantAddresses) { - t.Fatalf("relay addresses = %v, want %v", cfg.Management.Relays.Addresses, wantAddresses) - } + assert.ElementsMatch(t, wantAddresses, cfg.Management.Relays.Addresses) }