[PR #6854] [MERGED] [client] warm lazy connections from the DNS resolver #29126

Closed
opened 2026-08-05 08:07:30 -04:00 by saavagebueno · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/netbirdio/netbird/pull/6854
Author: @mlsmaycon
Created: 7/22/2026
Status: Merged
Merged: 7/22/2026
Merged by: @mlsmaycon

Base: mainHead: dns-lazy-conn-warmup


📝 Commits (10+)

  • 588735f [client] warm lazy connections from the DNS resolver
  • f4f3e29 Merge remote-tracking branch 'origin/main' into feature/dns-lazy-conn-warmup
  • b457cde [e2e] resolve endpoint before the proxy-peer gate to trigger DNS warm-up
  • 0325269 [e2e] resolve endpoint before the proxy-peer gate in the allowlist test
  • a0c1548 [e2e] register Bedrock provider with the normalized catalog model id
  • 82d8f0a [e2e] source Bedrock model from AWS_BEDROCK_MODEL and normalize catalog id
  • 38b377f [client] tie DNS warm-up dial to engine context; fix codespell
  • 7825036 Merge branch 'main' into feature/dns-lazy-conn-warmup
  • d820aaa [client] Address review comments on DNS lazy-connection warm-up
  • 7624c3d warm up only when multiple records

📊 Changes

14 files changed (+691 additions, -40 deletions)

View changed files

📝 client/internal/conn_mgr.go (+25 -3)
📝 client/internal/conn_mgr_test.go (+66 -0)
📝 client/internal/dns/local/local.go (+110 -20)
📝 client/internal/dns/local/local_test.go (+2 -2)
client/internal/dns/local/warmup_test.go (+204 -0)
📝 client/internal/dns/mock_server.go (+6 -0)
📝 client/internal/dns/server.go (+10 -2)
client/internal/dns_peer_activator.go (+76 -0)
client/internal/dns_peer_activator_test.go (+129 -0)
📝 client/internal/engine.go (+10 -0)
📝 e2e/agentnetwork/chat_test.go (+21 -4)
📝 e2e/agentnetwork/guardrail_test.go (+24 -5)
📝 e2e/agentnetwork/skiptls_test.go (+4 -2)
📝 e2e/agentnetwork/vllm_test.go (+4 -2)

📄 Description

Describe your changes

Supersedes #6767 (same change, moved to an unprefixed branch; review feedback from there is addressed here).

With lazy connections enabled, a peer is not dialed until on-demand traffic arrives, so the first request to a peer resolved by name (e.g. the agent-network reverse-proxy) races — or loses to — the WireGuard handshake. Activation was previously reactive only: a data-path packet or an inbound signal.

This adds a proactive, DNS-time trigger. When the local resolver answers for an overlay name, it now warms the lazy connection to the peer(s) the answer points at and waits briefly for one to connect before returning the response — so by the time the client sends its first packet the tunnel is already up.

  • dns/local: new PeerActivator capability + SetPeerActivator setter (mirrors the existing PeerConnectivity/SetPeerConnectivity injection). ServeDNS warms on the pre-filter answer, so activating a lazily-idle peer also lets it survive the disconnected-peer filter. Warm-up is scoped to match-only (non-authoritative) zones — the synthesized private-service zones and user-created zones — so plain peer-name lookups in the account's authoritative peer zone never wake idle peers. No-op when no activator is wired (lazy off) or the answer carries no peer IPs. Budget is NB_DNS_LAZY_WARMUP_TIMEOUT (default 2s, parsed once at construction, invalid values logged); on timeout the answer is returned anyway (never SERVFAIL).
  • The resolver-facing interfaces (PeerActivator, PeerConnectivity) take netip.Addr instead of string IPs; record addresses are extracted as netip.Addr (v4-mapped forms unmapped) and converted to string only at the peer.Status boundary.
  • SetPeerActivator is part of the dns.Server interface (no-op on the mock), so the engine wires it without a type assertion.
  • client/internal: a small engine-side adapter (dnsPeerActivator) resolves answer IPs to peers via Status.PeerStateByIP, activates them through ConnMgr.ActivatePeer (HA fan-out included), and polls PeerStateByIP until one is connected. The activation dial is tied to the engine's long-lived context so a handshake that outlasts the per-query wait still completes in the background. ConnMgr.ActivatePeer is safe for concurrent use (the lazy manager pointer is guarded by a dedicated RWMutex and the manager is internally synchronized), so the DNS path never contends with network-map processing on syncMsgMux.

Scope is overlay-only for free: the trigger lives in the local resolver, which only answers for NetBird-managed names; public/upstream DNS is unaffected. Already-connected peers short-circuit, so steady-state DNS latency is unchanged.

N/A — follow-up to the lazy-connection rollout; fixes the agent-network cold-start observed in the e2e (proxy peer stuck disconnected until traffic).

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)
  • This change does not modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — OR I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See CONTRIBUTING.md.

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Internal client behavior; the only knob is the optional NB_DNS_LAZY_WARMUP_TIMEOUT tuning env var with a safe default.

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Tests

  • dns/local: warm-up invokes the activator with the answer's peer address in match-only zones; authoritative-zone answers never trigger warm-up; no-activator path unchanged; no-answer queries don't invoke the activator; NB_DNS_LAZY_WARMUP_TIMEOUT parsing (valid/invalid/non-positive); extractRecordAddr unmaps v4-mapped record data.
  • client/internal: dnsPeerActivator skips connected/unknown/conn-less peers with no wait, returns as soon as a pending peer connects, and releases the DNS response at the budget when the peer stays idle; ConnMgr.ActivatePeer races the manager lifecycle cleanly under -race.
  • Agent-network e2e green on this change (with lazy connections enabled): https://github.com/netbirdio/netbird/actions/runs/29891467544


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features
    • DNS lookups can now trigger “lazy connection” warm-up for non-authoritative matching zones, activating peers found in A/AAAA answers.
    • Added a configurable per-query warm-up timeout for DNS-driven peer activation.
    • Bedrock routing now supports inference-profile configuration with normalized model identifiers.
  • Bug Fixes
    • Improved connection-manager safety during concurrent peer activation and lifecycle shutdown.
    • DNS warm-up now better handles IPv4/IPv6 addressing and avoids warming peers that are already connected, unknown, or unavailable.
  • Tests
    • Added concurrency and DNS warm-up coverage, including race-oriented lifecycle activation scenarios.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/netbirdio/netbird/pull/6854 **Author:** [@mlsmaycon](https://github.com/mlsmaycon) **Created:** 7/22/2026 **Status:** ✅ Merged **Merged:** 7/22/2026 **Merged by:** [@mlsmaycon](https://github.com/mlsmaycon) **Base:** `main` ← **Head:** `dns-lazy-conn-warmup` --- ### 📝 Commits (10+) - [`588735f`](https://github.com/netbirdio/netbird/commit/588735f2bcfe33324ce2e9b8313a4c9b41721e97) [client] warm lazy connections from the DNS resolver - [`f4f3e29`](https://github.com/netbirdio/netbird/commit/f4f3e29f0837ccc874f959c3d78d817232e5ef60) Merge remote-tracking branch 'origin/main' into feature/dns-lazy-conn-warmup - [`b457cde`](https://github.com/netbirdio/netbird/commit/b457cdedb54c433c3666dc1c69653632aed35d99) [e2e] resolve endpoint before the proxy-peer gate to trigger DNS warm-up - [`0325269`](https://github.com/netbirdio/netbird/commit/03252696b95abb7df8707eac056242153482b3bc) [e2e] resolve endpoint before the proxy-peer gate in the allowlist test - [`a0c1548`](https://github.com/netbirdio/netbird/commit/a0c1548069357d7cbc8ff7e38c193fd754a6cee3) [e2e] register Bedrock provider with the normalized catalog model id - [`82d8f0a`](https://github.com/netbirdio/netbird/commit/82d8f0a4ee8899f6da4491c867773ee10c517e71) [e2e] source Bedrock model from AWS_BEDROCK_MODEL and normalize catalog id - [`38b377f`](https://github.com/netbirdio/netbird/commit/38b377f21e28f3bf38880fc86b82ae02aa826dff) [client] tie DNS warm-up dial to engine context; fix codespell - [`7825036`](https://github.com/netbirdio/netbird/commit/7825036fae2b624cecff3489a596aec274184cf2) Merge branch 'main' into feature/dns-lazy-conn-warmup - [`d820aaa`](https://github.com/netbirdio/netbird/commit/d820aaa8fced831f23d3a4d17809b956e8fdc706) [client] Address review comments on DNS lazy-connection warm-up - [`7624c3d`](https://github.com/netbirdio/netbird/commit/7624c3d02c5843360fb0582a4e5be060476f34d3) warm up only when multiple records ### 📊 Changes **14 files changed** (+691 additions, -40 deletions) <details> <summary>View changed files</summary> 📝 `client/internal/conn_mgr.go` (+25 -3) 📝 `client/internal/conn_mgr_test.go` (+66 -0) 📝 `client/internal/dns/local/local.go` (+110 -20) 📝 `client/internal/dns/local/local_test.go` (+2 -2) ➕ `client/internal/dns/local/warmup_test.go` (+204 -0) 📝 `client/internal/dns/mock_server.go` (+6 -0) 📝 `client/internal/dns/server.go` (+10 -2) ➕ `client/internal/dns_peer_activator.go` (+76 -0) ➕ `client/internal/dns_peer_activator_test.go` (+129 -0) 📝 `client/internal/engine.go` (+10 -0) 📝 `e2e/agentnetwork/chat_test.go` (+21 -4) 📝 `e2e/agentnetwork/guardrail_test.go` (+24 -5) 📝 `e2e/agentnetwork/skiptls_test.go` (+4 -2) 📝 `e2e/agentnetwork/vllm_test.go` (+4 -2) </details> ### 📄 Description ## Describe your changes Supersedes #6767 (same change, moved to an unprefixed branch; review feedback from there is addressed here). With lazy connections enabled, a peer is not dialed until on-demand traffic arrives, so the first request to a peer resolved by name (e.g. the agent-network reverse-proxy) races — or loses to — the WireGuard handshake. Activation was previously reactive only: a data-path packet or an inbound signal. This adds a proactive, DNS-time trigger. When the local resolver answers for an overlay name, it now warms the lazy connection to the peer(s) the answer points at and waits briefly for one to connect before returning the response — so by the time the client sends its first packet the tunnel is already up. - `dns/local`: new `PeerActivator` capability + `SetPeerActivator` setter (mirrors the existing `PeerConnectivity`/`SetPeerConnectivity` injection). `ServeDNS` warms on the pre-filter answer, so activating a lazily-idle peer also lets it survive the disconnected-peer filter. Warm-up is scoped to match-only (non-authoritative) zones — the synthesized private-service zones and user-created zones — so plain peer-name lookups in the account's authoritative peer zone never wake idle peers. No-op when no activator is wired (lazy off) or the answer carries no peer IPs. Budget is `NB_DNS_LAZY_WARMUP_TIMEOUT` (default 2s, parsed once at construction, invalid values logged); on timeout the answer is returned anyway (never SERVFAIL). - The resolver-facing interfaces (`PeerActivator`, `PeerConnectivity`) take `netip.Addr` instead of string IPs; record addresses are extracted as `netip.Addr` (v4-mapped forms unmapped) and converted to string only at the `peer.Status` boundary. - `SetPeerActivator` is part of the `dns.Server` interface (no-op on the mock), so the engine wires it without a type assertion. - `client/internal`: a small engine-side adapter (`dnsPeerActivator`) resolves answer IPs to peers via `Status.PeerStateByIP`, activates them through `ConnMgr.ActivatePeer` (HA fan-out included), and polls `PeerStateByIP` until one is connected. The activation dial is tied to the engine's long-lived context so a handshake that outlasts the per-query wait still completes in the background. `ConnMgr.ActivatePeer` is safe for concurrent use (the lazy manager pointer is guarded by a dedicated RWMutex and the manager is internally synchronized), so the DNS path never contends with network-map processing on `syncMsgMux`. Scope is overlay-only for free: the trigger lives in the local resolver, which only answers for NetBird-managed names; public/upstream DNS is unaffected. Already-connected peers short-circuit, so steady-state DNS latency is unchanged. ## Issue ticket number and link N/A — follow-up to the lazy-connection rollout; fixes the agent-network cold-start observed in the e2e (proxy peer stuck disconnected until traffic). ### Checklist - [x] Is it a bug fix - [ ] Is a typo/documentation fix - [ ] Is a feature enhancement - [ ] It is a refactor - [x] Created tests that fail without the change (if possible) - [x] This change does **not** modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — **OR** I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See [CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first). > By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md). ## Documentation Select exactly one: - [ ] I added/updated documentation for this change - [x] Documentation is **not needed** for this change (explain why) Internal client behavior; the only knob is the optional `NB_DNS_LAZY_WARMUP_TIMEOUT` tuning env var with a safe default. ### Docs PR URL (required if "docs added" is checked) Paste the PR link from https://github.com/netbirdio/docs here: https://github.com/netbirdio/docs/pull/__ ## Tests - `dns/local`: warm-up invokes the activator with the answer's peer address in match-only zones; authoritative-zone answers never trigger warm-up; no-activator path unchanged; no-answer queries don't invoke the activator; `NB_DNS_LAZY_WARMUP_TIMEOUT` parsing (valid/invalid/non-positive); `extractRecordAddr` unmaps v4-mapped record data. - `client/internal`: `dnsPeerActivator` skips connected/unknown/conn-less peers with no wait, returns as soon as a pending peer connects, and releases the DNS response at the budget when the peer stays idle; `ConnMgr.ActivatePeer` races the manager lifecycle cleanly under `-race`. - Agent-network e2e green on this change (with lazy connections enabled): https://github.com/netbirdio/netbird/actions/runs/29891467544 --- <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/netbirdio/codesmith/netbird/pr/6854"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img alt="View with Codesmith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1787290846&installation_model_id=427504&pr_number=6854&repository=netbirdio%2Fnetbird&return_to=https%3A%2F%2Fgithub.com%2Fnetbirdio%2Fnetbird%2Fpull%2F6854&signature=9bd925b54ca6cc7844770b509aff6d20bbd1ed27aefca8f36bf5571e7ec7ff52"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with Codesmith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a> <sup>Need help on this PR? Tag <code>/codesmith</code> with what you need. Autofix is disabled.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * DNS lookups can now trigger “lazy connection” warm-up for non-authoritative matching zones, activating peers found in A/AAAA answers. * Added a configurable per-query warm-up timeout for DNS-driven peer activation. * Bedrock routing now supports inference-profile configuration with normalized model identifiers. * **Bug Fixes** * Improved connection-manager safety during concurrent peer activation and lifecycle shutdown. * DNS warm-up now better handles IPv4/IPv6 addressing and avoids warming peers that are already connected, unknown, or unavailable. * **Tests** * Added concurrency and DNS warm-up coverage, including race-oriented lifecycle activation scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
saavagebueno added the pull-request label 2026-08-05 08:07:30 -04:00
Sign in to join this conversation.
No Label pull-request
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#29126