diff --git a/service/interop/ivpn/evt_handlers.go b/service/interop/ivpn/evt_handlers.go index 064abc32..2d51de98 100644 --- a/service/interop/ivpn/evt_handlers.go +++ b/service/interop/ivpn/evt_handlers.go @@ -7,11 +7,17 @@ import ( "github.com/ivpn/desktop-app/daemon/protocol/ivpnclient" "github.com/safing/portmaster/service/mgr" + "github.com/safing/portmaster/service/netenv" "github.com/safing/portmaster/service/network/packet" ) // notification handler: VPN connection is going to start func (i *InteropIvpn) onConnectionStarting(wc *mgr.WorkerCtx, _ string, messageData string) { + // While VPN is active, ignore network-derived location sources. + // Virtual VPN interfaces/IPs can skew detected device location + // and lead to selecting an incorrect SPN home hub. + netenv.DisableNetworkDerivedLocation(true) + connInfo := ivpnclient.ConnectionStarting{} err := json.Unmarshal([]byte(messageData), &connInfo) if err != nil { @@ -38,6 +44,9 @@ func (i *InteropIvpn) onConnectionStarting(wc *mgr.WorkerCtx, _ string, messageD // notification handler: VPN connection stopped func (i *InteropIvpn) onConnectionStopped(wc *mgr.WorkerCtx, _ string, _ string) { + // Re-enable network-derived location methods now that VPN is inactive. + netenv.DisableNetworkDerivedLocation(false) + status := *i.getStatus() status.vpnConnection = vpnConnectionInfo{} status.connectedInfo = nil diff --git a/service/interop/ivpn/ivpn.go b/service/interop/ivpn/ivpn.go index 9a28c9b6..718eb05b 100644 --- a/service/interop/ivpn/ivpn.go +++ b/service/interop/ivpn/ivpn.go @@ -12,6 +12,7 @@ import ( "github.com/safing/portmaster/base/notifications" "github.com/safing/portmaster/service/firewall/interception" "github.com/safing/portmaster/service/mgr" + "github.com/safing/portmaster/service/netenv" "github.com/safing/portmaster/service/network" "github.com/safing/portmaster/service/network/packet" "github.com/safing/portmaster/spn/hub" @@ -115,6 +116,9 @@ var notifWarnOldVersion atomic.Pointer[notifications.Notification] // Synchronously connects to the IVPN client, sets up message handlers func (i *InteropIvpn) connectIvpnClient(wc *mgr.WorkerCtx) error { defer func() { + // Re-enable network-derived location methods when disconnecting from IVPN client, since VPN is no longer active. + netenv.DisableNetworkDerivedLocation(false) + // Clear client status on disconnect i.setStatus(nil) // Reset DNS tracking state diff --git a/service/netenv/location.go b/service/netenv/location.go index 71cdafef..123f7f11 100644 --- a/service/netenv/location.go +++ b/service/netenv/location.go @@ -6,6 +6,7 @@ import ( "net" "sort" "sync" + "sync/atomic" "time" "github.com/google/gopacket/layers" @@ -33,6 +34,12 @@ var ( locationsLock sync.Mutex gettingLocationsLock sync.Mutex locationNetworkChangedFlag = GetNetworkChangedFlag() + + // disableNetworkDerivedLocation disables location methods that depend on network + // configuration, such as reading interface IPs or using traceroute. + // Use this when a VPN is known to be active and network-derived location would + // reflect VPN egress rather than the device's physical uplink. + disableNetworkDerivedLocation atomic.Bool ) func prepLocation() (err error) { @@ -40,6 +47,13 @@ func prepLocation() (err error) { return err } +// DisableNetworkDerivedLocation disables or enables location methods that depend on network +// configuration, such as reading interface IPs or using traceroute. Use this when a VPN is known to be active and +// network-derived location would reflect VPN egress rather than the device's physical uplink. +func DisableNetworkDerivedLocation(disable bool) { + disableNetworkDerivedLocation.Store(disable) +} + // DeviceLocations holds multiple device locations. type DeviceLocations struct { All []*DeviceLocation @@ -332,6 +346,10 @@ func GetInternetLocation() (deviceLocations *DeviceLocations, ok bool) { } func getLocationFromInterfaces(dls *DeviceLocations) (v4ok, v6ok bool) { + if disableNetworkDerivedLocation.Load() { + return false, false + } + globalIPv4, globalIPv6, err := GetAssignedGlobalAddresses() if err != nil { log.Warningf("netenv: location: failed to get assigned global addresses: %s", err) @@ -362,6 +380,10 @@ func getLocationFromUPnP() (ok bool) { */ func getLocationFromTraceroute(dls *DeviceLocations) (dl *DeviceLocation, err error) { + if disableNetworkDerivedLocation.Load() { + return nil, fmt.Errorf("skipped network-derived location") + } + // Create connection. conn, err := icmp.ListenPacket("ip4:icmp", "") if err != nil {