diff --git a/client/iface/device/device_filter.go b/client/iface/device/device_filter.go index 015f71ff4..708f38d26 100644 --- a/client/iface/device/device_filter.go +++ b/client/iface/device/device_filter.go @@ -29,8 +29,9 @@ type PacketFilter interface { type FilteredDevice struct { tun.Device - filter PacketFilter - mutex sync.RWMutex + filter PacketFilter + mutex sync.RWMutex + closeOnce sync.Once } // newDeviceFilter constructor function @@ -40,6 +41,20 @@ func newDeviceFilter(device tun.Device) *FilteredDevice { } } +// Close closes the underlying tun device exactly once. +// wireguard-go's netTun.Close() panics on double-close due to a bare close(channel), +// and multiple code paths can trigger Close on the same device. +func (d *FilteredDevice) Close() error { + var err error + d.closeOnce.Do(func() { + err = d.Device.Close() + }) + if err != nil { + return err + } + return nil +} + // Read wraps read method with filtering feature func (d *FilteredDevice) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { if n, err = d.Device.Read(bufs, sizes, offset); err != nil { diff --git a/client/iface/device/device_netstack.go b/client/iface/device/device_netstack.go index 40d8fdac8..e457657f7 100644 --- a/client/iface/device/device_netstack.go +++ b/client/iface/device/device_netstack.go @@ -82,7 +82,9 @@ func (t *TunNetstackDevice) create() (WGConfigurer, error) { t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.bind.ActivityRecorder()) err = t.configurer.ConfigureInterface(t.key, t.port) if err != nil { - _ = tunIface.Close() + if cErr := tunIface.Close(); cErr != nil { + log.Debugf("failed to close tun device: %v", cErr) + } return nil, fmt.Errorf("error configuring interface: %s", err) } diff --git a/client/iface/netstack/tun.go b/client/iface/netstack/tun.go index b2506b50d..346ae29ec 100644 --- a/client/iface/netstack/tun.go +++ b/client/iface/netstack/tun.go @@ -66,7 +66,7 @@ func (t *NetStackTun) Create() (tun.Device, *netstack.Net, error) { } }() - return nsTunDev, tunNet, nil + return t.tundev, tunNet, nil } func (t *NetStackTun) Close() error { diff --git a/client/internal/engine.go b/client/internal/engine.go index 4dbd5f45e..631910eb6 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -543,11 +543,12 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL) // monitor WireGuard interface lifecycle and restart engine on changes e.wgIfaceMonitor = NewWGIfaceMonitor() e.shutdownWg.Add(1) + wgIfaceName := e.wgInterface.Name() go func() { defer e.shutdownWg.Done() - if shouldRestart, err := e.wgIfaceMonitor.Start(e.ctx, e.wgInterface.Name()); shouldRestart { + if shouldRestart, err := e.wgIfaceMonitor.Start(e.ctx, wgIfaceName); shouldRestart { log.Infof("WireGuard interface monitor: %s, restarting engine", err) e.triggerClientRestart() } else if err != nil {