Skip to content

Commit

Permalink
Enhanced monitoring for changes or removal of the V2Ray route, restor…
Browse files Browse the repository at this point in the history
…ing it if necessary.

ivpn/desktop-app-shadow#146
  • Loading branch information
stenya committed Nov 15, 2023
1 parent 12e44ad commit b6c182b
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 19 deletions.
5 changes: 4 additions & 1 deletion daemon/netchange/net_change_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion daemon/netchange/net_change_detector_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (d *Detector) doStart() {
}

// notify about routing change
d.notifyRoutingChange()
d.routingChangeDetected()
}
}

Expand Down
104 changes: 87 additions & 17 deletions daemon/v2r/v2ray.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand All @@ -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()
Expand All @@ -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()
}

Expand Down Expand Up @@ -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()
Expand All @@ -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)
}

0 comments on commit b6c182b

Please sign in to comment.