Skip to content

[client] Feature/lazy connection #3379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 109 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
db278db
Initial concept
pappz Feb 20, 2025
c8b031e
Merge branch 'main' into feature/lazy-connection
pappz Feb 24, 2025
d6e1850
Fix logic
pappz Feb 24, 2025
0457251
Add for engine
pappz Feb 24, 2025
8542674
Create conn mgr
pappz Feb 25, 2025
309e825
Add test for port allocator
pappz Feb 26, 2025
e2e1458
Remove watcher
pappz Feb 26, 2025
f898904
Handle env var
pappz Feb 26, 2025
e6e070b
Add log lines
pappz Feb 26, 2025
884d10c
Add log lines
pappz Feb 26, 2025
9188fca
Fix constructor of conn.go
pappz Feb 26, 2025
6ad66fe
Fix slow netbird status -d issue
pappz Feb 26, 2025
a27227f
Fix ip indication for lazy peers in status
pappz Feb 26, 2025
fcca194
Fix close
pappz Feb 27, 2025
4f668fd
Fix context cancellation
pappz Feb 27, 2025
aa3cd20
Fix blocking Close
pappz Feb 27, 2025
d0d37ba
Rename fake to listener
pappz Feb 27, 2025
4a5edc1
Fix postponed remove endpoint config
pappz Feb 27, 2025
4e33582
Fix deprecated event handling
pappz Feb 28, 2025
feb8355
Fix deadlock
pappz Feb 28, 2025
4c39ba3
Fix close channel creation
pappz Feb 28, 2025
f8a4cfb
Fix TestEngine_UpdateNetworkMap
pappz Feb 28, 2025
b38ddd8
Fix TestEngine_UpdateNetworkMap
pappz Feb 28, 2025
3ff3bbe
Replace os.setenv to t.setenv
pappz Feb 28, 2025
a89a6c0
Fix use mgr without start
pappz Feb 28, 2025
04f18bf
Fix unit test
pappz Feb 28, 2025
0f1f023
Disable lazy connection in multiple peers test
pappz Feb 28, 2025
a3e7604
Avoid panic in test
pappz Feb 28, 2025
f09d861
Fix udp address bind
pappz Feb 28, 2025
dddb4bc
Fix minimum port range
pappz Feb 28, 2025
ab7463d
Change retry limit
pappz Feb 28, 2025
bfe71a3
Merge branch 'main' into feature/lazy-connection
pappz Mar 7, 2025
04f127f
Merge feature/lazy-connection-idle
pappz Mar 14, 2025
3ead624
Merge branch 'main' into feature/lazy-connection
pappz Mar 14, 2025
fa24bee
Fix close methods
pappz Mar 14, 2025
96c3418
Change timeout period
pappz Mar 14, 2025
8d052e3
Remove peer dependency from lazy connection
pappz Mar 14, 2025
171b2a9
Update guard logic and fix status handling
pappz Mar 16, 2025
48cb292
Fix timeout threshold
pappz Mar 17, 2025
3e1c5cb
Rename function
pappz Mar 17, 2025
20de3b8
Read inactivity threshold from env
pappz Mar 17, 2025
bcbc3f3
Manage exclude list
pappz Mar 17, 2025
a3b31d4
Merge branch 'main' into feature/lazy-connection
pappz Mar 18, 2025
dbb784c
Put back context cancel check
pappz Mar 18, 2025
475dd8f
Change env var
pappz Mar 18, 2025
8b869c8
Fix exclude list handling in case of disabled lazy mgr
pappz Mar 18, 2025
3d5904c
Fix issue after merge
pappz Mar 18, 2025
160201b
Fix ctx handling
pappz Mar 18, 2025
8925f68
Fix engine tests
pappz Mar 20, 2025
8ed5670
Remove unused variable
pappz Mar 20, 2025
0565529
Fix unnecessary assignment to the blank identifier
pappz Mar 20, 2025
1a183f8
Fix error handling
pappz Mar 20, 2025
c7fdb7f
Fix status test
pappz Mar 20, 2025
dad77ab
Trigger test
pappz Mar 20, 2025
55dbd87
Start conn mgr in engine tests
pappz Mar 20, 2025
da27c32
Extend moc iface with updatePeerFunc
pappz Mar 20, 2025
2693d55
Fix sonar issue
pappz Mar 20, 2025
c0ea2f4
Add doc
pappz Mar 20, 2025
2a225b0
Add thread protection
pappz Mar 20, 2025
24cdc09
Fix ebpf
pappz Mar 20, 2025
294e77a
Merge branch 'main' into feature/lazy-connection
pappz Mar 20, 2025
9819f84
Fix import
pappz Mar 20, 2025
5b96fd6
Move en handling to upper layer
pappz Mar 21, 2025
3a3a7f2
Use connID instead of peerid
pappz Mar 21, 2025
1b01637
Fix lint
pappz Mar 21, 2025
20371f1
Fix lint
pappz Mar 21, 2025
7b5d370
Remove unused function
pappz Mar 21, 2025
49037c3
Use peer conn id inside sub packages
pappz Mar 21, 2025
4241d06
Add test
pappz Mar 21, 2025
34e448d
Change env var names
pappz Mar 23, 2025
3081c01
Merge branch 'main' into feature/lazy-connection
pappz Mar 24, 2025
fba3233
Source code fmt
pappz Mar 24, 2025
5ae8749
Fix connection indication
pappz Mar 24, 2025
08417c3
Fix test
pappz Mar 24, 2025
9415cbd
Lint fix
pappz Mar 24, 2025
e3c3cd8
Add command line parameter to enable-disable lazy connection
pappz Mar 25, 2025
f57293f
Fix test
pappz Mar 25, 2025
ab2db8e
Fix status output test
pappz Mar 25, 2025
062267f
Wire-up the env and cmd line variables
pappz Mar 25, 2025
103e267
Fix RunHealthProbes
pappz Mar 25, 2025
5ec57c5
Fix turn probes
pappz Mar 25, 2025
8dfb02e
Add lazy connection to UI
pappz Mar 25, 2025
a089690
Add experimental keyword to the UI and cmd line
pappz Mar 26, 2025
be731c8
Sonar fix
pappz Mar 26, 2025
65f1f87
Fix ICE worker state check
pappz Mar 26, 2025
0152bff
Explicit reset the conn state in Close()
pappz Mar 27, 2025
7fd031f
Merge branch 'main' into feature/lazy-connection
pappz Apr 17, 2025
b63e86b
Fix idle status filter
pappz Apr 24, 2025
0723d28
Fix deadlock
pappz Apr 24, 2025
70f55af
[client] Feature/lazy connection backward (#3718)
pappz Apr 25, 2025
fed83c8
Merge branch 'main' into feature/lazy-connection
pappz Apr 25, 2025
40a921e
[client] Automatically start the connection if exclude list is changi…
pappz May 2, 2025
c5f2b3d
Merge branch 'main' into feature/lazy-connection
pappz May 2, 2025
e2ccccd
Merge branch 'main' into feature/lazy-connection
pappz May 5, 2025
e503148
Fix nil pointer exception if the engine is not started yet and call s…
pappz May 6, 2025
7eb8e3b
Fix exclude list handling (#3797)
pappz May 7, 2025
8e64c9e
Feature/lazy connection status filter fix (#3795)
pappz May 7, 2025
895d589
Feature/lazy connection fix inactivity (#3801)
pappz May 8, 2025
63e3a17
[client] Feature/lazy manageable feature by mgm server (#3818)
pappz May 15, 2025
f6ac268
Fix command line description text
pappz May 15, 2025
0e7d6c3
Reduce the buffer size
pappz May 15, 2025
176b824
Remove port allocator
pappz May 15, 2025
71d683d
Change log message
pappz May 15, 2025
cf36afe
Convert bool to atomic bool
pappz May 15, 2025
e6b4db8
Add err context
pappz May 15, 2025
98fc917
Merge branch 'main' into feature/lazy-connection
pappz May 15, 2025
6469fcc
Update minimum version
pappz May 19, 2025
6fbe25f
Fix support test
pappz May 19, 2025
e021f77
Update minimum version
pappz May 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions client/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,23 @@ import (
)

const (
externalIPMapFlag = "external-ip-map"
dnsResolverAddress = "dns-resolver-address"
enableRosenpassFlag = "enable-rosenpass"
rosenpassPermissiveFlag = "rosenpass-permissive"
preSharedKeyFlag = "preshared-key"
interfaceNameFlag = "interface-name"
wireguardPortFlag = "wireguard-port"
networkMonitorFlag = "network-monitor"
disableAutoConnectFlag = "disable-auto-connect"
serverSSHAllowedFlag = "allow-server-ssh"
extraIFaceBlackListFlag = "extra-iface-blacklist"
dnsRouteIntervalFlag = "dns-router-interval"
systemInfoFlag = "system-info"
blockLANAccessFlag = "block-lan-access"
uploadBundle = "upload-bundle"
uploadBundleURL = "upload-bundle-url"
externalIPMapFlag = "external-ip-map"
dnsResolverAddress = "dns-resolver-address"
enableRosenpassFlag = "enable-rosenpass"
rosenpassPermissiveFlag = "rosenpass-permissive"
preSharedKeyFlag = "preshared-key"
interfaceNameFlag = "interface-name"
wireguardPortFlag = "wireguard-port"
networkMonitorFlag = "network-monitor"
disableAutoConnectFlag = "disable-auto-connect"
serverSSHAllowedFlag = "allow-server-ssh"
extraIFaceBlackListFlag = "extra-iface-blacklist"
dnsRouteIntervalFlag = "dns-router-interval"
systemInfoFlag = "system-info"
blockLANAccessFlag = "block-lan-access"
enableLazyConnectionFlag = "enable-lazy-connection"
uploadBundle = "upload-bundle"
uploadBundleURL = "upload-bundle-url"
)

var (
Expand Down Expand Up @@ -80,6 +81,7 @@ var (
blockLANAccess bool
debugUploadBundle bool
debugUploadBundleURL string
lazyConnEnabled bool

rootCmd = &cobra.Command{
Use: "netbird",
Expand Down Expand Up @@ -184,6 +186,7 @@ func init() {
upCmd.PersistentFlags().BoolVar(&rosenpassPermissive, rosenpassPermissiveFlag, false, "[Experimental] Enable Rosenpass in permissive mode to allow this peer to accept WireGuard connections without requiring Rosenpass functionality from peers that do not have Rosenpass enabled.")
upCmd.PersistentFlags().BoolVar(&serverSSHAllowed, serverSSHAllowedFlag, false, "Allow SSH server on peer. If enabled, the SSH server will be permitted")
upCmd.PersistentFlags().BoolVar(&autoConnectDisabled, disableAutoConnectFlag, false, "Disables auto-connect feature. If enabled, then the client won't connect automatically when the service starts.")
upCmd.PersistentFlags().BoolVar(&lazyConnEnabled, enableLazyConnectionFlag, false, "[Experimental] Enable the lazy connection feature. If enabled, the client will establish connections on-demand.")

debugCmd.PersistentFlags().BoolVarP(&debugSystemInfoFlag, systemInfoFlag, "S", true, "Adds system information to the debug bundle")
debugCmd.PersistentFlags().BoolVarP(&debugUploadBundle, uploadBundle, "U", false, fmt.Sprintf("Uploads the debug bundle to a server from URL defined by %s", uploadBundleURL))
Expand Down
6 changes: 3 additions & 3 deletions client/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func init() {
statusCmd.MarkFlagsMutuallyExclusive("detail", "json", "yaml", "ipv4")
statusCmd.PersistentFlags().StringSliceVar(&ipsFilter, "filter-by-ips", []string{}, "filters the detailed output by a list of one or more IPs, e.g., --filter-by-ips 100.64.0.100,100.64.0.200")
statusCmd.PersistentFlags().StringSliceVar(&prefixNamesFilter, "filter-by-names", []string{}, "filters the detailed output by a list of one or more peer FQDN or hostnames, e.g., --filter-by-names peer-a,peer-b.netbird.cloud")
statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(connected|disconnected), e.g., --filter-by-status connected")
statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(idle|connecting|connected), e.g., --filter-by-status connected")
}

func statusFunc(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -127,12 +127,12 @@ func getStatus(ctx context.Context) (*proto.StatusResponse, error) {

func parseFilters() error {
switch strings.ToLower(statusFilter) {
case "", "disconnected", "connected":
case "", "idle", "connecting", "connected":
if strings.ToLower(statusFilter) != "" {
enableDetailFlagWhenFilterFlag()
}
default:
return fmt.Errorf("wrong status filter, should be one of connected|disconnected, got: %s", statusFilter)
return fmt.Errorf("wrong status filter, should be one of connected|connecting|idle, got: %s", statusFilter)
}

if len(ipsFilter) > 0 {
Expand Down
8 changes: 8 additions & 0 deletions client/cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
ic.BlockLANAccess = &blockLANAccess
}

if cmd.Flag(enableLazyConnectionFlag).Changed {
ic.LazyConnectionEnabled = &lazyConnEnabled
}

providedSetupKey, err := getSetupKey()
if err != nil {
return err
Expand Down Expand Up @@ -332,6 +336,10 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
loginRequest.BlockLanAccess = &blockLANAccess
}

if cmd.Flag(enableLazyConnectionFlag).Changed {
loginRequest.LazyConnectionEnabled = &lazyConnEnabled
}

var loginErr error

var loginResp *proto.LoginResponse
Expand Down
32 changes: 24 additions & 8 deletions client/iface/configurer/kernel_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,30 @@ func (c *KernelConfigurer) configure(config wgtypes.Config) error {
func (c *KernelConfigurer) Close() {
}

func (c *KernelConfigurer) GetStats(peerKey string) (WGStats, error) {
peer, err := c.getPeer(c.deviceName, peerKey)
func (c *KernelConfigurer) GetStats() (map[string]WGStats, error) {
stats := make(map[string]WGStats)
wg, err := wgctrl.New()
if err != nil {
return nil, fmt.Errorf("wgctl: %w", err)
}
defer func() {
err = wg.Close()
if err != nil {
log.Errorf("Got error while closing wgctl: %v", err)
}
}()

wgDevice, err := wg.Device(c.deviceName)
if err != nil {
return WGStats{}, fmt.Errorf("get wireguard stats: %w", err)
return nil, fmt.Errorf("get device %s: %w", c.deviceName, err)
}

for _, peer := range wgDevice.Peers {
stats[peer.PublicKey.String()] = WGStats{
LastHandshake: peer.LastHandshakeTime,
TxBytes: peer.TransmitBytes,
RxBytes: peer.ReceiveBytes,
}
}
return WGStats{
LastHandshake: peer.LastHandshakeTime,
TxBytes: peer.TransmitBytes,
RxBytes: peer.ReceiveBytes,
}, nil
return stats, nil
}
140 changes: 74 additions & 66 deletions client/iface/configurer/usp.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package configurer

import (
"encoding/base64"
"encoding/hex"
"fmt"
"net"
Expand All @@ -17,6 +18,13 @@ import (
nbnet "github.com/netbirdio/netbird/util/net"
)

const (
ipcKeyLastHandshakeTimeSec = "last_handshake_time_sec"
ipcKeyLastHandshakeTimeNsec = "last_handshake_time_nsec"
ipcKeyTxBytes = "tx_bytes"
ipcKeyRxBytes = "rx_bytes"
)

var ErrAllowedIPNotFound = fmt.Errorf("allowed IP not found")

type WGUSPConfigurer struct {
Expand Down Expand Up @@ -217,91 +225,75 @@ func (t *WGUSPConfigurer) Close() {
}
}

func (t *WGUSPConfigurer) GetStats(peerKey string) (WGStats, error) {
func (t *WGUSPConfigurer) GetStats() (map[string]WGStats, error) {
ipc, err := t.device.IpcGet()
if err != nil {
return WGStats{}, fmt.Errorf("ipc get: %w", err)
return nil, fmt.Errorf("ipc get: %w", err)
}

stats, err := findPeerInfo(ipc, peerKey, []string{
"last_handshake_time_sec",
"last_handshake_time_nsec",
"tx_bytes",
"rx_bytes",
})
if err != nil {
return WGStats{}, fmt.Errorf("find peer info: %w", err)
}

sec, err := strconv.ParseInt(stats["last_handshake_time_sec"], 10, 64)
if err != nil {
return WGStats{}, fmt.Errorf("parse handshake sec: %w", err)
}
nsec, err := strconv.ParseInt(stats["last_handshake_time_nsec"], 10, 64)
if err != nil {
return WGStats{}, fmt.Errorf("parse handshake nsec: %w", err)
}
txBytes, err := strconv.ParseInt(stats["tx_bytes"], 10, 64)
if err != nil {
return WGStats{}, fmt.Errorf("parse tx_bytes: %w", err)
}
rxBytes, err := strconv.ParseInt(stats["rx_bytes"], 10, 64)
if err != nil {
return WGStats{}, fmt.Errorf("parse rx_bytes: %w", err)
}

return WGStats{
LastHandshake: time.Unix(sec, nsec),
TxBytes: txBytes,
RxBytes: rxBytes,
}, nil
return parseTransfers(ipc)
}

func findPeerInfo(ipcInput string, peerKey string, searchConfigKeys []string) (map[string]string, error) {
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
return nil, fmt.Errorf("parse key: %w", err)
}

hexKey := hex.EncodeToString(peerKeyParsed[:])

lines := strings.Split(ipcInput, "\n")

configFound := map[string]string{}
foundPeer := false
func parseTransfers(ipc string) (map[string]WGStats, error) {
stats := make(map[string]WGStats)
var (
currentKey string
currentStats WGStats
hasPeer bool
)
lines := strings.Split(ipc, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)

// If we're within the details of the found peer and encounter another public key,
// this means we're starting another peer's details. So, stop.
if strings.HasPrefix(line, "public_key=") && foundPeer {
break
if strings.HasPrefix(line, "public_key=") {
peerID := strings.TrimPrefix(line, "public_key=")
h, err := hex.DecodeString(peerID)
if err != nil {
return nil, fmt.Errorf("decode peerID: %w", err)
}
currentKey = base64.StdEncoding.EncodeToString(h)
currentStats = WGStats{} // Reset stats for the new peer
hasPeer = true
stats[currentKey] = currentStats
continue
}

// Identify the peer with the specific public key
if line == fmt.Sprintf("public_key=%s", hexKey) {
foundPeer = true
if !hasPeer {
continue
}

for _, key := range searchConfigKeys {
if foundPeer && strings.HasPrefix(line, key+"=") {
v := strings.SplitN(line, "=", 2)
configFound[v[0]] = v[1]
}
key := strings.SplitN(line, "=", 2)
if len(key) != 2 {
continue
}
}

// todo: use multierr
for _, key := range searchConfigKeys {
if _, ok := configFound[key]; !ok {
return configFound, fmt.Errorf("config key not found: %s", key)
switch key[0] {
case ipcKeyLastHandshakeTimeSec:
hs, err := toLastHandshake(key[1])
if err != nil {
return nil, err
}
currentStats.LastHandshake = hs
stats[currentKey] = currentStats
case ipcKeyRxBytes:
rxBytes, err := toBytes(key[1])
if err != nil {
return nil, fmt.Errorf("parse rx_bytes: %w", err)
}
currentStats.RxBytes = rxBytes
stats[currentKey] = currentStats
case ipcKeyTxBytes:
TxBytes, err := toBytes(key[1])
if err != nil {
return nil, fmt.Errorf("parse tx_bytes: %w", err)
}
currentStats.TxBytes = TxBytes
stats[currentKey] = currentStats
}
}
if !foundPeer {
return nil, fmt.Errorf("%w: %s", ErrPeerNotFound, peerKey)
}

return configFound, nil
return stats, nil
}

func toWgUserspaceString(wgCfg wgtypes.Config) string {
Expand Down Expand Up @@ -355,6 +347,22 @@ func toWgUserspaceString(wgCfg wgtypes.Config) string {
return sb.String()
}

func toLastHandshake(stringVar string) (time.Time, error) {
sec, err := strconv.ParseInt(stringVar, 10, 64)
if err != nil {
return time.Time{}, fmt.Errorf("parse handshake sec: %w", err)
}
nsec, err := strconv.ParseInt(stringVar, 10, 64)
Comment on lines +350 to +355
Copy link
Preview

Copilot AI May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The toLastHandshake helper parses both seconds and nanoseconds from the same input string, resulting in incorrect timestamps. It should accept and parse two distinct values (sec and nsec) or refactor to use separate inputs.

Suggested change
func toLastHandshake(stringVar string) (time.Time, error) {
sec, err := strconv.ParseInt(stringVar, 10, 64)
if err != nil {
return time.Time{}, fmt.Errorf("parse handshake sec: %w", err)
}
nsec, err := strconv.ParseInt(stringVar, 10, 64)
func toLastHandshake(secStr string, nsecStr string) (time.Time, error) {
sec, err := strconv.ParseInt(secStr, 10, 64)
if err != nil {
return time.Time{}, fmt.Errorf("parse handshake sec: %w", err)
}
nsec, err := strconv.ParseInt(nsecStr, 10, 64)

Copilot uses AI. Check for mistakes.

if err != nil {
return time.Time{}, fmt.Errorf("parse handshake nsec: %w", err)
}
return time.Unix(sec, nsec), nil
}

func toBytes(s string) (int64, error) {
return strconv.ParseInt(s, 10, 64)
}

func getFwmark() int {
if nbnet.AdvancedRouting() {
return nbnet.ControlPlaneMark
Expand Down
Loading
Loading