Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions client/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,9 @@ func (e *Engine) Start() error {

// starting network monitor at the very last to avoid disruptions
e.startNetworkMonitor()

// monitor WireGuard interface lifecycle and restart engine on changes
e.startWGIfaceMonitor()
return nil
}

Expand Down Expand Up @@ -1701,6 +1704,63 @@ func (e *Engine) startNetworkMonitor() {
}()
}

// startWGIfaceMonitor starts a background watcher that restarts the engine if
// the WireGuard interface is created or deleted externally while the engine is running.
// It relies on the engine context cancellation to stop.
func (e *Engine) startWGIfaceMonitor() {
// wgInterface should be initialized at this point
if e.wgInterface == nil {
return
}

name := e.wgInterface.Name()
if name == "" {
return
}

// Polling approach for cross-platform simplicity
go func(ctx context.Context, ifaceName string) {
log.Infof("Interface monitor: watching %s", ifaceName)

// Determine initial state
prevExists := interfaceExists(ifaceName)

ticker := time.NewTicker(2 * time.Second)
Copy link
Preview

Copilot AI Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The polling interval of 2 seconds is a magic number that should be made configurable or defined as a named constant for better maintainability and potential tuning.

Suggested change
ticker := time.NewTicker(2 * time.Second)
ticker := time.NewTicker(wgIfaceMonitorPollInterval)

Copilot uses AI. Check for mistakes.

defer ticker.Stop()

for {
select {
case <-ctx.Done():
log.Infof("Interface monitor: stopped for %s", ifaceName)
return
case <-ticker.C:
exists := interfaceExists(ifaceName)
if exists != prevExists {
state := "deleted"
if exists {
state = "created"
}
log.Infof("Interface monitor: %s %s, restarting engine", ifaceName, state)
e.restartEngine()
return
}
}
}
}(e.ctx, name)
}

// interfaceExists checks whether a network interface with the given name exists.
func interfaceExists(name string) bool {
if name == "" {
return false
}
ifi, err := net.InterfaceByName(name)
if err != nil || ifi == nil {
return false
}
return true
}

func (e *Engine) addrViaRoutes(addr netip.Addr) (bool, netip.Prefix, error) {
var vpnRoutes []netip.Prefix
for _, routes := range e.routeManager.GetClientRoutes() {
Expand Down
Loading