From d5d416e3f0cf4da0c134f8cec4bd0c85c31b53e2 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 07:53:55 +0000 Subject: [PATCH 01/19] proto: add ConnectionMode enum and p2p/relay timeout fields to PeerConfig Additive change for issue #5989 Phase 1. New fields use new tag numbers (11, 12, 13); existing fields (including LazyConnectionEnabled tag 6) are unchanged so old clients ignore the additions and old servers send UNSPECIFIED, which the new client maps back via the legacy boolean. Note: the regenerated pb.go files now report protoc v5.29.3 in their header (this branch was generated with locally-installed protoc 29.3 instead of upstream's v7.34.1). Functionally identical; header diff is the only delta beyond the actual schema additions. Co-Authored-By: Claude Opus 4.7 (1M context) --- shared/management/proto/management.pb.go | 594 ++++++++++++-------- shared/management/proto/management.proto | 32 ++ shared/management/proto/proxy_service.pb.go | 2 +- 3 files changed, 388 insertions(+), 240 deletions(-) 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 From b341bc0876ba20489e6380eacdbbeff95c8433d6 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:01:49 +0000 Subject: [PATCH 02/19] client: add connectionmode package with Mode type and proto bridge Defines Mode enum (relay-forced, p2p, p2p-lazy, p2p-dynamic plus the client-only sentinels Unspecified and FollowServer), ParseString for CLI/env input, ToProto/FromProto for wire translation, and the two backwards-compat helpers ResolveLegacyLazyBool / ToLazyConnectionEnabled that bridge the old Settings.LazyConnectionEnabled boolean. Phase 1 of issue #5989. Pure addition -- no existing callers touched in this commit; the engine/conn_mgr migration follows in subsequent commits in the same PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/peer/connectionmode/mode.go | 128 ++++++++++++++++++ .../internal/peer/connectionmode/mode_test.go | 106 +++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 client/internal/peer/connectionmode/mode.go create mode 100644 client/internal/peer/connectionmode/mode_test.go diff --git a/client/internal/peer/connectionmode/mode.go b/client/internal/peer/connectionmode/mode.go new file mode 100644 index 00000000000..d3b1c9e14e4 --- /dev/null +++ b/client/internal/peer/connectionmode/mode.go @@ -0,0 +1,128 @@ +// 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": + 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/client/internal/peer/connectionmode/mode_test.go b/client/internal/peer/connectionmode/mode_test.go new file mode 100644 index 00000000000..01a9c11c929 --- /dev/null +++ b/client/internal/peer/connectionmode/mode_test.go @@ -0,0 +1,106 @@ +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}, + {"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) + } + } +} From 9cad8d63c53bdfedf476c03b915dd581e0e26f30 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:06:17 +0000 Subject: [PATCH 03/19] client/peer: ResolveModeFromEnv with NB_CONNECTION_MODE and deprecation warns NB_CONNECTION_MODE wins over the legacy pair (NB_FORCE_RELAY, NB_ENABLE_EXPERIMENTAL_LAZY_CONN); when the legacy pair is set together, NB_FORCE_RELAY wins (most-restrictive, mirrors the group-conflict rule from issue #5990). Each legacy var emits a one-shot deprecation warning when it actually contributes to the resolved mode. NB_LAZY_CONN_INACTIVITY_THRESHOLD becomes an alias for the future relay_timeout setting and warns once. IsForceRelayed() is kept for callers that have not yet been migrated (conn.go, statusrecorder); they will be updated in the engine/conn refactor commits later in this PR. Phase 1 of issue #5989. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/lazyconn/env.go | 5 +++ client/internal/peer/env.go | 77 ++++++++++++++++++++++++++++++++ client/internal/peer/env_test.go | 58 ++++++++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 client/internal/peer/env_test.go 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/env.go b/client/internal/peer/env.go index ed6a3af5391..65cf036ab3b 100644 --- a/client/internal/peer/env.go +++ b/client/internal/peer/env.go @@ -3,14 +3,32 @@ package peer import ( "os" "runtime" + "strconv" "strings" + "sync" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/internal/peer/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 +36,65 @@ 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 != "" { + if d, err := time.ParseDuration(raw); err == nil { + timeoutSecs = uint32(d.Seconds()) + warnDeprecated(envInactivityThreshold, "the relay_timeout setting on the management server") + } else { + log.Warnf("ignoring %s=%q: %v", envInactivityThreshold, raw, err) + } + } + + 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..7ce10a51a3d --- /dev/null +++ b/client/internal/peer/env_test.go @@ -0,0 +1,58 @@ +package peer + +import ( + "testing" + + "github.com/netbirdio/netbird/client/internal/peer/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 unparseable 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}, + } + 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") + } +} From a4b0d043d136972b537af807dfca44f00cd898a7 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:13:06 +0000 Subject: [PATCH 04/19] client: add --connection-mode, --relay-timeout, --p2p-timeout CLI flags Three new CLI flags map onto the new connection-mode plumbing: - --connection-mode - --relay-timeout - --p2p-timeout Plumbed through three sites in cmd/up.go (SetConfigRequest, ConfigInput, LoginRequest), persisted in profilemanager.Config, and added as new fields on the daemon.proto IPC messages. Empty / not-changed flags fall back to the server-pushed value (which itself falls back to the legacy lazy_connection_enabled boolean for old servers). Phase 1 of issue #5989. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/cmd/root.go | 13 + client/cmd/up.go | 30 ++ client/internal/profilemanager/config.go | 24 ++ client/proto/daemon.pb.go | 90 ++++- client/proto/daemon.proto | 16 + client/proto/daemon_grpc.pb.go | 402 ++++++++++------------- 6 files changed, 342 insertions(+), 233 deletions(-) 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/profilemanager/config.go b/client/internal/profilemanager/config.go index 20c615d579d..a8c74a756aa 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,10 @@ type Config struct { LazyConnectionEnabled bool + ConnectionMode string `json:",omitempty"` + RelayTimeoutSeconds uint32 `json:",omitempty"` + P2pTimeoutSeconds uint32 `json:",omitempty"` + MTU uint16 } @@ -593,6 +601,22 @@ 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 && *input.RelayTimeoutSeconds != config.RelayTimeoutSeconds { + log.Infof("switching relay timeout to %d seconds", *input.RelayTimeoutSeconds) + config.RelayTimeoutSeconds = *input.RelayTimeoutSeconds + updated = true + } + if input.P2pTimeoutSeconds != nil && *input.P2pTimeoutSeconds != config.P2pTimeoutSeconds { + log.Infof("switching p2p timeout to %d seconds", *input.P2pTimeoutSeconds) + config.P2pTimeoutSeconds = *input.P2pTimeoutSeconds + 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/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, From 62f29c56e31664eff793c0384e798d56e2e1f455 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:20:13 +0000 Subject: [PATCH 05/19] client/conn_mgr: replace asymmetric Lazy/ForceRelay precedence with Mode EngineConfig gains ConnectionMode, RelayTimeoutSeconds, P2pTimeoutSeconds. ConnMgr now stores the resolved Mode plus the raw inputs (env, config) so it can re-resolve when the server pushes a new PeerConfig. UpdatedRemoteFeatureFlag is renamed to UpdatedRemotePeerConfig and takes the full PeerConfig pointer; a thin shim with the old name delegates to it for callers that haven't been updated yet. connect.go copies the three new fields from profilemanager.Config into the EngineConfig builder, with a tolerant parser that logs and falls through to Unspecified on invalid input. Phase 1 of issue #5989. peer/conn.go forwarding follows in C4. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/conn_mgr.go | 162 ++++++++++++++++++++++++------- client/internal/conn_mgr_test.go | 101 +++++++++++++++++++ client/internal/connect.go | 18 ++++ client/internal/engine.go | 20 +++- 4 files changed, 264 insertions(+), 37 deletions(-) create mode 100644 client/internal/conn_mgr_test.go diff --git a/client/internal/conn_mgr.go b/client/internal/conn_mgr.go index 112559132a1..bc844e6ea15 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/client/internal/peer/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..56d391cf543 --- /dev/null +++ b/client/internal/conn_mgr_test.go @@ -0,0 +1,101 @@ +package internal + +import ( + "testing" + + "github.com/netbirdio/netbird/client/internal/peer/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..df7e516d63c 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/client/internal/peer/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: config.RelayTimeoutSeconds, + P2pTimeoutSeconds: config.P2pTimeoutSeconds, + MTU: selectMTU(config.MTU, peerConfig.Mtu), LogPath: logPath, @@ -695,3 +700,16 @@ 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 +} diff --git a/client/internal/engine.go b/client/internal/engine.go index 351e4bfe940..5b54ebd8667 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/client/internal/peer/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 { From 0d4812436cb83d146e83f6805b5959569a843098 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:21:57 +0000 Subject: [PATCH 06/19] client/peer: connection mode drives skip-ICE branch in Open() ConnConfig gains a Mode field forwarded from the engine. Open() now checks Mode == ModeRelayForced instead of calling the global env-reader IsForceRelayed(). The local 'forceRelay' variable name is renamed to 'skipICE' to make the new branching intent explicit. The PeerStateUpdate block at the end of Open() also reads from conn.config.Mode now, so the StatusRecorder sees the per-peer mode rather than the global env var. A single remaining caller of IsForceRelayed() (srWatcher.Start in engine.go) is left for a follow-up; that path uses a process-wide flag not per-peer state, so it can be migrated in Phase 2 once srWatcher itself learns about ConnectionMode. Phase 1 of issue #5989. Engine forwarding (C5) follows. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/peer/conn.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index 1e416bfe707..395315842e1 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/client/internal/peer/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(), From 6d4041826a7e5d3f774da6d25b441ae09f06b97d Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:22:46 +0000 Subject: [PATCH 07/19] client/engine: forward resolved Mode to per-peer ConnConfig createPeerConn now reads ConnMgr.Mode() and copies it into peer.ConnConfig, so the per-peer Open() loop in conn.go can take the ModeRelayForced skip-ICE branch without reading the global env var. This is the last wiring commit for the client side of Phase 1; the server-side mgmt changes (Settings + OpenAPI + handler + audit + NetworkMap-build) follow in Section D. Phase 1 of issue #5989. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/engine.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client/internal/engine.go b/client/internal/engine.go index 5b54ebd8667..d90c1f08711 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -1573,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{ From be964443348822d04abecc69c383d0a7b766cc37 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:24:23 +0000 Subject: [PATCH 08/19] mgmt/types: add ConnectionMode + p2p/relay timeout to Settings All three fields are nullable to distinguish 'use built-in default' (NULL) from explicit values (incl. 0 = never tear down). Copy() now deep-clones the new pointer fields via two small helpers. GORM AutoMigrate creates the new columns at first start; existing accounts have NULL in all three columns and resolve via the legacy LazyConnectionEnabled boolean. Phase 1 of issue #5989. Co-Authored-By: Claude Opus 4.7 (1M context) --- management/server/types/settings.go | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) 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 +} From 92885d486fac429b41b102ae33a6a0d16b3c81a2 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:26:01 +0000 Subject: [PATCH 09/19] openapi: add connection_mode + p2p/relay timeout fields to AccountSettings Three new optional, nullable fields with descriptions of the NULL = built-in-default semantics and the Phase-1-vs-Phase-2 status of p2p-dynamic. Regenerated types.gen.go via the existing oapi-codegen tooling. The generated AccountSettingsConnectionMode enum has the canonical values relay-forced / p2p / p2p-lazy / p2p-dynamic, plus a Valid() helper for handler-side validation. Phase 1 of issue #5989. Co-Authored-By: Claude Opus 4.7 (1M context) --- shared/management/http/api/openapi.yml | 32 +++++++++++++ shared/management/http/api/types.gen.go | 63 +++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 5 deletions(-) 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"` From b00fee3dd05a0afaa0145376c2f8ac319e661979 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:27:10 +0000 Subject: [PATCH 10/19] mgmt/handlers/accounts: accept connection_mode + timeout settings on PUT PUT /api/accounts/{id} now accepts connection_mode (validated against the four-value enum via the generated AccountSettingsConnectionMode. Valid()), p2p_timeout_seconds and relay_timeout_seconds. NULL in the JSON body keeps the existing value untouched (= "no client-side override on this round-trip"); explicit NULL-clear via API uses a distinct PATCH-style call which is out-of-scope for Phase 1. Response payload mirrors the input fields back as nullable so the dashboard can distinguish "use default" from "explicit value". Phase 1 of issue #5989. Audit-event emission follows in D5. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../handlers/accounts/accounts_handler.go | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/management/server/http/handlers/accounts/accounts_handler.go b/management/server/http/handlers/accounts/accounts_handler.go index cc5567e3db6..d290b60112e 100644 --- a/management/server/http/handlers/accounts/accounts_handler.go +++ b/management/server/http/handlers/accounts/accounts_handler.go @@ -215,6 +215,25 @@ 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. Clients clear an override by + // sending JSON null (which lands here as a nil pointer and skips + // this whole block, leaving the existing value untouched). + s := modeStr + returnSettings.ConnectionMode = &s + } + if req.Settings.P2pTimeoutSeconds != nil { + v := uint32(*req.Settings.P2pTimeoutSeconds) + returnSettings.P2pTimeoutSeconds = &v + } + if req.Settings.RelayTimeoutSeconds != nil { + v := uint32(*req.Settings.RelayTimeoutSeconds) + returnSettings.RelayTimeoutSeconds = &v + } if req.Settings.AutoUpdateVersion != nil { _, err := goversion.NewSemver(*req.Settings.AutoUpdateVersion) if *req.Settings.AutoUpdateVersion == autoUpdateLatestVersion || @@ -349,6 +368,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, From 12ec6536a49c9c57242894f076d2b8c27c8a68a7 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:28:14 +0000 Subject: [PATCH 11/19] mgmt/activity: add three new account-scoped event codes AccountConnectionModeChanged (121), AccountRelayTimeoutChanged (122), AccountP2pTimeoutChanged (123) -- emitted from account.go when settings change. Per-peer / per-group event codes are reserved for Phase 3 (issue #5990). Phase 1 of issue #5989. Co-Authored-By: Claude Opus 4.7 (1M context) --- management/server/activity/codes.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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"}, From ddd53a3f68f40aaeed76bc9b66adf371e1c75174 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:29:24 +0000 Subject: [PATCH 12/19] mgmt/account: emit audit events for connection_mode + timeout changes handleConnectionModeSettings is invoked from the same diff-detection block as handleLazyConnectionSettings; emits one StoreEvent per changed field (ConnectionMode, RelayTimeoutSeconds, P2pTimeoutSeconds) with old/new values in the meta payload. Four small ptr-equality / deref helpers are added for nullable string and uint32 fields. They are package-private and named after the existing convention used elsewhere in the package. Phase 1 of issue #5989. Co-Authored-By: Claude Opus 4.7 (1M context) --- management/server/account.go | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/management/server/account.go b/management/server/account.go index 4b71ab486eb..84b1af93df3 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -371,6 +371,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 +456,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 From 68bf8e3ae1fddf0387b837681c7611897bdf8bd9 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:31:51 +0000 Subject: [PATCH 13/19] move connectionmode package + extend toPeerConfig to fill new wire fields Two changes in one commit because they're inseparable: 1. Move client/internal/peer/connectionmode/ to shared/connectionmode/. The package now needs to be importable from BOTH client/ and management/ (which is impossible while it lives under client/internal/ per Go's internal-package rule). All imports updated; tests pass on both sides. 2. Extend management/internals/shared/grpc/conversion.go::toPeerConfig to populate the three new PeerConfig fields (ConnectionMode, P2PTimeoutSeconds, RelayTimeoutSeconds) using the connectionmode helpers. The legacy LazyConnectionEnabled boolean is now derived from the resolved Mode via ToLazyConnectionEnabled() rather than copied verbatim from Settings -- this is the central backwards-compat contract: old clients see only the boolean, new clients prefer the explicit enum and ignore the bool. Resolution rules (Phase 1, account-wide only): - Settings.ConnectionMode != nil and parses -> wins - Otherwise -> ResolveLegacyLazyBool(LazyConnectionEnabled) - timeouts: Settings.RelayTimeoutSeconds / P2pTimeoutSeconds when non-NULL, else 0 (= server has no preference; client uses built-in default) Per-peer / per-group resolution comes in Phase 3 (#5990). Phase 1 of issue #5989. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/conn_mgr.go | 2 +- client/internal/conn_mgr_test.go | 2 +- client/internal/connect.go | 2 +- client/internal/engine.go | 2 +- client/internal/peer/conn.go | 2 +- client/internal/peer/env.go | 2 +- client/internal/peer/env_test.go | 2 +- .../internals/shared/grpc/conversion.go | 31 ++++++++++++++++++- .../peer => shared}/connectionmode/mode.go | 0 .../connectionmode/mode_test.go | 0 10 files changed, 37 insertions(+), 8 deletions(-) rename {client/internal/peer => shared}/connectionmode/mode.go (100%) rename {client/internal/peer => shared}/connectionmode/mode_test.go (100%) diff --git a/client/internal/conn_mgr.go b/client/internal/conn_mgr.go index bc844e6ea15..3836e506b5d 100644 --- a/client/internal/conn_mgr.go +++ b/client/internal/conn_mgr.go @@ -12,7 +12,7 @@ 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/client/internal/peer/connectionmode" + "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" diff --git a/client/internal/conn_mgr_test.go b/client/internal/conn_mgr_test.go index 56d391cf543..e422873e54a 100644 --- a/client/internal/conn_mgr_test.go +++ b/client/internal/conn_mgr_test.go @@ -3,7 +3,7 @@ package internal import ( "testing" - "github.com/netbirdio/netbird/client/internal/peer/connectionmode" + "github.com/netbirdio/netbird/shared/connectionmode" mgmProto "github.com/netbirdio/netbird/shared/management/proto" ) diff --git a/client/internal/connect.go b/client/internal/connect.go index df7e516d63c..0c803bf7aa6 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -25,7 +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/client/internal/peer/connectionmode" + "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" diff --git a/client/internal/engine.go b/client/internal/engine.go index d90c1f08711..08ee5fe7348 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -44,7 +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/client/internal/peer/connectionmode" + "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" diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index 395315842e1..3650ae7ff9f 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -16,7 +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/client/internal/peer/connectionmode" + "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" diff --git a/client/internal/peer/env.go b/client/internal/peer/env.go index 65cf036ab3b..fbee8f6808b 100644 --- a/client/internal/peer/env.go +++ b/client/internal/peer/env.go @@ -10,7 +10,7 @@ import ( log "github.com/sirupsen/logrus" - "github.com/netbirdio/netbird/client/internal/peer/connectionmode" + "github.com/netbirdio/netbird/shared/connectionmode" ) const ( diff --git a/client/internal/peer/env_test.go b/client/internal/peer/env_test.go index 7ce10a51a3d..3dd7b7345ec 100644 --- a/client/internal/peer/env_test.go +++ b/client/internal/peer/env_test.go @@ -3,7 +3,7 @@ package peer import ( "testing" - "github.com/netbirdio/netbird/client/internal/peer/connectionmode" + "github.com/netbirdio/netbird/shared/connectionmode" ) func TestResolveModeFromEnv(t *testing.T) { 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/client/internal/peer/connectionmode/mode.go b/shared/connectionmode/mode.go similarity index 100% rename from client/internal/peer/connectionmode/mode.go rename to shared/connectionmode/mode.go diff --git a/client/internal/peer/connectionmode/mode_test.go b/shared/connectionmode/mode_test.go similarity index 100% rename from client/internal/peer/connectionmode/mode_test.go rename to shared/connectionmode/mode_test.go From c272786d54466009264af050bdb6ddf159ba82e6 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Fri, 1 May 2026 09:33:25 +0000 Subject: [PATCH 14/19] mgmt/grpc: tests for toPeerConfig connection-mode resolution Nine sub-cases cover the Phase-1 resolution matrix from spec section 3: - no settings -> default (P2P + lazy=false) - legacy bool only -> mapped via ResolveLegacyLazyBool - explicit ConnectionMode -> wins over the legacy bool - timeouts propagate - garbage ConnectionMode value -> tolerant fallback to legacy bool Particular attention to the structural compat gap: relay-forced cannot be expressed via the legacy boolean, so the wire field for old clients is sent as false. Documented in the spec, asserted here. Existing TestAccount_GetPeerNetworkMap remains green: existing test peers have ConnectionMode=NULL in Settings, falls through to the legacy ResolveLegacyLazyBool(false) -> ModeP2P -> wire bool false. Phase 1 of issue #5989. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../internals/shared/grpc/conversion_test.go | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) 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 From 09c77ebb63de18caa0e52d899748919b8c18b797 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Sat, 2 May 2026 09:20:28 +0000 Subject: [PATCH 15/19] mgmt/account: trigger updateAccountPeers on ConnectionMode + timeout changes UpdateAccountSettings only set updateAccountPeers=true when one of a fixed list of fields changed (RoutingPeerDNSResolutionEnabled, LazyConnectionEnabled, DNSDomain, AutoUpdate*). The Phase 1 ConnectionMode plus RelayTimeoutSeconds and P2pTimeoutSeconds were omitted, so a settings change to any of these was persisted and audited but never bumped the account network serial -- connected peers kept running with the old values until an unrelated change or reconnect picked them up. Add the three Phase 1 fields to the gating block using the existing equalStringPtr / equalUint32Ptr helpers (already used a few lines down in handleConnectionModeSettings for audit emission). Test: TestDefaultAccountManager_UpdateAccountSettings_ConnectionModeIncrementsSerial verifies that: - re-applying identical settings does NOT increment Network.Serial - changing ConnectionMode increments Serial - changing RelayTimeoutSeconds increments Serial - changing P2pTimeoutSeconds increments Serial Co-Authored-By: Claude Opus 4.7 (1M context) --- management/server/account.go | 5 +- management/server/account_test.go | 96 +++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/management/server/account.go b/management/server/account.go index 84b1af93df3..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 } 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) From ed3e23a762cc8a81366efddf48b2cb6e036e097a Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Sat, 2 May 2026 09:22:29 +0000 Subject: [PATCH 16/19] mgmt/handlers/accounts: validate timeout bounds + clarify ConnectionMode null semantics parseAccountSettings narrowed *int64 timeout fields straight to uint32 without checking either end of the range. Negative values wrapped to a huge positive uint32, and values above math.MaxUint32 silently truncated. Both became persisted account state. Add validateUint32Timeout(field, ptr) -> (*uint32, error) and use it for p2p_timeout_seconds and relay_timeout_seconds. Returns a clear "%s must be >= 0 / <= MaxUint32" error so the API caller sees the problem instead of getting a corrupted setting saved. Also correct the ConnectionMode block comment: the previous wording implied JSON null could clear an existing override. The *api.AccountSettingsConnectionMode pointer cannot distinguish absent from explicit null at this layer, so both leave the stored value untouched. Document that and flag the API gap for a future revision. Test: TestValidateUint32Timeout covers nil, zero, common positive, MaxUint32 boundary, negative-rejected, and over-MaxUint32 rejected. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../handlers/accounts/accounts_handler.go | 44 ++++++++++++++++--- .../accounts/accounts_handler_test.go | 39 ++++++++++++++++ 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/management/server/http/handlers/accounts/accounts_handler.go b/management/server/http/handlers/accounts/accounts_handler.go index d290b60112e..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 @@ -220,19 +240,29 @@ func (h *handler) updateAccountRequestSettings(req api.PutApiAccountsAccountIdJS if !req.Settings.ConnectionMode.Valid() { return nil, fmt.Errorf("invalid connection_mode %q", modeStr) } - // Persist as the canonical string. Clients clear an override by - // sending JSON null (which lands here as a nil pointer and skips - // this whole block, leaving the existing value untouched). + // 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 := uint32(*req.Settings.P2pTimeoutSeconds) - returnSettings.P2pTimeoutSeconds = &v + v, err := validateUint32Timeout("p2p_timeout_seconds", req.Settings.P2pTimeoutSeconds) + if err != nil { + return nil, err + } + returnSettings.P2pTimeoutSeconds = v } if req.Settings.RelayTimeoutSeconds != nil { - v := uint32(*req.Settings.RelayTimeoutSeconds) - returnSettings.RelayTimeoutSeconds = &v + 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) 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) + } + }) + } +} From 885f278776eedda028d77d9e28a820caa2b57f58 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Sat, 2 May 2026 09:23:43 +0000 Subject: [PATCH 17/19] client/peer/env: bound NB_LAZY_CONN_INACTIVITY_THRESHOLD parse + codespell ResolveModeFromEnv parsed NB_LAZY_CONN_INACTIVITY_THRESHOLD with time.ParseDuration and then narrowed straight to uint32 via uint32(d.Seconds()). Two ways that silently produced wrong state: - negative durations (e.g. "-30s") wrapped to a huge positive uint32 - durations larger than ~136 years truncated when seconds overflowed uint32 Reject both with a warn-and-ignore so the daemon falls back to the server-pushed value or the built-in default instead of running with a nonsensical relay timeout. Test additions: - "unparsable inactivity duration is ignored" (existing case renamed from "unparseable" so codespell doesn't trip on the s/ss spelling) - "negative inactivity duration is ignored" - "oversized inactivity duration is ignored" (2000000h, > MaxUint32 s) Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/peer/env.go | 13 ++++++++++--- client/internal/peer/env_test.go | 5 ++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/client/internal/peer/env.go b/client/internal/peer/env.go index fbee8f6808b..ec4774addd4 100644 --- a/client/internal/peer/env.go +++ b/client/internal/peer/env.go @@ -1,6 +1,7 @@ package peer import ( + "math" "os" "runtime" "strconv" @@ -72,11 +73,17 @@ func ResolveModeFromEnv() (connectionmode.Mode, uint32) { timeoutSecs := uint32(0) if raw := os.Getenv(envInactivityThreshold); raw != "" { - if d, err := time.ParseDuration(raw); err == nil { + 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") - } else { - log.Warnf("ignoring %s=%q: %v", envInactivityThreshold, raw, err) } } diff --git a/client/internal/peer/env_test.go b/client/internal/peer/env_test.go index 3dd7b7345ec..9b8653f5929 100644 --- a/client/internal/peer/env_test.go +++ b/client/internal/peer/env_test.go @@ -22,9 +22,12 @@ func TestResolveModeFromEnv(t *testing.T) { {"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 unparseable falls through to legacy", "garbage", "true", "", "", connectionmode.ModeRelayForced, 0}, + {"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) { From 85f19fb64a528c66fef1553f6f70af5114c78350 Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Sat, 2 May 2026 09:27:27 +0000 Subject: [PATCH 18/19] client/profile: persist RelayTimeoutSeconds + P2pTimeoutSeconds as *uint32 The profile Config struct held both timeout fields as `uint32 \`json:",omitempty"\`` which conflated three states the rest of the code already cares about: - "no local override, follow whatever the server pushes" - "explicit zero" (a future "disable this timer" sentinel) - "explicit non-zero N seconds" omitempty drops zero values on save, so a user who explicitly typed `--relay-timeout 0` would see that intent erased the next time the profile JSON was rehydrated. Switch the persistence type to `*uint32`. ConfigInput already used pointer types so the input-side semantics stay the same; the new nil/value distinction is now preserved across save/load. connect.go flattens to the runtime uint32 expected by EngineConfig via a small derefUint32 helper -- the resolve chain in conn_mgr.go still uses 0 as "fall through" and is left untouched here, so a profile with explicit-zero is not yet honored at runtime, but the on-disk data is at least honest for when that resolve gets nullable-aware. Test: TestProfileTimeoutsNullableRoundtrip JSON marshals and unmarshals a matrix of {nil, explicit zero, non-zero} combinations and verifies nil-ness plus value survives the roundtrip. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/connect.go | 14 +++++- client/internal/profilemanager/config.go | 23 ++++++--- client/internal/profilemanager/config_test.go | 49 +++++++++++++++++++ 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/client/internal/connect.go b/client/internal/connect.go index 0c803bf7aa6..0605c47da64 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -568,8 +568,8 @@ func createEngineConfig(key wgtypes.Key, config *profilemanager.Config, peerConf LazyConnectionEnabled: config.LazyConnectionEnabled, ConnectionMode: parseConnectionMode(config.ConnectionMode), - RelayTimeoutSeconds: config.RelayTimeoutSeconds, - P2pTimeoutSeconds: config.P2pTimeoutSeconds, + RelayTimeoutSeconds: derefUint32(config.RelayTimeoutSeconds), + P2pTimeoutSeconds: derefUint32(config.P2pTimeoutSeconds), MTU: selectMTU(config.MTU, peerConfig.Mtu), LogPath: logPath, @@ -713,3 +713,13 @@ func parseConnectionMode(s string) connectionmode.Mode { } 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/profilemanager/config.go b/client/internal/profilemanager/config.go index a8c74a756aa..194ff409ebc 100644 --- a/client/internal/profilemanager/config.go +++ b/client/internal/profilemanager/config.go @@ -174,9 +174,16 @@ type Config struct { LazyConnectionEnabled bool - ConnectionMode string `json:",omitempty"` - RelayTimeoutSeconds uint32 `json:",omitempty"` - P2pTimeoutSeconds uint32 `json:",omitempty"` + 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 } @@ -606,14 +613,16 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) { config.ConnectionMode = *input.ConnectionMode updated = true } - if input.RelayTimeoutSeconds != nil && *input.RelayTimeoutSeconds != config.RelayTimeoutSeconds { + if input.RelayTimeoutSeconds != nil && (config.RelayTimeoutSeconds == nil || *input.RelayTimeoutSeconds != *config.RelayTimeoutSeconds) { log.Infof("switching relay timeout to %d seconds", *input.RelayTimeoutSeconds) - config.RelayTimeoutSeconds = *input.RelayTimeoutSeconds + v := *input.RelayTimeoutSeconds + config.RelayTimeoutSeconds = &v updated = true } - if input.P2pTimeoutSeconds != nil && *input.P2pTimeoutSeconds != config.P2pTimeoutSeconds { + if input.P2pTimeoutSeconds != nil && (config.P2pTimeoutSeconds == nil || *input.P2pTimeoutSeconds != *config.P2pTimeoutSeconds) { log.Infof("switching p2p timeout to %d seconds", *input.P2pTimeoutSeconds) - config.P2pTimeoutSeconds = *input.P2pTimeoutSeconds + v := *input.P2pTimeoutSeconds + config.P2pTimeoutSeconds = &v updated = true } 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) + } + }) + } +} From 5ef9b09dcd684635c3a98400336dbb3699f3601d Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Sat, 2 May 2026 09:28:41 +0000 Subject: [PATCH 19/19] shared/connectionmode: accept "relay" as alias for "relay-forced" ParseString only accepted the canonical "relay-forced" form, even though older CLI docs and the dashboard surface "Relay" as the user- facing label. A user passing --connection-mode relay got back an "unknown connection mode" error and silently fell through to ModeUnspecified, which was confusing given the spec uses both names interchangeably. Map both "relay-forced" and "relay" to ModeRelayForced. The canonical persisted form is unchanged (Mode.String() still returns "relay-forced"); the alias is only honored on parse so a config save + reload normalises to canonical. Tests cover the alias plus the existing case-insensitive and whitespace-tolerant behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) --- shared/connectionmode/mode.go | 6 +++++- shared/connectionmode/mode_test.go | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/shared/connectionmode/mode.go b/shared/connectionmode/mode.go index d3b1c9e14e4..c40a498a87e 100644 --- a/shared/connectionmode/mode.go +++ b/shared/connectionmode/mode.go @@ -55,7 +55,11 @@ func ParseString(s string) (Mode, error) { switch strings.ToLower(strings.TrimSpace(s)) { case "": return ModeUnspecified, nil - case "relay-forced": + 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 diff --git a/shared/connectionmode/mode_test.go b/shared/connectionmode/mode_test.go index 01a9c11c929..3bd169c8f58 100644 --- a/shared/connectionmode/mode_test.go +++ b/shared/connectionmode/mode_test.go @@ -13,6 +13,9 @@ func TestParseString(t *testing.T) { 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},