[GH-ISSUE #5990] Per-peer and per-group connection-mode and inactivity-threshold override on the management server #12621

Open
opened 2026-08-05 02:06:17 -04:00 by saavagebueno · 0 comments
Owner

Originally created by @MichaelUray on GitHub (Apr 25, 2026).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/5990

Summary

cc @pappz — same context as the companion proposal #5989.

RFC-style proposal: extend the connection-mode and inactivity-threshold settings (proposed in companion issue #5989) from single account-wide values to a hierarchical per-peer / per-group / account resolution on the management server. Each client receives the resolved values via the existing NetworkMap.PeerConfig channel; client-side override (covered in the companion proposal) remains available for power users.

This addresses the same use case as #4758 (per-peer "server-only mode" request) with a more general mechanism. If maintainers agree this is the right direction, the original author of #4758 may want to consider whether their issue is still independently needed or can be closed in favor of this work.

Background

The management server already computes PeerConfig.LazyConnectionEnabled per-peer (engine.go:1385 consumes the per-peer field). Today the value sent is uniformly the account-wide setting Settings.LazyConnectionEnabled (management/server/types/settings.go) — there is no way for an admin to express "all my mobiles should be p2p-dynamic-lazy, all my servers p2p, my dev-laptop p2p-lazy, with custom inactivity thresholds for the lazy ones" without touching every peer individually via env/CLI.

NetBird already has a mature group system used for Access Policies, Routes, Setup Keys, and Posture Checks. Group-based connection-mode and threshold configuration is the natural extensibility point.

Proposed solution

Server-side resolution per peer in NetworkMap, applied independently to each setting (the connection-mode itself and each of the inactivity thresholds):

1. Explicit per-peer setting   (admin set on a specific peer in the dashboard)
   ↓ falls back to
2. Per-group setting           (peer is member of N groups; conflict resolved per the rule below)
   ↓ falls back to
3. Account default              (as today)

The resolved values are sent via the existing PeerConfig channel — connection_mode, ice_idle_threshold, relay_idle_threshold (newly added in the companion proposal; today only LazyConnectionEnabled exists).

Account-default and per-group are kept conceptually separate as today (account-default is the universal fallback, group is a selective layer) rather than collapsing into a single "group named All" data model — they have different semantics in the data model (Settings vs Groups relations) and the existing dashboard reflects that distinction.

Group-conflict resolution

When a peer is a member of multiple groups with different connection-modes, the server picks one before sending the NetworkMap. Proposal: most-restrictive wins, with strict ordering:

relay-forced > p2p > p2p-dynamic > p2p-dynamic-lazy > p2p-lazy

Rationale: an admin who has explicitly set relay-forced on a "forced-relay" group expects relay enforcement even if the peer is also in a "low-latency" group with p2p. Battery savings (p2p-lazy / p2p-dynamic-lazy / p2p-dynamic) lose to explicit performance / security choices.

For thresholds (when both per-group settings exist with different values), proposal: take the longer threshold (most permissive — keeps connections alive longer). Rationale: an admin extending the threshold via group membership is signaling "this peer needs longer-lived connections"; collapsing to the shorter would silently override that intent.

Use-case examples

Scenario How it's expressed
All mobiles → p2p-dynamic, servers → p2p, dev-laptop → p2p-lazy Tag mobiles with group Mobile (mode p2p-dynamic), servers with group Server (mode p2p), set explicit per-peer p2p-lazy on dev-laptop
Account-wide default p2p-dynamic, but locked-down branch → relay-forced Account default p2p-dynamic, group forced-relay set to relay-forced, peers in that group resolve to relay-forced
One restricted-network peer can only reach the server, never other peers (the original #4758 ask) Set explicit per-peer relay-forced on that one peer
Mobile fleet should drop relay after 4h truly idle (not the default 1h) On the Mobile group, set relay-idle-threshold = 4h alongside the mode
Power user wants p2p regardless of org default Client config --connection-mode=p2p (handled by the companion proposal's client-side precedence)
Power user wants to drop their local override and follow org policy again Client config --connection-mode=follow-server (handled by the companion proposal)

API / Dashboard surface

  • Extend Settings with optional per-group and per-peer maps for connection_mode, ice_idle_threshold, and relay_idle_threshold (or expose as separate sub-resources).
  • Dashboard: existing peer-detail and group-detail pages get a "Connection mode" dropdown with the five enum values plus an explicit "Follow account default" / "Follow group setting" sentinel option (preferred over implicit "absence means follow" — better discoverability and audit-clarity). Threshold inputs appear under the dropdown when the selected mode actually uses them.
  • Management API: add fields to peer-update and group-update endpoints; document the resolution order so external automation can predict the resolved value.

Audit logging

Reuse the existing activity.AccountLazyConnectionEnabled / Disabled event pattern (account.go:451). New event types per scope:

  • PeerConnectionModeChanged (peer-id + old + new)
  • GroupConnectionModeChanged (group-id + old + new)
  • AccountConnectionModeChanged (account-default change)
  • PeerInactivityThresholdChanged / GroupInactivityThresholdChanged / AccountInactivityThresholdChanged (one per threshold name + scope)

Dependencies

  • Depends on the companion proposal #5989 for the five-valued enum, the threshold settings, and the new PeerConfig fields (connection_mode, ice_idle_threshold, relay_idle_threshold). Without it, this issue is not actionable.
  • #4758 — same use case (per-peer relay enforcement), generalized here. Original author may want to consider closing if accepted.
  • Companion proposal #5989 — connection-mode enum and client-side precedence.
  • #5589 — also benefits because the per-group mechanism makes any future default less load-bearing.
Originally created by @MichaelUray on GitHub (Apr 25, 2026). Original GitHub issue: https://github.com/netbirdio/netbird/issues/5990 ## Summary cc @pappz — same context as the companion proposal #5989. RFC-style proposal: extend the connection-mode and inactivity-threshold settings (proposed in companion issue #5989) from single account-wide values to a hierarchical per-peer / per-group / account resolution on the management server. Each client receives the resolved values via the existing `NetworkMap.PeerConfig` channel; client-side override (covered in the companion proposal) remains available for power users. This addresses the same use case as [#4758](https://github.com/netbirdio/netbird/issues/4758) (per-peer "server-only mode" request) with a more general mechanism. If maintainers agree this is the right direction, the original author of #4758 may want to consider whether their issue is still independently needed or can be closed in favor of this work. ## Background The management server already computes `PeerConfig.LazyConnectionEnabled` per-peer ([engine.go:1385](https://github.com/netbirdio/netbird/blob/main/client/internal/engine.go#L1385) consumes the per-peer field). Today the value sent is uniformly the account-wide setting `Settings.LazyConnectionEnabled` ([management/server/types/settings.go](https://github.com/netbirdio/netbird/blob/main/management/server/types/settings.go)) — there is no way for an admin to express "all my mobiles should be `p2p-dynamic-lazy`, all my servers `p2p`, my dev-laptop `p2p-lazy`, with custom inactivity thresholds for the lazy ones" without touching every peer individually via env/CLI. NetBird already has a mature group system used for Access Policies, Routes, Setup Keys, and Posture Checks. Group-based connection-mode and threshold configuration is the natural extensibility point. ## Proposed solution Server-side resolution per peer in NetworkMap, applied independently to each setting (the connection-mode itself and each of the inactivity thresholds): ``` 1. Explicit per-peer setting (admin set on a specific peer in the dashboard) ↓ falls back to 2. Per-group setting (peer is member of N groups; conflict resolved per the rule below) ↓ falls back to 3. Account default (as today) ``` The resolved values are sent via the existing `PeerConfig` channel — `connection_mode`, `ice_idle_threshold`, `relay_idle_threshold` (newly added in the companion proposal; today only `LazyConnectionEnabled` exists). Account-default and per-group are kept conceptually separate as today (account-default is the universal fallback, group is a selective layer) rather than collapsing into a single "group named All" data model — they have different semantics in the data model (Settings vs Groups relations) and the existing dashboard reflects that distinction. ### Group-conflict resolution When a peer is a member of multiple groups with different connection-modes, the server picks one before sending the NetworkMap. **Proposal: most-restrictive wins, with strict ordering:** ``` relay-forced > p2p > p2p-dynamic > p2p-dynamic-lazy > p2p-lazy ``` Rationale: an admin who has explicitly set `relay-forced` on a "forced-relay" group expects relay enforcement even if the peer is also in a "low-latency" group with `p2p`. Battery savings (`p2p-lazy` / `p2p-dynamic-lazy` / `p2p-dynamic`) lose to explicit performance / security choices. For thresholds (when both per-group settings exist with different values), proposal: take the longer threshold (most permissive — keeps connections alive longer). Rationale: an admin extending the threshold via group membership is signaling "this peer needs longer-lived connections"; collapsing to the shorter would silently override that intent. ### Use-case examples | Scenario | How it's expressed | |---|---| | All mobiles → `p2p-dynamic`, servers → `p2p`, dev-laptop → `p2p-lazy` | Tag mobiles with group `Mobile` (mode `p2p-dynamic`), servers with group `Server` (mode `p2p`), set explicit per-peer `p2p-lazy` on dev-laptop | | Account-wide default `p2p-dynamic`, but locked-down branch → `relay-forced` | Account default `p2p-dynamic`, group `forced-relay` set to `relay-forced`, peers in that group resolve to `relay-forced` | | One restricted-network peer can only reach the server, never other peers (the original [#4758](https://github.com/netbirdio/netbird/issues/4758) ask) | Set explicit per-peer `relay-forced` on that one peer | | Mobile fleet should drop relay after 4h truly idle (not the default 1h) | On the `Mobile` group, set `relay-idle-threshold = 4h` alongside the mode | | Power user wants `p2p` regardless of org default | Client config `--connection-mode=p2p` (handled by the companion proposal's client-side precedence) | | Power user wants to drop their local override and follow org policy again | Client config `--connection-mode=follow-server` (handled by the companion proposal) | ### API / Dashboard surface - Extend `Settings` with optional per-group and per-peer maps for `connection_mode`, `ice_idle_threshold`, and `relay_idle_threshold` (or expose as separate sub-resources). - Dashboard: existing peer-detail and group-detail pages get a "Connection mode" dropdown with the five enum values plus an explicit "Follow account default" / "Follow group setting" sentinel option (preferred over implicit "absence means follow" — better discoverability and audit-clarity). Threshold inputs appear under the dropdown when the selected mode actually uses them. - Management API: add fields to peer-update and group-update endpoints; document the resolution order so external automation can predict the resolved value. ### Audit logging Reuse the existing `activity.AccountLazyConnectionEnabled` / `Disabled` event pattern ([account.go:451](https://github.com/netbirdio/netbird/blob/main/management/server/account.go#L451)). New event types per scope: - `PeerConnectionModeChanged` (peer-id + old + new) - `GroupConnectionModeChanged` (group-id + old + new) - `AccountConnectionModeChanged` (account-default change) - `PeerInactivityThresholdChanged` / `GroupInactivityThresholdChanged` / `AccountInactivityThresholdChanged` (one per threshold name + scope) ## Dependencies - Depends on the companion proposal #5989 for the five-valued enum, the threshold settings, and the new `PeerConfig` fields (`connection_mode`, `ice_idle_threshold`, `relay_idle_threshold`). Without it, this issue is not actionable. ## Related issues - [#4758](https://github.com/netbirdio/netbird/issues/4758) — same use case (per-peer relay enforcement), generalized here. Original author may want to consider closing if accepted. - Companion proposal #5989 — connection-mode enum and client-side precedence. - [#5589](https://github.com/netbirdio/netbird/issues/5589) — also benefits because the per-group mechanism makes any future default less load-bearing.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#12621