From 231054c1720f7a2d166e5e0d69ef7f8999697482 Mon Sep 17 00:00:00 2001 From: mlsmaycon Date: Fri, 4 Sep 2026 04:04:56 +0200 Subject: [PATCH 1/3] [client] Honor the start context while the embedded client authenticates Start took a start context but ran auth.NewAuth and Login on a context derived from Background, so the timeout only began to apply once authentication had already returned. Both calls retry, and a management endpoint that accepts connections but never completes a stream gives them nothing of their own to time out against, so a client could sit in startup indefinitely with the caller's deadline ignored. A process running many clients behind a bounded start concurrency loses more than one client to this: the stalled starts hold every slot, and the rest never launch at all. Observed with 50 clients against a rate-limiting endpoint, where seven came up, ten held the slots, and the remaining thirty-three never started. Neither call outlives startup, so moving them onto startCtx changes nothing about the running client's lifetime. --- client/embed/embed.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/embed/embed.go b/client/embed/embed.go index 32e567be4..543ec9561 100644 --- a/client/embed/embed.go +++ b/client/embed/embed.go @@ -300,13 +300,18 @@ func (c *Client) Start(startCtx context.Context) error { // nolint:staticcheck ctx = context.WithValue(ctx, system.DeviceNameCtxKey, c.deviceName) - authClient, err := auth.NewAuth(ctx, c.config.PrivateKey, c.config.ManagementURL, c.config) + // Authentication runs on startCtx, not the client's own context. Both + // calls retry, and a management endpoint that accepts connections but + // never completes a stream leaves them retrying with no deadline of their + // own, so a caller's start timeout has to reach them. Neither outlives + // startup: authClient is closed below. + authClient, err := auth.NewAuth(startCtx, c.config.PrivateKey, c.config.ManagementURL, c.config) if err != nil { return fmt.Errorf("create auth client: %w", err) } defer authClient.Close() - if err, _ := authClient.Login(ctx, c.setupKey, c.jwtToken); err != nil { + if err, _ := authClient.Login(startCtx, c.setupKey, c.jwtToken); err != nil { return fmt.Errorf("login: %w", err) } client := internal.NewConnectClient(ctx, c.config, c.recorder) From 3823bf5be928aff43a9cac3dde0b0210b5387c86 Mon Sep 17 00:00:00 2001 From: mlsmaycon Date: Fri, 4 Sep 2026 04:27:56 +0200 Subject: [PATCH 2/3] [client] Keep the device name on the context used to authenticate Moving authentication onto the start context dropped the device name with it: registration happens during login and reads the name from the context, so every client registered under the host's hostname instead of its configured name. A run of 100 embedded clients produced 191 peers all sharing one name. Carry the value onto the start context explicitly so the deadline and the device name travel together. --- client/embed/embed.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/embed/embed.go b/client/embed/embed.go index 543ec9561..8e1ecca77 100644 --- a/client/embed/embed.go +++ b/client/embed/embed.go @@ -305,13 +305,18 @@ func (c *Client) Start(startCtx context.Context) error { // never completes a stream leaves them retrying with no deadline of their // own, so a caller's start timeout has to reach them. Neither outlives // startup: authClient is closed below. - authClient, err := auth.NewAuth(startCtx, c.config.PrivateKey, c.config.ManagementURL, c.config) + // Carry the device name onto the start context: registration happens during + // login and reads it from there, so authenticating on a context without it + // would register every client under the host's hostname instead. + authCtx := context.WithValue(startCtx, system.DeviceNameCtxKey, c.deviceName) //nolint:staticcheck + + authClient, err := auth.NewAuth(authCtx, c.config.PrivateKey, c.config.ManagementURL, c.config) if err != nil { return fmt.Errorf("create auth client: %w", err) } defer authClient.Close() - if err, _ := authClient.Login(startCtx, c.setupKey, c.jwtToken); err != nil { + if err, _ := authClient.Login(authCtx, c.setupKey, c.jwtToken); err != nil { return fmt.Errorf("login: %w", err) } client := internal.NewConnectClient(ctx, c.config, c.recorder) From 6b871aa2b47f1dfc631062971fc5106efe162cb7 Mon Sep 17 00:00:00 2001 From: mlsmaycon Date: Fri, 4 Sep 2026 20:52:26 +0200 Subject: [PATCH 3/3] Bump wireguard-go for the idempotent netTun.Close Picks up netbirdio/wireguard-go#19, which guards netTun.Close so a second call no longer closes an already closed channel and panics the process. Running many embedded clients in one process hit that reliably: 500 clients against a staging deployment, and locally at 35 of 100. The pin also crosses netbirdio/wireguard-go#18, which disables gVisor TCP RACK loss detection on Windows, since master had already moved past the previous commit. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cede9c22d..1d7f39581 100644 --- a/go.mod +++ b/go.mod @@ -329,7 +329,7 @@ replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-2024 replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 -replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20260628102922-2834bebf6c1a +replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20260904184957-e5c60e1195b3 replace github.com/cloudflare/circl => codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6 diff --git a/go.sum b/go.sum index e5bf6248d..ced0fd726 100644 --- a/go.sum +++ b/go.sum @@ -492,8 +492,8 @@ github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470 github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45/go.mod h1:5/sjFmLb8O96B5737VCqhHyGRzNFIaN/Bu7ZodXc3qQ= github.com/netbirdio/wails/v3 v3.0.0-beta.3.0.20260825085513-5f07a01f7a78 h1:B/jRv24jnFeoA+VccxoCx6K94PUgsqR9wnshpeu9M+8= github.com/netbirdio/wails/v3 v3.0.0-beta.3.0.20260825085513-5f07a01f7a78/go.mod h1:/6QR46/nhGCSADHbS++XtDb9dkTnenTHlGskTPRo9S0= -github.com/netbirdio/wireguard-go v0.0.0-20260628102922-2834bebf6c1a h1:3CWK+yTvRKOcC0Q8VCTGy4l60TEb27CQVS7LkMxwjmw= -github.com/netbirdio/wireguard-go v0.0.0-20260628102922-2834bebf6c1a/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= +github.com/netbirdio/wireguard-go v0.0.0-20260904184957-e5c60e1195b3 h1:ES7IumZAY/yubKdBcCUCnbSYk3eyqAOeX8s1GfBuHLw= +github.com/netbirdio/wireguard-go v0.0.0-20260904184957-e5c60e1195b3/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=