mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 07:16:38 +00:00
[client] Add WireGuard interface lifecycle monitoring (#4370)
* [client] Add WireGuard interface lifecycle monitoring
This commit is contained in:
@@ -198,6 +198,10 @@ type Engine struct {
|
||||
latestSyncResponse *mgmProto.SyncResponse
|
||||
connSemaphore *semaphoregroup.SemaphoreGroup
|
||||
flowManager nftypes.FlowManager
|
||||
|
||||
// WireGuard interface monitor
|
||||
wgIfaceMonitor *WGIfaceMonitor
|
||||
wgIfaceMonitorWg sync.WaitGroup
|
||||
}
|
||||
|
||||
// Peer is an instance of the Connection Peer
|
||||
@@ -341,6 +345,9 @@ func (e *Engine) Stop() error {
|
||||
log.Errorf("failed to persist state: %v", err)
|
||||
}
|
||||
|
||||
// Stop WireGuard interface monitor and wait for it to exit
|
||||
e.wgIfaceMonitorWg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -479,6 +486,22 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL)
|
||||
|
||||
// starting network monitor at the very last to avoid disruptions
|
||||
e.startNetworkMonitor()
|
||||
|
||||
// monitor WireGuard interface lifecycle and restart engine on changes
|
||||
e.wgIfaceMonitor = NewWGIfaceMonitor()
|
||||
e.wgIfaceMonitorWg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer e.wgIfaceMonitorWg.Done()
|
||||
|
||||
if shouldRestart, err := e.wgIfaceMonitor.Start(e.ctx, e.wgInterface.Name()); shouldRestart {
|
||||
log.Infof("WireGuard interface monitor: %s, restarting engine", err)
|
||||
e.restartEngine()
|
||||
} else if err != nil {
|
||||
log.Warnf("WireGuard interface monitor: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
98
client/internal/wg_iface_monitor.go
Normal file
98
client/internal/wg_iface_monitor.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// WGIfaceMonitor monitors the WireGuard interface lifecycle and restarts the engine
|
||||
// if the interface is deleted externally while the engine is running.
|
||||
type WGIfaceMonitor struct {
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewWGIfaceMonitor creates a new WGIfaceMonitor instance.
|
||||
func NewWGIfaceMonitor() *WGIfaceMonitor {
|
||||
return &WGIfaceMonitor{
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start begins monitoring the WireGuard interface.
|
||||
// It relies on the provided context cancellation to stop.
|
||||
func (m *WGIfaceMonitor) Start(ctx context.Context, ifaceName string) (shouldRestart bool, err error) {
|
||||
defer close(m.done)
|
||||
|
||||
// Skip on mobile platforms as they handle interface lifecycle differently
|
||||
if runtime.GOOS == "android" || runtime.GOOS == "ios" {
|
||||
log.Debugf("Interface monitor: skipped on %s platform", runtime.GOOS)
|
||||
return false, errors.New("not supported on mobile platforms")
|
||||
}
|
||||
|
||||
if ifaceName == "" {
|
||||
log.Debugf("Interface monitor: empty interface name, skipping monitor")
|
||||
return false, errors.New("empty interface name")
|
||||
}
|
||||
|
||||
// Get initial interface index to track the specific interface instance
|
||||
expectedIndex, err := getInterfaceIndex(ifaceName)
|
||||
if err != nil {
|
||||
log.Debugf("Interface monitor: interface %s not found, skipping monitor", ifaceName)
|
||||
return false, fmt.Errorf("interface %s not found: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
log.Infof("Interface monitor: watching %s (index: %d)", ifaceName, expectedIndex)
|
||||
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Infof("Interface monitor: stopped for %s", ifaceName)
|
||||
return false, fmt.Errorf("wg interface monitor stopped: %v", ctx.Err())
|
||||
case <-ticker.C:
|
||||
currentIndex, err := getInterfaceIndex(ifaceName)
|
||||
if err != nil {
|
||||
// Interface was deleted
|
||||
log.Infof("Interface monitor: %s deleted", ifaceName)
|
||||
return true, fmt.Errorf("interface %s deleted: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
// Check if interface index changed (interface was recreated)
|
||||
if currentIndex != expectedIndex {
|
||||
log.Infof("Interface monitor: %s recreated (index changed from %d to %d), restarting engine",
|
||||
ifaceName, expectedIndex, currentIndex)
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// getInterfaceIndex returns the index of a network interface by name.
|
||||
// Returns an error if the interface is not found.
|
||||
func getInterfaceIndex(name string) (int, error) {
|
||||
if name == "" {
|
||||
return 0, fmt.Errorf("empty interface name")
|
||||
}
|
||||
ifi, err := net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
// Check if it's specifically a "not found" error
|
||||
if errors.Is(err, &net.OpError{}) {
|
||||
// On some systems, this might be a "not found" error
|
||||
return 0, fmt.Errorf("interface not found: %w", err)
|
||||
}
|
||||
return 0, fmt.Errorf("failed to lookup interface: %w", err)
|
||||
}
|
||||
if ifi == nil {
|
||||
return 0, fmt.Errorf("interface not found")
|
||||
}
|
||||
return ifi.Index, nil
|
||||
}
|
||||
Reference in New Issue
Block a user