mirror of
https://github.com/netbirdio/netbird.git
synced 2025-07-25 10:06: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.
265 lines
7.1 KiB
Go
265 lines
7.1 KiB
Go
package iface
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/pion/transport/v3"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
|
|
wgdevice "golang.zx2c4.com/wireguard/device"
|
|
|
|
"github.com/netbirdio/netbird/client/errors"
|
|
"github.com/netbirdio/netbird/client/iface/bind"
|
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
|
"github.com/netbirdio/netbird/client/iface/device"
|
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
|
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
|
)
|
|
|
|
const (
|
|
DefaultMTU = 1280
|
|
DefaultWgPort = 51820
|
|
WgInterfaceDefault = configurer.WgInterfaceDefault
|
|
)
|
|
|
|
type wgProxyFactory interface {
|
|
GetProxy() wgproxy.Proxy
|
|
Free() error
|
|
}
|
|
|
|
type WGIFaceOpts struct {
|
|
IFaceName string
|
|
Address string
|
|
WGPort int
|
|
WGPrivKey string
|
|
MTU int
|
|
MobileArgs *device.MobileIFaceArguments
|
|
TransportNet transport.Net
|
|
FilterFn bind.FilterFn
|
|
}
|
|
|
|
// WGIface represents an interface instance
|
|
type WGIface struct {
|
|
tun WGTunDevice
|
|
userspaceBind bool
|
|
mu sync.Mutex
|
|
|
|
configurer device.WGConfigurer
|
|
filter device.PacketFilter
|
|
wgProxyFactory wgProxyFactory
|
|
}
|
|
|
|
func (w *WGIface) GetProxy() wgproxy.Proxy {
|
|
return w.wgProxyFactory.GetProxy()
|
|
}
|
|
|
|
// IsUserspaceBind indicates whether this interfaces is userspace with bind.ICEBind
|
|
func (w *WGIface) IsUserspaceBind() bool {
|
|
return w.userspaceBind
|
|
}
|
|
|
|
// Name returns the interface name
|
|
func (w *WGIface) Name() string {
|
|
return w.tun.DeviceName()
|
|
}
|
|
|
|
// Address returns the interface address
|
|
func (w *WGIface) Address() wgaddr.Address {
|
|
return w.tun.WgAddress()
|
|
}
|
|
|
|
// ToInterface returns the net.Interface for the Wireguard interface
|
|
func (r *WGIface) ToInterface() *net.Interface {
|
|
name := r.tun.DeviceName()
|
|
intf, err := net.InterfaceByName(name)
|
|
if err != nil {
|
|
log.Warnf("Failed to get interface by name %s: %v", name, err)
|
|
intf = &net.Interface{
|
|
Name: name,
|
|
}
|
|
}
|
|
return intf
|
|
}
|
|
|
|
// Up configures a Wireguard interface
|
|
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
|
func (w *WGIface) Up() (*bind.UniversalUDPMuxDefault, error) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
return w.tun.Up()
|
|
}
|
|
|
|
// UpdateAddr updates address of the interface
|
|
func (w *WGIface) UpdateAddr(newAddr string) error {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
addr, err := wgaddr.ParseWGAddress(newAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return w.tun.UpdateAddr(addr)
|
|
}
|
|
|
|
// UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist
|
|
// Endpoint is optional
|
|
func (w *WGIface) UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
netIPNets := prefixesToIPNets(allowedIps)
|
|
log.Debugf("updating interface %s peer %s, endpoint %s", w.tun.DeviceName(), peerKey, endpoint)
|
|
return w.configurer.UpdatePeer(peerKey, netIPNets, keepAlive, endpoint, preSharedKey)
|
|
}
|
|
|
|
// RemovePeer removes a Wireguard Peer from the interface iface
|
|
func (w *WGIface) RemovePeer(peerKey string) error {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
log.Debugf("Removing peer %s from interface %s ", peerKey, w.tun.DeviceName())
|
|
return w.configurer.RemovePeer(peerKey)
|
|
}
|
|
|
|
// AddAllowedIP adds a prefix to the allowed IPs list of peer
|
|
func (w *WGIface) AddAllowedIP(peerKey string, allowedIP string) error {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
log.Debugf("Adding allowed IP to interface %s and peer %s: allowed IP %s ", w.tun.DeviceName(), peerKey, allowedIP)
|
|
return w.configurer.AddAllowedIP(peerKey, allowedIP)
|
|
}
|
|
|
|
// RemoveAllowedIP removes a prefix from the allowed IPs list of peer
|
|
func (w *WGIface) RemoveAllowedIP(peerKey string, allowedIP string) error {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
log.Debugf("Removing allowed IP from interface %s and peer %s: allowed IP %s ", w.tun.DeviceName(), peerKey, allowedIP)
|
|
return w.configurer.RemoveAllowedIP(peerKey, allowedIP)
|
|
}
|
|
|
|
// Close closes the tunnel interface
|
|
func (w *WGIface) Close() error {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
var result *multierror.Error
|
|
|
|
if err := w.wgProxyFactory.Free(); err != nil {
|
|
result = multierror.Append(result, fmt.Errorf("failed to free WireGuard proxy: %w", err))
|
|
}
|
|
|
|
if err := w.tun.Close(); err != nil {
|
|
result = multierror.Append(result, fmt.Errorf("failed to close wireguard interface %s: %w", w.Name(), err))
|
|
}
|
|
|
|
if err := w.waitUntilRemoved(); err != nil {
|
|
log.Warnf("failed to remove WireGuard interface %s: %v", w.Name(), err)
|
|
if err := w.Destroy(); err != nil {
|
|
result = multierror.Append(result, fmt.Errorf("failed to remove WireGuard interface %s: %w", w.Name(), err))
|
|
return errors.FormatErrorOrNil(result)
|
|
}
|
|
log.Infof("interface %s successfully removed", w.Name())
|
|
}
|
|
|
|
return errors.FormatErrorOrNil(result)
|
|
}
|
|
|
|
// SetFilter sets packet filters for the userspace implementation
|
|
func (w *WGIface) SetFilter(filter device.PacketFilter) error {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
if w.tun.FilteredDevice() == nil {
|
|
return fmt.Errorf("userspace packet filtering not handled on this device")
|
|
}
|
|
|
|
w.filter = filter
|
|
w.filter.SetNetwork(w.tun.WgAddress().Network)
|
|
|
|
w.tun.FilteredDevice().SetFilter(filter)
|
|
return nil
|
|
}
|
|
|
|
// GetFilter returns packet filter used by interface if it uses userspace device implementation
|
|
func (w *WGIface) GetFilter() device.PacketFilter {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
return w.filter
|
|
}
|
|
|
|
// GetDevice to interact with raw device (with filtering)
|
|
func (w *WGIface) GetDevice() *device.FilteredDevice {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
return w.tun.FilteredDevice()
|
|
}
|
|
|
|
// GetWGDevice returns the WireGuard device
|
|
func (w *WGIface) GetWGDevice() *wgdevice.Device {
|
|
return w.tun.Device()
|
|
}
|
|
|
|
// GetStats returns the last handshake time, rx and tx bytes
|
|
func (w *WGIface) GetStats() (map[string]configurer.WGStats, error) {
|
|
return w.configurer.GetStats()
|
|
}
|
|
|
|
func (w *WGIface) waitUntilRemoved() error {
|
|
maxWaitTime := 5 * time.Second
|
|
timeout := time.NewTimer(maxWaitTime)
|
|
defer timeout.Stop()
|
|
|
|
for {
|
|
iface, err := net.InterfaceByName(w.Name())
|
|
if err != nil {
|
|
if _, ok := err.(*net.OpError); ok {
|
|
log.Infof("interface %s has been removed", w.Name())
|
|
return nil
|
|
}
|
|
log.Debugf("failed to get interface by name %s: %v", w.Name(), err)
|
|
} else if iface == nil {
|
|
log.Infof("interface %s has been removed", w.Name())
|
|
return nil
|
|
}
|
|
|
|
select {
|
|
case <-timeout.C:
|
|
return fmt.Errorf("timeout when waiting for interface %s to be removed", w.Name())
|
|
default:
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetNet returns the netstack.Net for the netstack device
|
|
func (w *WGIface) GetNet() *netstack.Net {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
return w.tun.GetNet()
|
|
}
|
|
|
|
func prefixesToIPNets(prefixes []netip.Prefix) []net.IPNet {
|
|
ipNets := make([]net.IPNet, len(prefixes))
|
|
for i, prefix := range prefixes {
|
|
ipNets[i] = net.IPNet{
|
|
IP: net.IP(prefix.Addr().AsSlice()), // Convert netip.Addr to net.IP
|
|
Mask: net.CIDRMask(prefix.Bits(), prefix.Addr().BitLen()), // Create subnet mask
|
|
}
|
|
}
|
|
return ipNets
|
|
}
|