diff --git a/client/cmd/root.go b/client/cmd/root.go index c872fe9f673..22f7a17b103 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -39,6 +39,9 @@ const ( extraIFaceBlackListFlag = "extra-iface-blacklist" dnsRouteIntervalFlag = "dns-router-interval" enableLazyConnectionFlag = "enable-lazy-connection" + connectionModeFlag = "connection-mode" + relayTimeoutFlag = "relay-timeout" + p2pTimeoutFlag = "p2p-timeout" mtuFlag = "mtu" ) @@ -72,6 +75,9 @@ var ( anonymizeFlag bool dnsRouteInterval time.Duration lazyConnEnabled bool + connectionMode string + relayTimeoutSecs uint32 + p2pTimeoutSecs uint32 mtu uint16 profilesDisabled bool updateSettingsDisabled bool @@ -191,6 +197,13 @@ 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(&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. Note: this setting may be overridden by management configuration.") + upCmd.PersistentFlags().StringVar(&connectionMode, connectionModeFlag, "", + "[Experimental] Peer connection mode: relay-forced, p2p, p2p-lazy, p2p-dynamic, or follow-server. "+ + "Overrides the server-pushed value when set. Use follow-server to clear a previously-set local override.") + upCmd.PersistentFlags().Uint32Var(&relayTimeoutSecs, relayTimeoutFlag, 0, + "[Experimental] Relay-worker idle timeout in seconds. 0 = use server-pushed value (or built-in default).") + upCmd.PersistentFlags().Uint32Var(&p2pTimeoutSecs, p2pTimeoutFlag, 0, + "[Experimental] ICE-worker idle timeout in seconds. 0 = use server-pushed value (or built-in default). Only effective in p2p-dynamic mode (Phase 2).") } diff --git a/client/cmd/up.go b/client/cmd/up.go index f5766522a54..b1108fbbd35 100644 --- a/client/cmd/up.go +++ b/client/cmd/up.go @@ -434,6 +434,16 @@ func setupSetConfigReq(customDNSAddressConverted []byte, cmd *cobra.Command, pro req.LazyConnectionEnabled = &lazyConnEnabled } + if cmd.Flag(connectionModeFlag).Changed { + req.ConnectionMode = &connectionMode + } + if cmd.Flag(relayTimeoutFlag).Changed { + req.RelayTimeoutSeconds = &relayTimeoutSecs + } + if cmd.Flag(p2pTimeoutFlag).Changed { + req.P2PTimeoutSeconds = &p2pTimeoutSecs + } + return &req } @@ -550,6 +560,16 @@ func setupConfig(customDNSAddressConverted []byte, cmd *cobra.Command, configFil if cmd.Flag(enableLazyConnectionFlag).Changed { ic.LazyConnectionEnabled = &lazyConnEnabled } + + if cmd.Flag(connectionModeFlag).Changed { + ic.ConnectionMode = &connectionMode + } + if cmd.Flag(relayTimeoutFlag).Changed { + ic.RelayTimeoutSeconds = &relayTimeoutSecs + } + if cmd.Flag(p2pTimeoutFlag).Changed { + ic.P2pTimeoutSeconds = &p2pTimeoutSecs + } return &ic, nil } @@ -664,6 +684,16 @@ func setupLoginRequest(providedSetupKey string, customDNSAddressConverted []byte if cmd.Flag(enableLazyConnectionFlag).Changed { loginRequest.LazyConnectionEnabled = &lazyConnEnabled } + + if cmd.Flag(connectionModeFlag).Changed { + loginRequest.ConnectionMode = &connectionMode + } + if cmd.Flag(relayTimeoutFlag).Changed { + loginRequest.RelayTimeoutSeconds = &relayTimeoutSecs + } + if cmd.Flag(p2pTimeoutFlag).Changed { + loginRequest.P2PTimeoutSeconds = &p2pTimeoutSecs + } return &loginRequest, nil } diff --git a/client/internal/conn_mgr.go b/client/internal/conn_mgr.go index 112559132a1..3836e506b5d 100644 --- a/client/internal/conn_mgr.go +++ b/client/internal/conn_mgr.go @@ -12,8 +12,10 @@ import ( "github.com/netbirdio/netbird/client/internal/lazyconn" "github.com/netbirdio/netbird/client/internal/lazyconn/manager" "github.com/netbirdio/netbird/client/internal/peer" + "github.com/netbirdio/netbird/shared/connectionmode" "github.com/netbirdio/netbird/client/internal/peerstore" "github.com/netbirdio/netbird/route" + mgmProto "github.com/netbirdio/netbird/shared/management/proto" ) // ConnMgr coordinates both lazy connections (established on-demand) and permanent peer connections. @@ -28,9 +30,19 @@ type ConnMgr struct { peerStore *peerstore.Store statusRecorder *peer.Status iface lazyconn.WGIface - enabledLocally bool rosenpassEnabled bool + // Resolved values used to drive lifecycle decisions. Updated when + // the management server pushes a new PeerConfig. + mode connectionmode.Mode + relayTimeoutSecs uint32 + + // Raw inputs kept so we can re-resolve when server-pushed value changes. + envMode connectionmode.Mode + envRelayTimeout uint32 + cfgMode connectionmode.Mode + cfgRelayTimeout uint32 + lazyConnMgr *manager.Manager wg sync.WaitGroup @@ -39,72 +51,140 @@ type ConnMgr struct { } func NewConnMgr(engineConfig *EngineConfig, statusRecorder *peer.Status, peerStore *peerstore.Store, iface lazyconn.WGIface) *ConnMgr { - e := &ConnMgr{ + envMode, envRelayTimeout := peer.ResolveModeFromEnv() + + // First-pass resolution without server input -- updated later when + // the first NetworkMap arrives via UpdatedRemotePeerConfig. + mode, relayTimeout := resolveConnectionMode( + envMode, envRelayTimeout, + engineConfig.ConnectionMode, engineConfig.RelayTimeoutSeconds, + nil, + ) + + return &ConnMgr{ peerStore: peerStore, statusRecorder: statusRecorder, iface: iface, rosenpassEnabled: engineConfig.RosenpassEnabled, + mode: mode, + relayTimeoutSecs: relayTimeout, + envMode: envMode, + envRelayTimeout: envRelayTimeout, + cfgMode: engineConfig.ConnectionMode, + cfgRelayTimeout: engineConfig.RelayTimeoutSeconds, + } +} + +// resolveConnectionMode applies the spec-section-4.1 precedence chain: +// 1. client env (already resolved by caller via peer.ResolveModeFromEnv) +// 2. client config (from profile, including the FollowServer sentinel) +// 3. server-pushed PeerConfig.ConnectionMode (with UNSPECIFIED -> +// legacy LazyConnectionEnabled fallback) +// +// Returns the resolved Mode and the resolved relay-timeout in seconds +// (0 = use built-in default at the call site). +func resolveConnectionMode( + envMode connectionmode.Mode, + envRelayTimeout uint32, + cfgMode connectionmode.Mode, + cfgRelayTimeout uint32, + serverPC *mgmProto.PeerConfig, +) (connectionmode.Mode, uint32) { + mode := envMode + if mode == connectionmode.ModeUnspecified { + if cfgMode != connectionmode.ModeUnspecified && cfgMode != connectionmode.ModeFollowServer { + mode = cfgMode + } } - if engineConfig.LazyConnectionEnabled || lazyconn.IsLazyConnEnabledByEnv() { - e.enabledLocally = true + if mode == connectionmode.ModeUnspecified { + if serverPC != nil { + serverMode := connectionmode.FromProto(serverPC.GetConnectionMode()) + if serverMode != connectionmode.ModeUnspecified { + mode = serverMode + } else { + mode = connectionmode.ResolveLegacyLazyBool(serverPC.GetLazyConnectionEnabled()) + } + } else { + mode = connectionmode.ModeP2P // safe default when nothing at all is known + } } - return e + + // Relay-timeout precedence (analog). + relay := envRelayTimeout + if relay == 0 { + relay = cfgRelayTimeout + } + if relay == 0 && serverPC != nil { + relay = serverPC.GetRelayTimeoutSeconds() + } + + return mode, relay } -// Start initializes the connection manager and starts the lazy connection manager if enabled by env var or cmd line option. +// Start initializes the connection manager. If the resolved Mode at +// daemon startup is ModeP2PLazy, the lazy connection manager is brought +// up immediately; otherwise it stays dormant until UpdatedRemotePeerConfig +// transitions into lazy mode. func (e *ConnMgr) Start(ctx context.Context) { if e.lazyConnMgr != nil { log.Errorf("lazy connection manager is already started") return } - - if !e.enabledLocally { - log.Infof("lazy connection manager is disabled") + if e.mode != connectionmode.ModeP2PLazy { + log.Infof("lazy connection manager is disabled (mode=%s)", e.mode) return } - if e.rosenpassEnabled { - log.Warnf("rosenpass connection manager is enabled, lazy connection manager will not be started") + log.Warnf("rosenpass enabled, lazy connection manager will not be started") return } - e.initLazyManager(ctx) e.statusRecorder.UpdateLazyConnection(true) } -// UpdatedRemoteFeatureFlag is called when the remote feature flag is updated. -// If enabled, it initializes the lazy connection manager and start it. Do not need to call Start() again. -// If disabled, then it closes the lazy connection manager and open the connections to all peers. -func (e *ConnMgr) UpdatedRemoteFeatureFlag(ctx context.Context, enabled bool) error { - // do not disable lazy connection manager if it was enabled by env var - if e.enabledLocally { +// UpdatedRemotePeerConfig is called when the management server pushes a +// new PeerConfig. Re-resolves the effective mode through the precedence +// chain and starts/stops the lazy manager accordingly. +func (e *ConnMgr) UpdatedRemotePeerConfig(ctx context.Context, pc *mgmProto.PeerConfig) error { + newMode, newRelay := resolveConnectionMode(e.envMode, e.envRelayTimeout, e.cfgMode, e.cfgRelayTimeout, pc) + + if newMode == e.mode && newRelay == e.relayTimeoutSecs { return nil } - - if enabled { - // if the lazy connection manager is already started, do not start it again - if e.lazyConnMgr != nil { - return nil - } - + prev := e.mode + e.mode = newMode + e.relayTimeoutSecs = newRelay + + wasLazy := prev == connectionmode.ModeP2PLazy + isLazy := newMode == connectionmode.ModeP2PLazy + switch { + case !wasLazy && isLazy: if e.rosenpassEnabled { - log.Infof("rosenpass connection manager is enabled, lazy connection manager will not be started") + log.Warnf("rosenpass enabled, ignoring lazy mode push") return nil } - - log.Warnf("lazy connection manager is enabled by management feature flag") - e.initLazyManager(ctx) - e.statusRecorder.UpdateLazyConnection(true) - return e.addPeersToLazyConnManager() - } else { if e.lazyConnMgr == nil { - return nil + log.Infof("lazy connection manager enabled by management push (mode=%s)", newMode) + e.initLazyManager(ctx) } - log.Infof("lazy connection manager is disabled by management feature flag") + e.statusRecorder.UpdateLazyConnection(true) + return e.addPeersToLazyConnManager() + case wasLazy && !isLazy: + log.Infof("lazy connection manager disabled by management push (mode=%s)", newMode) e.closeManager(ctx) e.statusRecorder.UpdateLazyConnection(false) - return nil } + return nil +} + +// UpdatedRemoteFeatureFlag is the legacy entry point that only knows the +// boolean LazyConnectionEnabled field. Kept as a thin shim that builds a +// synthetic PeerConfig and delegates to UpdatedRemotePeerConfig. +// +// Deprecated: callers should switch to UpdatedRemotePeerConfig and pass +// the real PeerConfig so the new ConnectionMode + timeouts propagate. +func (e *ConnMgr) UpdatedRemoteFeatureFlag(ctx context.Context, enabled bool) error { + return e.UpdatedRemotePeerConfig(ctx, &mgmProto.PeerConfig{LazyConnectionEnabled: enabled}) } // UpdateRouteHAMap updates the route HA mappings in the lazy connection manager @@ -309,6 +389,18 @@ func (e *ConnMgr) isStartedWithLazyMgr() bool { return e.lazyConnMgr != nil && e.lazyCtxCancel != nil } +// Mode returns the currently resolved connection mode. Used by the engine +// when constructing per-peer connections (Phase 1 forwards it into +// peer.ConnConfig in a follow-up commit). +func (e *ConnMgr) Mode() connectionmode.Mode { + return e.mode +} + +// RelayTimeout returns the resolved relay-worker idle timeout in seconds. +func (e *ConnMgr) RelayTimeout() uint32 { + return e.relayTimeoutSecs +} + func inactivityThresholdEnv() *time.Duration { envValue := os.Getenv(lazyconn.EnvInactivityThreshold) if envValue == "" { diff --git a/client/internal/conn_mgr_test.go b/client/internal/conn_mgr_test.go new file mode 100644 index 00000000000..e422873e54a --- /dev/null +++ b/client/internal/conn_mgr_test.go @@ -0,0 +1,101 @@ +package internal + +import ( + "testing" + + "github.com/netbirdio/netbird/shared/connectionmode" + mgmProto "github.com/netbirdio/netbird/shared/management/proto" +) + +func TestResolveConnectionMode(t *testing.T) { + cases := []struct { + name string + envMode connectionmode.Mode + envTimeout uint32 + cfgMode connectionmode.Mode + cfgRelayTimeout uint32 + serverPC *mgmProto.PeerConfig + wantMode connectionmode.Mode + wantRelay uint32 + }{ + { + name: "all unspecified, server says legacy false -> P2P", + serverPC: &mgmProto.PeerConfig{LazyConnectionEnabled: false}, + wantMode: connectionmode.ModeP2P, + }, + { + name: "all unspecified, server says legacy true -> P2P_LAZY", + serverPC: &mgmProto.PeerConfig{LazyConnectionEnabled: true}, + wantMode: connectionmode.ModeP2PLazy, + }, + { + name: "server pushes new enum -> wins over legacy bool", + serverPC: &mgmProto.PeerConfig{ + ConnectionMode: mgmProto.ConnectionMode_CONNECTION_MODE_RELAY_FORCED, + LazyConnectionEnabled: false, + }, + wantMode: connectionmode.ModeRelayForced, + }, + { + name: "client config overrides server", + cfgMode: connectionmode.ModeP2PLazy, + serverPC: &mgmProto.PeerConfig{ + ConnectionMode: mgmProto.ConnectionMode_CONNECTION_MODE_P2P, + }, + wantMode: connectionmode.ModeP2PLazy, + }, + { + name: "follow-server in client config clears local override", + cfgMode: connectionmode.ModeFollowServer, + serverPC: &mgmProto.PeerConfig{ + ConnectionMode: mgmProto.ConnectionMode_CONNECTION_MODE_P2P_LAZY, + }, + wantMode: connectionmode.ModeP2PLazy, + }, + { + name: "env var beats client config", + envMode: connectionmode.ModeRelayForced, + cfgMode: connectionmode.ModeP2PLazy, + serverPC: &mgmProto.PeerConfig{ + ConnectionMode: mgmProto.ConnectionMode_CONNECTION_MODE_P2P, + }, + wantMode: connectionmode.ModeRelayForced, + }, + { + name: "env timeout beats server timeout", + envTimeout: 42, + serverPC: &mgmProto.PeerConfig{RelayTimeoutSeconds: 100}, + wantMode: connectionmode.ModeP2P, + wantRelay: 42, + }, + { + name: "client config timeout beats server", + cfgRelayTimeout: 50, + serverPC: &mgmProto.PeerConfig{RelayTimeoutSeconds: 200}, + wantMode: connectionmode.ModeP2P, + wantRelay: 50, + }, + { + name: "no env, no client, only server timeout", + serverPC: &mgmProto.PeerConfig{RelayTimeoutSeconds: 300}, + wantMode: connectionmode.ModeP2P, + wantRelay: 300, + }, + { + name: "nil serverPC defaults to P2P", + serverPC: nil, + wantMode: connectionmode.ModeP2P, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + gotMode, gotRelay := resolveConnectionMode(c.envMode, c.envTimeout, c.cfgMode, c.cfgRelayTimeout, c.serverPC) + if gotMode != c.wantMode { + t.Errorf("mode = %v, want %v", gotMode, c.wantMode) + } + if gotRelay != c.wantRelay { + t.Errorf("relay-timeout = %v, want %v", gotRelay, c.wantRelay) + } + }) + } +} diff --git a/client/internal/connect.go b/client/internal/connect.go index 72e096a80a1..0605c47da64 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -25,6 +25,7 @@ import ( "github.com/netbirdio/netbird/client/internal/listener" "github.com/netbirdio/netbird/client/internal/metrics" "github.com/netbirdio/netbird/client/internal/peer" + "github.com/netbirdio/netbird/shared/connectionmode" "github.com/netbirdio/netbird/client/internal/profilemanager" "github.com/netbirdio/netbird/client/internal/statemanager" "github.com/netbirdio/netbird/client/internal/stdnet" @@ -566,6 +567,10 @@ func createEngineConfig(key wgtypes.Key, config *profilemanager.Config, peerConf LazyConnectionEnabled: config.LazyConnectionEnabled, + ConnectionMode: parseConnectionMode(config.ConnectionMode), + RelayTimeoutSeconds: derefUint32(config.RelayTimeoutSeconds), + P2pTimeoutSeconds: derefUint32(config.P2pTimeoutSeconds), + MTU: selectMTU(config.MTU, peerConfig.Mtu), LogPath: logPath, @@ -695,3 +700,26 @@ func closeConnWithLog(conn *net.UDPConn) { log.Warnf("closing the testing port %d took %s. Usually it is safe to ignore, but continuous warnings may indicate a problem.", conn.LocalAddr().(*net.UDPAddr).Port, time.Since(startClosing)) } } + +// parseConnectionMode is a tolerant wrapper used by the EngineConfig builder. +// An invalid string in the persisted profile (e.g. left over from a +// downgrade-then-upgrade cycle) is logged and treated as Unspecified so the +// daemon falls through to env / server resolution rather than panicking. +func parseConnectionMode(s string) connectionmode.Mode { + m, err := connectionmode.ParseString(s) + if err != nil { + log.Warnf("ignoring invalid connection_mode %q in profile config: %v", s, err) + return connectionmode.ModeUnspecified + } + return m +} + +// derefUint32 returns 0 when ptr is nil, otherwise the dereferenced +// value. Used to flatten the nullable persisted timeouts into the +// uint32 fields the resolve chain currently expects. +func derefUint32(ptr *uint32) uint32 { + if ptr == nil { + return 0 + } + return *ptr +} diff --git a/client/internal/engine.go b/client/internal/engine.go index 351e4bfe940..08ee5fe7348 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -44,6 +44,7 @@ import ( nftypes "github.com/netbirdio/netbird/client/internal/netflow/types" "github.com/netbirdio/netbird/client/internal/networkmonitor" "github.com/netbirdio/netbird/client/internal/peer" + "github.com/netbirdio/netbird/shared/connectionmode" "github.com/netbirdio/netbird/client/internal/peer/guard" icemaker "github.com/netbirdio/netbird/client/internal/peer/ice" "github.com/netbirdio/netbird/client/internal/peerstore" @@ -135,6 +136,21 @@ type EngineConfig struct { LazyConnectionEnabled bool + // ConnectionMode is the resolved peer-connection mode for this daemon + // session. ModeUnspecified means "fall back to LazyConnectionEnabled". + // Set by the caller of NewEngine; usually populated from + // profilemanager.Config.ConnectionMode in connect.go. + ConnectionMode connectionmode.Mode + + // RelayTimeoutSeconds, when > 0, overrides the server-pushed relay + // timeout. 0 means "follow server-pushed value". + RelayTimeoutSeconds uint32 + + // P2pTimeoutSeconds, when > 0, overrides the server-pushed p2p timeout. + // 0 means "follow server-pushed value". Reserved for Phase 2 -- has no + // effect in Phase 1. + P2pTimeoutSeconds uint32 + MTU uint16 // for debug bundle generation @@ -1227,8 +1243,8 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error { return nil } - if err := e.connMgr.UpdatedRemoteFeatureFlag(e.ctx, networkMap.GetPeerConfig().GetLazyConnectionEnabled()); err != nil { - log.Errorf("failed to update lazy connection feature flag: %v", err) + if err := e.connMgr.UpdatedRemotePeerConfig(e.ctx, networkMap.GetPeerConfig()); err != nil { + log.Errorf("failed to update connection mode from PeerConfig: %v", err) } if e.firewall != nil { @@ -1557,6 +1573,7 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs []netip.Prefix, agentV PermissiveMode: e.config.RosenpassPermissive, }, ICEConfig: e.createICEConfig(), + Mode: e.connMgr.Mode(), } serviceDependencies := peer.ServiceDependencies{ diff --git a/client/internal/lazyconn/env.go b/client/internal/lazyconn/env.go index 649d1cd65de..cfdcc67d61d 100644 --- a/client/internal/lazyconn/env.go +++ b/client/internal/lazyconn/env.go @@ -12,6 +12,11 @@ const ( EnvInactivityThreshold = "NB_LAZY_CONN_INACTIVITY_THRESHOLD" ) +// IsLazyConnEnabledByEnv reads NB_ENABLE_EXPERIMENTAL_LAZY_CONN. +// +// Deprecated: use peer.ResolveModeFromEnv() -- kept here to not break +// existing callers in conn_mgr.go during the Phase-1 refactor; will be +// removed once all call sites use the new resolver. func IsLazyConnEnabledByEnv() bool { val := os.Getenv(EnvEnableLazyConn) if val == "" { diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index 1e416bfe707..3650ae7ff9f 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -16,6 +16,7 @@ import ( "github.com/netbirdio/netbird/client/iface/configurer" "github.com/netbirdio/netbird/client/iface/wgproxy" "github.com/netbirdio/netbird/client/internal/metrics" + "github.com/netbirdio/netbird/shared/connectionmode" "github.com/netbirdio/netbird/client/internal/peer/conntype" "github.com/netbirdio/netbird/client/internal/peer/dispatcher" "github.com/netbirdio/netbird/client/internal/peer/guard" @@ -86,6 +87,11 @@ type ConnConfig struct { // ICEConfig ICE protocol configuration ICEConfig icemaker.Config + + // Mode is the resolved connection mode for this peer (forwarded + // from the engine, which got it from the conn_mgr precedence chain). + // Phase 1 uses it to pick the skip-ICE branch when ModeRelayForced. + Mode connectionmode.Mode } type Conn struct { @@ -185,8 +191,12 @@ func (conn *Conn) Open(engineCtx context.Context) error { conn.workerRelay = NewWorkerRelay(conn.ctx, conn.Log, isController(conn.config), conn.config, conn, conn.relayManager) - forceRelay := IsForceRelayed() - if !forceRelay { + // Mode-driven branching. ModeRelayForced skips ICE entirely; all + // other modes (P2P, P2PLazy, P2PDynamic) construct workerICE + // eagerly in Phase 1. Phase 2 will branch P2PDynamic separately + // to defer the OnNewOffer registration. + skipICE := conn.config.Mode == connectionmode.ModeRelayForced + if !skipICE { relayIsSupportedLocally := conn.workerRelay.RelayIsSupportedLocally() workerICE, err := NewWorkerICE(conn.ctx, conn.Log, conn.config, conn, conn.signaler, conn.iFaceDiscover, conn.statusRecorder, relayIsSupportedLocally) if err != nil { @@ -198,7 +208,7 @@ func (conn *Conn) Open(engineCtx context.Context) error { conn.handshaker = NewHandshaker(conn.Log, conn.config, conn.signaler, conn.workerICE, conn.workerRelay, conn.metricsStages) conn.handshaker.AddRelayListener(conn.workerRelay.OnNewOffer) - if !forceRelay { + if !skipICE { conn.handshaker.AddICEListener(conn.workerICE.OnNewOffer) } @@ -740,7 +750,7 @@ func (conn *Conn) isConnectedOnAllWay() (status guard.ConnStatus) { } return evalConnStatus(connStatusInputs{ - forceRelay: IsForceRelayed(), + forceRelay: conn.config.Mode == connectionmode.ModeRelayForced, peerUsesRelay: conn.workerRelay.IsRelayConnectionSupportedWithPeer(), relayConnected: conn.statusRelay.Get() == worker.StatusConnected, remoteSupportsICE: conn.handshaker.RemoteICESupported(), diff --git a/client/internal/peer/env.go b/client/internal/peer/env.go index ed6a3af5391..ec4774addd4 100644 --- a/client/internal/peer/env.go +++ b/client/internal/peer/env.go @@ -1,16 +1,35 @@ package peer import ( + "math" "os" "runtime" + "strconv" "strings" + "sync" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/shared/connectionmode" ) const ( + EnvKeyNBConnectionMode = "NB_CONNECTION_MODE" EnvKeyNBForceRelay = "NB_FORCE_RELAY" EnvKeyNBHomeRelayServers = "NB_HOME_RELAY_SERVERS" + + envEnableLazyConn = "NB_ENABLE_EXPERIMENTAL_LAZY_CONN" + envInactivityThreshold = "NB_LAZY_CONN_INACTIVITY_THRESHOLD" ) +var deprecationOnce sync.Map // env-var name -> *sync.Once + +// IsForceRelayed reports whether legacy NB_FORCE_RELAY is set, plus the +// runtime-special-case js (always relayed because of browser limitations). +// +// Deprecated: prefer ResolveModeFromEnv. Kept for callers that haven't +// migrated yet (Phase 1 backwards compat). func IsForceRelayed() bool { if runtime.GOOS == "js" { return true @@ -18,6 +37,71 @@ func IsForceRelayed() bool { return strings.EqualFold(os.Getenv(EnvKeyNBForceRelay), "true") } +// ResolveModeFromEnv reads all three legacy env vars plus the new +// NB_CONNECTION_MODE, applies the documented precedence and returns +// the resolved Mode and relay-timeout (in seconds, 0 if unset). +// +// Precedence: +// 1. NB_CONNECTION_MODE if parseable -> wins +// 2. NB_FORCE_RELAY=true -> ModeRelayForced (most-restrictive) +// 3. NB_ENABLE_EXPERIMENTAL_LAZY_CONN=true -> ModeP2PLazy +// 4. otherwise -> ModeUnspecified (caller falls through) +// +// NB_LAZY_CONN_INACTIVITY_THRESHOLD is parsed independently as the +// relay-timeout (alias) and emits a deprecation-warning if used. +func ResolveModeFromEnv() (connectionmode.Mode, uint32) { + mode := connectionmode.ModeUnspecified + + if raw := os.Getenv(EnvKeyNBConnectionMode); raw != "" { + parsed, err := connectionmode.ParseString(raw) + if err != nil { + log.Warnf("ignoring %s=%q: %v", EnvKeyNBConnectionMode, raw, err) + } else if parsed != connectionmode.ModeUnspecified { + mode = parsed + } + } + + if mode == connectionmode.ModeUnspecified { + if strings.EqualFold(os.Getenv(EnvKeyNBForceRelay), "true") { + warnDeprecated(EnvKeyNBForceRelay, EnvKeyNBConnectionMode+"=relay-forced") + mode = connectionmode.ModeRelayForced + } else if isLazyEnvTrue() { + warnDeprecated(envEnableLazyConn, EnvKeyNBConnectionMode+"=p2p-lazy") + mode = connectionmode.ModeP2PLazy + } + } + + timeoutSecs := uint32(0) + if raw := os.Getenv(envInactivityThreshold); raw != "" { + d, err := time.ParseDuration(raw) + switch { + case err != nil: + log.Warnf("ignoring %s=%q: %v", envInactivityThreshold, raw, err) + case d < 0: + log.Warnf("ignoring %s=%q: must be >= 0", envInactivityThreshold, raw) + case d.Seconds() > float64(math.MaxUint32): + log.Warnf("ignoring %s=%q: %d s exceeds the supported range", envInactivityThreshold, raw, int64(d.Seconds())) + default: + timeoutSecs = uint32(d.Seconds()) + warnDeprecated(envInactivityThreshold, "the relay_timeout setting on the management server") + } + } + + return mode, timeoutSecs +} + +func isLazyEnvTrue() bool { + v, err := strconv.ParseBool(os.Getenv(envEnableLazyConn)) + return err == nil && v +} + +func warnDeprecated(envName, replacement string) { + once, _ := deprecationOnce.LoadOrStore(envName, &sync.Once{}) + once.(*sync.Once).Do(func() { + log.Warnf("env var %s is deprecated; use %s instead. The legacy var still works in this release but may be removed in a future major version.", envName, replacement) + }) +} + // OverrideRelayURLs returns the relay server URL list set in // NB_HOME_RELAY_SERVERS (comma-separated) and a boolean indicating whether // the override is active. When the env var is unset, the boolean is false diff --git a/client/internal/peer/env_test.go b/client/internal/peer/env_test.go new file mode 100644 index 00000000000..9b8653f5929 --- /dev/null +++ b/client/internal/peer/env_test.go @@ -0,0 +1,61 @@ +package peer + +import ( + "testing" + + "github.com/netbirdio/netbird/shared/connectionmode" +) + +func TestResolveModeFromEnv(t *testing.T) { + cases := []struct { + name string + envConnMode string + envForceRelay string + envEnableLazy string + envInactivity string + wantMode connectionmode.Mode + wantTimeoutSecs uint32 + }{ + {"all unset", "", "", "", "", connectionmode.ModeUnspecified, 0}, + {"connection_mode wins", "p2p-dynamic", "true", "true", "10s", connectionmode.ModeP2PDynamic, 10}, + {"force_relay alone", "", "true", "", "", connectionmode.ModeRelayForced, 0}, + {"lazy alone", "", "", "true", "", connectionmode.ModeP2PLazy, 0}, + {"force_relay AND lazy: force_relay wins", "", "true", "true", "", connectionmode.ModeRelayForced, 0}, + {"only inactivity threshold", "", "", "", "30m", connectionmode.ModeUnspecified, 1800}, + {"connection_mode unparsable falls through to legacy", "garbage", "true", "", "", connectionmode.ModeRelayForced, 0}, + {"connection_mode parses p2p-lazy", "p2p-lazy", "", "", "", connectionmode.ModeP2PLazy, 0}, + {"force-relay value is true (case-insensitive)", "", "TRUE", "", "", connectionmode.ModeRelayForced, 0}, + {"unparsable inactivity duration is ignored", "", "", "", "not-a-duration", connectionmode.ModeUnspecified, 0}, + {"negative inactivity duration is ignored", "", "", "", "-30s", connectionmode.ModeUnspecified, 0}, + {"oversized inactivity duration is ignored", "", "", "", "2000000h", connectionmode.ModeUnspecified, 0}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + t.Setenv(EnvKeyNBConnectionMode, c.envConnMode) + t.Setenv(EnvKeyNBForceRelay, c.envForceRelay) + t.Setenv("NB_ENABLE_EXPERIMENTAL_LAZY_CONN", c.envEnableLazy) + t.Setenv("NB_LAZY_CONN_INACTIVITY_THRESHOLD", c.envInactivity) + + gotMode, gotTimeout := ResolveModeFromEnv() + if gotMode != c.wantMode { + t.Errorf("mode = %v, want %v", gotMode, c.wantMode) + } + if gotTimeout != c.wantTimeoutSecs { + t.Errorf("timeout = %v, want %v", gotTimeout, c.wantTimeoutSecs) + } + }) + } +} + +func TestIsForceRelayedBackwardsCompat(t *testing.T) { + // IsForceRelayed must remain functional for existing callers + // during the migration window (env.go still exposes it). + t.Setenv(EnvKeyNBForceRelay, "true") + if !IsForceRelayed() { + t.Error("IsForceRelayed() should return true when NB_FORCE_RELAY=true") + } + t.Setenv(EnvKeyNBForceRelay, "false") + if IsForceRelayed() { + t.Error("IsForceRelayed() should return false when NB_FORCE_RELAY=false") + } +} diff --git a/client/internal/profilemanager/config.go b/client/internal/profilemanager/config.go index 20c615d579d..194ff409ebc 100644 --- a/client/internal/profilemanager/config.go +++ b/client/internal/profilemanager/config.go @@ -96,6 +96,10 @@ type ConfigInput struct { LazyConnectionEnabled *bool + ConnectionMode *string + RelayTimeoutSeconds *uint32 + P2pTimeoutSeconds *uint32 + MTU *uint16 } @@ -170,6 +174,17 @@ type Config struct { LazyConnectionEnabled bool + ConnectionMode string `json:",omitempty"` + // RelayTimeoutSeconds and P2pTimeoutSeconds are pointer-typed so the + // profile JSON can distinguish "no local override -> follow server" + // (nil / absent) from an explicit zero (a future "disable" sentinel). + // The current resolve chain in conn_mgr.go still flattens to uint32 + // and treats 0 as "fall through", so explicit zero is not yet honored + // at runtime; persisting the user's intent here keeps the profile + // data honest for when that resolve gets nullable-aware. + RelayTimeoutSeconds *uint32 `json:",omitempty"` + P2pTimeoutSeconds *uint32 `json:",omitempty"` + MTU uint16 } @@ -593,6 +608,24 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) { updated = true } + if input.ConnectionMode != nil && *input.ConnectionMode != config.ConnectionMode { + log.Infof("switching connection mode to %s", *input.ConnectionMode) + config.ConnectionMode = *input.ConnectionMode + updated = true + } + if input.RelayTimeoutSeconds != nil && (config.RelayTimeoutSeconds == nil || *input.RelayTimeoutSeconds != *config.RelayTimeoutSeconds) { + log.Infof("switching relay timeout to %d seconds", *input.RelayTimeoutSeconds) + v := *input.RelayTimeoutSeconds + config.RelayTimeoutSeconds = &v + updated = true + } + if input.P2pTimeoutSeconds != nil && (config.P2pTimeoutSeconds == nil || *input.P2pTimeoutSeconds != *config.P2pTimeoutSeconds) { + log.Infof("switching p2p timeout to %d seconds", *input.P2pTimeoutSeconds) + v := *input.P2pTimeoutSeconds + config.P2pTimeoutSeconds = &v + updated = true + } + if input.MTU != nil && *input.MTU != config.MTU { log.Infof("updating MTU to %d (old value %d)", *input.MTU, config.MTU) config.MTU = *input.MTU diff --git a/client/internal/profilemanager/config_test.go b/client/internal/profilemanager/config_test.go index 5216f2423cf..0c3e7fb3357 100644 --- a/client/internal/profilemanager/config_test.go +++ b/client/internal/profilemanager/config_test.go @@ -2,6 +2,7 @@ package profilemanager import ( "context" + "encoding/json" "errors" "os" "path/filepath" @@ -303,3 +304,51 @@ func TestUpdateOldManagementURL(t *testing.T) { }) } } + +// TestProfileTimeoutsNullableRoundtrip verifies that the *uint32 typing +// of RelayTimeoutSeconds and P2pTimeoutSeconds preserves an explicit +// zero across JSON marshal/unmarshal. With the previous `uint32 + +// omitempty` shape a user-set 0 would be dropped on save and rehydrate +// as the default 0, indistinguishable from "no override". +func TestProfileTimeoutsNullableRoundtrip(t *testing.T) { + zero := uint32(0) + val := uint32(43200) + + cases := []struct { + name string + cfg Config + }{ + {"both nil (no override)", Config{}}, + {"explicit zero relay only", Config{RelayTimeoutSeconds: &zero}}, + {"explicit zero p2p only", Config{P2pTimeoutSeconds: &zero}}, + {"both explicit zero", Config{RelayTimeoutSeconds: &zero, P2pTimeoutSeconds: &zero}}, + {"non-zero values", Config{RelayTimeoutSeconds: &val, P2pTimeoutSeconds: &val}}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + data, err := json.Marshal(c.cfg) + require.NoError(t, err) + + var out Config + require.NoError(t, json.Unmarshal(data, &out)) + + // Compare via the same nil/value rules. + if (c.cfg.RelayTimeoutSeconds == nil) != (out.RelayTimeoutSeconds == nil) { + t.Fatalf("RelayTimeoutSeconds nil-ness changed: in=%v out=%v", + c.cfg.RelayTimeoutSeconds, out.RelayTimeoutSeconds) + } + if c.cfg.RelayTimeoutSeconds != nil && *c.cfg.RelayTimeoutSeconds != *out.RelayTimeoutSeconds { + t.Fatalf("RelayTimeoutSeconds value lost: in=%d out=%d", + *c.cfg.RelayTimeoutSeconds, *out.RelayTimeoutSeconds) + } + if (c.cfg.P2pTimeoutSeconds == nil) != (out.P2pTimeoutSeconds == nil) { + t.Fatalf("P2pTimeoutSeconds nil-ness changed: in=%v out=%v", + c.cfg.P2pTimeoutSeconds, out.P2pTimeoutSeconds) + } + if c.cfg.P2pTimeoutSeconds != nil && *c.cfg.P2pTimeoutSeconds != *out.P2pTimeoutSeconds { + t.Fatalf("P2pTimeoutSeconds value lost: in=%d out=%d", + *c.cfg.P2pTimeoutSeconds, *out.P2pTimeoutSeconds) + } + }) + } +} diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go index 31658d5a1d0..a4d559d1bd9 100644 --- a/client/proto/daemon.pb.go +++ b/client/proto/daemon.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.6 -// protoc v6.33.1 +// protoc v5.29.3 // source: daemon.proto package proto @@ -342,8 +342,15 @@ type LoginRequest struct { EnableSSHRemotePortForwarding *bool `protobuf:"varint,37,opt,name=enableSSHRemotePortForwarding,proto3,oneof" json:"enableSSHRemotePortForwarding,omitempty"` DisableSSHAuth *bool `protobuf:"varint,38,opt,name=disableSSHAuth,proto3,oneof" json:"disableSSHAuth,omitempty"` SshJWTCacheTTL *int32 `protobuf:"varint,39,opt,name=sshJWTCacheTTL,proto3,oneof" json:"sshJWTCacheTTL,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Phase 1 (#5989): peer-connection mode and idle timeouts. + // connection_mode is a string (relay-forced, p2p, p2p-lazy, p2p-dynamic, + // follow-server, or empty); parsed via connectionmode.ParseString at the + // daemon side. Empty means "no client-side override, use server value". + ConnectionMode *string `protobuf:"bytes,40,opt,name=connection_mode,json=connectionMode,proto3,oneof" json:"connection_mode,omitempty"` + P2PTimeoutSeconds *uint32 `protobuf:"varint,41,opt,name=p2p_timeout_seconds,json=p2pTimeoutSeconds,proto3,oneof" json:"p2p_timeout_seconds,omitempty"` + RelayTimeoutSeconds *uint32 `protobuf:"varint,42,opt,name=relay_timeout_seconds,json=relayTimeoutSeconds,proto3,oneof" json:"relay_timeout_seconds,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *LoginRequest) Reset() { @@ -650,6 +657,27 @@ func (x *LoginRequest) GetSshJWTCacheTTL() int32 { return 0 } +func (x *LoginRequest) GetConnectionMode() string { + if x != nil && x.ConnectionMode != nil { + return *x.ConnectionMode + } + return "" +} + +func (x *LoginRequest) GetP2PTimeoutSeconds() uint32 { + if x != nil && x.P2PTimeoutSeconds != nil { + return *x.P2PTimeoutSeconds + } + return 0 +} + +func (x *LoginRequest) GetRelayTimeoutSeconds() uint32 { + if x != nil && x.RelayTimeoutSeconds != nil { + return *x.RelayTimeoutSeconds + } + return 0 +} + type LoginResponse struct { state protoimpl.MessageState `protogen:"open.v1"` NeedsSSOLogin bool `protobuf:"varint,1,opt,name=needsSSOLogin,proto3" json:"needsSSOLogin,omitempty"` @@ -4009,8 +4037,15 @@ type SetConfigRequest struct { EnableSSHRemotePortForwarding *bool `protobuf:"varint,32,opt,name=enableSSHRemotePortForwarding,proto3,oneof" json:"enableSSHRemotePortForwarding,omitempty"` DisableSSHAuth *bool `protobuf:"varint,33,opt,name=disableSSHAuth,proto3,oneof" json:"disableSSHAuth,omitempty"` SshJWTCacheTTL *int32 `protobuf:"varint,34,opt,name=sshJWTCacheTTL,proto3,oneof" json:"sshJWTCacheTTL,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Phase 1 (#5989): peer-connection mode and idle timeouts. + // connection_mode is a string (relay-forced, p2p, p2p-lazy, p2p-dynamic, + // follow-server, or empty); parsed via connectionmode.ParseString at the + // daemon side. Empty means "no client-side override, use server value". + ConnectionMode *string `protobuf:"bytes,40,opt,name=connection_mode,json=connectionMode,proto3,oneof" json:"connection_mode,omitempty"` + P2PTimeoutSeconds *uint32 `protobuf:"varint,41,opt,name=p2p_timeout_seconds,json=p2pTimeoutSeconds,proto3,oneof" json:"p2p_timeout_seconds,omitempty"` + RelayTimeoutSeconds *uint32 `protobuf:"varint,42,opt,name=relay_timeout_seconds,json=relayTimeoutSeconds,proto3,oneof" json:"relay_timeout_seconds,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SetConfigRequest) Reset() { @@ -4281,6 +4316,27 @@ func (x *SetConfigRequest) GetSshJWTCacheTTL() int32 { return 0 } +func (x *SetConfigRequest) GetConnectionMode() string { + if x != nil && x.ConnectionMode != nil { + return *x.ConnectionMode + } + return "" +} + +func (x *SetConfigRequest) GetP2PTimeoutSeconds() uint32 { + if x != nil && x.P2PTimeoutSeconds != nil { + return *x.P2PTimeoutSeconds + } + return 0 +} + +func (x *SetConfigRequest) GetRelayTimeoutSeconds() uint32 { + if x != nil && x.RelayTimeoutSeconds != nil { + return *x.RelayTimeoutSeconds + } + return 0 +} + type SetConfigResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -5904,7 +5960,7 @@ var File_daemon_proto protoreflect.FileDescriptor const file_daemon_proto_rawDesc = "" + "\n" + "\fdaemon.proto\x12\x06daemon\x1a google/protobuf/descriptor.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\"\x0e\n" + - "\fEmptyRequest\"\xb6\x12\n" + + "\fEmptyRequest\"\x98\x14\n" + "\fLoginRequest\x12\x1a\n" + "\bsetupKey\x18\x01 \x01(\tR\bsetupKey\x12&\n" + "\fpreSharedKey\x18\x02 \x01(\tB\x02\x18\x01R\fpreSharedKey\x12$\n" + @@ -5948,7 +6004,10 @@ const file_daemon_proto_rawDesc = "" + "\x1cenableSSHLocalPortForwarding\x18$ \x01(\bH\x17R\x1cenableSSHLocalPortForwarding\x88\x01\x01\x12I\n" + "\x1denableSSHRemotePortForwarding\x18% \x01(\bH\x18R\x1denableSSHRemotePortForwarding\x88\x01\x01\x12+\n" + "\x0edisableSSHAuth\x18& \x01(\bH\x19R\x0edisableSSHAuth\x88\x01\x01\x12+\n" + - "\x0esshJWTCacheTTL\x18' \x01(\x05H\x1aR\x0esshJWTCacheTTL\x88\x01\x01B\x13\n" + + "\x0esshJWTCacheTTL\x18' \x01(\x05H\x1aR\x0esshJWTCacheTTL\x88\x01\x01\x12,\n" + + "\x0fconnection_mode\x18( \x01(\tH\x1bR\x0econnectionMode\x88\x01\x01\x123\n" + + "\x13p2p_timeout_seconds\x18) \x01(\rH\x1cR\x11p2pTimeoutSeconds\x88\x01\x01\x127\n" + + "\x15relay_timeout_seconds\x18* \x01(\rH\x1dR\x13relayTimeoutSeconds\x88\x01\x01B\x13\n" + "\x11_rosenpassEnabledB\x10\n" + "\x0e_interfaceNameB\x10\n" + "\x0e_wireguardPortB\x17\n" + @@ -5975,7 +6034,10 @@ const file_daemon_proto_rawDesc = "" + "\x1d_enableSSHLocalPortForwardingB \n" + "\x1e_enableSSHRemotePortForwardingB\x11\n" + "\x0f_disableSSHAuthB\x11\n" + - "\x0f_sshJWTCacheTTL\"\xb5\x01\n" + + "\x0f_sshJWTCacheTTLB\x12\n" + + "\x10_connection_modeB\x16\n" + + "\x14_p2p_timeout_secondsB\x18\n" + + "\x16_relay_timeout_seconds\"\xb5\x01\n" + "\rLoginResponse\x12$\n" + "\rneedsSSOLogin\x18\x01 \x01(\bR\rneedsSSOLogin\x12\x1a\n" + "\buserCode\x18\x02 \x01(\tR\buserCode\x12(\n" + @@ -6252,7 +6314,7 @@ const file_daemon_proto_rawDesc = "" + "\busername\x18\x02 \x01(\tH\x01R\busername\x88\x01\x01B\x0e\n" + "\f_profileNameB\v\n" + "\t_username\"\x17\n" + - "\x15SwitchProfileResponse\"\xdf\x10\n" + + "\x15SwitchProfileResponse\"\xc1\x12\n" + "\x10SetConfigRequest\x12\x1a\n" + "\busername\x18\x01 \x01(\tR\busername\x12 \n" + "\vprofileName\x18\x02 \x01(\tR\vprofileName\x12$\n" + @@ -6291,7 +6353,10 @@ const file_daemon_proto_rawDesc = "" + "\x1cenableSSHLocalPortForwarding\x18\x1f \x01(\bH\x14R\x1cenableSSHLocalPortForwarding\x88\x01\x01\x12I\n" + "\x1denableSSHRemotePortForwarding\x18 \x01(\bH\x15R\x1denableSSHRemotePortForwarding\x88\x01\x01\x12+\n" + "\x0edisableSSHAuth\x18! \x01(\bH\x16R\x0edisableSSHAuth\x88\x01\x01\x12+\n" + - "\x0esshJWTCacheTTL\x18\" \x01(\x05H\x17R\x0esshJWTCacheTTL\x88\x01\x01B\x13\n" + + "\x0esshJWTCacheTTL\x18\" \x01(\x05H\x17R\x0esshJWTCacheTTL\x88\x01\x01\x12,\n" + + "\x0fconnection_mode\x18( \x01(\tH\x18R\x0econnectionMode\x88\x01\x01\x123\n" + + "\x13p2p_timeout_seconds\x18) \x01(\rH\x19R\x11p2pTimeoutSeconds\x88\x01\x01\x127\n" + + "\x15relay_timeout_seconds\x18* \x01(\rH\x1aR\x13relayTimeoutSeconds\x88\x01\x01B\x13\n" + "\x11_rosenpassEnabledB\x10\n" + "\x0e_interfaceNameB\x10\n" + "\x0e_wireguardPortB\x17\n" + @@ -6315,7 +6380,10 @@ const file_daemon_proto_rawDesc = "" + "\x1d_enableSSHLocalPortForwardingB \n" + "\x1e_enableSSHRemotePortForwardingB\x11\n" + "\x0f_disableSSHAuthB\x11\n" + - "\x0f_sshJWTCacheTTL\"\x13\n" + + "\x0f_sshJWTCacheTTLB\x12\n" + + "\x10_connection_modeB\x16\n" + + "\x14_p2p_timeout_secondsB\x18\n" + + "\x16_relay_timeout_seconds\"\x13\n" + "\x11SetConfigResponse\"Q\n" + "\x11AddProfileRequest\x12\x1a\n" + "\busername\x18\x01 \x01(\tR\busername\x12 \n" + diff --git a/client/proto/daemon.proto b/client/proto/daemon.proto index f4e5b8e4d87..8939d3cab2b 100644 --- a/client/proto/daemon.proto +++ b/client/proto/daemon.proto @@ -193,6 +193,14 @@ message LoginRequest { optional bool enableSSHRemotePortForwarding = 37; optional bool disableSSHAuth = 38; optional int32 sshJWTCacheTTL = 39; + + // Phase 1 (#5989): peer-connection mode and idle timeouts. + // connection_mode is a string (relay-forced, p2p, p2p-lazy, p2p-dynamic, + // follow-server, or empty); parsed via connectionmode.ParseString at the + // daemon side. Empty means "no client-side override, use server value". + optional string connection_mode = 40; + optional uint32 p2p_timeout_seconds = 41; + optional uint32 relay_timeout_seconds = 42; } message LoginResponse { @@ -661,6 +669,14 @@ message SetConfigRequest { optional bool enableSSHRemotePortForwarding = 32; optional bool disableSSHAuth = 33; optional int32 sshJWTCacheTTL = 34; + + // Phase 1 (#5989): peer-connection mode and idle timeouts. + // connection_mode is a string (relay-forced, p2p, p2p-lazy, p2p-dynamic, + // follow-server, or empty); parsed via connectionmode.ParseString at the + // daemon side. Empty means "no client-side override, use server value". + optional string connection_mode = 40; + optional uint32 p2p_timeout_seconds = 41; + optional uint32 relay_timeout_seconds = 42; } message SetConfigResponse{} diff --git a/client/proto/daemon_grpc.pb.go b/client/proto/daemon_grpc.pb.go index 026ee23616a..7a1b7dcf635 100644 --- a/client/proto/daemon_grpc.pb.go +++ b/client/proto/daemon_grpc.pb.go @@ -1,8 +1,4 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.6.1 -// - protoc v6.33.1 -// source: daemon.proto package proto @@ -15,47 +11,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.64.0 or later. -const _ = grpc.SupportPackageIsVersion9 - -const ( - DaemonService_Login_FullMethodName = "/daemon.DaemonService/Login" - DaemonService_WaitSSOLogin_FullMethodName = "/daemon.DaemonService/WaitSSOLogin" - DaemonService_Up_FullMethodName = "/daemon.DaemonService/Up" - DaemonService_Status_FullMethodName = "/daemon.DaemonService/Status" - DaemonService_Down_FullMethodName = "/daemon.DaemonService/Down" - DaemonService_GetConfig_FullMethodName = "/daemon.DaemonService/GetConfig" - DaemonService_ListNetworks_FullMethodName = "/daemon.DaemonService/ListNetworks" - DaemonService_SelectNetworks_FullMethodName = "/daemon.DaemonService/SelectNetworks" - DaemonService_DeselectNetworks_FullMethodName = "/daemon.DaemonService/DeselectNetworks" - DaemonService_ForwardingRules_FullMethodName = "/daemon.DaemonService/ForwardingRules" - DaemonService_DebugBundle_FullMethodName = "/daemon.DaemonService/DebugBundle" - DaemonService_GetLogLevel_FullMethodName = "/daemon.DaemonService/GetLogLevel" - DaemonService_SetLogLevel_FullMethodName = "/daemon.DaemonService/SetLogLevel" - DaemonService_ListStates_FullMethodName = "/daemon.DaemonService/ListStates" - DaemonService_CleanState_FullMethodName = "/daemon.DaemonService/CleanState" - DaemonService_DeleteState_FullMethodName = "/daemon.DaemonService/DeleteState" - DaemonService_SetSyncResponsePersistence_FullMethodName = "/daemon.DaemonService/SetSyncResponsePersistence" - DaemonService_TracePacket_FullMethodName = "/daemon.DaemonService/TracePacket" - DaemonService_SubscribeEvents_FullMethodName = "/daemon.DaemonService/SubscribeEvents" - DaemonService_GetEvents_FullMethodName = "/daemon.DaemonService/GetEvents" - DaemonService_SwitchProfile_FullMethodName = "/daemon.DaemonService/SwitchProfile" - DaemonService_SetConfig_FullMethodName = "/daemon.DaemonService/SetConfig" - DaemonService_AddProfile_FullMethodName = "/daemon.DaemonService/AddProfile" - DaemonService_RemoveProfile_FullMethodName = "/daemon.DaemonService/RemoveProfile" - DaemonService_ListProfiles_FullMethodName = "/daemon.DaemonService/ListProfiles" - DaemonService_GetActiveProfile_FullMethodName = "/daemon.DaemonService/GetActiveProfile" - DaemonService_Logout_FullMethodName = "/daemon.DaemonService/Logout" - DaemonService_GetFeatures_FullMethodName = "/daemon.DaemonService/GetFeatures" - DaemonService_TriggerUpdate_FullMethodName = "/daemon.DaemonService/TriggerUpdate" - DaemonService_GetPeerSSHHostKey_FullMethodName = "/daemon.DaemonService/GetPeerSSHHostKey" - DaemonService_RequestJWTAuth_FullMethodName = "/daemon.DaemonService/RequestJWTAuth" - DaemonService_WaitJWTToken_FullMethodName = "/daemon.DaemonService/WaitJWTToken" - DaemonService_StartCPUProfile_FullMethodName = "/daemon.DaemonService/StartCPUProfile" - DaemonService_StopCPUProfile_FullMethodName = "/daemon.DaemonService/StopCPUProfile" - DaemonService_GetInstallerResult_FullMethodName = "/daemon.DaemonService/GetInstallerResult" - DaemonService_ExposeService_FullMethodName = "/daemon.DaemonService/ExposeService" -) +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 // DaemonServiceClient is the client API for DaemonService service. // @@ -96,7 +53,7 @@ type DaemonServiceClient interface { // SetSyncResponsePersistence enables or disables sync response persistence SetSyncResponsePersistence(ctx context.Context, in *SetSyncResponsePersistenceRequest, opts ...grpc.CallOption) (*SetSyncResponsePersistenceResponse, error) TracePacket(ctx context.Context, in *TracePacketRequest, opts ...grpc.CallOption) (*TracePacketResponse, error) - SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[SystemEvent], error) + SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (DaemonService_SubscribeEventsClient, error) GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error) SwitchProfile(ctx context.Context, in *SwitchProfileRequest, opts ...grpc.CallOption) (*SwitchProfileResponse, error) SetConfig(ctx context.Context, in *SetConfigRequest, opts ...grpc.CallOption) (*SetConfigResponse, error) @@ -122,7 +79,7 @@ type DaemonServiceClient interface { StopCPUProfile(ctx context.Context, in *StopCPUProfileRequest, opts ...grpc.CallOption) (*StopCPUProfileResponse, error) GetInstallerResult(ctx context.Context, in *InstallerResultRequest, opts ...grpc.CallOption) (*InstallerResultResponse, error) // ExposeService exposes a local port via the NetBird reverse proxy - ExposeService(ctx context.Context, in *ExposeServiceRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ExposeServiceEvent], error) + ExposeService(ctx context.Context, in *ExposeServiceRequest, opts ...grpc.CallOption) (DaemonService_ExposeServiceClient, error) } type daemonServiceClient struct { @@ -134,9 +91,8 @@ func NewDaemonServiceClient(cc grpc.ClientConnInterface) DaemonServiceClient { } func (c *daemonServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(LoginResponse) - err := c.cc.Invoke(ctx, DaemonService_Login_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/Login", in, out, opts...) if err != nil { return nil, err } @@ -144,9 +100,8 @@ func (c *daemonServiceClient) Login(ctx context.Context, in *LoginRequest, opts } func (c *daemonServiceClient) WaitSSOLogin(ctx context.Context, in *WaitSSOLoginRequest, opts ...grpc.CallOption) (*WaitSSOLoginResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(WaitSSOLoginResponse) - err := c.cc.Invoke(ctx, DaemonService_WaitSSOLogin_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/WaitSSOLogin", in, out, opts...) if err != nil { return nil, err } @@ -154,9 +109,8 @@ func (c *daemonServiceClient) WaitSSOLogin(ctx context.Context, in *WaitSSOLogin } func (c *daemonServiceClient) Up(ctx context.Context, in *UpRequest, opts ...grpc.CallOption) (*UpResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UpResponse) - err := c.cc.Invoke(ctx, DaemonService_Up_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/Up", in, out, opts...) if err != nil { return nil, err } @@ -164,9 +118,8 @@ func (c *daemonServiceClient) Up(ctx context.Context, in *UpRequest, opts ...grp } func (c *daemonServiceClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(StatusResponse) - err := c.cc.Invoke(ctx, DaemonService_Status_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/Status", in, out, opts...) if err != nil { return nil, err } @@ -174,9 +127,8 @@ func (c *daemonServiceClient) Status(ctx context.Context, in *StatusRequest, opt } func (c *daemonServiceClient) Down(ctx context.Context, in *DownRequest, opts ...grpc.CallOption) (*DownResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DownResponse) - err := c.cc.Invoke(ctx, DaemonService_Down_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/Down", in, out, opts...) if err != nil { return nil, err } @@ -184,9 +136,8 @@ func (c *daemonServiceClient) Down(ctx context.Context, in *DownRequest, opts .. } func (c *daemonServiceClient) GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetConfigResponse) - err := c.cc.Invoke(ctx, DaemonService_GetConfig_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetConfig", in, out, opts...) if err != nil { return nil, err } @@ -194,9 +145,8 @@ func (c *daemonServiceClient) GetConfig(ctx context.Context, in *GetConfigReques } func (c *daemonServiceClient) ListNetworks(ctx context.Context, in *ListNetworksRequest, opts ...grpc.CallOption) (*ListNetworksResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListNetworksResponse) - err := c.cc.Invoke(ctx, DaemonService_ListNetworks_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/ListNetworks", in, out, opts...) if err != nil { return nil, err } @@ -204,9 +154,8 @@ func (c *daemonServiceClient) ListNetworks(ctx context.Context, in *ListNetworks } func (c *daemonServiceClient) SelectNetworks(ctx context.Context, in *SelectNetworksRequest, opts ...grpc.CallOption) (*SelectNetworksResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SelectNetworksResponse) - err := c.cc.Invoke(ctx, DaemonService_SelectNetworks_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/SelectNetworks", in, out, opts...) if err != nil { return nil, err } @@ -214,9 +163,8 @@ func (c *daemonServiceClient) SelectNetworks(ctx context.Context, in *SelectNetw } func (c *daemonServiceClient) DeselectNetworks(ctx context.Context, in *SelectNetworksRequest, opts ...grpc.CallOption) (*SelectNetworksResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SelectNetworksResponse) - err := c.cc.Invoke(ctx, DaemonService_DeselectNetworks_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/DeselectNetworks", in, out, opts...) if err != nil { return nil, err } @@ -224,9 +172,8 @@ func (c *daemonServiceClient) DeselectNetworks(ctx context.Context, in *SelectNe } func (c *daemonServiceClient) ForwardingRules(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*ForwardingRulesResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ForwardingRulesResponse) - err := c.cc.Invoke(ctx, DaemonService_ForwardingRules_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/ForwardingRules", in, out, opts...) if err != nil { return nil, err } @@ -234,9 +181,8 @@ func (c *daemonServiceClient) ForwardingRules(ctx context.Context, in *EmptyRequ } func (c *daemonServiceClient) DebugBundle(ctx context.Context, in *DebugBundleRequest, opts ...grpc.CallOption) (*DebugBundleResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DebugBundleResponse) - err := c.cc.Invoke(ctx, DaemonService_DebugBundle_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/DebugBundle", in, out, opts...) if err != nil { return nil, err } @@ -244,9 +190,8 @@ func (c *daemonServiceClient) DebugBundle(ctx context.Context, in *DebugBundleRe } func (c *daemonServiceClient) GetLogLevel(ctx context.Context, in *GetLogLevelRequest, opts ...grpc.CallOption) (*GetLogLevelResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetLogLevelResponse) - err := c.cc.Invoke(ctx, DaemonService_GetLogLevel_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetLogLevel", in, out, opts...) if err != nil { return nil, err } @@ -254,9 +199,8 @@ func (c *daemonServiceClient) GetLogLevel(ctx context.Context, in *GetLogLevelRe } func (c *daemonServiceClient) SetLogLevel(ctx context.Context, in *SetLogLevelRequest, opts ...grpc.CallOption) (*SetLogLevelResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SetLogLevelResponse) - err := c.cc.Invoke(ctx, DaemonService_SetLogLevel_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetLogLevel", in, out, opts...) if err != nil { return nil, err } @@ -264,9 +208,8 @@ func (c *daemonServiceClient) SetLogLevel(ctx context.Context, in *SetLogLevelRe } func (c *daemonServiceClient) ListStates(ctx context.Context, in *ListStatesRequest, opts ...grpc.CallOption) (*ListStatesResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListStatesResponse) - err := c.cc.Invoke(ctx, DaemonService_ListStates_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/ListStates", in, out, opts...) if err != nil { return nil, err } @@ -274,9 +217,8 @@ func (c *daemonServiceClient) ListStates(ctx context.Context, in *ListStatesRequ } func (c *daemonServiceClient) CleanState(ctx context.Context, in *CleanStateRequest, opts ...grpc.CallOption) (*CleanStateResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CleanStateResponse) - err := c.cc.Invoke(ctx, DaemonService_CleanState_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/CleanState", in, out, opts...) if err != nil { return nil, err } @@ -284,9 +226,8 @@ func (c *daemonServiceClient) CleanState(ctx context.Context, in *CleanStateRequ } func (c *daemonServiceClient) DeleteState(ctx context.Context, in *DeleteStateRequest, opts ...grpc.CallOption) (*DeleteStateResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeleteStateResponse) - err := c.cc.Invoke(ctx, DaemonService_DeleteState_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/DeleteState", in, out, opts...) if err != nil { return nil, err } @@ -294,9 +235,8 @@ func (c *daemonServiceClient) DeleteState(ctx context.Context, in *DeleteStateRe } func (c *daemonServiceClient) SetSyncResponsePersistence(ctx context.Context, in *SetSyncResponsePersistenceRequest, opts ...grpc.CallOption) (*SetSyncResponsePersistenceResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SetSyncResponsePersistenceResponse) - err := c.cc.Invoke(ctx, DaemonService_SetSyncResponsePersistence_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetSyncResponsePersistence", in, out, opts...) if err != nil { return nil, err } @@ -304,22 +244,20 @@ func (c *daemonServiceClient) SetSyncResponsePersistence(ctx context.Context, in } func (c *daemonServiceClient) TracePacket(ctx context.Context, in *TracePacketRequest, opts ...grpc.CallOption) (*TracePacketResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(TracePacketResponse) - err := c.cc.Invoke(ctx, DaemonService_TracePacket_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/TracePacket", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *daemonServiceClient) SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[SystemEvent], error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &DaemonService_ServiceDesc.Streams[0], DaemonService_SubscribeEvents_FullMethodName, cOpts...) +func (c *daemonServiceClient) SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (DaemonService_SubscribeEventsClient, error) { + stream, err := c.cc.NewStream(ctx, &DaemonService_ServiceDesc.Streams[0], "/daemon.DaemonService/SubscribeEvents", opts...) if err != nil { return nil, err } - x := &grpc.GenericClientStream[SubscribeRequest, SystemEvent]{ClientStream: stream} + x := &daemonServiceSubscribeEventsClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } @@ -329,13 +267,26 @@ func (c *daemonServiceClient) SubscribeEvents(ctx context.Context, in *Subscribe return x, nil } -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type DaemonService_SubscribeEventsClient = grpc.ServerStreamingClient[SystemEvent] +type DaemonService_SubscribeEventsClient interface { + Recv() (*SystemEvent, error) + grpc.ClientStream +} + +type daemonServiceSubscribeEventsClient struct { + grpc.ClientStream +} + +func (x *daemonServiceSubscribeEventsClient) Recv() (*SystemEvent, error) { + m := new(SystemEvent) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} func (c *daemonServiceClient) GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetEventsResponse) - err := c.cc.Invoke(ctx, DaemonService_GetEvents_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetEvents", in, out, opts...) if err != nil { return nil, err } @@ -343,9 +294,8 @@ func (c *daemonServiceClient) GetEvents(ctx context.Context, in *GetEventsReques } func (c *daemonServiceClient) SwitchProfile(ctx context.Context, in *SwitchProfileRequest, opts ...grpc.CallOption) (*SwitchProfileResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SwitchProfileResponse) - err := c.cc.Invoke(ctx, DaemonService_SwitchProfile_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/SwitchProfile", in, out, opts...) if err != nil { return nil, err } @@ -353,9 +303,8 @@ func (c *daemonServiceClient) SwitchProfile(ctx context.Context, in *SwitchProfi } func (c *daemonServiceClient) SetConfig(ctx context.Context, in *SetConfigRequest, opts ...grpc.CallOption) (*SetConfigResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SetConfigResponse) - err := c.cc.Invoke(ctx, DaemonService_SetConfig_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetConfig", in, out, opts...) if err != nil { return nil, err } @@ -363,9 +312,8 @@ func (c *daemonServiceClient) SetConfig(ctx context.Context, in *SetConfigReques } func (c *daemonServiceClient) AddProfile(ctx context.Context, in *AddProfileRequest, opts ...grpc.CallOption) (*AddProfileResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AddProfileResponse) - err := c.cc.Invoke(ctx, DaemonService_AddProfile_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/AddProfile", in, out, opts...) if err != nil { return nil, err } @@ -373,9 +321,8 @@ func (c *daemonServiceClient) AddProfile(ctx context.Context, in *AddProfileRequ } func (c *daemonServiceClient) RemoveProfile(ctx context.Context, in *RemoveProfileRequest, opts ...grpc.CallOption) (*RemoveProfileResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(RemoveProfileResponse) - err := c.cc.Invoke(ctx, DaemonService_RemoveProfile_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/RemoveProfile", in, out, opts...) if err != nil { return nil, err } @@ -383,9 +330,8 @@ func (c *daemonServiceClient) RemoveProfile(ctx context.Context, in *RemoveProfi } func (c *daemonServiceClient) ListProfiles(ctx context.Context, in *ListProfilesRequest, opts ...grpc.CallOption) (*ListProfilesResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListProfilesResponse) - err := c.cc.Invoke(ctx, DaemonService_ListProfiles_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/ListProfiles", in, out, opts...) if err != nil { return nil, err } @@ -393,9 +339,8 @@ func (c *daemonServiceClient) ListProfiles(ctx context.Context, in *ListProfiles } func (c *daemonServiceClient) GetActiveProfile(ctx context.Context, in *GetActiveProfileRequest, opts ...grpc.CallOption) (*GetActiveProfileResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetActiveProfileResponse) - err := c.cc.Invoke(ctx, DaemonService_GetActiveProfile_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetActiveProfile", in, out, opts...) if err != nil { return nil, err } @@ -403,9 +348,8 @@ func (c *daemonServiceClient) GetActiveProfile(ctx context.Context, in *GetActiv } func (c *daemonServiceClient) Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(LogoutResponse) - err := c.cc.Invoke(ctx, DaemonService_Logout_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/Logout", in, out, opts...) if err != nil { return nil, err } @@ -413,9 +357,8 @@ func (c *daemonServiceClient) Logout(ctx context.Context, in *LogoutRequest, opt } func (c *daemonServiceClient) GetFeatures(ctx context.Context, in *GetFeaturesRequest, opts ...grpc.CallOption) (*GetFeaturesResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetFeaturesResponse) - err := c.cc.Invoke(ctx, DaemonService_GetFeatures_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetFeatures", in, out, opts...) if err != nil { return nil, err } @@ -423,9 +366,8 @@ func (c *daemonServiceClient) GetFeatures(ctx context.Context, in *GetFeaturesRe } func (c *daemonServiceClient) TriggerUpdate(ctx context.Context, in *TriggerUpdateRequest, opts ...grpc.CallOption) (*TriggerUpdateResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(TriggerUpdateResponse) - err := c.cc.Invoke(ctx, DaemonService_TriggerUpdate_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/TriggerUpdate", in, out, opts...) if err != nil { return nil, err } @@ -433,9 +375,8 @@ func (c *daemonServiceClient) TriggerUpdate(ctx context.Context, in *TriggerUpda } func (c *daemonServiceClient) GetPeerSSHHostKey(ctx context.Context, in *GetPeerSSHHostKeyRequest, opts ...grpc.CallOption) (*GetPeerSSHHostKeyResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetPeerSSHHostKeyResponse) - err := c.cc.Invoke(ctx, DaemonService_GetPeerSSHHostKey_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetPeerSSHHostKey", in, out, opts...) if err != nil { return nil, err } @@ -443,9 +384,8 @@ func (c *daemonServiceClient) GetPeerSSHHostKey(ctx context.Context, in *GetPeer } func (c *daemonServiceClient) RequestJWTAuth(ctx context.Context, in *RequestJWTAuthRequest, opts ...grpc.CallOption) (*RequestJWTAuthResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(RequestJWTAuthResponse) - err := c.cc.Invoke(ctx, DaemonService_RequestJWTAuth_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/RequestJWTAuth", in, out, opts...) if err != nil { return nil, err } @@ -453,9 +393,8 @@ func (c *daemonServiceClient) RequestJWTAuth(ctx context.Context, in *RequestJWT } func (c *daemonServiceClient) WaitJWTToken(ctx context.Context, in *WaitJWTTokenRequest, opts ...grpc.CallOption) (*WaitJWTTokenResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(WaitJWTTokenResponse) - err := c.cc.Invoke(ctx, DaemonService_WaitJWTToken_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/WaitJWTToken", in, out, opts...) if err != nil { return nil, err } @@ -463,9 +402,8 @@ func (c *daemonServiceClient) WaitJWTToken(ctx context.Context, in *WaitJWTToken } func (c *daemonServiceClient) StartCPUProfile(ctx context.Context, in *StartCPUProfileRequest, opts ...grpc.CallOption) (*StartCPUProfileResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(StartCPUProfileResponse) - err := c.cc.Invoke(ctx, DaemonService_StartCPUProfile_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/StartCPUProfile", in, out, opts...) if err != nil { return nil, err } @@ -473,9 +411,8 @@ func (c *daemonServiceClient) StartCPUProfile(ctx context.Context, in *StartCPUP } func (c *daemonServiceClient) StopCPUProfile(ctx context.Context, in *StopCPUProfileRequest, opts ...grpc.CallOption) (*StopCPUProfileResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(StopCPUProfileResponse) - err := c.cc.Invoke(ctx, DaemonService_StopCPUProfile_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/StopCPUProfile", in, out, opts...) if err != nil { return nil, err } @@ -483,22 +420,20 @@ func (c *daemonServiceClient) StopCPUProfile(ctx context.Context, in *StopCPUPro } func (c *daemonServiceClient) GetInstallerResult(ctx context.Context, in *InstallerResultRequest, opts ...grpc.CallOption) (*InstallerResultResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(InstallerResultResponse) - err := c.cc.Invoke(ctx, DaemonService_GetInstallerResult_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetInstallerResult", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *daemonServiceClient) ExposeService(ctx context.Context, in *ExposeServiceRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ExposeServiceEvent], error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &DaemonService_ServiceDesc.Streams[1], DaemonService_ExposeService_FullMethodName, cOpts...) +func (c *daemonServiceClient) ExposeService(ctx context.Context, in *ExposeServiceRequest, opts ...grpc.CallOption) (DaemonService_ExposeServiceClient, error) { + stream, err := c.cc.NewStream(ctx, &DaemonService_ServiceDesc.Streams[1], "/daemon.DaemonService/ExposeService", opts...) if err != nil { return nil, err } - x := &grpc.GenericClientStream[ExposeServiceRequest, ExposeServiceEvent]{ClientStream: stream} + x := &daemonServiceExposeServiceClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } @@ -508,12 +443,26 @@ func (c *daemonServiceClient) ExposeService(ctx context.Context, in *ExposeServi return x, nil } -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type DaemonService_ExposeServiceClient = grpc.ServerStreamingClient[ExposeServiceEvent] +type DaemonService_ExposeServiceClient interface { + Recv() (*ExposeServiceEvent, error) + grpc.ClientStream +} + +type daemonServiceExposeServiceClient struct { + grpc.ClientStream +} + +func (x *daemonServiceExposeServiceClient) Recv() (*ExposeServiceEvent, error) { + m := new(ExposeServiceEvent) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} // DaemonServiceServer is the server API for DaemonService service. // All implementations must embed UnimplementedDaemonServiceServer -// for forward compatibility. +// for forward compatibility type DaemonServiceServer interface { // Login uses setup key to prepare configuration for the daemon. Login(context.Context, *LoginRequest) (*LoginResponse, error) @@ -550,7 +499,7 @@ type DaemonServiceServer interface { // SetSyncResponsePersistence enables or disables sync response persistence SetSyncResponsePersistence(context.Context, *SetSyncResponsePersistenceRequest) (*SetSyncResponsePersistenceResponse, error) TracePacket(context.Context, *TracePacketRequest) (*TracePacketResponse, error) - SubscribeEvents(*SubscribeRequest, grpc.ServerStreamingServer[SystemEvent]) error + SubscribeEvents(*SubscribeRequest, DaemonService_SubscribeEventsServer) error GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error) SwitchProfile(context.Context, *SwitchProfileRequest) (*SwitchProfileResponse, error) SetConfig(context.Context, *SetConfigRequest) (*SetConfigResponse, error) @@ -576,127 +525,123 @@ type DaemonServiceServer interface { StopCPUProfile(context.Context, *StopCPUProfileRequest) (*StopCPUProfileResponse, error) GetInstallerResult(context.Context, *InstallerResultRequest) (*InstallerResultResponse, error) // ExposeService exposes a local port via the NetBird reverse proxy - ExposeService(*ExposeServiceRequest, grpc.ServerStreamingServer[ExposeServiceEvent]) error + ExposeService(*ExposeServiceRequest, DaemonService_ExposeServiceServer) error mustEmbedUnimplementedDaemonServiceServer() } -// UnimplementedDaemonServiceServer must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedDaemonServiceServer struct{} +// UnimplementedDaemonServiceServer must be embedded to have forward compatible implementations. +type UnimplementedDaemonServiceServer struct { +} func (UnimplementedDaemonServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) { - return nil, status.Error(codes.Unimplemented, "method Login not implemented") + return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") } func (UnimplementedDaemonServiceServer) WaitSSOLogin(context.Context, *WaitSSOLoginRequest) (*WaitSSOLoginResponse, error) { - return nil, status.Error(codes.Unimplemented, "method WaitSSOLogin not implemented") + return nil, status.Errorf(codes.Unimplemented, "method WaitSSOLogin not implemented") } func (UnimplementedDaemonServiceServer) Up(context.Context, *UpRequest) (*UpResponse, error) { - return nil, status.Error(codes.Unimplemented, "method Up not implemented") + return nil, status.Errorf(codes.Unimplemented, "method Up not implemented") } func (UnimplementedDaemonServiceServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) { - return nil, status.Error(codes.Unimplemented, "method Status not implemented") + return nil, status.Errorf(codes.Unimplemented, "method Status not implemented") } func (UnimplementedDaemonServiceServer) Down(context.Context, *DownRequest) (*DownResponse, error) { - return nil, status.Error(codes.Unimplemented, "method Down not implemented") + return nil, status.Errorf(codes.Unimplemented, "method Down not implemented") } func (UnimplementedDaemonServiceServer) GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetConfig not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented") } func (UnimplementedDaemonServiceServer) ListNetworks(context.Context, *ListNetworksRequest) (*ListNetworksResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ListNetworks not implemented") + return nil, status.Errorf(codes.Unimplemented, "method ListNetworks not implemented") } func (UnimplementedDaemonServiceServer) SelectNetworks(context.Context, *SelectNetworksRequest) (*SelectNetworksResponse, error) { - return nil, status.Error(codes.Unimplemented, "method SelectNetworks not implemented") + return nil, status.Errorf(codes.Unimplemented, "method SelectNetworks not implemented") } func (UnimplementedDaemonServiceServer) DeselectNetworks(context.Context, *SelectNetworksRequest) (*SelectNetworksResponse, error) { - return nil, status.Error(codes.Unimplemented, "method DeselectNetworks not implemented") + return nil, status.Errorf(codes.Unimplemented, "method DeselectNetworks not implemented") } func (UnimplementedDaemonServiceServer) ForwardingRules(context.Context, *EmptyRequest) (*ForwardingRulesResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ForwardingRules not implemented") + return nil, status.Errorf(codes.Unimplemented, "method ForwardingRules not implemented") } func (UnimplementedDaemonServiceServer) DebugBundle(context.Context, *DebugBundleRequest) (*DebugBundleResponse, error) { - return nil, status.Error(codes.Unimplemented, "method DebugBundle not implemented") + return nil, status.Errorf(codes.Unimplemented, "method DebugBundle not implemented") } func (UnimplementedDaemonServiceServer) GetLogLevel(context.Context, *GetLogLevelRequest) (*GetLogLevelResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetLogLevel not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetLogLevel not implemented") } func (UnimplementedDaemonServiceServer) SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error) { - return nil, status.Error(codes.Unimplemented, "method SetLogLevel not implemented") + return nil, status.Errorf(codes.Unimplemented, "method SetLogLevel not implemented") } func (UnimplementedDaemonServiceServer) ListStates(context.Context, *ListStatesRequest) (*ListStatesResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ListStates not implemented") + return nil, status.Errorf(codes.Unimplemented, "method ListStates not implemented") } func (UnimplementedDaemonServiceServer) CleanState(context.Context, *CleanStateRequest) (*CleanStateResponse, error) { - return nil, status.Error(codes.Unimplemented, "method CleanState not implemented") + return nil, status.Errorf(codes.Unimplemented, "method CleanState not implemented") } func (UnimplementedDaemonServiceServer) DeleteState(context.Context, *DeleteStateRequest) (*DeleteStateResponse, error) { - return nil, status.Error(codes.Unimplemented, "method DeleteState not implemented") + return nil, status.Errorf(codes.Unimplemented, "method DeleteState not implemented") } func (UnimplementedDaemonServiceServer) SetSyncResponsePersistence(context.Context, *SetSyncResponsePersistenceRequest) (*SetSyncResponsePersistenceResponse, error) { - return nil, status.Error(codes.Unimplemented, "method SetSyncResponsePersistence not implemented") + return nil, status.Errorf(codes.Unimplemented, "method SetSyncResponsePersistence not implemented") } func (UnimplementedDaemonServiceServer) TracePacket(context.Context, *TracePacketRequest) (*TracePacketResponse, error) { - return nil, status.Error(codes.Unimplemented, "method TracePacket not implemented") + return nil, status.Errorf(codes.Unimplemented, "method TracePacket not implemented") } -func (UnimplementedDaemonServiceServer) SubscribeEvents(*SubscribeRequest, grpc.ServerStreamingServer[SystemEvent]) error { - return status.Error(codes.Unimplemented, "method SubscribeEvents not implemented") +func (UnimplementedDaemonServiceServer) SubscribeEvents(*SubscribeRequest, DaemonService_SubscribeEventsServer) error { + return status.Errorf(codes.Unimplemented, "method SubscribeEvents not implemented") } func (UnimplementedDaemonServiceServer) GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetEvents not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented") } func (UnimplementedDaemonServiceServer) SwitchProfile(context.Context, *SwitchProfileRequest) (*SwitchProfileResponse, error) { - return nil, status.Error(codes.Unimplemented, "method SwitchProfile not implemented") + return nil, status.Errorf(codes.Unimplemented, "method SwitchProfile not implemented") } func (UnimplementedDaemonServiceServer) SetConfig(context.Context, *SetConfigRequest) (*SetConfigResponse, error) { - return nil, status.Error(codes.Unimplemented, "method SetConfig not implemented") + return nil, status.Errorf(codes.Unimplemented, "method SetConfig not implemented") } func (UnimplementedDaemonServiceServer) AddProfile(context.Context, *AddProfileRequest) (*AddProfileResponse, error) { - return nil, status.Error(codes.Unimplemented, "method AddProfile not implemented") + return nil, status.Errorf(codes.Unimplemented, "method AddProfile not implemented") } func (UnimplementedDaemonServiceServer) RemoveProfile(context.Context, *RemoveProfileRequest) (*RemoveProfileResponse, error) { - return nil, status.Error(codes.Unimplemented, "method RemoveProfile not implemented") + return nil, status.Errorf(codes.Unimplemented, "method RemoveProfile not implemented") } func (UnimplementedDaemonServiceServer) ListProfiles(context.Context, *ListProfilesRequest) (*ListProfilesResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ListProfiles not implemented") + return nil, status.Errorf(codes.Unimplemented, "method ListProfiles not implemented") } func (UnimplementedDaemonServiceServer) GetActiveProfile(context.Context, *GetActiveProfileRequest) (*GetActiveProfileResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetActiveProfile not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetActiveProfile not implemented") } func (UnimplementedDaemonServiceServer) Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) { - return nil, status.Error(codes.Unimplemented, "method Logout not implemented") + return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented") } func (UnimplementedDaemonServiceServer) GetFeatures(context.Context, *GetFeaturesRequest) (*GetFeaturesResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetFeatures not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetFeatures not implemented") } func (UnimplementedDaemonServiceServer) TriggerUpdate(context.Context, *TriggerUpdateRequest) (*TriggerUpdateResponse, error) { - return nil, status.Error(codes.Unimplemented, "method TriggerUpdate not implemented") + return nil, status.Errorf(codes.Unimplemented, "method TriggerUpdate not implemented") } func (UnimplementedDaemonServiceServer) GetPeerSSHHostKey(context.Context, *GetPeerSSHHostKeyRequest) (*GetPeerSSHHostKeyResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetPeerSSHHostKey not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetPeerSSHHostKey not implemented") } func (UnimplementedDaemonServiceServer) RequestJWTAuth(context.Context, *RequestJWTAuthRequest) (*RequestJWTAuthResponse, error) { - return nil, status.Error(codes.Unimplemented, "method RequestJWTAuth not implemented") + return nil, status.Errorf(codes.Unimplemented, "method RequestJWTAuth not implemented") } func (UnimplementedDaemonServiceServer) WaitJWTToken(context.Context, *WaitJWTTokenRequest) (*WaitJWTTokenResponse, error) { - return nil, status.Error(codes.Unimplemented, "method WaitJWTToken not implemented") + return nil, status.Errorf(codes.Unimplemented, "method WaitJWTToken not implemented") } func (UnimplementedDaemonServiceServer) StartCPUProfile(context.Context, *StartCPUProfileRequest) (*StartCPUProfileResponse, error) { - return nil, status.Error(codes.Unimplemented, "method StartCPUProfile not implemented") + return nil, status.Errorf(codes.Unimplemented, "method StartCPUProfile not implemented") } func (UnimplementedDaemonServiceServer) StopCPUProfile(context.Context, *StopCPUProfileRequest) (*StopCPUProfileResponse, error) { - return nil, status.Error(codes.Unimplemented, "method StopCPUProfile not implemented") + return nil, status.Errorf(codes.Unimplemented, "method StopCPUProfile not implemented") } func (UnimplementedDaemonServiceServer) GetInstallerResult(context.Context, *InstallerResultRequest) (*InstallerResultResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetInstallerResult not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetInstallerResult not implemented") } -func (UnimplementedDaemonServiceServer) ExposeService(*ExposeServiceRequest, grpc.ServerStreamingServer[ExposeServiceEvent]) error { - return status.Error(codes.Unimplemented, "method ExposeService not implemented") +func (UnimplementedDaemonServiceServer) ExposeService(*ExposeServiceRequest, DaemonService_ExposeServiceServer) error { + return status.Errorf(codes.Unimplemented, "method ExposeService not implemented") } func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {} -func (UnimplementedDaemonServiceServer) testEmbeddedByValue() {} // UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to DaemonServiceServer will @@ -706,13 +651,6 @@ type UnsafeDaemonServiceServer interface { } func RegisterDaemonServiceServer(s grpc.ServiceRegistrar, srv DaemonServiceServer) { - // If the following call panics, it indicates UnimplementedDaemonServiceServer was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } s.RegisterService(&DaemonService_ServiceDesc, srv) } @@ -726,7 +664,7 @@ func _DaemonService_Login_Handler(srv interface{}, ctx context.Context, dec func } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_Login_FullMethodName, + FullMethod: "/daemon.DaemonService/Login", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).Login(ctx, req.(*LoginRequest)) @@ -744,7 +682,7 @@ func _DaemonService_WaitSSOLogin_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_WaitSSOLogin_FullMethodName, + FullMethod: "/daemon.DaemonService/WaitSSOLogin", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).WaitSSOLogin(ctx, req.(*WaitSSOLoginRequest)) @@ -762,7 +700,7 @@ func _DaemonService_Up_Handler(srv interface{}, ctx context.Context, dec func(in } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_Up_FullMethodName, + FullMethod: "/daemon.DaemonService/Up", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).Up(ctx, req.(*UpRequest)) @@ -780,7 +718,7 @@ func _DaemonService_Status_Handler(srv interface{}, ctx context.Context, dec fun } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_Status_FullMethodName, + FullMethod: "/daemon.DaemonService/Status", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).Status(ctx, req.(*StatusRequest)) @@ -798,7 +736,7 @@ func _DaemonService_Down_Handler(srv interface{}, ctx context.Context, dec func( } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_Down_FullMethodName, + FullMethod: "/daemon.DaemonService/Down", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).Down(ctx, req.(*DownRequest)) @@ -816,7 +754,7 @@ func _DaemonService_GetConfig_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_GetConfig_FullMethodName, + FullMethod: "/daemon.DaemonService/GetConfig", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).GetConfig(ctx, req.(*GetConfigRequest)) @@ -834,7 +772,7 @@ func _DaemonService_ListNetworks_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_ListNetworks_FullMethodName, + FullMethod: "/daemon.DaemonService/ListNetworks", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).ListNetworks(ctx, req.(*ListNetworksRequest)) @@ -852,7 +790,7 @@ func _DaemonService_SelectNetworks_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_SelectNetworks_FullMethodName, + FullMethod: "/daemon.DaemonService/SelectNetworks", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).SelectNetworks(ctx, req.(*SelectNetworksRequest)) @@ -870,7 +808,7 @@ func _DaemonService_DeselectNetworks_Handler(srv interface{}, ctx context.Contex } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_DeselectNetworks_FullMethodName, + FullMethod: "/daemon.DaemonService/DeselectNetworks", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).DeselectNetworks(ctx, req.(*SelectNetworksRequest)) @@ -888,7 +826,7 @@ func _DaemonService_ForwardingRules_Handler(srv interface{}, ctx context.Context } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_ForwardingRules_FullMethodName, + FullMethod: "/daemon.DaemonService/ForwardingRules", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).ForwardingRules(ctx, req.(*EmptyRequest)) @@ -906,7 +844,7 @@ func _DaemonService_DebugBundle_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_DebugBundle_FullMethodName, + FullMethod: "/daemon.DaemonService/DebugBundle", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).DebugBundle(ctx, req.(*DebugBundleRequest)) @@ -924,7 +862,7 @@ func _DaemonService_GetLogLevel_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_GetLogLevel_FullMethodName, + FullMethod: "/daemon.DaemonService/GetLogLevel", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).GetLogLevel(ctx, req.(*GetLogLevelRequest)) @@ -942,7 +880,7 @@ func _DaemonService_SetLogLevel_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_SetLogLevel_FullMethodName, + FullMethod: "/daemon.DaemonService/SetLogLevel", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).SetLogLevel(ctx, req.(*SetLogLevelRequest)) @@ -960,7 +898,7 @@ func _DaemonService_ListStates_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_ListStates_FullMethodName, + FullMethod: "/daemon.DaemonService/ListStates", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).ListStates(ctx, req.(*ListStatesRequest)) @@ -978,7 +916,7 @@ func _DaemonService_CleanState_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_CleanState_FullMethodName, + FullMethod: "/daemon.DaemonService/CleanState", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).CleanState(ctx, req.(*CleanStateRequest)) @@ -996,7 +934,7 @@ func _DaemonService_DeleteState_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_DeleteState_FullMethodName, + FullMethod: "/daemon.DaemonService/DeleteState", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).DeleteState(ctx, req.(*DeleteStateRequest)) @@ -1014,7 +952,7 @@ func _DaemonService_SetSyncResponsePersistence_Handler(srv interface{}, ctx cont } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_SetSyncResponsePersistence_FullMethodName, + FullMethod: "/daemon.DaemonService/SetSyncResponsePersistence", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).SetSyncResponsePersistence(ctx, req.(*SetSyncResponsePersistenceRequest)) @@ -1032,7 +970,7 @@ func _DaemonService_TracePacket_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_TracePacket_FullMethodName, + FullMethod: "/daemon.DaemonService/TracePacket", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).TracePacket(ctx, req.(*TracePacketRequest)) @@ -1045,11 +983,21 @@ func _DaemonService_SubscribeEvents_Handler(srv interface{}, stream grpc.ServerS if err := stream.RecvMsg(m); err != nil { return err } - return srv.(DaemonServiceServer).SubscribeEvents(m, &grpc.GenericServerStream[SubscribeRequest, SystemEvent]{ServerStream: stream}) + return srv.(DaemonServiceServer).SubscribeEvents(m, &daemonServiceSubscribeEventsServer{stream}) +} + +type DaemonService_SubscribeEventsServer interface { + Send(*SystemEvent) error + grpc.ServerStream +} + +type daemonServiceSubscribeEventsServer struct { + grpc.ServerStream } -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type DaemonService_SubscribeEventsServer = grpc.ServerStreamingServer[SystemEvent] +func (x *daemonServiceSubscribeEventsServer) Send(m *SystemEvent) error { + return x.ServerStream.SendMsg(m) +} func _DaemonService_GetEvents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetEventsRequest) @@ -1061,7 +1009,7 @@ func _DaemonService_GetEvents_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_GetEvents_FullMethodName, + FullMethod: "/daemon.DaemonService/GetEvents", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).GetEvents(ctx, req.(*GetEventsRequest)) @@ -1079,7 +1027,7 @@ func _DaemonService_SwitchProfile_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_SwitchProfile_FullMethodName, + FullMethod: "/daemon.DaemonService/SwitchProfile", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).SwitchProfile(ctx, req.(*SwitchProfileRequest)) @@ -1097,7 +1045,7 @@ func _DaemonService_SetConfig_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_SetConfig_FullMethodName, + FullMethod: "/daemon.DaemonService/SetConfig", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).SetConfig(ctx, req.(*SetConfigRequest)) @@ -1115,7 +1063,7 @@ func _DaemonService_AddProfile_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_AddProfile_FullMethodName, + FullMethod: "/daemon.DaemonService/AddProfile", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).AddProfile(ctx, req.(*AddProfileRequest)) @@ -1133,7 +1081,7 @@ func _DaemonService_RemoveProfile_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_RemoveProfile_FullMethodName, + FullMethod: "/daemon.DaemonService/RemoveProfile", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).RemoveProfile(ctx, req.(*RemoveProfileRequest)) @@ -1151,7 +1099,7 @@ func _DaemonService_ListProfiles_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_ListProfiles_FullMethodName, + FullMethod: "/daemon.DaemonService/ListProfiles", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).ListProfiles(ctx, req.(*ListProfilesRequest)) @@ -1169,7 +1117,7 @@ func _DaemonService_GetActiveProfile_Handler(srv interface{}, ctx context.Contex } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_GetActiveProfile_FullMethodName, + FullMethod: "/daemon.DaemonService/GetActiveProfile", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).GetActiveProfile(ctx, req.(*GetActiveProfileRequest)) @@ -1187,7 +1135,7 @@ func _DaemonService_Logout_Handler(srv interface{}, ctx context.Context, dec fun } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_Logout_FullMethodName, + FullMethod: "/daemon.DaemonService/Logout", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).Logout(ctx, req.(*LogoutRequest)) @@ -1205,7 +1153,7 @@ func _DaemonService_GetFeatures_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_GetFeatures_FullMethodName, + FullMethod: "/daemon.DaemonService/GetFeatures", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).GetFeatures(ctx, req.(*GetFeaturesRequest)) @@ -1223,7 +1171,7 @@ func _DaemonService_TriggerUpdate_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_TriggerUpdate_FullMethodName, + FullMethod: "/daemon.DaemonService/TriggerUpdate", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).TriggerUpdate(ctx, req.(*TriggerUpdateRequest)) @@ -1241,7 +1189,7 @@ func _DaemonService_GetPeerSSHHostKey_Handler(srv interface{}, ctx context.Conte } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_GetPeerSSHHostKey_FullMethodName, + FullMethod: "/daemon.DaemonService/GetPeerSSHHostKey", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).GetPeerSSHHostKey(ctx, req.(*GetPeerSSHHostKeyRequest)) @@ -1259,7 +1207,7 @@ func _DaemonService_RequestJWTAuth_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_RequestJWTAuth_FullMethodName, + FullMethod: "/daemon.DaemonService/RequestJWTAuth", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).RequestJWTAuth(ctx, req.(*RequestJWTAuthRequest)) @@ -1277,7 +1225,7 @@ func _DaemonService_WaitJWTToken_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_WaitJWTToken_FullMethodName, + FullMethod: "/daemon.DaemonService/WaitJWTToken", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).WaitJWTToken(ctx, req.(*WaitJWTTokenRequest)) @@ -1295,7 +1243,7 @@ func _DaemonService_StartCPUProfile_Handler(srv interface{}, ctx context.Context } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_StartCPUProfile_FullMethodName, + FullMethod: "/daemon.DaemonService/StartCPUProfile", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).StartCPUProfile(ctx, req.(*StartCPUProfileRequest)) @@ -1313,7 +1261,7 @@ func _DaemonService_StopCPUProfile_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_StopCPUProfile_FullMethodName, + FullMethod: "/daemon.DaemonService/StopCPUProfile", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).StopCPUProfile(ctx, req.(*StopCPUProfileRequest)) @@ -1331,7 +1279,7 @@ func _DaemonService_GetInstallerResult_Handler(srv interface{}, ctx context.Cont } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: DaemonService_GetInstallerResult_FullMethodName, + FullMethod: "/daemon.DaemonService/GetInstallerResult", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DaemonServiceServer).GetInstallerResult(ctx, req.(*InstallerResultRequest)) @@ -1344,11 +1292,21 @@ func _DaemonService_ExposeService_Handler(srv interface{}, stream grpc.ServerStr if err := stream.RecvMsg(m); err != nil { return err } - return srv.(DaemonServiceServer).ExposeService(m, &grpc.GenericServerStream[ExposeServiceRequest, ExposeServiceEvent]{ServerStream: stream}) + return srv.(DaemonServiceServer).ExposeService(m, &daemonServiceExposeServiceServer{stream}) +} + +type DaemonService_ExposeServiceServer interface { + Send(*ExposeServiceEvent) error + grpc.ServerStream } -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type DaemonService_ExposeServiceServer = grpc.ServerStreamingServer[ExposeServiceEvent] +type daemonServiceExposeServiceServer struct { + grpc.ServerStream +} + +func (x *daemonServiceExposeServiceServer) Send(m *ExposeServiceEvent) error { + return x.ServerStream.SendMsg(m) +} // DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service. // It's only intended for direct use with grpc.RegisterService, diff --git a/management/internals/shared/grpc/conversion.go b/management/internals/shared/grpc/conversion.go index ef417d3cfb5..5cccf252ed4 100644 --- a/management/internals/shared/grpc/conversion.go +++ b/management/internals/shared/grpc/conversion.go @@ -9,6 +9,7 @@ import ( log "github.com/sirupsen/logrus" integrationsConfig "github.com/netbirdio/management-integrations/integrations/config" + "github.com/netbirdio/netbird/shared/connectionmode" "github.com/netbirdio/netbird/client/ssh/auth" nbdns "github.com/netbirdio/netbird/dns" @@ -100,12 +101,40 @@ func toPeerConfig(peer *nbpeer.Peer, network *types.Network, dnsName string, set sshConfig.JwtConfig = buildJWTConfig(httpConfig, deviceFlowConfig) } + // Resolve the effective ConnectionMode for this peer. + // Phase 1: account-wide settings only (per-peer / per-group resolution + // follows in Phase 3 / issue #5990). The new ConnectionMode field wins + // over the legacy LazyConnectionEnabled boolean. UNSPECIFIED in Settings + // (i.e. ConnectionMode == nil) falls back to the legacy bool. + resolvedMode := connectionmode.ResolveLegacyLazyBool(settings.LazyConnectionEnabled) + if settings.ConnectionMode != nil { + if m, err := connectionmode.ParseString(*settings.ConnectionMode); err == nil && m != connectionmode.ModeUnspecified { + resolvedMode = m + } + } + + relayTO := uint32(0) + if settings.RelayTimeoutSeconds != nil { + relayTO = *settings.RelayTimeoutSeconds + } + p2pTO := uint32(0) + if settings.P2pTimeoutSeconds != nil { + p2pTO = *settings.P2pTimeoutSeconds + } + return &proto.PeerConfig{ Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), SshConfig: sshConfig, Fqdn: fqdn, RoutingPeerDnsResolutionEnabled: settings.RoutingPeerDNSResolutionEnabled, - LazyConnectionEnabled: settings.LazyConnectionEnabled, + // Send BOTH the new enum (for new clients) and the legacy boolean + // (for old clients). New clients prefer the explicit enum and + // ignore the bool; old clients ignore the unknown enum field + // (proto3 default behaviour) and fall back to the bool. + LazyConnectionEnabled: resolvedMode.ToLazyConnectionEnabled(), + ConnectionMode: resolvedMode.ToProto(), + P2PTimeoutSeconds: p2pTO, + RelayTimeoutSeconds: relayTO, AutoUpdate: &proto.AutoUpdateSettings{ Version: settings.AutoUpdateVersion, AlwaysUpdate: settings.AutoUpdateAlways, diff --git a/management/internals/shared/grpc/conversion_test.go b/management/internals/shared/grpc/conversion_test.go index 1e75caf959a..4646f6bdde2 100644 --- a/management/internals/shared/grpc/conversion_test.go +++ b/management/internals/shared/grpc/conversion_test.go @@ -2,6 +2,7 @@ package grpc import ( "fmt" + "net" "net/netip" "reflect" "testing" @@ -12,8 +13,125 @@ import ( "github.com/netbirdio/netbird/management/internals/controllers/network_map" "github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache" nbconfig "github.com/netbirdio/netbird/management/internals/server/config" + nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/management/server/types" + mgmProto "github.com/netbirdio/netbird/shared/management/proto" ) +// TestToPeerConfig_ConnectionModeResolution covers Phase 1 of issue #5989: +// the management server resolves the effective ConnectionMode from +// Settings (with the new ConnectionMode field winning over the legacy +// LazyConnectionEnabled boolean), then writes BOTH wire fields so old +// clients (boolean only) and new clients (enum only) see consistent +// behaviour. +func TestToPeerConfig_ConnectionModeResolution(t *testing.T) { + cases := []struct { + name string + settingsMode *string + settingsLazyBool bool + settingsRelayTO *uint32 + settingsP2pTO *uint32 + wantPCMode mgmProto.ConnectionMode + wantPCLazyBool bool + wantPCRelayTO uint32 + wantPCP2pTO uint32 + }{ + { + name: "no settings -> P2P + lazy=false", + wantPCMode: mgmProto.ConnectionMode_CONNECTION_MODE_P2P, + wantPCLazyBool: false, + }, + { + name: "only legacy lazy=true -> P2P_LAZY + lazy=true", + settingsLazyBool: true, + wantPCMode: mgmProto.ConnectionMode_CONNECTION_MODE_P2P_LAZY, + wantPCLazyBool: true, + }, + { + name: "ConnectionMode=p2p-lazy explicit -> P2P_LAZY + lazy=true", + settingsMode: strPtrTest("p2p-lazy"), + wantPCMode: mgmProto.ConnectionMode_CONNECTION_MODE_P2P_LAZY, + wantPCLazyBool: true, + }, + { + name: "ConnectionMode=p2p explicit -> P2P + lazy=false", + settingsMode: strPtrTest("p2p"), + wantPCMode: mgmProto.ConnectionMode_CONNECTION_MODE_P2P, + wantPCLazyBool: false, + }, + { + name: "ConnectionMode=relay-forced -> RELAY_FORCED + lazy=false (structural compat gap)", + settingsMode: strPtrTest("relay-forced"), + wantPCMode: mgmProto.ConnectionMode_CONNECTION_MODE_RELAY_FORCED, + wantPCLazyBool: false, + }, + { + name: "ConnectionMode wins over conflicting legacy bool", + settingsMode: strPtrTest("relay-forced"), + settingsLazyBool: true, // ignored + wantPCMode: mgmProto.ConnectionMode_CONNECTION_MODE_RELAY_FORCED, + wantPCLazyBool: false, + }, + { + name: "RelayTimeout propagates", + settingsMode: strPtrTest("p2p-lazy"), + settingsRelayTO: u32PtrTest(42), + wantPCMode: mgmProto.ConnectionMode_CONNECTION_MODE_P2P_LAZY, + wantPCLazyBool: true, + wantPCRelayTO: 42, + }, + { + name: "P2pTimeout propagates", + settingsMode: strPtrTest("p2p-dynamic"), + settingsP2pTO: u32PtrTest(180), + wantPCMode: mgmProto.ConnectionMode_CONNECTION_MODE_P2P_DYNAMIC, + wantPCLazyBool: false, // p2p-dynamic maps to lazy=false (best-match for old clients) + wantPCP2pTO: 180, + }, + { + name: "Garbage in ConnectionMode falls back to legacy bool", + settingsMode: strPtrTest("not-a-mode"), + settingsLazyBool: true, + wantPCMode: mgmProto.ConnectionMode_CONNECTION_MODE_P2P_LAZY, + wantPCLazyBool: true, + }, + } + + // Minimal Network and Peer fixtures shared across cases. + _, ipnet, _ := net.ParseCIDR("10.0.0.0/16") + network := &types.Network{Net: *ipnet} + peer := &nbpeer.Peer{ + ID: "p1", + Name: "test-peer", + DNSLabel: "test-peer", + IP: net.IPv4(10, 0, 0, 5), + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + settings := &types.Settings{ + LazyConnectionEnabled: c.settingsLazyBool, + ConnectionMode: c.settingsMode, + RelayTimeoutSeconds: c.settingsRelayTO, + P2pTimeoutSeconds: c.settingsP2pTO, + } + pc := toPeerConfig(peer, network, "example.local", settings, nil, nil, false) + + assert.Equal(t, c.wantPCMode, pc.GetConnectionMode(), + "ConnectionMode wire field") + assert.Equal(t, c.wantPCLazyBool, pc.GetLazyConnectionEnabled(), + "LazyConnectionEnabled wire field (backwards-compat)") + assert.Equal(t, c.wantPCRelayTO, pc.GetRelayTimeoutSeconds(), + "RelayTimeoutSeconds wire field") + assert.Equal(t, c.wantPCP2pTO, pc.GetP2PTimeoutSeconds(), + "P2PTimeoutSeconds wire field") + }) + } +} + +func strPtrTest(s string) *string { return &s } +func u32PtrTest(v uint32) *uint32 { return &v } + func TestToProtocolDNSConfigWithCache(t *testing.T) { var cache cache.DNSConfigCache diff --git a/management/server/account.go b/management/server/account.go index 4b71ab486eb..94f1b10e258 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -333,7 +333,10 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco oldSettings.LazyConnectionEnabled != newSettings.LazyConnectionEnabled || oldSettings.DNSDomain != newSettings.DNSDomain || oldSettings.AutoUpdateVersion != newSettings.AutoUpdateVersion || - oldSettings.AutoUpdateAlways != newSettings.AutoUpdateAlways { + oldSettings.AutoUpdateAlways != newSettings.AutoUpdateAlways || + !equalStringPtr(oldSettings.ConnectionMode, newSettings.ConnectionMode) || + !equalUint32Ptr(oldSettings.RelayTimeoutSeconds, newSettings.RelayTimeoutSeconds) || + !equalUint32Ptr(oldSettings.P2pTimeoutSeconds, newSettings.P2pTimeoutSeconds) { updateAccountPeers = true } @@ -371,6 +374,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco am.handleRoutingPeerDNSResolutionSettings(ctx, oldSettings, newSettings, userID, accountID) am.handleLazyConnectionSettings(ctx, oldSettings, newSettings, userID, accountID) + am.handleConnectionModeSettings(ctx, oldSettings, newSettings, userID, accountID) am.handlePeerLoginExpirationSettings(ctx, oldSettings, newSettings, userID, accountID) am.handleGroupsPropagationSettings(ctx, oldSettings, newSettings, userID, accountID) am.handleAutoUpdateVersionSettings(ctx, oldSettings, newSettings, userID, accountID) @@ -455,6 +459,66 @@ func (am *DefaultAccountManager) handleLazyConnectionSettings(ctx context.Contex } } +// handleConnectionModeSettings emits one audit event per changed Phase-1 +// connection-mode setting (mode, relay timeout, p2p timeout). Each event +// carries old/new values in the meta payload so administrators can audit +// the full transition. NULL transitions show as empty string / 0 in the +// meta — chosen over a sentinel so the frontend can render uniformly. +func (am *DefaultAccountManager) handleConnectionModeSettings(ctx context.Context, oldSettings, newSettings *types.Settings, userID, accountID string) { + if !equalStringPtr(oldSettings.ConnectionMode, newSettings.ConnectionMode) { + am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountConnectionModeChanged, map[string]any{ + "old": derefStringPtr(oldSettings.ConnectionMode), + "new": derefStringPtr(newSettings.ConnectionMode), + }) + } + if !equalUint32Ptr(oldSettings.RelayTimeoutSeconds, newSettings.RelayTimeoutSeconds) { + am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountRelayTimeoutChanged, map[string]any{ + "old": derefUint32Ptr(oldSettings.RelayTimeoutSeconds), + "new": derefUint32Ptr(newSettings.RelayTimeoutSeconds), + }) + } + if !equalUint32Ptr(oldSettings.P2pTimeoutSeconds, newSettings.P2pTimeoutSeconds) { + am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountP2pTimeoutChanged, map[string]any{ + "old": derefUint32Ptr(oldSettings.P2pTimeoutSeconds), + "new": derefUint32Ptr(newSettings.P2pTimeoutSeconds), + }) + } +} + +func equalStringPtr(a, b *string) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return *a == *b +} + +func equalUint32Ptr(a, b *uint32) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return *a == *b +} + +func derefStringPtr(p *string) string { + if p == nil { + return "" + } + return *p +} + +func derefUint32Ptr(p *uint32) uint32 { + if p == nil { + return 0 + } + return *p +} + func (am *DefaultAccountManager) handlePeerLoginExpirationSettings(ctx context.Context, oldSettings, newSettings *types.Settings, userID, accountID string) { if oldSettings.PeerLoginExpirationEnabled != newSettings.PeerLoginExpirationEnabled { event := activity.AccountPeerLoginExpirationEnabled diff --git a/management/server/account_test.go b/management/server/account_test.go index 756c4242168..9ab4a2c1333 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -2064,6 +2064,102 @@ func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) { require.Error(t, err, "expecting to fail when providing PeerLoginExpiration more than 180 days") } +// TestDefaultAccountManager_UpdateAccountSettings_ConnectionModeIncrementsSerial +// verifies that changes to ConnectionMode, RelayTimeoutSeconds, or +// P2pTimeoutSeconds bump the account network serial so connected peers +// receive the new values via the next push instead of waiting for an +// unrelated change or reconnect. Regression guard for the original +// gating omission noted on PR #6047. +func TestDefaultAccountManager_UpdateAccountSettings_ConnectionModeIncrementsSerial(t *testing.T) { + manager, _, err := createManager(t) + require.NoError(t, err, "unable to create account manager") + + accountID, err := manager.GetAccountIDByUserID(context.Background(), auth.UserAuth{UserId: userID}) + require.NoError(t, err, "unable to get account by user id") + + baseAccount, err := manager.Store.GetAccount(context.Background(), accountID) + require.NoError(t, err, "unable to load account") + + // Establish a stable baseline by passing the existing settings back + // unchanged. Some default initialisation paths in UpdateAccountSettings + // can flip booleans on the very first call, so we burn that here. + settingsCopy := *baseAccount.Settings + if baseAccount.Settings.Extra != nil { + extraCopy := *baseAccount.Settings.Extra + settingsCopy.Extra = &extraCopy + } + _, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &settingsCopy) + require.NoError(t, err, "settings echo should succeed") + + a, err := manager.Store.GetAccount(context.Background(), accountID) + require.NoError(t, err) + baseSerial := a.Network.CurrentSerial() + + // 1. Re-applying the same settings must NOT bump the serial again. + settingsCopy2 := *a.Settings + if a.Settings.Extra != nil { + extraCopy := *a.Settings.Extra + settingsCopy2.Extra = &extraCopy + } + _, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &settingsCopy2) + require.NoError(t, err, "second echo should succeed") + + a, err = manager.Store.GetAccount(context.Background(), accountID) + require.NoError(t, err) + require.Equal(t, baseSerial, a.Network.CurrentSerial(), + "identical settings update must not increment network serial") + + // 2. ConnectionMode change must bump serial. + mode := "p2p-dynamic" + settingsWithMode := *a.Settings + if a.Settings.Extra != nil { + extraCopy := *a.Settings.Extra + settingsWithMode.Extra = &extraCopy + } + settingsWithMode.ConnectionMode = &mode + _, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &settingsWithMode) + require.NoError(t, err) + + a, err = manager.Store.GetAccount(context.Background(), accountID) + require.NoError(t, err) + afterMode := a.Network.CurrentSerial() + require.Greater(t, afterMode, baseSerial, + "ConnectionMode change must increment network serial so peers receive the push") + + // 3. RelayTimeoutSeconds change must bump serial. + relay := uint32(43200) + settingsWithRelay := *a.Settings + if a.Settings.Extra != nil { + extraCopy := *a.Settings.Extra + settingsWithRelay.Extra = &extraCopy + } + settingsWithRelay.RelayTimeoutSeconds = &relay + _, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &settingsWithRelay) + require.NoError(t, err) + + a, err = manager.Store.GetAccount(context.Background(), accountID) + require.NoError(t, err) + afterRelay := a.Network.CurrentSerial() + require.Greater(t, afterRelay, afterMode, + "RelayTimeoutSeconds change must increment network serial") + + // 4. P2pTimeoutSeconds change must bump serial. + p2p := uint32(5400) + settingsWithP2p := *a.Settings + if a.Settings.Extra != nil { + extraCopy := *a.Settings.Extra + settingsWithP2p.Extra = &extraCopy + } + settingsWithP2p.P2pTimeoutSeconds = &p2p + _, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &settingsWithP2p) + require.NoError(t, err) + + a, err = manager.Store.GetAccount(context.Background(), accountID) + require.NoError(t, err) + require.Greater(t, a.Network.CurrentSerial(), afterRelay, + "P2pTimeoutSeconds change must increment network serial") +} + func TestDefaultAccountManager_UpdateAccountSettings_PeerApproval(t *testing.T) { manager, _, account, peer1, peer2, peer3 := setupNetworkMapTest(t) diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index ddc3e00c38d..edd17bc7302 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -232,6 +232,16 @@ const ( // DomainValidated indicates that a custom domain was validated DomainValidated Activity = 120 + // AccountConnectionModeChanged indicates the account-wide ConnectionMode + // setting was changed (Phase 1 of issue #5989). + AccountConnectionModeChanged Activity = 121 + // AccountRelayTimeoutChanged indicates the account-wide RelayTimeoutSeconds + // setting was changed. + AccountRelayTimeoutChanged Activity = 122 + // AccountP2pTimeoutChanged indicates the account-wide P2pTimeoutSeconds + // setting was changed. + AccountP2pTimeoutChanged Activity = 123 + AccountDeleted Activity = 99999 ) @@ -335,6 +345,10 @@ var activityMap = map[Activity]Code{ AccountLazyConnectionEnabled: {"Account lazy connection enabled", "account.setting.lazy.connection.enable"}, AccountLazyConnectionDisabled: {"Account lazy connection disabled", "account.setting.lazy.connection.disable"}, + AccountConnectionModeChanged: {"Account connection mode changed", "account.setting.connection_mode.change"}, + AccountRelayTimeoutChanged: {"Account relay timeout changed", "account.setting.relay_timeout.change"}, + AccountP2pTimeoutChanged: {"Account p2p timeout changed", "account.setting.p2p_timeout.change"}, + AccountNetworkRangeUpdated: {"Account network range updated", "account.network.range.update"}, PeerIPUpdated: {"Peer IP updated", "peer.ip.update"}, diff --git a/management/server/http/handlers/accounts/accounts_handler.go b/management/server/http/handlers/accounts/accounts_handler.go index cc5567e3db6..31046e0f53d 100644 --- a/management/server/http/handlers/accounts/accounts_handler.go +++ b/management/server/http/handlers/accounts/accounts_handler.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "math" "net/http" "net/netip" "time" @@ -21,6 +22,25 @@ import ( "github.com/netbirdio/netbird/shared/management/status" ) +// validateUint32Timeout converts a request-side *int64 timeout into the +// internal *uint32 representation, rejecting negative values and values +// that would silently wrap when narrowed. Used for the Phase 1 +// p2p_timeout_seconds and relay_timeout_seconds fields. +func validateUint32Timeout(field string, ptr *int64) (*uint32, error) { + if ptr == nil { + return nil, nil + } + v := *ptr + if v < 0 { + return nil, fmt.Errorf("%s must be >= 0, got %d", field, v) + } + if v > int64(math.MaxUint32) { + return nil, fmt.Errorf("%s must be <= %d, got %d", field, uint32(math.MaxUint32), v) + } + out := uint32(v) + return &out, nil +} + const ( // PeerBufferPercentage is the percentage of peers to add as buffer for network range calculations PeerBufferPercentage = 0.5 @@ -215,6 +235,35 @@ func (h *handler) updateAccountRequestSettings(req api.PutApiAccountsAccountIdJS if req.Settings.LazyConnectionEnabled != nil { returnSettings.LazyConnectionEnabled = *req.Settings.LazyConnectionEnabled } + if req.Settings.ConnectionMode != nil { + modeStr := string(*req.Settings.ConnectionMode) + if !req.Settings.ConnectionMode.Valid() { + return nil, fmt.Errorf("invalid connection_mode %q", modeStr) + } + // Persist as the canonical string. Note: the request type uses a + // non-pointer `*api.AccountSettingsConnectionMode` and JSON null + // is indistinguishable from an absent field at this layer -- both + // land here as a nil pointer and skip this whole block, leaving + // the existing value untouched. There is currently no API path + // that lets a client *clear* an explicit override; the next + // settings revision should switch to a sentinel-aware wrapper. + s := modeStr + returnSettings.ConnectionMode = &s + } + if req.Settings.P2pTimeoutSeconds != nil { + v, err := validateUint32Timeout("p2p_timeout_seconds", req.Settings.P2pTimeoutSeconds) + if err != nil { + return nil, err + } + returnSettings.P2pTimeoutSeconds = v + } + if req.Settings.RelayTimeoutSeconds != nil { + v, err := validateUint32Timeout("relay_timeout_seconds", req.Settings.RelayTimeoutSeconds) + if err != nil { + return nil, err + } + returnSettings.RelayTimeoutSeconds = v + } if req.Settings.AutoUpdateVersion != nil { _, err := goversion.NewSemver(*req.Settings.AutoUpdateVersion) if *req.Settings.AutoUpdateVersion == autoUpdateLatestVersion || @@ -349,6 +398,27 @@ func toAccountResponse(accountID string, settings *types.Settings, meta *types.A PeerExposeEnabled: settings.PeerExposeEnabled, PeerExposeGroups: settings.PeerExposeGroups, LazyConnectionEnabled: &settings.LazyConnectionEnabled, + ConnectionMode: func() *api.AccountSettingsConnectionMode { + if settings.ConnectionMode == nil { + return nil + } + v := api.AccountSettingsConnectionMode(*settings.ConnectionMode) + return &v + }(), + P2pTimeoutSeconds: func() *int64 { + if settings.P2pTimeoutSeconds == nil { + return nil + } + v := int64(*settings.P2pTimeoutSeconds) + return &v + }(), + RelayTimeoutSeconds: func() *int64 { + if settings.RelayTimeoutSeconds == nil { + return nil + } + v := int64(*settings.RelayTimeoutSeconds) + return &v + }(), DnsDomain: &settings.DNSDomain, AutoUpdateVersion: &settings.AutoUpdateVersion, AutoUpdateAlways: &settings.AutoUpdateAlways, diff --git a/management/server/http/handlers/accounts/accounts_handler_test.go b/management/server/http/handlers/accounts/accounts_handler_test.go index 739dfe2f655..ae34b1c8359 100644 --- a/management/server/http/handlers/accounts/accounts_handler_test.go +++ b/management/server/http/handlers/accounts/accounts_handler_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "io" + "math" "net/http" "net/http/httptest" "testing" @@ -336,3 +337,41 @@ func TestAccounts_AccountsHandler(t *testing.T) { }) } } + +func TestValidateUint32Timeout(t *testing.T) { + pi := func(v int64) *int64 { return &v } + + tests := []struct { + name string + in *int64 + want *uint32 + wantErr bool + }{ + {"nil passes through", nil, nil, false}, + {"zero is allowed", pi(0), func() *uint32 { v := uint32(0); return &v }(), false}, + {"common positive", pi(86400), func() *uint32 { v := uint32(86400); return &v }(), false}, + {"max uint32 boundary", pi(int64(math.MaxUint32)), func() *uint32 { v := uint32(math.MaxUint32); return &v }(), false}, + {"negative rejected", pi(-1), nil, true}, + {"over max uint32 rejected", pi(int64(math.MaxUint32) + 1), nil, true}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := validateUint32Timeout("test_field", tc.in) + if tc.wantErr { + if err == nil { + t.Fatalf("expected error, got nil") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if (got == nil) != (tc.want == nil) { + t.Fatalf("nil-ness mismatch: got=%v want=%v", got, tc.want) + } + if got != nil && *got != *tc.want { + t.Fatalf("value mismatch: got=%d want=%d", *got, *tc.want) + } + }) + } +} diff --git a/management/server/types/settings.go b/management/server/types/settings.go index 4ea79ec72fc..78c4108cdd2 100644 --- a/management/server/types/settings.go +++ b/management/server/types/settings.go @@ -58,6 +58,20 @@ type Settings struct { // LazyConnectionEnabled indicates if the experimental feature is enabled or disabled LazyConnectionEnabled bool `gorm:"default:false"` + // ConnectionMode is the account-wide default connection mode (Phase 1 + // of issue #5989). Nullable: NULL means "fall back to LazyConnectionEnabled". + // Stored as the canonical lower-kebab-case string (e.g. "p2p-lazy"). + ConnectionMode *string `gorm:"type:varchar(32);default:null"` + + // RelayTimeoutSeconds, when non-NULL, overrides the built-in default + // (5 min). 0 = "never tear down". Nullable to distinguish "use default" + // from "explicit 0". + RelayTimeoutSeconds *uint32 `gorm:"default:null"` + + // P2pTimeoutSeconds is reserved for Phase 2; same nullable semantics. + // Built-in default in Phase 1: 180 min, but not yet effective. + P2pTimeoutSeconds *uint32 `gorm:"default:null"` + // AutoUpdateVersion client auto-update version AutoUpdateVersion string `gorm:"default:'disabled'"` @@ -92,6 +106,9 @@ func (s *Settings) Copy() *Settings { PeerExposeEnabled: s.PeerExposeEnabled, PeerExposeGroups: slices.Clone(s.PeerExposeGroups), LazyConnectionEnabled: s.LazyConnectionEnabled, + ConnectionMode: cloneStringPtr(s.ConnectionMode), + RelayTimeoutSeconds: cloneUint32Ptr(s.RelayTimeoutSeconds), + P2pTimeoutSeconds: cloneUint32Ptr(s.P2pTimeoutSeconds), DNSDomain: s.DNSDomain, NetworkRange: s.NetworkRange, AutoUpdateVersion: s.AutoUpdateVersion, @@ -138,3 +155,23 @@ func (e *ExtraSettings) Copy() *ExtraSettings { FlowDnsCollectionEnabled: e.FlowDnsCollectionEnabled, } } + +// cloneStringPtr returns a deep copy of a *string (nil-safe). Used by +// Settings.Copy for the new nullable ConnectionMode field. +func cloneStringPtr(p *string) *string { + if p == nil { + return nil + } + v := *p + return &v +} + +// cloneUint32Ptr returns a deep copy of a *uint32 (nil-safe). Used by +// Settings.Copy for the new nullable timeout fields. +func cloneUint32Ptr(p *uint32) *uint32 { + if p == nil { + return nil + } + v := *p + return &v +} diff --git a/shared/connectionmode/mode.go b/shared/connectionmode/mode.go new file mode 100644 index 00000000000..c40a498a87e --- /dev/null +++ b/shared/connectionmode/mode.go @@ -0,0 +1,132 @@ +// Package connectionmode defines the Mode type used to control how a peer +// establishes connections to other peers. Introduced in Phase 1 of the +// connection-mode consolidation (issue #5989) to replace the historical +// pair (NB_FORCE_RELAY, NB_ENABLE_EXPERIMENTAL_LAZY_CONN). +package connectionmode + +import ( + "fmt" + "strings" + + mgmProto "github.com/netbirdio/netbird/shared/management/proto" +) + +// Mode is a connection mode for peer-to-peer (or relay-only) connections. +// ModeUnspecified is the zero value and indicates "fall back to the next +// resolution source" (env -> config -> server-pushed -> legacy bool). +type Mode int + +const ( + ModeUnspecified Mode = iota + ModeRelayForced + ModeP2P + ModeP2PLazy + ModeP2PDynamic + // ModeFollowServer is a client-side sentinel: setting this in the + // client config explicitly clears any local override so the + // server-pushed value (or its legacy fallback) is used. It MUST NOT + // be sent on the wire -- ToProto returns UNSPECIFIED for it. + ModeFollowServer +) + +// String returns the canonical lower-kebab-case name of the mode. +func (m Mode) String() string { + switch m { + case ModeRelayForced: + return "relay-forced" + case ModeP2P: + return "p2p" + case ModeP2PLazy: + return "p2p-lazy" + case ModeP2PDynamic: + return "p2p-dynamic" + case ModeFollowServer: + return "follow-server" + default: + return "" + } +} + +// ParseString accepts the canonical name (case-insensitive, surrounding +// whitespace tolerated) and returns the corresponding Mode. Empty input +// returns ModeUnspecified with no error. Unknown input returns +// ModeUnspecified with an error. +func ParseString(s string) (Mode, error) { + switch strings.ToLower(strings.TrimSpace(s)) { + case "": + return ModeUnspecified, nil + case "relay-forced", "relay": + // "relay" is accepted as a forgiving alias for "relay-forced": + // older docs / CLI examples used the shorter form and the + // dashboard label is "Relay". The canonical persisted form is + // always "relay-forced" via Mode.String() (see RoundTripString). + return ModeRelayForced, nil + case "p2p": + return ModeP2P, nil + case "p2p-lazy": + return ModeP2PLazy, nil + case "p2p-dynamic": + return ModeP2PDynamic, nil + case "follow-server": + return ModeFollowServer, nil + default: + return ModeUnspecified, fmt.Errorf("unknown connection mode %q", s) + } +} + +// FromProto translates a proto enum value to the internal Mode. +func FromProto(m mgmProto.ConnectionMode) Mode { + switch m { + case mgmProto.ConnectionMode_CONNECTION_MODE_RELAY_FORCED: + return ModeRelayForced + case mgmProto.ConnectionMode_CONNECTION_MODE_P2P: + return ModeP2P + case mgmProto.ConnectionMode_CONNECTION_MODE_P2P_LAZY: + return ModeP2PLazy + case mgmProto.ConnectionMode_CONNECTION_MODE_P2P_DYNAMIC: + return ModeP2PDynamic + default: + return ModeUnspecified + } +} + +// ToProto translates the internal Mode to a proto enum value. +// ModeFollowServer is a client-side concept and intentionally maps to +// UNSPECIFIED so it never appears on the wire. +func (m Mode) ToProto() mgmProto.ConnectionMode { + switch m { + case ModeRelayForced: + return mgmProto.ConnectionMode_CONNECTION_MODE_RELAY_FORCED + case ModeP2P: + return mgmProto.ConnectionMode_CONNECTION_MODE_P2P + case ModeP2PLazy: + return mgmProto.ConnectionMode_CONNECTION_MODE_P2P_LAZY + case ModeP2PDynamic: + return mgmProto.ConnectionMode_CONNECTION_MODE_P2P_DYNAMIC + default: + return mgmProto.ConnectionMode_CONNECTION_MODE_UNSPECIFIED + } +} + +// ResolveLegacyLazyBool maps the historical Settings.LazyConnectionEnabled +// boolean to the new Mode. Used when a new client receives an old server's +// PeerConfig (ConnectionMode = UNSPECIFIED) or when the management server +// has no explicit Settings.ConnectionMode set yet. +func ResolveLegacyLazyBool(lazy bool) Mode { + if lazy { + return ModeP2PLazy + } + return ModeP2P +} + +// ToLazyConnectionEnabled is the inverse mapping for backwards-compat. +// Used by toPeerConfig() so old clients (which only know the boolean) +// still get a sensible behaviour. +// +// Note: ModeRelayForced cannot be expressed via the legacy boolean and +// falls back to false. This is a structural compat gap documented in the +// release notes; admins must set NB_FORCE_RELAY=true on old clients +// or upgrade them. +func (m Mode) ToLazyConnectionEnabled() bool { + return m == ModeP2PLazy +} diff --git a/shared/connectionmode/mode_test.go b/shared/connectionmode/mode_test.go new file mode 100644 index 00000000000..3bd169c8f58 --- /dev/null +++ b/shared/connectionmode/mode_test.go @@ -0,0 +1,109 @@ +package connectionmode + +import ( + "testing" + + mgmProto "github.com/netbirdio/netbird/shared/management/proto" +) + +func TestParseString(t *testing.T) { + cases := []struct { + input string + want Mode + wantErr bool + }{ + {"relay-forced", ModeRelayForced, false}, + {"relay", ModeRelayForced, false}, // forgiving alias for older docs / dashboard label + {"RELAY", ModeRelayForced, false}, // case-insensitive + {" relay ", ModeRelayForced, false}, // whitespace-tolerant + {"p2p", ModeP2P, false}, + {"p2p-lazy", ModeP2PLazy, false}, + {"p2p-dynamic", ModeP2PDynamic, false}, + {"follow-server", ModeFollowServer, false}, + {"", ModeUnspecified, false}, + {"P2P", ModeP2P, false}, + {" p2p-lazy ", ModeP2PLazy, false}, + {"junk", ModeUnspecified, true}, + } + for _, c := range cases { + got, err := ParseString(c.input) + if (err != nil) != c.wantErr { + t.Errorf("ParseString(%q): err=%v wantErr=%v", c.input, err, c.wantErr) + continue + } + if got != c.want { + t.Errorf("ParseString(%q) = %v, want %v", c.input, got, c.want) + } + } +} + +func TestFromProto(t *testing.T) { + cases := []struct { + input mgmProto.ConnectionMode + want Mode + }{ + {mgmProto.ConnectionMode_CONNECTION_MODE_UNSPECIFIED, ModeUnspecified}, + {mgmProto.ConnectionMode_CONNECTION_MODE_RELAY_FORCED, ModeRelayForced}, + {mgmProto.ConnectionMode_CONNECTION_MODE_P2P, ModeP2P}, + {mgmProto.ConnectionMode_CONNECTION_MODE_P2P_LAZY, ModeP2PLazy}, + {mgmProto.ConnectionMode_CONNECTION_MODE_P2P_DYNAMIC, ModeP2PDynamic}, + } + for _, c := range cases { + got := FromProto(c.input) + if got != c.want { + t.Errorf("FromProto(%v) = %v, want %v", c.input, got, c.want) + } + } +} + +func TestToProto(t *testing.T) { + for _, m := range []Mode{ModeUnspecified, ModeRelayForced, ModeP2P, ModeP2PLazy, ModeP2PDynamic} { + got := FromProto(m.ToProto()) + if got != m { + t.Errorf("round-trip Mode %v -> proto -> Mode = %v", m, got) + } + } + if got := ModeFollowServer.ToProto(); got != mgmProto.ConnectionMode_CONNECTION_MODE_UNSPECIFIED { + t.Errorf("ModeFollowServer.ToProto() = %v, want UNSPECIFIED", got) + } +} + +func TestResolveLegacyLazyBool(t *testing.T) { + if got := ResolveLegacyLazyBool(true); got != ModeP2PLazy { + t.Errorf("ResolveLegacyLazyBool(true) = %v, want ModeP2PLazy", got) + } + if got := ResolveLegacyLazyBool(false); got != ModeP2P { + t.Errorf("ResolveLegacyLazyBool(false) = %v, want ModeP2P", got) + } +} + +func TestToLazyConnectionEnabled(t *testing.T) { + cases := []struct { + mode Mode + want bool + }{ + {ModeRelayForced, false}, + {ModeP2P, false}, + {ModeP2PLazy, true}, + {ModeP2PDynamic, false}, + {ModeUnspecified, false}, + } + for _, c := range cases { + got := c.mode.ToLazyConnectionEnabled() + if got != c.want { + t.Errorf("Mode %v ToLazyConnectionEnabled() = %v, want %v", c.mode, got, c.want) + } + } +} + +func TestStringRoundTrip(t *testing.T) { + for _, m := range []Mode{ModeRelayForced, ModeP2P, ModeP2PLazy, ModeP2PDynamic, ModeFollowServer} { + got, err := ParseString(m.String()) + if err != nil { + t.Errorf("round-trip parse of %v.String() failed: %v", m, err) + } + if got != m { + t.Errorf("round-trip %v -> %q -> %v", m, m.String(), got) + } + } +} diff --git a/shared/management/http/api/openapi.yml b/shared/management/http/api/openapi.yml index 327e2061425..e57e60a3b45 100644 --- a/shared/management/http/api/openapi.yml +++ b/shared/management/http/api/openapi.yml @@ -359,6 +359,38 @@ components: description: Enables or disables experimental lazy connection type: boolean example: true + connection_mode: + x-experimental: true + type: string + enum: [relay-forced, p2p, p2p-lazy, p2p-dynamic] + nullable: true + description: | + Account-wide default peer-connection mode. NULL means + "fall back to lazy_connection_enabled" for backwards compatibility. + Phase 1 of issue #5989: relay-forced, p2p, and p2p-lazy are + functional. p2p-dynamic is reserved (passes through as p2p in + Phase 1; will become functional in Phase 2). + p2p_timeout_seconds: + x-experimental: true + type: integer + format: int64 + minimum: 0 + nullable: true + description: | + Default ICE-worker idle timeout in seconds. 0 = never tear down. + Effective only in p2p-dynamic mode (added in Phase 2). + NULL means "use built-in default" (180 minutes). + relay_timeout_seconds: + x-experimental: true + type: integer + format: int64 + minimum: 0 + nullable: true + description: | + Default relay-worker idle timeout in seconds. 0 = never tear + down. Effective in p2p-lazy and p2p-dynamic modes. Backwards- + compat alias for NB_LAZY_CONN_INACTIVITY_THRESHOLD on the + client. NULL means "use built-in default" (5 minutes). auto_update_version: description: Set Clients auto-update version. "latest", "disabled", or a specific version (e.g "0.50.1") type: string diff --git a/shared/management/http/api/types.gen.go b/shared/management/http/api/types.gen.go index dc916f81ac9..471567da8ff 100644 --- a/shared/management/http/api/types.gen.go +++ b/shared/management/http/api/types.gen.go @@ -38,6 +38,30 @@ func (e AccessRestrictionsCrowdsecMode) Valid() bool { } } +// Defines values for AccountSettingsConnectionMode. +const ( + AccountSettingsConnectionModeP2p AccountSettingsConnectionMode = "p2p" + AccountSettingsConnectionModeP2pDynamic AccountSettingsConnectionMode = "p2p-dynamic" + AccountSettingsConnectionModeP2pLazy AccountSettingsConnectionMode = "p2p-lazy" + AccountSettingsConnectionModeRelayForced AccountSettingsConnectionMode = "relay-forced" +) + +// Valid indicates whether the value is a known member of the AccountSettingsConnectionMode enum. +func (e AccountSettingsConnectionMode) Valid() bool { + switch e { + case AccountSettingsConnectionModeP2p: + return true + case AccountSettingsConnectionModeP2pDynamic: + return true + case AccountSettingsConnectionModeP2pLazy: + return true + case AccountSettingsConnectionModeRelayForced: + return true + default: + return false + } +} + // Defines values for CreateAzureIntegrationRequestHost. const ( CreateAzureIntegrationRequestHostMicrosoftCom CreateAzureIntegrationRequestHost = "microsoft.com" @@ -511,6 +535,7 @@ func (e GroupMinimumIssued) Valid() bool { // Defines values for IdentityProviderType. const ( + IdentityProviderTypeAdfs IdentityProviderType = "adfs" IdentityProviderTypeEntra IdentityProviderType = "entra" IdentityProviderTypeGoogle IdentityProviderType = "google" IdentityProviderTypeMicrosoft IdentityProviderType = "microsoft" @@ -518,12 +543,13 @@ const ( IdentityProviderTypeOkta IdentityProviderType = "okta" IdentityProviderTypePocketid IdentityProviderType = "pocketid" IdentityProviderTypeZitadel IdentityProviderType = "zitadel" - IdentityProviderTypeAdfs IdentityProviderType = "adfs" ) // Valid indicates whether the value is a known member of the IdentityProviderType enum. func (e IdentityProviderType) Valid() bool { switch e { + case IdentityProviderTypeAdfs: + return true case IdentityProviderTypeEntra: return true case IdentityProviderTypeGoogle: @@ -538,8 +564,6 @@ func (e IdentityProviderType) Valid() bool { return true case IdentityProviderTypeZitadel: return true - case IdentityProviderTypeAdfs: - return true default: return false } @@ -1455,6 +1479,13 @@ type AccountSettings struct { // AutoUpdateVersion Set Clients auto-update version. "latest", "disabled", or a specific version (e.g "0.50.1") AutoUpdateVersion *string `json:"auto_update_version,omitempty"` + // ConnectionMode Account-wide default peer-connection mode. NULL means + // "fall back to lazy_connection_enabled" for backwards compatibility. + // Phase 1 of issue #5989: relay-forced, p2p, and p2p-lazy are + // functional. p2p-dynamic is reserved (passes through as p2p in + // Phase 1; will become functional in Phase 2). + ConnectionMode *AccountSettingsConnectionMode `json:"connection_mode,omitempty"` + // DnsDomain Allows to define a custom dns domain for the account DnsDomain *string `json:"dns_domain,omitempty"` @@ -1483,6 +1514,11 @@ type AccountSettings struct { // NetworkRange Allows to define a custom network range for the account in CIDR format NetworkRange *string `json:"network_range,omitempty"` + // P2pTimeoutSeconds Default ICE-worker idle timeout in seconds. 0 = never tear down. + // Effective only in p2p-dynamic mode (added in Phase 2). + // NULL means "use built-in default" (180 minutes). + P2pTimeoutSeconds *int64 `json:"p2p_timeout_seconds,omitempty"` + // PeerExposeEnabled Enables or disables peer expose. If enabled, peers can expose local services through the reverse proxy using the CLI. PeerExposeEnabled bool `json:"peer_expose_enabled"` @@ -1504,10 +1540,23 @@ type AccountSettings struct { // RegularUsersViewBlocked Allows blocking regular users from viewing parts of the system. RegularUsersViewBlocked bool `json:"regular_users_view_blocked"` + // RelayTimeoutSeconds Default relay-worker idle timeout in seconds. 0 = never tear + // down. Effective in p2p-lazy and p2p-dynamic modes. Backwards- + // compat alias for NB_LAZY_CONN_INACTIVITY_THRESHOLD on the + // client. NULL means "use built-in default" (5 minutes). + RelayTimeoutSeconds *int64 `json:"relay_timeout_seconds,omitempty"` + // RoutingPeerDnsResolutionEnabled Enables or disables DNS resolution on the routing peers RoutingPeerDnsResolutionEnabled *bool `json:"routing_peer_dns_resolution_enabled,omitempty"` } +// AccountSettingsConnectionMode Account-wide default peer-connection mode. NULL means +// "fall back to lazy_connection_enabled" for backwards compatibility. +// Phase 1 of issue #5989: relay-forced, p2p, and p2p-lazy are +// functional. p2p-dynamic is reserved (passes through as p2p in +// Phase 1; will become functional in Phase 2). +type AccountSettingsConnectionMode string + // AvailablePorts defines model for AvailablePorts. type AvailablePorts struct { // Tcp Number of available TCP ports left on the ingress peer @@ -1626,7 +1675,9 @@ type Checks struct { // OsVersionCheck Posture check for the version of operating system OsVersionCheck *OSVersionCheck `json:"os_version_check,omitempty"` - // PeerNetworkRangeCheck Posture check for allow or deny access based on the peer's IP addresses. A range matches when it contains any of the peer's local network interface IPs or its public connection (NAT egress) IP, so ranges may target private subnets, public CIDRs, or single hosts via a /32 or /128. + // PeerNetworkRangeCheck Posture check for allow or deny access based on the peer's IP addresses. A range matches when it + // contains any of the peer's local network interface IPs or its public connection (NAT egress) IP, + // so ranges may target private subnets, public CIDRs, or single hosts via a /32 or /128. PeerNetworkRangeCheck *PeerNetworkRangeCheck `json:"peer_network_range_check,omitempty"` // ProcessCheck Posture Check for binaries exist and are running in the peer’s system @@ -3312,7 +3363,9 @@ type PeerMinimum struct { Name string `json:"name"` } -// PeerNetworkRangeCheck Posture check for allow or deny access based on the peer's IP addresses. A range matches when it contains any of the peer's local network interface IPs or its public connection (NAT egress) IP, so ranges may target private subnets, public CIDRs, or single hosts via a /32 or /128. +// PeerNetworkRangeCheck Posture check for allow or deny access based on the peer's IP addresses. A range matches when it +// contains any of the peer's local network interface IPs or its public connection (NAT egress) IP, +// so ranges may target private subnets, public CIDRs, or single hosts via a /32 or /128. type PeerNetworkRangeCheck struct { // Action Action to take upon policy match Action PeerNetworkRangeCheckAction `json:"action"` diff --git a/shared/management/proto/management.pb.go b/shared/management/proto/management.pb.go index 604f9c79385..f2e1ab0c5a8 100644 --- a/shared/management/proto/management.pb.go +++ b/shared/management/proto/management.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v7.34.1 +// protoc v5.29.3 // source: management.proto package proto @@ -71,6 +71,66 @@ func (JobStatus) EnumDescriptor() ([]byte, []int) { return file_management_proto_rawDescGZIP(), []int{0} } +// ConnectionMode controls how a peer establishes connections to other peers. +// Added in Phase 1 of the connection-mode consolidation (see issue #5989). +// CONNECTION_MODE_UNSPECIFIED is the proto default and means "fall back to +// the legacy LazyConnectionEnabled boolean field" -- required for backwards +// compatibility with old management servers that don't set this field. +type ConnectionMode int32 + +const ( + ConnectionMode_CONNECTION_MODE_UNSPECIFIED ConnectionMode = 0 + ConnectionMode_CONNECTION_MODE_RELAY_FORCED ConnectionMode = 1 + ConnectionMode_CONNECTION_MODE_P2P ConnectionMode = 2 + ConnectionMode_CONNECTION_MODE_P2P_LAZY ConnectionMode = 3 + ConnectionMode_CONNECTION_MODE_P2P_DYNAMIC ConnectionMode = 4 +) + +// Enum value maps for ConnectionMode. +var ( + ConnectionMode_name = map[int32]string{ + 0: "CONNECTION_MODE_UNSPECIFIED", + 1: "CONNECTION_MODE_RELAY_FORCED", + 2: "CONNECTION_MODE_P2P", + 3: "CONNECTION_MODE_P2P_LAZY", + 4: "CONNECTION_MODE_P2P_DYNAMIC", + } + ConnectionMode_value = map[string]int32{ + "CONNECTION_MODE_UNSPECIFIED": 0, + "CONNECTION_MODE_RELAY_FORCED": 1, + "CONNECTION_MODE_P2P": 2, + "CONNECTION_MODE_P2P_LAZY": 3, + "CONNECTION_MODE_P2P_DYNAMIC": 4, + } +) + +func (x ConnectionMode) Enum() *ConnectionMode { + p := new(ConnectionMode) + *p = x + return p +} + +func (x ConnectionMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConnectionMode) Descriptor() protoreflect.EnumDescriptor { + return file_management_proto_enumTypes[1].Descriptor() +} + +func (ConnectionMode) Type() protoreflect.EnumType { + return &file_management_proto_enumTypes[1] +} + +func (x ConnectionMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConnectionMode.Descriptor instead. +func (ConnectionMode) EnumDescriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{1} +} + type RuleProtocol int32 const ( @@ -113,11 +173,11 @@ func (x RuleProtocol) String() string { } func (RuleProtocol) Descriptor() protoreflect.EnumDescriptor { - return file_management_proto_enumTypes[1].Descriptor() + return file_management_proto_enumTypes[2].Descriptor() } func (RuleProtocol) Type() protoreflect.EnumType { - return &file_management_proto_enumTypes[1] + return &file_management_proto_enumTypes[2] } func (x RuleProtocol) Number() protoreflect.EnumNumber { @@ -126,7 +186,7 @@ func (x RuleProtocol) Number() protoreflect.EnumNumber { // Deprecated: Use RuleProtocol.Descriptor instead. func (RuleProtocol) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{1} + return file_management_proto_rawDescGZIP(), []int{2} } type RuleDirection int32 @@ -159,11 +219,11 @@ func (x RuleDirection) String() string { } func (RuleDirection) Descriptor() protoreflect.EnumDescriptor { - return file_management_proto_enumTypes[2].Descriptor() + return file_management_proto_enumTypes[3].Descriptor() } func (RuleDirection) Type() protoreflect.EnumType { - return &file_management_proto_enumTypes[2] + return &file_management_proto_enumTypes[3] } func (x RuleDirection) Number() protoreflect.EnumNumber { @@ -172,7 +232,7 @@ func (x RuleDirection) Number() protoreflect.EnumNumber { // Deprecated: Use RuleDirection.Descriptor instead. func (RuleDirection) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{2} + return file_management_proto_rawDescGZIP(), []int{3} } type RuleAction int32 @@ -205,11 +265,11 @@ func (x RuleAction) String() string { } func (RuleAction) Descriptor() protoreflect.EnumDescriptor { - return file_management_proto_enumTypes[3].Descriptor() + return file_management_proto_enumTypes[4].Descriptor() } func (RuleAction) Type() protoreflect.EnumType { - return &file_management_proto_enumTypes[3] + return &file_management_proto_enumTypes[4] } func (x RuleAction) Number() protoreflect.EnumNumber { @@ -218,7 +278,7 @@ func (x RuleAction) Number() protoreflect.EnumNumber { // Deprecated: Use RuleAction.Descriptor instead. func (RuleAction) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{3} + return file_management_proto_rawDescGZIP(), []int{4} } type ExposeProtocol int32 @@ -260,11 +320,11 @@ func (x ExposeProtocol) String() string { } func (ExposeProtocol) Descriptor() protoreflect.EnumDescriptor { - return file_management_proto_enumTypes[4].Descriptor() + return file_management_proto_enumTypes[5].Descriptor() } func (ExposeProtocol) Type() protoreflect.EnumType { - return &file_management_proto_enumTypes[4] + return &file_management_proto_enumTypes[5] } func (x ExposeProtocol) Number() protoreflect.EnumNumber { @@ -273,7 +333,7 @@ func (x ExposeProtocol) Number() protoreflect.EnumNumber { // Deprecated: Use ExposeProtocol.Descriptor instead. func (ExposeProtocol) EnumDescriptor() ([]byte, []int) { - return file_management_proto_rawDescGZIP(), []int{4} + return file_management_proto_rawDescGZIP(), []int{5} } type HostConfig_Protocol int32 @@ -315,11 +375,11 @@ func (x HostConfig_Protocol) String() string { } func (HostConfig_Protocol) Descriptor() protoreflect.EnumDescriptor { - return file_management_proto_enumTypes[5].Descriptor() + return file_management_proto_enumTypes[6].Descriptor() } func (HostConfig_Protocol) Type() protoreflect.EnumType { - return &file_management_proto_enumTypes[5] + return &file_management_proto_enumTypes[6] } func (x HostConfig_Protocol) Number() protoreflect.EnumNumber { @@ -358,11 +418,11 @@ func (x DeviceAuthorizationFlowProvider) String() string { } func (DeviceAuthorizationFlowProvider) Descriptor() protoreflect.EnumDescriptor { - return file_management_proto_enumTypes[6].Descriptor() + return file_management_proto_enumTypes[7].Descriptor() } func (DeviceAuthorizationFlowProvider) Type() protoreflect.EnumType { - return &file_management_proto_enumTypes[6] + return &file_management_proto_enumTypes[7] } func (x DeviceAuthorizationFlowProvider) Number() protoreflect.EnumNumber { @@ -2163,6 +2223,17 @@ type PeerConfig struct { Mtu int32 `protobuf:"varint,7,opt,name=mtu,proto3" json:"mtu,omitempty"` // Auto-update config AutoUpdate *AutoUpdateSettings `protobuf:"bytes,8,opt,name=autoUpdate,proto3" json:"autoUpdate,omitempty"` + // Connection-mode resolved by the management server. UNSPECIFIED = use + // legacy LazyConnectionEnabled fallback. Added in Phase 1 (#5989). + ConnectionMode ConnectionMode `protobuf:"varint,11,opt,name=ConnectionMode,proto3,enum=management.ConnectionMode" json:"ConnectionMode,omitempty"` + // Idle timeout for the ICE worker in seconds. 0 = never tear down. + // Effective in p2p-dynamic mode (added in Phase 2). Sent unconditionally + // for forward-compat. Added in Phase 1 (#5989). + P2PTimeoutSeconds uint32 `protobuf:"varint,12,opt,name=P2pTimeoutSeconds,proto3" json:"P2pTimeoutSeconds,omitempty"` + // Idle timeout for the relay worker in seconds. 0 = never tear down. + // Effective in p2p-lazy and p2p-dynamic modes. Backwards-compat alias for + // NB_LAZY_CONN_INACTIVITY_THRESHOLD. Added in Phase 1 (#5989). + RelayTimeoutSeconds uint32 `protobuf:"varint,13,opt,name=RelayTimeoutSeconds,proto3" json:"RelayTimeoutSeconds,omitempty"` } func (x *PeerConfig) Reset() { @@ -2253,6 +2324,27 @@ func (x *PeerConfig) GetAutoUpdate() *AutoUpdateSettings { return nil } +func (x *PeerConfig) GetConnectionMode() ConnectionMode { + if x != nil { + return x.ConnectionMode + } + return ConnectionMode_CONNECTION_MODE_UNSPECIFIED +} + +func (x *PeerConfig) GetP2PTimeoutSeconds() uint32 { + if x != nil { + return x.P2PTimeoutSeconds + } + return 0 +} + +func (x *PeerConfig) GetRelayTimeoutSeconds() uint32 { + if x != nil { + return x.RelayTimeoutSeconds + } + return 0 +} + type AutoUpdateSettings struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4715,7 +4807,7 @@ var file_management_proto_rawDesc = []byte{ 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0xd3, 0x02, 0x0a, 0x0a, 0x50, 0x65, + 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x83, 0x04, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, @@ -4736,7 +4828,18 @@ var file_management_proto_rawDesc = []byte{ 0x3e, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, + 0x6e, 0x67, 0x73, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x42, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, + 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, + 0x6f, 0x64, 0x65, 0x52, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, + 0x6f, 0x64, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x50, 0x32, 0x70, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, + 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, + 0x50, 0x32, 0x70, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x12, 0x30, 0x0a, 0x13, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, + 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, + 0x52, 0x65, 0x6c, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x4a, 0x04, 0x08, 0x0a, 0x10, 0x0b, 0x22, 0x52, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, @@ -5057,80 +5160,91 @@ var file_management_proto_rawDesc = []byte{ 0x3a, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x10, 0x01, 0x12, - 0x0a, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x02, 0x2a, 0x4c, 0x0a, 0x0c, 0x52, - 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, - 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, - 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x12, 0x0a, 0x0a, - 0x06, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x10, 0x05, 0x2a, 0x20, 0x0a, 0x0d, 0x52, 0x75, 0x6c, - 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, - 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x2a, 0x22, 0x0a, 0x0a, 0x52, - 0x75, 0x6c, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, - 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x2a, - 0x63, 0x0a, 0x0e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x12, 0x0f, 0x0a, 0x0b, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, - 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x54, - 0x50, 0x53, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x54, - 0x43, 0x50, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x55, - 0x44, 0x50, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x54, - 0x4c, 0x53, 0x10, 0x04, 0x32, 0xfd, 0x06, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, - 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x0a, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x02, 0x2a, 0xab, 0x01, 0x0a, 0x0e, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1f, + 0x0a, 0x1b, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, + 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x20, 0x0a, 0x1c, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, + 0x44, 0x45, 0x5f, 0x52, 0x45, 0x4c, 0x41, 0x59, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x10, + 0x01, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x32, 0x50, 0x10, 0x02, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, + 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x32, + 0x50, 0x5f, 0x4c, 0x41, 0x5a, 0x59, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x43, 0x4f, 0x4e, 0x4e, + 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x32, 0x50, 0x5f, + 0x44, 0x59, 0x4e, 0x41, 0x4d, 0x49, 0x43, 0x10, 0x04, 0x2a, 0x4c, 0x0a, 0x0c, 0x52, 0x75, 0x6c, + 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, + 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, + 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x43, + 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x10, 0x05, 0x2a, 0x20, 0x0a, 0x0d, 0x52, 0x75, 0x6c, 0x65, 0x44, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, + 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x2a, 0x22, 0x0a, 0x0a, 0x52, 0x75, 0x6c, + 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, + 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x2a, 0x63, 0x0a, + 0x0e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, + 0x0f, 0x0a, 0x0b, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, + 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x53, + 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x54, 0x43, 0x50, + 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x55, 0x44, 0x50, + 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x54, 0x4c, 0x53, + 0x10, 0x04, 0x32, 0xfd, 0x06, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, + 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, + 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, - 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, - 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, - 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, + 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, + 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, - 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, - 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, + 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, - 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, - 0x74, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, - 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x1c, 0x2e, 0x6d, 0x61, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, + 0x74, 0x61, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, + 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x00, 0x12, 0x47, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4c, 0x0a, 0x0c, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4c, 0x0a, - 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0b, 0x52, - 0x65, 0x6e, 0x65, 0x77, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, - 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0b, 0x52, 0x65, 0x6e, + 0x65, 0x77, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, - 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5145,166 +5259,168 @@ func file_management_proto_rawDescGZIP() []byte { return file_management_proto_rawDescData } -var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 7) +var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 8) var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 55) var file_management_proto_goTypes = []interface{}{ (JobStatus)(0), // 0: management.JobStatus - (RuleProtocol)(0), // 1: management.RuleProtocol - (RuleDirection)(0), // 2: management.RuleDirection - (RuleAction)(0), // 3: management.RuleAction - (ExposeProtocol)(0), // 4: management.ExposeProtocol - (HostConfig_Protocol)(0), // 5: management.HostConfig.Protocol - (DeviceAuthorizationFlowProvider)(0), // 6: management.DeviceAuthorizationFlow.provider - (*EncryptedMessage)(nil), // 7: management.EncryptedMessage - (*JobRequest)(nil), // 8: management.JobRequest - (*JobResponse)(nil), // 9: management.JobResponse - (*BundleParameters)(nil), // 10: management.BundleParameters - (*BundleResult)(nil), // 11: management.BundleResult - (*SyncRequest)(nil), // 12: management.SyncRequest - (*SyncResponse)(nil), // 13: management.SyncResponse - (*SyncMetaRequest)(nil), // 14: management.SyncMetaRequest - (*LoginRequest)(nil), // 15: management.LoginRequest - (*PeerKeys)(nil), // 16: management.PeerKeys - (*Environment)(nil), // 17: management.Environment - (*File)(nil), // 18: management.File - (*Flags)(nil), // 19: management.Flags - (*PeerSystemMeta)(nil), // 20: management.PeerSystemMeta - (*LoginResponse)(nil), // 21: management.LoginResponse - (*ServerKeyResponse)(nil), // 22: management.ServerKeyResponse - (*Empty)(nil), // 23: management.Empty - (*NetbirdConfig)(nil), // 24: management.NetbirdConfig - (*HostConfig)(nil), // 25: management.HostConfig - (*RelayConfig)(nil), // 26: management.RelayConfig - (*FlowConfig)(nil), // 27: management.FlowConfig - (*JWTConfig)(nil), // 28: management.JWTConfig - (*ProtectedHostConfig)(nil), // 29: management.ProtectedHostConfig - (*PeerConfig)(nil), // 30: management.PeerConfig - (*AutoUpdateSettings)(nil), // 31: management.AutoUpdateSettings - (*NetworkMap)(nil), // 32: management.NetworkMap - (*SSHAuth)(nil), // 33: management.SSHAuth - (*MachineUserIndexes)(nil), // 34: management.MachineUserIndexes - (*RemotePeerConfig)(nil), // 35: management.RemotePeerConfig - (*SSHConfig)(nil), // 36: management.SSHConfig - (*DeviceAuthorizationFlowRequest)(nil), // 37: management.DeviceAuthorizationFlowRequest - (*DeviceAuthorizationFlow)(nil), // 38: management.DeviceAuthorizationFlow - (*PKCEAuthorizationFlowRequest)(nil), // 39: management.PKCEAuthorizationFlowRequest - (*PKCEAuthorizationFlow)(nil), // 40: management.PKCEAuthorizationFlow - (*ProviderConfig)(nil), // 41: management.ProviderConfig - (*Route)(nil), // 42: management.Route - (*DNSConfig)(nil), // 43: management.DNSConfig - (*CustomZone)(nil), // 44: management.CustomZone - (*SimpleRecord)(nil), // 45: management.SimpleRecord - (*NameServerGroup)(nil), // 46: management.NameServerGroup - (*NameServer)(nil), // 47: management.NameServer - (*FirewallRule)(nil), // 48: management.FirewallRule - (*NetworkAddress)(nil), // 49: management.NetworkAddress - (*Checks)(nil), // 50: management.Checks - (*PortInfo)(nil), // 51: management.PortInfo - (*RouteFirewallRule)(nil), // 52: management.RouteFirewallRule - (*ForwardingRule)(nil), // 53: management.ForwardingRule - (*ExposeServiceRequest)(nil), // 54: management.ExposeServiceRequest - (*ExposeServiceResponse)(nil), // 55: management.ExposeServiceResponse - (*RenewExposeRequest)(nil), // 56: management.RenewExposeRequest - (*RenewExposeResponse)(nil), // 57: management.RenewExposeResponse - (*StopExposeRequest)(nil), // 58: management.StopExposeRequest - (*StopExposeResponse)(nil), // 59: management.StopExposeResponse - nil, // 60: management.SSHAuth.MachineUsersEntry - (*PortInfo_Range)(nil), // 61: management.PortInfo.Range - (*timestamppb.Timestamp)(nil), // 62: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 63: google.protobuf.Duration + (ConnectionMode)(0), // 1: management.ConnectionMode + (RuleProtocol)(0), // 2: management.RuleProtocol + (RuleDirection)(0), // 3: management.RuleDirection + (RuleAction)(0), // 4: management.RuleAction + (ExposeProtocol)(0), // 5: management.ExposeProtocol + (HostConfig_Protocol)(0), // 6: management.HostConfig.Protocol + (DeviceAuthorizationFlowProvider)(0), // 7: management.DeviceAuthorizationFlow.provider + (*EncryptedMessage)(nil), // 8: management.EncryptedMessage + (*JobRequest)(nil), // 9: management.JobRequest + (*JobResponse)(nil), // 10: management.JobResponse + (*BundleParameters)(nil), // 11: management.BundleParameters + (*BundleResult)(nil), // 12: management.BundleResult + (*SyncRequest)(nil), // 13: management.SyncRequest + (*SyncResponse)(nil), // 14: management.SyncResponse + (*SyncMetaRequest)(nil), // 15: management.SyncMetaRequest + (*LoginRequest)(nil), // 16: management.LoginRequest + (*PeerKeys)(nil), // 17: management.PeerKeys + (*Environment)(nil), // 18: management.Environment + (*File)(nil), // 19: management.File + (*Flags)(nil), // 20: management.Flags + (*PeerSystemMeta)(nil), // 21: management.PeerSystemMeta + (*LoginResponse)(nil), // 22: management.LoginResponse + (*ServerKeyResponse)(nil), // 23: management.ServerKeyResponse + (*Empty)(nil), // 24: management.Empty + (*NetbirdConfig)(nil), // 25: management.NetbirdConfig + (*HostConfig)(nil), // 26: management.HostConfig + (*RelayConfig)(nil), // 27: management.RelayConfig + (*FlowConfig)(nil), // 28: management.FlowConfig + (*JWTConfig)(nil), // 29: management.JWTConfig + (*ProtectedHostConfig)(nil), // 30: management.ProtectedHostConfig + (*PeerConfig)(nil), // 31: management.PeerConfig + (*AutoUpdateSettings)(nil), // 32: management.AutoUpdateSettings + (*NetworkMap)(nil), // 33: management.NetworkMap + (*SSHAuth)(nil), // 34: management.SSHAuth + (*MachineUserIndexes)(nil), // 35: management.MachineUserIndexes + (*RemotePeerConfig)(nil), // 36: management.RemotePeerConfig + (*SSHConfig)(nil), // 37: management.SSHConfig + (*DeviceAuthorizationFlowRequest)(nil), // 38: management.DeviceAuthorizationFlowRequest + (*DeviceAuthorizationFlow)(nil), // 39: management.DeviceAuthorizationFlow + (*PKCEAuthorizationFlowRequest)(nil), // 40: management.PKCEAuthorizationFlowRequest + (*PKCEAuthorizationFlow)(nil), // 41: management.PKCEAuthorizationFlow + (*ProviderConfig)(nil), // 42: management.ProviderConfig + (*Route)(nil), // 43: management.Route + (*DNSConfig)(nil), // 44: management.DNSConfig + (*CustomZone)(nil), // 45: management.CustomZone + (*SimpleRecord)(nil), // 46: management.SimpleRecord + (*NameServerGroup)(nil), // 47: management.NameServerGroup + (*NameServer)(nil), // 48: management.NameServer + (*FirewallRule)(nil), // 49: management.FirewallRule + (*NetworkAddress)(nil), // 50: management.NetworkAddress + (*Checks)(nil), // 51: management.Checks + (*PortInfo)(nil), // 52: management.PortInfo + (*RouteFirewallRule)(nil), // 53: management.RouteFirewallRule + (*ForwardingRule)(nil), // 54: management.ForwardingRule + (*ExposeServiceRequest)(nil), // 55: management.ExposeServiceRequest + (*ExposeServiceResponse)(nil), // 56: management.ExposeServiceResponse + (*RenewExposeRequest)(nil), // 57: management.RenewExposeRequest + (*RenewExposeResponse)(nil), // 58: management.RenewExposeResponse + (*StopExposeRequest)(nil), // 59: management.StopExposeRequest + (*StopExposeResponse)(nil), // 60: management.StopExposeResponse + nil, // 61: management.SSHAuth.MachineUsersEntry + (*PortInfo_Range)(nil), // 62: management.PortInfo.Range + (*timestamppb.Timestamp)(nil), // 63: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 64: google.protobuf.Duration } var file_management_proto_depIdxs = []int32{ - 10, // 0: management.JobRequest.bundle:type_name -> management.BundleParameters + 11, // 0: management.JobRequest.bundle:type_name -> management.BundleParameters 0, // 1: management.JobResponse.status:type_name -> management.JobStatus - 11, // 2: management.JobResponse.bundle:type_name -> management.BundleResult - 20, // 3: management.SyncRequest.meta:type_name -> management.PeerSystemMeta - 24, // 4: management.SyncResponse.netbirdConfig:type_name -> management.NetbirdConfig - 30, // 5: management.SyncResponse.peerConfig:type_name -> management.PeerConfig - 35, // 6: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig - 32, // 7: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap - 50, // 8: management.SyncResponse.Checks:type_name -> management.Checks - 20, // 9: management.SyncMetaRequest.meta:type_name -> management.PeerSystemMeta - 20, // 10: management.LoginRequest.meta:type_name -> management.PeerSystemMeta - 16, // 11: management.LoginRequest.peerKeys:type_name -> management.PeerKeys - 49, // 12: management.PeerSystemMeta.networkAddresses:type_name -> management.NetworkAddress - 17, // 13: management.PeerSystemMeta.environment:type_name -> management.Environment - 18, // 14: management.PeerSystemMeta.files:type_name -> management.File - 19, // 15: management.PeerSystemMeta.flags:type_name -> management.Flags - 24, // 16: management.LoginResponse.netbirdConfig:type_name -> management.NetbirdConfig - 30, // 17: management.LoginResponse.peerConfig:type_name -> management.PeerConfig - 50, // 18: management.LoginResponse.Checks:type_name -> management.Checks - 62, // 19: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp - 25, // 20: management.NetbirdConfig.stuns:type_name -> management.HostConfig - 29, // 21: management.NetbirdConfig.turns:type_name -> management.ProtectedHostConfig - 25, // 22: management.NetbirdConfig.signal:type_name -> management.HostConfig - 26, // 23: management.NetbirdConfig.relay:type_name -> management.RelayConfig - 27, // 24: management.NetbirdConfig.flow:type_name -> management.FlowConfig - 5, // 25: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol - 63, // 26: management.FlowConfig.interval:type_name -> google.protobuf.Duration - 25, // 27: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig - 36, // 28: management.PeerConfig.sshConfig:type_name -> management.SSHConfig - 31, // 29: management.PeerConfig.autoUpdate:type_name -> management.AutoUpdateSettings - 30, // 30: management.NetworkMap.peerConfig:type_name -> management.PeerConfig - 35, // 31: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig - 42, // 32: management.NetworkMap.Routes:type_name -> management.Route - 43, // 33: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig - 35, // 34: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig - 48, // 35: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule - 52, // 36: management.NetworkMap.routesFirewallRules:type_name -> management.RouteFirewallRule - 53, // 37: management.NetworkMap.forwardingRules:type_name -> management.ForwardingRule - 33, // 38: management.NetworkMap.sshAuth:type_name -> management.SSHAuth - 60, // 39: management.SSHAuth.machine_users:type_name -> management.SSHAuth.MachineUsersEntry - 36, // 40: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig - 28, // 41: management.SSHConfig.jwtConfig:type_name -> management.JWTConfig - 6, // 42: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider - 41, // 43: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig - 41, // 44: management.PKCEAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig - 46, // 45: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup - 44, // 46: management.DNSConfig.CustomZones:type_name -> management.CustomZone - 45, // 47: management.CustomZone.Records:type_name -> management.SimpleRecord - 47, // 48: management.NameServerGroup.NameServers:type_name -> management.NameServer - 2, // 49: management.FirewallRule.Direction:type_name -> management.RuleDirection - 3, // 50: management.FirewallRule.Action:type_name -> management.RuleAction - 1, // 51: management.FirewallRule.Protocol:type_name -> management.RuleProtocol - 51, // 52: management.FirewallRule.PortInfo:type_name -> management.PortInfo - 61, // 53: management.PortInfo.range:type_name -> management.PortInfo.Range - 3, // 54: management.RouteFirewallRule.action:type_name -> management.RuleAction - 1, // 55: management.RouteFirewallRule.protocol:type_name -> management.RuleProtocol - 51, // 56: management.RouteFirewallRule.portInfo:type_name -> management.PortInfo - 1, // 57: management.ForwardingRule.protocol:type_name -> management.RuleProtocol - 51, // 58: management.ForwardingRule.destinationPort:type_name -> management.PortInfo - 51, // 59: management.ForwardingRule.translatedPort:type_name -> management.PortInfo - 4, // 60: management.ExposeServiceRequest.protocol:type_name -> management.ExposeProtocol - 34, // 61: management.SSHAuth.MachineUsersEntry.value:type_name -> management.MachineUserIndexes - 7, // 62: management.ManagementService.Login:input_type -> management.EncryptedMessage - 7, // 63: management.ManagementService.Sync:input_type -> management.EncryptedMessage - 23, // 64: management.ManagementService.GetServerKey:input_type -> management.Empty - 23, // 65: management.ManagementService.isHealthy:input_type -> management.Empty - 7, // 66: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage - 7, // 67: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage - 7, // 68: management.ManagementService.SyncMeta:input_type -> management.EncryptedMessage - 7, // 69: management.ManagementService.Logout:input_type -> management.EncryptedMessage - 7, // 70: management.ManagementService.Job:input_type -> management.EncryptedMessage - 7, // 71: management.ManagementService.CreateExpose:input_type -> management.EncryptedMessage - 7, // 72: management.ManagementService.RenewExpose:input_type -> management.EncryptedMessage - 7, // 73: management.ManagementService.StopExpose:input_type -> management.EncryptedMessage - 7, // 74: management.ManagementService.Login:output_type -> management.EncryptedMessage - 7, // 75: management.ManagementService.Sync:output_type -> management.EncryptedMessage - 22, // 76: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse - 23, // 77: management.ManagementService.isHealthy:output_type -> management.Empty - 7, // 78: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage - 7, // 79: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage - 23, // 80: management.ManagementService.SyncMeta:output_type -> management.Empty - 23, // 81: management.ManagementService.Logout:output_type -> management.Empty - 7, // 82: management.ManagementService.Job:output_type -> management.EncryptedMessage - 7, // 83: management.ManagementService.CreateExpose:output_type -> management.EncryptedMessage - 7, // 84: management.ManagementService.RenewExpose:output_type -> management.EncryptedMessage - 7, // 85: management.ManagementService.StopExpose:output_type -> management.EncryptedMessage - 74, // [74:86] is the sub-list for method output_type - 62, // [62:74] is the sub-list for method input_type - 62, // [62:62] is the sub-list for extension type_name - 62, // [62:62] is the sub-list for extension extendee - 0, // [0:62] is the sub-list for field type_name + 12, // 2: management.JobResponse.bundle:type_name -> management.BundleResult + 21, // 3: management.SyncRequest.meta:type_name -> management.PeerSystemMeta + 25, // 4: management.SyncResponse.netbirdConfig:type_name -> management.NetbirdConfig + 31, // 5: management.SyncResponse.peerConfig:type_name -> management.PeerConfig + 36, // 6: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig + 33, // 7: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap + 51, // 8: management.SyncResponse.Checks:type_name -> management.Checks + 21, // 9: management.SyncMetaRequest.meta:type_name -> management.PeerSystemMeta + 21, // 10: management.LoginRequest.meta:type_name -> management.PeerSystemMeta + 17, // 11: management.LoginRequest.peerKeys:type_name -> management.PeerKeys + 50, // 12: management.PeerSystemMeta.networkAddresses:type_name -> management.NetworkAddress + 18, // 13: management.PeerSystemMeta.environment:type_name -> management.Environment + 19, // 14: management.PeerSystemMeta.files:type_name -> management.File + 20, // 15: management.PeerSystemMeta.flags:type_name -> management.Flags + 25, // 16: management.LoginResponse.netbirdConfig:type_name -> management.NetbirdConfig + 31, // 17: management.LoginResponse.peerConfig:type_name -> management.PeerConfig + 51, // 18: management.LoginResponse.Checks:type_name -> management.Checks + 63, // 19: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp + 26, // 20: management.NetbirdConfig.stuns:type_name -> management.HostConfig + 30, // 21: management.NetbirdConfig.turns:type_name -> management.ProtectedHostConfig + 26, // 22: management.NetbirdConfig.signal:type_name -> management.HostConfig + 27, // 23: management.NetbirdConfig.relay:type_name -> management.RelayConfig + 28, // 24: management.NetbirdConfig.flow:type_name -> management.FlowConfig + 6, // 25: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol + 64, // 26: management.FlowConfig.interval:type_name -> google.protobuf.Duration + 26, // 27: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig + 37, // 28: management.PeerConfig.sshConfig:type_name -> management.SSHConfig + 32, // 29: management.PeerConfig.autoUpdate:type_name -> management.AutoUpdateSettings + 1, // 30: management.PeerConfig.ConnectionMode:type_name -> management.ConnectionMode + 31, // 31: management.NetworkMap.peerConfig:type_name -> management.PeerConfig + 36, // 32: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig + 43, // 33: management.NetworkMap.Routes:type_name -> management.Route + 44, // 34: management.NetworkMap.DNSConfig:type_name -> management.DNSConfig + 36, // 35: management.NetworkMap.offlinePeers:type_name -> management.RemotePeerConfig + 49, // 36: management.NetworkMap.FirewallRules:type_name -> management.FirewallRule + 53, // 37: management.NetworkMap.routesFirewallRules:type_name -> management.RouteFirewallRule + 54, // 38: management.NetworkMap.forwardingRules:type_name -> management.ForwardingRule + 34, // 39: management.NetworkMap.sshAuth:type_name -> management.SSHAuth + 61, // 40: management.SSHAuth.machine_users:type_name -> management.SSHAuth.MachineUsersEntry + 37, // 41: management.RemotePeerConfig.sshConfig:type_name -> management.SSHConfig + 29, // 42: management.SSHConfig.jwtConfig:type_name -> management.JWTConfig + 7, // 43: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider + 42, // 44: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 42, // 45: management.PKCEAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 47, // 46: management.DNSConfig.NameServerGroups:type_name -> management.NameServerGroup + 45, // 47: management.DNSConfig.CustomZones:type_name -> management.CustomZone + 46, // 48: management.CustomZone.Records:type_name -> management.SimpleRecord + 48, // 49: management.NameServerGroup.NameServers:type_name -> management.NameServer + 3, // 50: management.FirewallRule.Direction:type_name -> management.RuleDirection + 4, // 51: management.FirewallRule.Action:type_name -> management.RuleAction + 2, // 52: management.FirewallRule.Protocol:type_name -> management.RuleProtocol + 52, // 53: management.FirewallRule.PortInfo:type_name -> management.PortInfo + 62, // 54: management.PortInfo.range:type_name -> management.PortInfo.Range + 4, // 55: management.RouteFirewallRule.action:type_name -> management.RuleAction + 2, // 56: management.RouteFirewallRule.protocol:type_name -> management.RuleProtocol + 52, // 57: management.RouteFirewallRule.portInfo:type_name -> management.PortInfo + 2, // 58: management.ForwardingRule.protocol:type_name -> management.RuleProtocol + 52, // 59: management.ForwardingRule.destinationPort:type_name -> management.PortInfo + 52, // 60: management.ForwardingRule.translatedPort:type_name -> management.PortInfo + 5, // 61: management.ExposeServiceRequest.protocol:type_name -> management.ExposeProtocol + 35, // 62: management.SSHAuth.MachineUsersEntry.value:type_name -> management.MachineUserIndexes + 8, // 63: management.ManagementService.Login:input_type -> management.EncryptedMessage + 8, // 64: management.ManagementService.Sync:input_type -> management.EncryptedMessage + 24, // 65: management.ManagementService.GetServerKey:input_type -> management.Empty + 24, // 66: management.ManagementService.isHealthy:input_type -> management.Empty + 8, // 67: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage + 8, // 68: management.ManagementService.GetPKCEAuthorizationFlow:input_type -> management.EncryptedMessage + 8, // 69: management.ManagementService.SyncMeta:input_type -> management.EncryptedMessage + 8, // 70: management.ManagementService.Logout:input_type -> management.EncryptedMessage + 8, // 71: management.ManagementService.Job:input_type -> management.EncryptedMessage + 8, // 72: management.ManagementService.CreateExpose:input_type -> management.EncryptedMessage + 8, // 73: management.ManagementService.RenewExpose:input_type -> management.EncryptedMessage + 8, // 74: management.ManagementService.StopExpose:input_type -> management.EncryptedMessage + 8, // 75: management.ManagementService.Login:output_type -> management.EncryptedMessage + 8, // 76: management.ManagementService.Sync:output_type -> management.EncryptedMessage + 23, // 77: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse + 24, // 78: management.ManagementService.isHealthy:output_type -> management.Empty + 8, // 79: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage + 8, // 80: management.ManagementService.GetPKCEAuthorizationFlow:output_type -> management.EncryptedMessage + 24, // 81: management.ManagementService.SyncMeta:output_type -> management.Empty + 24, // 82: management.ManagementService.Logout:output_type -> management.Empty + 8, // 83: management.ManagementService.Job:output_type -> management.EncryptedMessage + 8, // 84: management.ManagementService.CreateExpose:output_type -> management.EncryptedMessage + 8, // 85: management.ManagementService.RenewExpose:output_type -> management.EncryptedMessage + 8, // 86: management.ManagementService.StopExpose:output_type -> management.EncryptedMessage + 75, // [75:87] is the sub-list for method output_type + 63, // [63:75] is the sub-list for method input_type + 63, // [63:63] is the sub-list for extension type_name + 63, // [63:63] is the sub-list for extension extendee + 0, // [0:63] is the sub-list for field type_name } func init() { file_management_proto_init() } @@ -5977,7 +6093,7 @@ func file_management_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_management_proto_rawDesc, - NumEnums: 7, + NumEnums: 8, NumMessages: 55, NumExtensions: 0, NumServices: 1, diff --git a/shared/management/proto/management.proto b/shared/management/proto/management.proto index 70a53067974..04364b1491f 100644 --- a/shared/management/proto/management.proto +++ b/shared/management/proto/management.proto @@ -335,6 +335,38 @@ message PeerConfig { // Auto-update config AutoUpdateSettings autoUpdate = 8; + + // Tags 9 and 10 are intentionally left unused so that future small + // additions can land without re-numbering the new connection-mode + // fields. Reserved here to make the gap explicit for any reviewer. + reserved 9, 10; + + // Connection-mode resolved by the management server. UNSPECIFIED = use + // legacy LazyConnectionEnabled fallback. Added in Phase 1 (#5989). + ConnectionMode ConnectionMode = 11; + + // Idle timeout for the ICE worker in seconds. 0 = never tear down. + // Effective in p2p-dynamic mode (added in Phase 2). Sent unconditionally + // for forward-compat. Added in Phase 1 (#5989). + uint32 P2pTimeoutSeconds = 12; + + // Idle timeout for the relay worker in seconds. 0 = never tear down. + // Effective in p2p-lazy and p2p-dynamic modes. Backwards-compat alias for + // NB_LAZY_CONN_INACTIVITY_THRESHOLD. Added in Phase 1 (#5989). + uint32 RelayTimeoutSeconds = 13; +} + +// ConnectionMode controls how a peer establishes connections to other peers. +// Added in Phase 1 of the connection-mode consolidation (see issue #5989). +// CONNECTION_MODE_UNSPECIFIED is the proto default and means "fall back to +// the legacy LazyConnectionEnabled boolean field" -- required for backwards +// compatibility with old management servers that don't set this field. +enum ConnectionMode { + CONNECTION_MODE_UNSPECIFIED = 0; + CONNECTION_MODE_RELAY_FORCED = 1; + CONNECTION_MODE_P2P = 2; + CONNECTION_MODE_P2P_LAZY = 3; + CONNECTION_MODE_P2P_DYNAMIC = 4; } message AutoUpdateSettings { diff --git a/shared/management/proto/proxy_service.pb.go b/shared/management/proto/proxy_service.pb.go index 1095b641161..6a7b5facbcf 100644 --- a/shared/management/proto/proxy_service.pb.go +++ b/shared/management/proto/proxy_service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v7.34.1 +// protoc v5.29.3 // source: proxy_service.proto package proto