diff --git a/olm/connect.go b/olm/connect.go index 38f0483..9e2a77a 100644 --- a/olm/connect.go +++ b/olm/connect.go @@ -176,7 +176,7 @@ func (o *Olm) handleConnect(msg websocket.WSMessage) { logger.Error("Failed to o.tunnelConfigure interface: %v", err) } - if err := network.AddRoutes([]string{wgData.UtilitySubnet}, o.tunnelConfig.InterfaceName); err != nil { // also route the utility subnet + if err := network.AddRoutesWithSource([]string{wgData.UtilitySubnet}, o.tunnelConfig.InterfaceName, interfaceIP); err != nil { // also route the utility subnet logger.Error("Failed to add route for utility subnet: %v", err) } diff --git a/olm/exitnode.go b/olm/exitnode.go index fb0699a..c11620c 100644 --- a/olm/exitnode.go +++ b/olm/exitnode.go @@ -106,7 +106,12 @@ persistent_keepalive_interval=%d`, util.FixKey(cfg.PublicKey), allowedIP, resolv // its corresponding NetworkSettings entry, which is what surfaces it via the // API) is silently never added. serverIPForRoute := strings.Split(cfg.ServerIP, "/")[0] + "/32" - if err := network.AddRouteForServerIP(serverIPForRoute, interfaceName); err != nil { + // The route must also be pinned to our exit node tunnel address as its source + // (darwin route(8) -ifa): the interface carries a second address for the site + // tunnel too, and without an explicit source darwin picks that one instead, + // which the exit node's WireGuard AllowedIPs filtering then silently drops. + tunnelIPForRoute := strings.Split(cfg.TunnelIP, "/")[0] + if err := network.AddRouteForServerIPWithSource(serverIPForRoute, interfaceName, tunnelIPForRoute); err != nil { logger.Warn("Failed to add route for exit node server IP: %v", err) } diff --git a/peers/manager.go b/peers/manager.go index 573767a..2b4f888 100644 --- a/peers/manager.go +++ b/peers/manager.go @@ -44,7 +44,11 @@ type PeerManager struct { peerMonitor *monitor.PeerMonitor dnsProxy *dns.DNSProxy interfaceName string - privateKey wgtypes.Key + // localIP is our own address on the site tunnel (as opposed to any exit + // node's secondary address that may also be present on the interface). + // Routes for site peers are pinned to it on darwin - see AddRoutesWithSource. + localIP string + privateKey wgtypes.Key // allowedIPOwners tracks which peer currently "owns" each allowed IP in WireGuard // key is the CIDR string, value is the siteId that has it configured in WG allowedIPOwners map[string]int @@ -88,6 +92,7 @@ func NewPeerManager(config PeerManagerConfig) *PeerManager { peers: make(map[int]SiteConfig), dnsProxy: config.DNSProxy, interfaceName: config.InterfaceName, + localIP: config.LocalIP, privateKey: config.PrivateKey, allowedIPOwners: make(map[string]int), allowedIPClaims: make(map[string]map[int]bool), @@ -216,10 +221,10 @@ func (pm *PeerManager) AddPeer(siteConfig SiteConfig) error { return err } - if err := network.AddRouteForServerIP(siteConfig.ServerIP, pm.interfaceName); err != nil { + if err := network.AddRouteForServerIPWithSource(siteConfig.ServerIP, pm.interfaceName, pm.localIP); err != nil { logger.Error("Failed to add route for server IP: %v", err) } - if err := network.AddRoutes(siteConfig.RemoteSubnets, pm.interfaceName); err != nil { + if err := network.AddRoutesWithSource(siteConfig.RemoteSubnets, pm.interfaceName, pm.localIP); err != nil { logger.Error("Failed to add routes for remote subnets: %v", err) } @@ -516,7 +521,7 @@ func (pm *PeerManager) UpdatePeer(siteConfig SiteConfig) error { // Add routes for added subnets if len(addedSubnets) > 0 { - if err := network.AddRoutes(addedSubnets, pm.interfaceName); err != nil { + if err := network.AddRoutesWithSource(addedSubnets, pm.interfaceName, pm.localIP); err != nil { logger.Error("Failed to add routes: %v", err) } } @@ -717,7 +722,7 @@ func (pm *PeerManager) AddRemoteSubnet(siteId int, cidr string) error { } // Add route - if err := network.AddRoutes([]string{cidr}, pm.interfaceName); err != nil { + if err := network.AddRoutesWithSource([]string{cidr}, pm.interfaceName, pm.localIP); err != nil { return err }