From 12e44adf4e6fbcdb427f4d1ccfdd393e3e1ba72f Mon Sep 17 00:00:00 2001 From: Alexandr Stelnykovych Date: Wed, 15 Nov 2023 02:05:14 +0200 Subject: [PATCH 1/2] Checking for the v2ray route for changes/removal (restore it, if necessary) https://github.com/ivpn/desktop-app-shadow/issues/146 --- daemon/v2r/v2ray.go | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/daemon/v2r/v2ray.go b/daemon/v2r/v2ray.go index ef041ef7..0e2c512d 100644 --- a/daemon/v2r/v2ray.go +++ b/daemon/v2r/v2ray.go @@ -77,6 +77,9 @@ type V2RayWrapper struct { // IP address of the default gateway which was used for static route to V2Ray server defaultGeteway net.IP + // IP address of local interface which is in use for communication with V2Ray server + // (we use use it to detect changes of the route to V2Ray server) + localInterfaceIp net.IP } // CreateV2RayWrapper - creates new V2RayWrapper object @@ -157,13 +160,26 @@ func (v *V2RayWrapper) UpdateMainRoute() error { v.mutex.Lock() defer v.mutex.Unlock() + // check: do we need to update route to V2Ray server? (due to it was changed or removed) + isRouteChanged := false + if lInterfaceIp, err := v.getMainRouteLocalInfAddress(); err == nil { + if v.localInterfaceIp != nil && !v.localInterfaceIp.Equal(lInterfaceIp) { + isRouteChanged = true + } + } + + // check: do we need to update route to V2Ray server? (due to change of default gateway) + isGatewayChanged := false gwIp, err := netinfo.DefaultGatewayIP() if err != nil { return fmt.Errorf("getting default gateway ip error : %w", err) } + if v.defaultGeteway != nil && !v.defaultGeteway.Equal(gwIp) { + isGatewayChanged = true + } - if v.defaultGeteway != nil && v.defaultGeteway.Equal(gwIp) { - return nil //gateway did not chnage. Nothing to update + if !isGatewayChanged && !isRouteChanged { + return nil // nothing to update } log.Info("Updating route to V2Ray server...") @@ -183,10 +199,25 @@ func (v *V2RayWrapper) setMainRoute(defaultGateway net.IP) error { } if err := v.implSetMainRoute(defaultGateway); err == nil { v.defaultGeteway = defaultGateway + // save IP address of local interface which is in use for communication with V2Ray server + v.localInterfaceIp, err = v.getMainRouteLocalInfAddress() + if err != nil { + return fmt.Errorf("unable to obtain local interface info for V2Ray connection: %w", err) + } } return err } +// Get IP address of local interface which is in use for communication with V2Ray server +// (it depends of routing table) +func (v *V2RayWrapper) getMainRouteLocalInfAddress() (net.IP, error) { + remoteHost, _, err := v.getRemoteEndpoint() + if err != nil { + return nil, err + } + return netinfo.GetOutboundIPEx(remoteHost) +} + func (v *V2RayWrapper) deleteMainRoute() error { v.defaultGeteway = nil return v.implDeleteMainRoute() From b6c182b7a94b457f24d2fb435fa7c679ed89a5bd Mon Sep 17 00:00:00 2001 From: Alexandr Stelnykovych Date: Wed, 15 Nov 2023 18:45:22 +0200 Subject: [PATCH 2/2] Enhanced monitoring for changes or removal of the V2Ray route, restoring it if necessary. https://github.com/ivpn/desktop-app-shadow/issues/146 --- daemon/netchange/net_change_detector.go | 5 +- .../netchange/net_change_detector_windows.go | 2 +- daemon/v2r/v2ray.go | 104 +++++++++++++++--- 3 files changed, 92 insertions(+), 19 deletions(-) diff --git a/daemon/netchange/net_change_detector.go b/daemon/netchange/net_change_detector.go index 72002e7e..461f0691 100644 --- a/daemon/netchange/net_change_detector.go +++ b/daemon/netchange/net_change_detector.go @@ -106,11 +106,14 @@ func (d *Detector) DelayBeforeNotify() time.Duration { return d.delayBeforeNotify } -// must be called when routing change detected (called from platform-specific sources) +// Must be called when routing change detected (called from platform-specific sources) +// It notifies about routing change with delay 'd.DelayBeforeNotify()'. This reduces amount of multiple consecutive notifications func (d *Detector) routingChangeDetected() { d.timerNotifyAfterDelay.Reset(d.DelayBeforeNotify()) } +// Immediately notify about routing change. +// Consider using routingChangeDetected() instead func (d *Detector) notifyRoutingChange() { if d.routingChangeNotifyChan == nil { return diff --git a/daemon/netchange/net_change_detector_windows.go b/daemon/netchange/net_change_detector_windows.go index ca90c6cb..bf1bddb8 100644 --- a/daemon/netchange/net_change_detector_windows.go +++ b/daemon/netchange/net_change_detector_windows.go @@ -116,7 +116,7 @@ func (d *Detector) doStart() { } // notify about routing change - d.notifyRoutingChange() + d.routingChangeDetected() } } diff --git a/daemon/v2r/v2ray.go b/daemon/v2r/v2ray.go index 0e2c512d..118dcb40 100644 --- a/daemon/v2r/v2ray.go +++ b/daemon/v2r/v2ray.go @@ -75,6 +75,7 @@ type V2RayWrapper struct { mutex sync.Mutex stoppedChan chan struct{} + routeStatusMutex sync.Mutex // IP address of the default gateway which was used for static route to V2Ray server defaultGeteway net.IP // IP address of local interface which is in use for communication with V2Ray server @@ -160,21 +161,37 @@ func (v *V2RayWrapper) UpdateMainRoute() error { v.mutex.Lock() defer v.mutex.Unlock() + var curDefaultGeteway net.IP + var curLocalInterfaceIp net.IP + func() { + // lock access to defaultGeteway and localInterfaceIp + v.routeStatusMutex.Lock() + defer v.routeStatusMutex.Unlock() + curDefaultGeteway = v.defaultGeteway + curLocalInterfaceIp = v.localInterfaceIp + }() + // check: do we need to update route to V2Ray server? (due to it was changed or removed) isRouteChanged := false - if lInterfaceIp, err := v.getMainRouteLocalInfAddress(); err == nil { - if v.localInterfaceIp != nil && !v.localInterfaceIp.Equal(lInterfaceIp) { - isRouteChanged = true + if curLocalInterfaceIp != nil { + if lInterfaceIp, err := v.getMainRouteLocalInfAddress(); err == nil { + if !curLocalInterfaceIp.Equal(lInterfaceIp) { + isRouteChanged = true + } } } + if !isRouteChanged && curDefaultGeteway == nil { + return nil + } + // check: do we need to update route to V2Ray server? (due to change of default gateway) isGatewayChanged := false gwIp, err := netinfo.DefaultGatewayIP() if err != nil { - return fmt.Errorf("getting default gateway ip error : %w", err) + return fmt.Errorf("failed to check V2Ray route consistency: %w", err) } - if v.defaultGeteway != nil && !v.defaultGeteway.Equal(gwIp) { + if !curDefaultGeteway.Equal(gwIp) { isGatewayChanged = true } @@ -198,6 +215,10 @@ func (v *V2RayWrapper) setMainRoute(defaultGateway net.IP) error { } } if err := v.implSetMainRoute(defaultGateway); err == nil { + // lock access to defaultGeteway and localInterfaceIp + v.routeStatusMutex.Lock() + defer v.routeStatusMutex.Unlock() + v.defaultGeteway = defaultGateway // save IP address of local interface which is in use for communication with V2Ray server v.localInterfaceIp, err = v.getMainRouteLocalInfAddress() @@ -208,18 +229,14 @@ func (v *V2RayWrapper) setMainRoute(defaultGateway net.IP) error { return err } -// Get IP address of local interface which is in use for communication with V2Ray server -// (it depends of routing table) -func (v *V2RayWrapper) getMainRouteLocalInfAddress() (net.IP, error) { - remoteHost, _, err := v.getRemoteEndpoint() - if err != nil { - return nil, err - } - return netinfo.GetOutboundIPEx(remoteHost) -} - func (v *V2RayWrapper) deleteMainRoute() error { - v.defaultGeteway = nil + func() { + // lock access to defaultGeteway and localInterfaceIp + v.routeStatusMutex.Lock() + defer v.routeStatusMutex.Unlock() + v.defaultGeteway = nil + v.localInterfaceIp = nil + }() return v.implDeleteMainRoute() } @@ -354,8 +371,29 @@ func (v *V2RayWrapper) start() (retError error) { return startError } - // log when process finished + // routine alive until process finished go func() { + + // Periodically checking the IP address of the local interface used for communication with the V2Ray server + // and update/restore route, if necessary + done := make(chan struct{}, 1) + defer close(done) + go func() { + for { + select { + case <-time.After(time.Second * 20): + if v.isMainRouteLocalInfAddressChanged() { + log.Info("The IP address of the local interface used for communication with the V2Ray server has changed") + if err := v.UpdateMainRoute(); err != nil { + log.Error(err) + } + } + case <-done: + return + } + } + }() + v.command.Wait() // ensure route is deleted v.implDeleteMainRoute() @@ -365,3 +403,35 @@ func (v *V2RayWrapper) start() (retError error) { log.Info(fmt.Sprintf("V2Ray client started (port %s)", configuredPortStr)) return nil } + +func (v *V2RayWrapper) isMainRouteLocalInfAddressChanged() bool { + var curLocalInterfaceIp net.IP + func() { + // lock access to defaultGeteway and localInterfaceIp + v.routeStatusMutex.Lock() + defer v.routeStatusMutex.Unlock() + curLocalInterfaceIp = v.localInterfaceIp + }() + + if curLocalInterfaceIp == nil { + return false + } + + // check: do we need to update route to V2Ray server? (due to it was changed or removed) + if lInterfaceIp, err := v.getMainRouteLocalInfAddress(); err == nil { + if !curLocalInterfaceIp.Equal(lInterfaceIp) { + return true + } + } + return false +} + +// Get IP address of local interface which is in use for communication with V2Ray server +// (it depends of routing table) +func (v *V2RayWrapper) getMainRouteLocalInfAddress() (net.IP, error) { + remoteHost, _, err := v.getRemoteEndpoint() + if err != nil { + return nil, err + } + return netinfo.GetOutboundIPEx(remoteHost) +}