mirror of
https://github.com/netbirdio/netbird.git
synced 2025-07-22 08:40:36 +02:00
With the lazy connection feature, the peer will connect to target peers on-demand. The trigger can be any IP traffic. This feature can be enabled with the NB_ENABLE_EXPERIMENTAL_LAZY_CONN environment variable. When the engine receives a network map, it binds a free UDP port for every remote peer, and the system configures WireGuard endpoints for these ports. When traffic appears on a UDP socket, the system removes this listener and starts the peer connection procedure immediately. Key changes Fix slow netbird status -d command Move from engine.go file to conn_mgr.go the peer connection related code Refactor the iface interface usage and moved interface file next to the engine code Add new command line flag and UI option to enable feature The peer.Conn struct is reusable after it has been closed. Change connection states Connection states Idle: The peer is not attempting to establish a connection. This typically means it's in a lazy state or the remote peer is expired. Connecting: The peer is actively trying to establish a connection. This occurs when the peer has entered an active state and is continuously attempting to reach the remote peer. Connected: A successful peer-to-peer connection has been established and communication is active.
139 lines
3.8 KiB
Go
139 lines
3.8 KiB
Go
package guard
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/cenkalti/backoff/v4"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type isConnectedFunc func() bool
|
|
|
|
// Guard is responsible for the reconnection logic.
|
|
// It will trigger to send an offer to the peer then has connection issues.
|
|
// Watch these events:
|
|
// - Relay client reconnected to home server
|
|
// - Signal server connection state changed
|
|
// - ICE connection disconnected
|
|
// - Relayed connection disconnected
|
|
// - ICE candidate changes
|
|
type Guard struct {
|
|
Reconnect chan struct{}
|
|
log *log.Entry
|
|
isConnectedOnAllWay isConnectedFunc
|
|
timeout time.Duration
|
|
srWatcher *SRWatcher
|
|
relayedConnDisconnected chan struct{}
|
|
iCEConnDisconnected chan struct{}
|
|
}
|
|
|
|
func NewGuard(log *log.Entry, isConnectedFn isConnectedFunc, timeout time.Duration, srWatcher *SRWatcher) *Guard {
|
|
return &Guard{
|
|
Reconnect: make(chan struct{}, 1),
|
|
log: log,
|
|
isConnectedOnAllWay: isConnectedFn,
|
|
timeout: timeout,
|
|
srWatcher: srWatcher,
|
|
relayedConnDisconnected: make(chan struct{}, 1),
|
|
iCEConnDisconnected: make(chan struct{}, 1),
|
|
}
|
|
}
|
|
|
|
func (g *Guard) Start(ctx context.Context, eventCallback func()) {
|
|
g.reconnectLoopWithRetry(ctx, eventCallback)
|
|
}
|
|
|
|
func (g *Guard) SetRelayedConnDisconnected() {
|
|
select {
|
|
case g.relayedConnDisconnected <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (g *Guard) SetICEConnDisconnected() {
|
|
select {
|
|
case g.iCEConnDisconnected <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// reconnectLoopWithRetry periodically check the connection status.
|
|
// Try to send offer while the P2P is not established or while the Relay is not connected if is it supported
|
|
func (g *Guard) reconnectLoopWithRetry(ctx context.Context, callback func()) {
|
|
waitForInitialConnectionTry(ctx)
|
|
|
|
srReconnectedChan := g.srWatcher.NewListener()
|
|
defer g.srWatcher.RemoveListener(srReconnectedChan)
|
|
|
|
ticker := g.prepareExponentTicker(ctx)
|
|
defer ticker.Stop()
|
|
|
|
tickerChannel := ticker.C
|
|
|
|
g.log.Infof("start reconnect loop...")
|
|
for {
|
|
select {
|
|
case t := <-tickerChannel:
|
|
if t.IsZero() {
|
|
g.log.Infof("retry timed out, stop periodic offer sending")
|
|
// after backoff timeout the ticker.C will be closed. We need to a dummy channel to avoid loop
|
|
tickerChannel = make(<-chan time.Time)
|
|
continue
|
|
}
|
|
|
|
if !g.isConnectedOnAllWay() {
|
|
callback()
|
|
}
|
|
|
|
case <-g.relayedConnDisconnected:
|
|
g.log.Debugf("Relay connection changed, reset reconnection ticker")
|
|
ticker.Stop()
|
|
ticker = g.prepareExponentTicker(ctx)
|
|
tickerChannel = ticker.C
|
|
|
|
case <-g.iCEConnDisconnected:
|
|
g.log.Debugf("ICE connection changed, reset reconnection ticker")
|
|
ticker.Stop()
|
|
ticker = g.prepareExponentTicker(ctx)
|
|
tickerChannel = ticker.C
|
|
|
|
case <-srReconnectedChan:
|
|
g.log.Debugf("has network changes, reset reconnection ticker")
|
|
ticker.Stop()
|
|
ticker = g.prepareExponentTicker(ctx)
|
|
tickerChannel = ticker.C
|
|
|
|
case <-ctx.Done():
|
|
g.log.Debugf("context is done, stop reconnect loop")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *Guard) prepareExponentTicker(ctx context.Context) *backoff.Ticker {
|
|
bo := backoff.WithContext(&backoff.ExponentialBackOff{
|
|
InitialInterval: 800 * time.Millisecond,
|
|
RandomizationFactor: 0.1,
|
|
Multiplier: 2,
|
|
MaxInterval: g.timeout,
|
|
Stop: backoff.Stop,
|
|
Clock: backoff.SystemClock,
|
|
}, ctx)
|
|
|
|
ticker := backoff.NewTicker(bo)
|
|
<-ticker.C // consume the initial tick what is happening right after the ticker has been created
|
|
|
|
return ticker
|
|
}
|
|
|
|
// Give chance to the peer to establish the initial connection.
|
|
// With it, we can decrease to send necessary offer
|
|
func waitForInitialConnectionTry(ctx context.Context) {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-time.After(3 * time.Second):
|
|
}
|
|
}
|