diff --git a/clients/clients.go b/clients/clients.go index d9916b4..73f6549 100644 --- a/clients/clients.go +++ b/clients/clients.go @@ -832,16 +832,11 @@ func (s *WireGuardService) syncTargets(desiredTargets []Target) error { func (s *WireGuardService) ensureWireguardInterface(wgconfig WgConfig) error { s.mu.Lock() - // split off the cidr from the IP address - parts := strings.Split(wgconfig.IpAddress, "/") - if len(parts) != 2 { + interfaceAddress, tunnelIP, err := normalizeInterfaceAddress(wgconfig.IpAddress) + if err != nil { s.mu.Unlock() - return fmt.Errorf("invalid IP address format: %s", wgconfig.IpAddress) + return err } - // Parse the IP address and CIDR mask - tunnelIP := netip.MustParseAddr(parts[0]) - - var err error if s.useNativeInterface { // Create native TUN device @@ -917,7 +912,7 @@ func (s *WireGuardService) ensureWireguardInterface(wgconfig WgConfig) error { } // Configure the network interface with IP address - if err := network.ConfigureInterface(interfaceName, wgconfig.IpAddress, s.mtu); err != nil { + if err := network.ConfigureInterface(interfaceName, interfaceAddress, s.mtu); err != nil { s.mu.Unlock() return fmt.Errorf("failed to configure interface: %v", err) } diff --git a/clients/ip.go b/clients/ip.go new file mode 100644 index 0000000..2f30f0a --- /dev/null +++ b/clients/ip.go @@ -0,0 +1,23 @@ +package clients + +import ( + "fmt" + "net/netip" +) + +// normalizeInterfaceAddress accepts the address shape emitted by Pangolin's +// site configuration. Older Pangolin versions can send a bare host address; +// the client WireGuard interface needs an address/prefix pair, so treat a +// bare IPv4 address as /32 and a bare IPv6 address as /128. +func normalizeInterfaceAddress(raw string) (string, netip.Addr, error) { + if prefix, err := netip.ParsePrefix(raw); err == nil { + return prefix.String(), prefix.Addr(), nil + } + + addr, err := netip.ParseAddr(raw) + if err != nil { + return "", netip.Addr{}, fmt.Errorf("invalid IP address format: %s", raw) + } + + return netip.PrefixFrom(addr, addr.BitLen()).String(), addr, nil +} diff --git a/clients/ip_test.go b/clients/ip_test.go new file mode 100644 index 0000000..1478d16 --- /dev/null +++ b/clients/ip_test.go @@ -0,0 +1,34 @@ +package clients + +import "testing" + +func TestNormalizeInterfaceAddress(t *testing.T) { + tests := []struct { + name string + input string + wantCIDR string + wantIP string + wantErr bool + }{ + {name: "bare IPv4", input: "100.90.128.4", wantCIDR: "100.90.128.4/32", wantIP: "100.90.128.4"}, + {name: "bare IPv6", input: "2001:db8::4", wantCIDR: "2001:db8::4/128", wantIP: "2001:db8::4"}, + {name: "IPv4 prefix", input: "100.90.128.4/24", wantCIDR: "100.90.128.4/24", wantIP: "100.90.128.4"}, + {name: "IPv6 prefix", input: "2001:db8::4/64", wantCIDR: "2001:db8::4/64", wantIP: "2001:db8::4"}, + {name: "invalid", input: "100.90.128.4/33", wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotCIDR, gotIP, err := normalizeInterfaceAddress(tt.input) + if (err != nil) != tt.wantErr { + t.Fatalf("normalizeInterfaceAddress(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) + } + if tt.wantErr { + return + } + if gotCIDR != tt.wantCIDR || gotIP.String() != tt.wantIP { + t.Fatalf("normalizeInterfaceAddress(%q) = (%q, %q), want (%q, %q)", tt.input, gotCIDR, gotIP, tt.wantCIDR, tt.wantIP) + } + }) + } +}