diff --git a/peers/manager.go b/peers/manager.go index 71c0507..20ec25d 100644 --- a/peers/manager.go +++ b/peers/manager.go @@ -85,6 +85,25 @@ const ( routeSwitchCooldown = 30 * time.Second ) +// normalizeServerRouteDestination converts the bare IPv4 address returned by +// Pangolin's site configuration into the host CIDR expected by Newt's route +// helpers. Pangolin 1.21 returns values such as "100.90.128.4", while the +// Darwin route and NetworkSettings implementations require "100.90.128.4/32". +// Preserve already-CIDR values and invalid values so the downstream helper can +// report its normal validation error. +func normalizeServerRouteDestination(serverIP string) string { + if strings.Contains(serverIP, "/") { + return serverIP + } + + ip := net.ParseIP(serverIP) + if ip == nil || ip.To4() == nil { + return serverIP + } + + return ip.To4().String() + "/32" +} + // NewPeerManager creates a new PeerManager with an internal PeerMonitor func NewPeerManager(config PeerManagerConfig) *PeerManager { pm := &PeerManager{ @@ -221,7 +240,8 @@ func (pm *PeerManager) AddPeer(siteConfig SiteConfig) error { return err } - if err := network.AddRouteForServerIPWithSource(siteConfig.ServerIP, pm.interfaceName, pm.localIP); err != nil { + serverRouteDestination := normalizeServerRouteDestination(siteConfig.ServerIP) + if err := network.AddRouteForServerIPWithSource(serverRouteDestination, pm.interfaceName, pm.localIP); err != nil { logger.Error("Failed to add route for server IP: %v", err) } if err := network.AddRoutesWithSource(siteConfig.RemoteSubnets, pm.interfaceName, pm.localIP); err != nil { @@ -285,7 +305,8 @@ func (pm *PeerManager) RemovePeer(siteId int) error { return err } - if err := network.RemoveRouteForServerIPWithSource(peer.ServerIP, pm.interfaceName, pm.localIP); err != nil { + serverRouteDestination := normalizeServerRouteDestination(peer.ServerIP) + if err := network.RemoveRouteForServerIPWithSource(serverRouteDestination, pm.interfaceName, pm.localIP); err != nil { logger.Error("Failed to remove route for server IP: %v", err) } diff --git a/peers/route_test.go b/peers/route_test.go new file mode 100644 index 0000000..546cdc4 --- /dev/null +++ b/peers/route_test.go @@ -0,0 +1,45 @@ +package peers + +import "testing" + +func TestNormalizeServerRouteDestination(t *testing.T) { + tests := []struct { + name string + serverIP string + want string + }{ + { + name: "bare IPv4 address", + serverIP: "100.90.128.4", + want: "100.90.128.4/32", + }, + { + name: "IPv4 host CIDR", + serverIP: "100.90.128.4/32", + want: "100.90.128.4/32", + }, + { + name: "other IPv4 CIDR", + serverIP: "192.0.2.1/24", + want: "192.0.2.1/24", + }, + { + name: "IPv6 address is preserved", + serverIP: "2001:db8::4", + want: "2001:db8::4", + }, + { + name: "invalid address is preserved", + serverIP: "not-an-ip", + want: "not-an-ip", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := normalizeServerRouteDestination(tt.serverIP); got != tt.want { + t.Fatalf("normalizeServerRouteDestination(%q) = %q, want %q", tt.serverIP, got, tt.want) + } + }) + } +}