mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-01 02:59:08 +02:00
When a peer went idle under lazy connections, the WireGuard peer was removed and the wake endpoint re-armed with only the overlay allowed IPs, dropping routed prefixes installed by the route manager. The asynchronous route-watcher re-add uses update_only, which is a silent no-op while the peer is absent, so routed subnets stayed black-holed until the peer was woken by other means. Split the idle transition from the full close: Conn.Idle tears down transports, cancels pending delayed endpoint updates and updates the status without touching the WireGuard peer. The activity listener then arms the wake endpoint via the new IdlePeerEndpoint operation, which removes and re-creates the peer in a single transaction: handshake state is dropped (keeping the handshake-first wake semantics of packet staging and first-packet capture/reinjection) while the currently installed allowed IPs are preserved. The read-modify-write runs under the interface mutex, serializing it against concurrent allowed IP updates from the route manager. Conn.Close no longer takes signalToRemote: the idle transition is the only path that signals GOAWAY to the remote peer.
151 lines
3.8 KiB
Go
151 lines
3.8 KiB
Go
package peer
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
)
|
|
|
|
const (
|
|
defaultWgKeepAlive = 25 * time.Second
|
|
fallbackDelay = 5 * time.Second
|
|
)
|
|
|
|
type EndpointUpdater struct {
|
|
log *logrus.Entry
|
|
wgConfig WgConfig
|
|
initiator bool
|
|
|
|
// mu protects cancelFunc
|
|
mu sync.Mutex
|
|
cancelFunc func()
|
|
updateWg sync.WaitGroup
|
|
}
|
|
|
|
func NewEndpointUpdater(log *logrus.Entry, wgConfig WgConfig, initiator bool) *EndpointUpdater {
|
|
return &EndpointUpdater{
|
|
log: log,
|
|
wgConfig: wgConfig,
|
|
initiator: initiator,
|
|
}
|
|
}
|
|
|
|
func (e *EndpointUpdater) ConfigureWGEndpoint(addr *net.UDPAddr, presharedKey *wgtypes.Key) error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
if e.initiator {
|
|
e.log.Debugf("configure up WireGuard as initiator")
|
|
return e.configureAsInitiator(addr, presharedKey)
|
|
}
|
|
|
|
e.log.Debugf("configure up WireGuard as responder")
|
|
return e.configureAsResponder(addr, presharedKey)
|
|
}
|
|
|
|
func (e *EndpointUpdater) SwitchWGEndpoint(addr *net.UDPAddr, presharedKey *wgtypes.Key) error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
// prevent to run new update while cancel the previous update
|
|
e.waitForCloseTheDelayedUpdate()
|
|
|
|
return e.updateWireGuardPeer(addr, presharedKey)
|
|
}
|
|
|
|
func (e *EndpointUpdater) RemoveWgPeer() error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
e.waitForCloseTheDelayedUpdate()
|
|
return e.wgConfig.WgInterface.RemovePeer(e.wgConfig.RemoteKey)
|
|
}
|
|
|
|
// CancelPendingUpdates stops a scheduled delayed endpoint update without touching the
|
|
// WireGuard peer. Used on the idle transition where the peer is kept so a pending
|
|
// responder-side update cannot overwrite the wake endpoint later.
|
|
func (e *EndpointUpdater) CancelPendingUpdates() {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
e.waitForCloseTheDelayedUpdate()
|
|
}
|
|
|
|
func (e *EndpointUpdater) RemoveEndpointAddress() error {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
e.waitForCloseTheDelayedUpdate()
|
|
return e.wgConfig.WgInterface.RemoveEndpointAddress(e.wgConfig.RemoteKey)
|
|
}
|
|
|
|
func (e *EndpointUpdater) configureAsInitiator(addr *net.UDPAddr, presharedKey *wgtypes.Key) error {
|
|
if err := e.updateWireGuardPeer(addr, presharedKey); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *EndpointUpdater) configureAsResponder(addr *net.UDPAddr, presharedKey *wgtypes.Key) error {
|
|
// prevent to run new update while cancel the previous update
|
|
e.waitForCloseTheDelayedUpdate()
|
|
|
|
e.log.Debugf("configure up WireGuard and wait for handshake")
|
|
var ctx context.Context
|
|
ctx, e.cancelFunc = context.WithCancel(context.Background())
|
|
e.updateWg.Add(1)
|
|
go e.scheduleDelayedUpdate(ctx, addr, presharedKey)
|
|
|
|
if err := e.updateWireGuardPeer(nil, presharedKey); err != nil {
|
|
e.waitForCloseTheDelayedUpdate()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *EndpointUpdater) waitForCloseTheDelayedUpdate() {
|
|
if e.cancelFunc == nil {
|
|
return
|
|
}
|
|
|
|
e.cancelFunc()
|
|
e.cancelFunc = nil
|
|
e.updateWg.Wait()
|
|
}
|
|
|
|
// scheduleDelayedUpdate waits for the fallback period before updating the endpoint
|
|
func (e *EndpointUpdater) scheduleDelayedUpdate(ctx context.Context, addr *net.UDPAddr, presharedKey *wgtypes.Key) {
|
|
defer e.updateWg.Done()
|
|
t := time.NewTimer(fallbackDelay)
|
|
defer t.Stop()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-t.C:
|
|
if err := e.updateWireGuardPeer(addr, presharedKey); err != nil {
|
|
e.log.Errorf("failed to update WireGuard peer, address: %s, error: %v", addr, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *EndpointUpdater) updateWireGuardPeer(endpoint *net.UDPAddr, presharedKey *wgtypes.Key) error {
|
|
return e.wgConfig.WgInterface.UpdatePeer(
|
|
e.wgConfig.RemoteKey,
|
|
e.wgConfig.AllowedIps,
|
|
defaultWgKeepAlive,
|
|
endpoint,
|
|
presharedKey,
|
|
)
|
|
}
|
|
|
|
// wgConfigWorkaround is a workaround for the issue with WireGuard configuration update
|
|
// When update a peer configuration in near to each other time, the second update can be ignored by WireGuard
|
|
func wgConfigWorkaround() {
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|