From 9ef3399e8f948a80d9a23c3d27fa2e23f4677677 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 25 Sep 2026 15:12:26 -0400 Subject: [PATCH] Add DisableRoutesAndAliases --- api/api.go | 37 +++++++++---------- config.go | 74 +++++++++++++++++++++++--------------- go.mod | 10 +++--- go.sum | 14 ++++---- main.go | 39 ++++++++++---------- olm/connect.go | 92 +++++++++++++++++++++++++++--------------------- olm/olm.go | 31 ++++++++-------- olm/types.go | 10 ++++++ peers/manager.go | 89 +++++++++++++++++++++++++++++++++++++--------- 9 files changed, 246 insertions(+), 150 deletions(-) diff --git a/api/api.go b/api/api.go index 5ed7bbb..8de6729 100644 --- a/api/api.go +++ b/api/api.go @@ -19,24 +19,25 @@ import ( // non-empty, and is how olm later matches server-pushed gateway updates to the // resource the user actually connected through. type ConnectionRequest struct { - ID string `json:"id"` - Secret string `json:"secret"` - Endpoint string `json:"endpoint"` - UserToken string `json:"userToken,omitempty"` - MTU int `json:"mtu,omitempty"` - DNS string `json:"dns,omitempty"` - DNSProxyIP string `json:"dnsProxyIP,omitempty"` - UpstreamDNS []string `json:"upstreamDNS,omitempty"` - InterfaceName string `json:"interfaceName,omitempty"` - Holepunch bool `json:"holepunch,omitempty"` - TlsClientCert string `json:"tlsClientCert,omitempty"` - PingInterval string `json:"pingInterval,omitempty"` - PingTimeout string `json:"pingTimeout,omitempty"` - OrgID string `json:"orgId,omitempty"` - MatchDomains []string `json:"matchDomains,omitempty"` - SubnetRouter bool `json:"subnetRouter,omitempty"` - GatewaySiteResourceId int `json:"gatewaySiteResourceId,omitempty"` - GatewaySiteIds []int `json:"gatewaySiteIds,omitempty"` + ID string `json:"id"` + Secret string `json:"secret"` + Endpoint string `json:"endpoint"` + UserToken string `json:"userToken,omitempty"` + MTU int `json:"mtu,omitempty"` + DNS string `json:"dns,omitempty"` + DNSProxyIP string `json:"dnsProxyIP,omitempty"` + UpstreamDNS []string `json:"upstreamDNS,omitempty"` + InterfaceName string `json:"interfaceName,omitempty"` + Holepunch bool `json:"holepunch,omitempty"` + TlsClientCert string `json:"tlsClientCert,omitempty"` + PingInterval string `json:"pingInterval,omitempty"` + PingTimeout string `json:"pingTimeout,omitempty"` + OrgID string `json:"orgId,omitempty"` + MatchDomains []string `json:"matchDomains,omitempty"` + SubnetRouter bool `json:"subnetRouter,omitempty"` + DisableRoutesAndAliases bool `json:"disableRoutesAndAliases,omitempty"` + GatewaySiteResourceId int `json:"gatewaySiteResourceId,omitempty"` + GatewaySiteIds []int `json:"gatewaySiteIds,omitempty"` } // SwitchOrgRequest defines the structure for switching organizations diff --git a/config.go b/config.go index 2440bbb..b07419f 100644 --- a/config.go +++ b/config.go @@ -47,13 +47,14 @@ type OlmConfig struct { PingTimeout string `json:"pingTimeout"` // Advanced - DisableHolepunch bool `json:"disableHolepunch"` - TlsClientCert string `json:"tlsClientCert"` - OverrideDNS bool `json:"overrideDNS"` - TunnelDNS bool `json:"tunnelDNS"` - DisableRelay bool `json:"disableRelay"` - PreferLocalRoutes bool `json:"preferLocalRoutes"` - SubnetRouter bool `json:"subnetRouter"` + DisableHolepunch bool `json:"disableHolepunch"` + TlsClientCert string `json:"tlsClientCert"` + OverrideDNS bool `json:"overrideDNS"` + TunnelDNS bool `json:"tunnelDNS"` + DisableRelay bool `json:"disableRelay"` + PreferLocalRoutes bool `json:"preferLocalRoutes"` + SubnetRouter bool `json:"subnetRouter"` + DisableRoutesAndAliases bool `json:"disableRoutesAndAliases"` // DoNotCreateNewClient bool `json:"doNotCreateNewClient"` // Parsed values (not in JSON) @@ -122,6 +123,7 @@ func DefaultConfig() *OlmConfig { config.sources["disableRelay"] = string(SourceDefault) config.sources["preferLocalRoutes"] = string(SourceDefault) config.sources["subnetRouter"] = string(SourceDefault) + config.sources["disableRoutesAndAliases"] = string(SourceDefault) // config.sources["doNotCreateNewClient"] = string(SourceDefault) return config @@ -297,6 +299,10 @@ func loadConfigFromEnv(config *OlmConfig) { config.SubnetRouter = true config.sources["subnetRouter"] = string(SourceEnv) } + if val := os.Getenv("DISABLE_ROUTES_AND_ALIASES"); val == "true" { + config.DisableRoutesAndAliases = true + config.sources["disableRoutesAndAliases"] = string(SourceEnv) + } // if val := os.Getenv("DO_NOT_CREATE_NEW_CLIENT"); val == "true" { // config.DoNotCreateNewClient = true // config.sources["doNotCreateNewClient"] = string(SourceEnv) @@ -309,28 +315,29 @@ func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) { // Store original values to detect changes origValues := map[string]interface{}{ - "endpoint": config.Endpoint, - "id": config.ID, - "secret": config.Secret, - "org": config.OrgID, - "userToken": config.UserToken, - "mtu": config.MTU, - "dns": config.DNS, - "upstreamDNS": fmt.Sprintf("%v", config.UpstreamDNS), - "matchDomains": fmt.Sprintf("%v", config.MatchDomains), - "logLevel": config.LogLevel, - "interface": config.InterfaceName, - "httpAddr": config.HTTPAddr, - "socketPath": config.SocketPath, - "pingInterval": config.PingInterval, - "pingTimeout": config.PingTimeout, - "enableApi": config.EnableAPI, - "disableHolepunch": config.DisableHolepunch, - "overrideDNS": config.OverrideDNS, - "disableRelay": config.DisableRelay, - "preferLocalRoutes": config.PreferLocalRoutes, - "tunnelDNS": config.TunnelDNS, - "subnetRouter": config.SubnetRouter, + "endpoint": config.Endpoint, + "id": config.ID, + "secret": config.Secret, + "org": config.OrgID, + "userToken": config.UserToken, + "mtu": config.MTU, + "dns": config.DNS, + "upstreamDNS": fmt.Sprintf("%v", config.UpstreamDNS), + "matchDomains": fmt.Sprintf("%v", config.MatchDomains), + "logLevel": config.LogLevel, + "interface": config.InterfaceName, + "httpAddr": config.HTTPAddr, + "socketPath": config.SocketPath, + "pingInterval": config.PingInterval, + "pingTimeout": config.PingTimeout, + "enableApi": config.EnableAPI, + "disableHolepunch": config.DisableHolepunch, + "overrideDNS": config.OverrideDNS, + "disableRelay": config.DisableRelay, + "preferLocalRoutes": config.PreferLocalRoutes, + "tunnelDNS": config.TunnelDNS, + "subnetRouter": config.SubnetRouter, + "disableRoutesAndAliases": config.DisableRoutesAndAliases, // "doNotCreateNewClient": config.DoNotCreateNewClient, } @@ -359,6 +366,7 @@ func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) { serviceFlags.BoolVar(&config.PreferLocalRoutes, "prefer-local-routes", config.PreferLocalRoutes, "Add tunnel routes with a high metric so overlapping local/connected routes take precedence (default false)") serviceFlags.BoolVar(&config.TunnelDNS, "tunnel-dns", config.TunnelDNS, "When enabled, DNS queries are routed through the tunnel for remote resolution. To ensure queries are tunneled correctly, you must define the DNS server as a Pangolin resource and enter its address as an Upstream DNS Server. (default false)") serviceFlags.BoolVar(&config.SubnetRouter, "subnet-router", config.SubnetRouter, "Enable this client to act as a subnet router: traffic forwarded from the local network is NATed to this client's own tunnel IP before going out over the tunnel. Linux only, requires CAP_NET_ADMIN. (default false)") + serviceFlags.BoolVar(&config.DisableRoutesAndAliases, "disable-routes-and-aliases", config.DisableRoutesAndAliases, "Do not add routes to the system routing table and do not run the DNS proxy (aliases are not resolved). Gateway routes are still added. (default false)") // serviceFlags.BoolVar(&config.DoNotCreateNewClient, "do-not-create-new-client", config.DoNotCreateNewClient, "Do not create new client") version := serviceFlags.Bool("version", false, "Print the version") @@ -451,6 +459,9 @@ func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) { if config.SubnetRouter != origValues["subnetRouter"].(bool) { config.sources["subnetRouter"] = string(SourceCLI) } + if config.DisableRoutesAndAliases != origValues["disableRoutesAndAliases"].(bool) { + config.sources["disableRoutesAndAliases"] = string(SourceCLI) + } // if config.DoNotCreateNewClient != origValues["doNotCreateNewClient"].(bool) { // config.sources["doNotCreateNewClient"] = string(SourceCLI) // } @@ -587,6 +598,10 @@ func mergeConfigs(dest, src *OlmConfig) { dest.SubnetRouter = src.SubnetRouter dest.sources["subnetRouter"] = string(SourceFile) } + if src.DisableRoutesAndAliases { + dest.DisableRoutesAndAliases = src.DisableRoutesAndAliases + dest.sources["disableRoutesAndAliases"] = string(SourceFile) + } // if src.DoNotCreateNewClient { // dest.DoNotCreateNewClient = src.DoNotCreateNewClient // dest.sources["doNotCreateNewClient"] = string(SourceFile) @@ -681,6 +696,7 @@ func (c *OlmConfig) ShowConfig() { fmt.Printf(" disable-relay = %v [%s]\n", c.DisableRelay, getSource("disableRelay")) fmt.Printf(" prefer-local-routes = %v [%s]\n", c.PreferLocalRoutes, getSource("preferLocalRoutes")) fmt.Printf(" subnet-router = %v [%s]\n", c.SubnetRouter, getSource("subnetRouter")) + fmt.Printf(" disable-routes-and-aliases = %v [%s]\n", c.DisableRoutesAndAliases, getSource("disableRoutesAndAliases")) // fmt.Printf(" do-not-create-new-client = %v [%s]\n", c.DoNotCreateNewClient, getSource("doNotCreateNewClient")) if c.TlsClientCert != "" { fmt.Printf(" tls-cert = %s [%s]\n", c.TlsClientCert, getSource("tlsClientCert")) diff --git a/go.mod b/go.mod index f0fa360..4420b91 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/fosrl/olm -go 1.25.0 +go 1.26.0 require ( github.com/Microsoft/go-winio v0.6.2 @@ -9,8 +9,8 @@ require ( github.com/google/nftables v0.3.0 github.com/gorilla/websocket v1.5.3 github.com/miekg/dns v1.1.70 - golang.org/x/net v0.56.0 - golang.org/x/sys v0.46.0 + golang.org/x/net v0.59.0 + golang.org/x/sys v0.48.0 golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c @@ -24,7 +24,7 @@ require ( github.com/mdlayher/socket v0.5.1 // indirect github.com/vishvananda/netlink v1.3.1 // indirect github.com/vishvananda/netns v0.0.5 // indirect - golang.org/x/crypto v0.53.0 // indirect + golang.org/x/crypto v0.57.0 // indirect golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect golang.org/x/mod v0.34.0 // indirect golang.org/x/sync v0.20.0 // indirect @@ -35,4 +35,4 @@ require ( ) // To be used ONLY for local development -// replace github.com/fosrl/newt => ../newt +replace github.com/fosrl/newt => ../newt diff --git a/go.sum b/go.sum index b41a6e2..4767df4 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/fosrl/newt v1.16.0 h1:Nf70uNFn/WqHoTvRg5xTPO3xz9vLgh3BsnMVMoniEEw= -github.com/fosrl/newt v1.16.0/go.mod h1:l6kWoZPSaXT+ZRUjiyPgwflRqZWYaXpUj9oQ0sOPh4o= github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= @@ -22,20 +20,20 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= +golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= diff --git a/main.go b/main.go index 5cd6bf4..04f147e 100644 --- a/main.go +++ b/main.go @@ -256,25 +256,26 @@ func runOlmMainWithArgs(ctx context.Context, cancel context.CancelFunc, signalCt if config.ID != "" && config.Secret != "" && config.Endpoint != "" { tunnelConfig := olmpkg.TunnelConfig{ - Endpoint: config.Endpoint, - ID: config.ID, - Secret: config.Secret, - UserToken: config.UserToken, - MTU: config.MTU, - DNS: config.DNS, - UpstreamDNS: config.UpstreamDNS, - MatchDomains: config.MatchDomains, - InterfaceName: config.InterfaceName, - Holepunch: !config.DisableHolepunch, - TlsClientCert: config.TlsClientCert, - PingIntervalDuration: config.PingIntervalDuration, - PingTimeoutDuration: config.PingTimeoutDuration, - OrgID: config.OrgID, - OverrideDNS: config.OverrideDNS, - DisableRelay: config.DisableRelay, - PreferLocalRoutes: config.PreferLocalRoutes, - SubnetRouter: config.SubnetRouter, - EnableUAPI: true, + Endpoint: config.Endpoint, + ID: config.ID, + Secret: config.Secret, + UserToken: config.UserToken, + MTU: config.MTU, + DNS: config.DNS, + UpstreamDNS: config.UpstreamDNS, + MatchDomains: config.MatchDomains, + InterfaceName: config.InterfaceName, + Holepunch: !config.DisableHolepunch, + TlsClientCert: config.TlsClientCert, + PingIntervalDuration: config.PingIntervalDuration, + PingTimeoutDuration: config.PingTimeoutDuration, + OrgID: config.OrgID, + OverrideDNS: config.OverrideDNS, + DisableRelay: config.DisableRelay, + PreferLocalRoutes: config.PreferLocalRoutes, + SubnetRouter: config.SubnetRouter, + DisableRoutesAndAliases: config.DisableRoutesAndAliases, + EnableUAPI: true, } go olm.StartTunnel(tunnelConfig) } else { diff --git a/olm/connect.go b/olm/connect.go index 9a2c4d7..5218b09 100644 --- a/olm/connect.go +++ b/olm/connect.go @@ -181,10 +181,17 @@ func (o *Olm) handleConnect(msg websocket.WSMessage) { logger.Warn("Failed to parse tunnel IP %q: %v", interfaceIP, err) } - // Create and start DNS proxy - o.dnsProxy, err = dns.NewDNSProxy(o.middleDev, o.tunnelConfig.MTU, wgData.UtilitySubnet, o.tunnelConfig.UpstreamDNS, o.tunnelConfig.TunnelDNS, interfaceIP, o.tunnelConfig.MatchDomains, o.tunnelConfig.PublicDNS) - if err != nil { - logger.Error("Failed to create DNS proxy: %v", err) + // Create the DNS proxy, unless routes/aliases are disabled - the proxy is + // what resolves aliases, and the utility subnet it lives on is only + // reachable through a system route we wouldn't add. o.dnsProxy stays nil + // in that case; everything that uses it is nil-checked. + if o.tunnelConfig.DisableRoutesAndAliases { + logger.Info("Routes and aliases disabled: not adding system routes and not starting the DNS proxy (gateway routes are unaffected)") + } else { + o.dnsProxy, err = dns.NewDNSProxy(o.middleDev, o.tunnelConfig.MTU, wgData.UtilitySubnet, o.tunnelConfig.UpstreamDNS, o.tunnelConfig.TunnelDNS, interfaceIP, o.tunnelConfig.MatchDomains, o.tunnelConfig.PublicDNS) + if err != nil { + logger.Error("Failed to create DNS proxy: %v", err) + } } // Tell the system DNS monitor to exclude the proxy IP so that subsequent @@ -207,8 +214,10 @@ func (o *Olm) handleConnect(msg websocket.WSMessage) { } } - 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) + if !o.tunnelConfig.DisableRoutesAndAliases { + 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) + } } // Create peer manager with integrated peer monitoring @@ -223,6 +232,7 @@ func (o *Olm) handleConnect(msg websocket.WSMessage) { WSClient: o.websocket, APIServer: o.apiServer, PublicDNS: o.tunnelConfig.PublicDNS, + DisableRoutes: o.tunnelConfig.DisableRoutesAndAliases, }) for i := range wgData.Sites { @@ -257,42 +267,44 @@ func (o *Olm) handleConnect(msg websocket.WSMessage) { // endpoints it already recorded now that there's somewhere to put them. o.flushPendingHolepunchBypassEndpoints() - if err := o.dnsProxy.Start(); err != nil { // start DNS proxy first so there is no downtime - logger.Error("Failed to start DNS proxy: %v", err) + if o.dnsProxy != nil { + if err := o.dnsProxy.Start(); err != nil { // start DNS proxy first so there is no downtime + logger.Error("Failed to start DNS proxy: %v", err) + } + + // Register JIT handler: when the DNS proxy resolves a local record, check whether + // the owning site is already connected and, if not, initiate a JIT connection. + o.dnsProxy.SetJITHandler(func(siteId int) { + pm := o.getPeerManager() + if pm == nil || o.websocket == nil { + return + } + + // Site already has an active peer connection - nothing to do. + if _, exists := pm.GetPeer(siteId); exists { + return + } + + o.peerSendMu.Lock() + defer o.peerSendMu.Unlock() + + // A JIT request for this site is already in-flight - avoid duplicate sends. + if _, pending := o.jitPendingSites[siteId]; pending { + return + } + + chainId := generateChainId() + logger.Info("DNS-triggered JIT connect for site %d (chainId=%s)", siteId, chainId) + stopFunc, _ := o.websocket.SendMessageInterval("olm/wg/server/peer/init", map[string]interface{}{ + "siteId": siteId, + "chainId": chainId, + }, 2*time.Second, 10) + o.stopPeerInits[chainId] = stopFunc + o.jitPendingSites[siteId] = chainId + }) } - // Register JIT handler: when the DNS proxy resolves a local record, check whether - // the owning site is already connected and, if not, initiate a JIT connection. - o.dnsProxy.SetJITHandler(func(siteId int) { - pm := o.getPeerManager() - if pm == nil || o.websocket == nil { - return - } - - // Site already has an active peer connection - nothing to do. - if _, exists := pm.GetPeer(siteId); exists { - return - } - - o.peerSendMu.Lock() - defer o.peerSendMu.Unlock() - - // A JIT request for this site is already in-flight - avoid duplicate sends. - if _, pending := o.jitPendingSites[siteId]; pending { - return - } - - chainId := generateChainId() - logger.Info("DNS-triggered JIT connect for site %d (chainId=%s)", siteId, chainId) - stopFunc, _ := o.websocket.SendMessageInterval("olm/wg/server/peer/init", map[string]interface{}{ - "siteId": siteId, - "chainId": chainId, - }, 2*time.Second, 10) - o.stopPeerInits[chainId] = stopFunc - o.jitPendingSites[siteId] = chainId - }) - - if o.tunnelConfig.OverrideDNS { + if o.tunnelConfig.OverrideDNS && o.dnsProxy != nil { if err := o.applyDNSOverride(true); err != nil { logger.Error("%v", err) return diff --git a/olm/olm.go b/olm/olm.go index 60fd1f1..f1d5158 100644 --- a/olm/olm.go +++ b/olm/olm.go @@ -289,21 +289,22 @@ func (o *Olm) registerAPICallbacks() { logger.Info("Received connection request via HTTP: id=%s, endpoint=%s", req.ID, req.Endpoint) tunnelConfig := TunnelConfig{ - Endpoint: req.Endpoint, - ID: req.ID, - Secret: req.Secret, - UserToken: req.UserToken, - MTU: req.MTU, - DNS: req.DNS, - UpstreamDNS: req.UpstreamDNS, - InterfaceName: req.InterfaceName, - Holepunch: req.Holepunch, - TlsClientCert: req.TlsClientCert, - OrgID: req.OrgID, - MatchDomains: req.MatchDomains, - SubnetRouter: req.SubnetRouter, - GatewaySiteIds: req.GatewaySiteIds, - GatewaySiteResourceId: req.GatewaySiteResourceId, + Endpoint: req.Endpoint, + ID: req.ID, + Secret: req.Secret, + UserToken: req.UserToken, + MTU: req.MTU, + DNS: req.DNS, + UpstreamDNS: req.UpstreamDNS, + InterfaceName: req.InterfaceName, + Holepunch: req.Holepunch, + TlsClientCert: req.TlsClientCert, + OrgID: req.OrgID, + MatchDomains: req.MatchDomains, + SubnetRouter: req.SubnetRouter, + DisableRoutesAndAliases: req.DisableRoutesAndAliases, + GatewaySiteIds: req.GatewaySiteIds, + GatewaySiteResourceId: req.GatewaySiteResourceId, } var err error diff --git a/olm/types.go b/olm/types.go index 06907b8..de36b45 100644 --- a/olm/types.go +++ b/olm/types.go @@ -182,6 +182,16 @@ type TunnelConfig struct { // LAN address it originally arrived with. Linux only. Defaults to false. SubnetRouter bool + // DisableRoutesAndAliases, when enabled, stops olm from adding routes to + // the host's routing table (server IPs, remote subnets, the utility + // subnet) and from launching the DNS proxy, so aliases are not resolved + // either. WireGuard AllowedIPs are still configured, so the tunnel can + // still be used by anything that reaches it without the OS routing table + // (e.g. a file descriptor/netstack consumer). Gateway routes (the + // default-route-equivalent and its endpoint bypass routes) are unaffected + // and are still installed. Defaults to false. + DisableRoutesAndAliases bool + // GatewaySiteIds, when non-empty, designates these site IDs as gateway // (full-tunnel/default-route) candidates from the moment the tunnel // starts, for callers that want a gateway already established rather diff --git a/peers/manager.go b/peers/manager.go index c44cbde..a8d0271 100644 --- a/peers/manager.go +++ b/peers/manager.go @@ -36,6 +36,10 @@ type PeerManagerConfig struct { WSClient *websocket.Client APIServer *api.API PublicDNS []string + // DisableRoutes stops the manager from adding/removing routes in the + // system routing table for server IPs and remote subnets. Gateway routes + // are unaffected. + DisableRoutes bool } type PeerManager struct { @@ -58,6 +62,7 @@ type PeerManager struct { allowedIPClaims map[string]map[int]bool APIServer *api.API publicDNS []string + disableRoutes bool PersistentKeepalive int @@ -158,6 +163,7 @@ func NewPeerManager(config PeerManagerConfig) *PeerManager { allowedIPClaims: make(map[string]map[int]bool), APIServer: config.APIServer, publicDNS: config.PublicDNS, + disableRoutes: config.DisableRoutes, lastOwnerChange: make(map[string]time.Time), gatewaySiteIds: make(map[int]bool), gatewayExcludedIPs: make(map[string]int), @@ -640,6 +646,59 @@ func (pm *PeerManager) GetAllPeers() []SiteConfig { return peers } +// addRoutes/removeRoutes/addServerRoute/removeServerRoute wrap the system +// route helpers for site traffic (server IPs and remote subnets) and are a +// no-op when route management is disabled. They deliberately do NOT cover the +// gateway default-route-equivalent or its bypass routes, which are always +// installed regardless (see activateGatewayLocked). +func (pm *PeerManager) addRoutes(subnets []string) error { + if pm.disableRoutes { + return nil + } + return network.AddRoutesWithSource(subnets, pm.interfaceName, pm.localIP) +} + +func (pm *PeerManager) removeRoutes(subnets []string) error { + if pm.disableRoutes { + return nil + } + return network.RemoveRoutes(subnets, pm.interfaceName) +} + +func (pm *PeerManager) addServerRoute(serverIP string) error { + if pm.disableRoutes { + return nil + } + return network.AddRouteForServerIPWithSource(normalizeServerRouteDestination(serverIP), pm.interfaceName, pm.localIP) +} + +func (pm *PeerManager) removeServerRoute(serverIP string) error { + if pm.disableRoutes { + return nil + } + return network.RemoveRouteForServerIPWithSource(normalizeServerRouteDestination(serverIP), pm.interfaceName, pm.localIP) +} + +// The DNS proxy is not created when aliases are disabled, so every alias +// record operation must tolerate a nil proxy. +func (pm *PeerManager) addDNSRecord(alias string, address net.IP, siteId int) { + if pm.dnsProxy != nil { + pm.dnsProxy.AddDNSRecord(alias, address, siteId) + } +} + +func (pm *PeerManager) removeDNSRecord(alias string, address net.IP) { + if pm.dnsProxy != nil { + pm.dnsProxy.RemoveDNSRecord(alias, address) + } +} + +func (pm *PeerManager) removeDNSRecordForSite(alias string, address net.IP, siteId int) { + if pm.dnsProxy != nil { + pm.dnsProxy.RemoveDNSRecordForSite(alias, address, siteId) + } +} + func (pm *PeerManager) AddPeer(siteConfig SiteConfig) error { pm.mu.Lock() defer pm.mu.Unlock() @@ -649,7 +708,7 @@ func (pm *PeerManager) AddPeer(siteConfig SiteConfig) error { if address == nil { continue } - pm.dnsProxy.AddDNSRecord(alias.Alias, address, siteConfig.SiteId) + pm.addDNSRecord(alias.Alias, address, siteConfig.SiteId) } if siteConfig.PublicKey == "" { @@ -694,11 +753,10 @@ func (pm *PeerManager) AddPeer(siteConfig SiteConfig) error { return err } - serverRouteDestination := normalizeServerRouteDestination(siteConfig.ServerIP) - if err := network.AddRouteForServerIPWithSource(serverRouteDestination, pm.interfaceName, pm.localIP); err != nil { + if err := pm.addServerRoute(siteConfig.ServerIP); 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 { + if err := pm.addRoutes(siteConfig.RemoteSubnets); err != nil { logger.Error("Failed to add routes for remote subnets: %v", err) } @@ -769,8 +827,7 @@ func (pm *PeerManager) RemovePeer(siteId int) error { return err } - serverRouteDestination := normalizeServerRouteDestination(peer.ServerIP) - if err := network.RemoveRouteForServerIPWithSource(serverRouteDestination, pm.interfaceName, pm.localIP); err != nil { + if err := pm.removeServerRoute(peer.ServerIP); err != nil { logger.Error("Failed to remove route for server IP: %v", err) } @@ -792,7 +849,7 @@ func (pm *PeerManager) RemovePeer(siteId int) error { } } if !subnetStillInUse { - if err := network.RemoveRoutes([]string{subnet}, pm.interfaceName); err != nil { + if err := pm.removeRoutes([]string{subnet}); err != nil { logger.Error("Failed to remove route for remote subnet %s: %v", subnet, err) } } @@ -804,7 +861,7 @@ func (pm *PeerManager) RemovePeer(siteId int) error { if address == nil { continue } - pm.dnsProxy.RemoveDNSRecord(alias.Alias, address) + pm.removeDNSRecord(alias.Alias, address) } // Release all IP claims and promote other peers as needed. Scan @@ -905,7 +962,7 @@ func (pm *PeerManager) UpdatePeer(siteConfig SiteConfig) error { if address == nil { continue } - pm.dnsProxy.RemoveDNSRecord(alias.Alias, address) + pm.removeDNSRecord(alias.Alias, address) } // Add new aliases for _, alias := range siteConfig.Aliases { @@ -913,7 +970,7 @@ func (pm *PeerManager) UpdatePeer(siteConfig SiteConfig) error { if address == nil { continue } - pm.dnsProxy.AddDNSRecord(alias.Alias, address, siteConfig.SiteId) + pm.addDNSRecord(alias.Alias, address, siteConfig.SiteId) } if siteConfig.PublicKey == "" { @@ -1031,7 +1088,7 @@ func (pm *PeerManager) UpdatePeer(siteConfig SiteConfig) error { } } if !subnetStillInUse { - if err := network.RemoveRoutes([]string{subnet}, pm.interfaceName); err != nil { + if err := pm.removeRoutes([]string{subnet}); err != nil { logger.Error("Failed to remove route for subnet %s: %v", subnet, err) } } @@ -1039,7 +1096,7 @@ func (pm *PeerManager) UpdatePeer(siteConfig SiteConfig) error { // Add routes for added subnets if len(addedSubnets) > 0 { - if err := network.AddRoutesWithSource(addedSubnets, pm.interfaceName, pm.localIP); err != nil { + if err := pm.addRoutes(addedSubnets); err != nil { logger.Error("Failed to add routes: %v", err) } } @@ -1250,7 +1307,7 @@ func (pm *PeerManager) AddRemoteSubnet(siteId int, cidr string) error { } // Add route - if err := network.AddRoutesWithSource([]string{cidr}, pm.interfaceName, pm.localIP); err != nil { + if err := pm.addRoutes([]string{cidr}); err != nil { return err } @@ -1310,7 +1367,7 @@ func (pm *PeerManager) RemoveRemoteSubnet(siteId int, ip string) error { // Only remove route if no other peer needs it if !subnetStillInUse { - if err := network.RemoveRoutes([]string{ip}, pm.interfaceName); err != nil { + if err := pm.removeRoutes([]string{ip}); err != nil { return err } } @@ -1333,7 +1390,7 @@ func (pm *PeerManager) AddAlias(siteId int, alias Alias) error { address := net.ParseIP(alias.AliasAddress) if address != nil { - pm.dnsProxy.AddDNSRecord(alias.Alias, address, siteId) + pm.addDNSRecord(alias.Alias, address, siteId) } // Add an allowed IP for the alias @@ -1371,7 +1428,7 @@ func (pm *PeerManager) RemoveAlias(siteId int, aliasName string) error { address := net.ParseIP(aliasToRemove.AliasAddress) if address != nil { - pm.dnsProxy.RemoveDNSRecordForSite(aliasName, address, siteId) + pm.removeDNSRecordForSite(aliasName, address, siteId) } peer.Aliases = newAliases