mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-13 18:29:07 +02:00
Gated behind NB_DISABLE_WG_KEEP_ALIVE for battery measurements; unset the variable and the behaviour is unchanged. Peers start with the 25s persistent keepalive so an idle responder still emits traffic and triggers the handshake initiation, then the interval drops to zero once the watcher observes a fresh handshake. The keepalive is re-armed whenever a connection attempt restarts, so every new handshake gets initiated the same way. The watcher also stops its periodic handshake check after that first observation: with keepalive disabled an idle peer never rotates its handshake, so the periodic check would report a false disconnect every checkPeriod and trigger a full reconnect.
168 lines
4.0 KiB
Go
168 lines
4.0 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 and keepAlive
|
|
mu sync.Mutex
|
|
cancelFunc func()
|
|
updateWg sync.WaitGroup
|
|
keepAlive time.Duration
|
|
}
|
|
|
|
func NewEndpointUpdater(log *logrus.Entry, wgConfig WgConfig, initiator bool) *EndpointUpdater {
|
|
return &EndpointUpdater{
|
|
log: log,
|
|
wgConfig: wgConfig,
|
|
initiator: initiator,
|
|
keepAlive: defaultWgKeepAlive,
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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) DisableKeepAlive(presharedKey *wgtypes.Key) error {
|
|
if !isWgKeepAliveDisabled() {
|
|
return nil
|
|
}
|
|
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
if e.keepAlive == 0 {
|
|
return nil
|
|
}
|
|
|
|
e.waitForCloseTheDelayedUpdate()
|
|
e.keepAlive = 0
|
|
e.log.Debugf("disable WireGuard persistent keepalive")
|
|
return e.updateWireGuardPeer(nil, presharedKey)
|
|
}
|
|
|
|
func (e *EndpointUpdater) EnableKeepAlive() {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
e.keepAlive = defaultWgKeepAlive
|
|
}
|
|
|
|
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,
|
|
e.keepAlive,
|
|
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)
|
|
}
|