diff --git a/client/anonymize/anonymize.go b/client/anonymize/anonymize.go index ad31682f25a..9a6d9720794 100644 --- a/client/anonymize/anonymize.go +++ b/client/anonymize/anonymize.go @@ -12,6 +12,8 @@ import ( "strings" ) +const anonTLD = ".domain" + type Anonymizer struct { ipAnonymizer map[netip.Addr]netip.Addr domainAnonymizer map[string]string @@ -83,29 +85,39 @@ func (a *Anonymizer) AnonymizeIPString(ip string) string { } func (a *Anonymizer) AnonymizeDomain(domain string) string { - if strings.HasSuffix(domain, "netbird.io") || - strings.HasSuffix(domain, "netbird.selfhosted") || - strings.HasSuffix(domain, "netbird.cloud") || - strings.HasSuffix(domain, "netbird.stage") || - strings.HasSuffix(domain, ".domain") { + baseDomain := domain + hasDot := strings.HasSuffix(domain, ".") + if hasDot { + baseDomain = domain[:len(domain)-1] + } + + if strings.HasSuffix(baseDomain, "netbird.io") || + strings.HasSuffix(baseDomain, "netbird.selfhosted") || + strings.HasSuffix(baseDomain, "netbird.cloud") || + strings.HasSuffix(baseDomain, "netbird.stage") || + strings.HasSuffix(baseDomain, anonTLD) { return domain } - parts := strings.Split(domain, ".") + parts := strings.Split(baseDomain, ".") if len(parts) < 2 { return domain } - baseDomain := parts[len(parts)-2] + "." + parts[len(parts)-1] + baseForLookup := parts[len(parts)-2] + "." + parts[len(parts)-1] - anonymized, ok := a.domainAnonymizer[baseDomain] + anonymized, ok := a.domainAnonymizer[baseForLookup] if !ok { - anonymizedBase := "anon-" + generateRandomString(5) + ".domain" - a.domainAnonymizer[baseDomain] = anonymizedBase + anonymizedBase := "anon-" + generateRandomString(5) + anonTLD + a.domainAnonymizer[baseForLookup] = anonymizedBase anonymized = anonymizedBase } - return strings.Replace(domain, baseDomain, anonymized, 1) + result := strings.Replace(baseDomain, baseForLookup, anonymized, 1) + if hasDot { + result += "." + } + return result } func (a *Anonymizer) AnonymizeURI(uri string) string { @@ -168,10 +180,10 @@ func (a *Anonymizer) AnonymizeDNSLogLine(logEntry string) string { parts := strings.Split(match, `"`) if len(parts) >= 2 { domain := parts[1] - if strings.HasSuffix(domain, ".domain") { + if strings.HasSuffix(domain, anonTLD) { return match } - randomDomain := generateRandomString(10) + ".domain" + randomDomain := generateRandomString(10) + anonTLD return strings.Replace(match, domain, randomDomain, 1) } return match diff --git a/client/anonymize/anonymize_test.go b/client/anonymize/anonymize_test.go index 605788ab54a..a3aae1ee982 100644 --- a/client/anonymize/anonymize_test.go +++ b/client/anonymize/anonymize_test.go @@ -67,18 +67,36 @@ func TestAnonymizeDomain(t *testing.T) { `^anon-[a-zA-Z0-9]+\.domain$`, true, }, + { + "Domain with Trailing Dot", + "example.com.", + `^anon-[a-zA-Z0-9]+\.domain.$`, + true, + }, { "Subdomain", "sub.example.com", `^sub\.anon-[a-zA-Z0-9]+\.domain$`, true, }, + { + "Subdomain with Trailing Dot", + "sub.example.com.", + `^sub\.anon-[a-zA-Z0-9]+\.domain.$`, + true, + }, { "Protected Domain", "netbird.io", `^netbird\.io$`, false, }, + { + "Protected Domain with Trailing Dot", + "netbird.io.", + `^netbird\.io.$`, + false, + }, } for _, tc := range tests { diff --git a/client/cmd/debug.go b/client/cmd/debug.go index 9abd2039dd5..c7ab87b4766 100644 --- a/client/cmd/debug.go +++ b/client/cmd/debug.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "strings" "time" log "github.com/sirupsen/logrus" @@ -61,6 +62,15 @@ var forCmd = &cobra.Command{ RunE: runForDuration, } +var persistenceCmd = &cobra.Command{ + Use: "persistence [on|off]", + Short: "Set network map memory persistence", + Long: `Configure whether the latest network map should persist in memory. When enabled, the last known network map will be kept in memory.`, + Example: " netbird debug persistence on", + Args: cobra.ExactArgs(1), + RunE: setNetworkMapPersistence, +} + func debugBundle(cmd *cobra.Command, _ []string) error { conn, err := getClient(cmd) if err != nil { @@ -171,6 +181,13 @@ func runForDuration(cmd *cobra.Command, args []string) error { time.Sleep(1 * time.Second) + // Enable network map persistence before bringing the service up + if _, err := client.SetNetworkMapPersistence(cmd.Context(), &proto.SetNetworkMapPersistenceRequest{ + Enabled: true, + }); err != nil { + return fmt.Errorf("failed to enable network map persistence: %v", status.Convert(err).Message()) + } + if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil { return fmt.Errorf("failed to up: %v", status.Convert(err).Message()) } @@ -200,6 +217,13 @@ func runForDuration(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message()) } + // Disable network map persistence after creating the debug bundle + if _, err := client.SetNetworkMapPersistence(cmd.Context(), &proto.SetNetworkMapPersistenceRequest{ + Enabled: false, + }); err != nil { + return fmt.Errorf("failed to disable network map persistence: %v", status.Convert(err).Message()) + } + if stateWasDown { if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil { return fmt.Errorf("failed to down: %v", status.Convert(err).Message()) @@ -219,6 +243,34 @@ func runForDuration(cmd *cobra.Command, args []string) error { return nil } +func setNetworkMapPersistence(cmd *cobra.Command, args []string) error { + conn, err := getClient(cmd) + if err != nil { + return err + } + defer func() { + if err := conn.Close(); err != nil { + log.Errorf(errCloseConnection, err) + } + }() + + persistence := strings.ToLower(args[0]) + if persistence != "on" && persistence != "off" { + return fmt.Errorf("invalid persistence value: %s. Use 'on' or 'off'", args[0]) + } + + client := proto.NewDaemonServiceClient(conn) + _, err = client.SetNetworkMapPersistence(cmd.Context(), &proto.SetNetworkMapPersistenceRequest{ + Enabled: persistence == "on", + }) + if err != nil { + return fmt.Errorf("failed to set network map persistence: %v", status.Convert(err).Message()) + } + + cmd.Printf("Network map persistence set to: %s\n", persistence) + return nil +} + func getStatusOutput(cmd *cobra.Command) string { var statusOutputString string statusResp, err := getStatus(cmd.Context()) diff --git a/client/cmd/root.go b/client/cmd/root.go index 8dae6e27374..3f2d04ef304 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -155,6 +155,7 @@ func init() { debugCmd.AddCommand(logCmd) logCmd.AddCommand(logLevelCmd) debugCmd.AddCommand(forCmd) + debugCmd.AddCommand(persistenceCmd) upCmd.PersistentFlags().StringSliceVar(&natExternalIPs, externalIPMapFlag, nil, `Sets external IPs maps between local addresses and interfaces.`+ diff --git a/client/internal/connect.go b/client/internal/connect.go index 8c2ad4aa1db..21f6d3ec869 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -40,6 +40,8 @@ type ConnectClient struct { statusRecorder *peer.Status engine *Engine engineMutex sync.Mutex + + persistNetworkMap bool } func NewConnectClient( @@ -258,7 +260,7 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, probes *ProbeHold c.engineMutex.Lock() c.engine = NewEngineWithProbes(engineCtx, cancel, signalClient, mgmClient, relayManager, engineConfig, mobileDependency, c.statusRecorder, probes, checks) - + c.engine.SetNetworkMapPersistence(c.persistNetworkMap) c.engineMutex.Unlock() if err := c.engine.Start(); err != nil { @@ -362,6 +364,22 @@ func (c *ConnectClient) isContextCancelled() bool { } } +// SetNetworkMapPersistence enables or disables network map persistence. +// When enabled, the last received network map will be stored and can be retrieved +// through the Engine's getLatestNetworkMap method. When disabled, any stored +// network map will be cleared. This functionality is primarily used for debugging +// and should not be enabled during normal operation. +func (c *ConnectClient) SetNetworkMapPersistence(enabled bool) { + c.engineMutex.Lock() + c.persistNetworkMap = enabled + c.engineMutex.Unlock() + + engine := c.Engine() + if engine != nil { + engine.SetNetworkMapPersistence(enabled) + } +} + // createEngineConfig converts configuration received from Management Service to EngineConfig func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.PeerConfig) (*EngineConfig, error) { nm := false diff --git a/client/internal/engine.go b/client/internal/engine.go index 920c295cdde..fc9620d801e 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -21,6 +21,7 @@ import ( "github.com/pion/stun/v2" log "github.com/sirupsen/logrus" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "google.golang.org/protobuf/proto" "github.com/netbirdio/netbird/client/firewall" "github.com/netbirdio/netbird/client/firewall/manager" @@ -172,6 +173,10 @@ type Engine struct { relayManager *relayClient.Manager stateManager *statemanager.Manager srWatcher *guard.SRWatcher + + // Network map persistence + persistNetworkMap bool + latestNetworkMap *mgmProto.NetworkMap } // Peer is an instance of the Connection Peer @@ -573,13 +578,22 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error { return err } - if update.GetNetworkMap() != nil { - // only apply new changes and ignore old ones - err := e.updateNetworkMap(update.GetNetworkMap()) - if err != nil { - return err - } + nm := update.GetNetworkMap() + if nm == nil { + return nil + } + + // Store network map if persistence is enabled + if e.persistNetworkMap { + e.latestNetworkMap = nm + log.Debugf("network map persisted with serial %d", nm.GetSerial()) + } + + // only apply new changes and ignore old ones + if err := e.updateNetworkMap(nm); err != nil { + return err } + return nil } @@ -1500,6 +1514,46 @@ func (e *Engine) stopDNSServer() { e.statusRecorder.UpdateDNSStates(nsGroupStates) } +// SetNetworkMapPersistence enables or disables network map persistence +func (e *Engine) SetNetworkMapPersistence(enabled bool) { + e.syncMsgMux.Lock() + defer e.syncMsgMux.Unlock() + + if enabled == e.persistNetworkMap { + return + } + e.persistNetworkMap = enabled + log.Debugf("Network map persistence is set to %t", enabled) + + if !enabled { + e.latestNetworkMap = nil + } +} + +// GetLatestNetworkMap returns the stored network map if persistence is enabled +func (e *Engine) GetLatestNetworkMap() (*mgmProto.NetworkMap, error) { + e.syncMsgMux.Lock() + defer e.syncMsgMux.Unlock() + + if !e.persistNetworkMap { + return nil, errors.New("network map persistence is disabled") + } + + if e.latestNetworkMap == nil { + //nolint:nilnil + return nil, nil + } + + // Create a deep copy to avoid external modifications + nm, ok := proto.Clone(e.latestNetworkMap).(*mgmProto.NetworkMap) + if !ok { + + return nil, fmt.Errorf("failed to clone network map") + } + + return nm, nil +} + // isChecksEqual checks if two slices of checks are equal. func isChecksEqual(checks []*mgmProto.Checks, oChecks []*mgmProto.Checks) bool { for _, check := range checks { diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go index b942d8b6e0a..0eae4e0d618 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.26.0 -// protoc v3.21.12 +// protoc v4.23.4 // source: daemon.proto package proto @@ -2103,6 +2103,91 @@ func (*SetLogLevelResponse) Descriptor() ([]byte, []int) { return file_daemon_proto_rawDescGZIP(), []int{30} } +type SetNetworkMapPersistenceRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` +} + +func (x *SetNetworkMapPersistenceRequest) Reset() { + *x = SetNetworkMapPersistenceRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetNetworkMapPersistenceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetNetworkMapPersistenceRequest) ProtoMessage() {} + +func (x *SetNetworkMapPersistenceRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetNetworkMapPersistenceRequest.ProtoReflect.Descriptor instead. +func (*SetNetworkMapPersistenceRequest) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{31} +} + +func (x *SetNetworkMapPersistenceRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type SetNetworkMapPersistenceResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *SetNetworkMapPersistenceResponse) Reset() { + *x = SetNetworkMapPersistenceResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_daemon_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetNetworkMapPersistenceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetNetworkMapPersistenceResponse) ProtoMessage() {} + +func (x *SetNetworkMapPersistenceResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[32] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetNetworkMapPersistenceResponse.ProtoReflect.Descriptor instead. +func (*SetNetworkMapPersistenceResponse) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{32} +} + var File_daemon_proto protoreflect.FileDescriptor var file_daemon_proto_rawDesc = []byte{ @@ -2399,66 +2484,79 @@ var file_daemon_proto_rawDesc = []byte{ 0x32, 0x10, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x2a, 0x62, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x41, 0x4e, - 0x49, 0x43, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x02, 0x12, - 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, - 0x52, 0x4e, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x05, 0x12, 0x09, - 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x06, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, - 0x43, 0x45, 0x10, 0x07, 0x32, 0xb8, 0x06, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, - 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, - 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, - 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, - 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, - 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, - 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, - 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, - 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, - 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, - 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x64, - 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, + 0x22, 0x3b, 0x0a, 0x1f, 0x53, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, + 0x70, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x22, 0x0a, + 0x20, 0x53, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x50, 0x65, + 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2a, 0x62, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0b, 0x0a, + 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x41, + 0x4e, 0x49, 0x43, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x02, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, + 0x41, 0x52, 0x4e, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x05, 0x12, + 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x06, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, + 0x41, 0x43, 0x45, 0x10, 0x07, 0x32, 0xa9, 0x07, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, + 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, + 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, + 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, + 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x45, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x65, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x65, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, - 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, - 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x47, - 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, - 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, - 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, - 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, + 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, + 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, + 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, + 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x6f, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, + 0x70, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x27, 0x2e, 0x64, + 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x4d, 0x61, 0x70, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, + 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x50, 0x65, 0x72, 0x73, + 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -2474,50 +2572,52 @@ func file_daemon_proto_rawDescGZIP() []byte { } var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 34) var file_daemon_proto_goTypes = []interface{}{ - (LogLevel)(0), // 0: daemon.LogLevel - (*LoginRequest)(nil), // 1: daemon.LoginRequest - (*LoginResponse)(nil), // 2: daemon.LoginResponse - (*WaitSSOLoginRequest)(nil), // 3: daemon.WaitSSOLoginRequest - (*WaitSSOLoginResponse)(nil), // 4: daemon.WaitSSOLoginResponse - (*UpRequest)(nil), // 5: daemon.UpRequest - (*UpResponse)(nil), // 6: daemon.UpResponse - (*StatusRequest)(nil), // 7: daemon.StatusRequest - (*StatusResponse)(nil), // 8: daemon.StatusResponse - (*DownRequest)(nil), // 9: daemon.DownRequest - (*DownResponse)(nil), // 10: daemon.DownResponse - (*GetConfigRequest)(nil), // 11: daemon.GetConfigRequest - (*GetConfigResponse)(nil), // 12: daemon.GetConfigResponse - (*PeerState)(nil), // 13: daemon.PeerState - (*LocalPeerState)(nil), // 14: daemon.LocalPeerState - (*SignalState)(nil), // 15: daemon.SignalState - (*ManagementState)(nil), // 16: daemon.ManagementState - (*RelayState)(nil), // 17: daemon.RelayState - (*NSGroupState)(nil), // 18: daemon.NSGroupState - (*FullStatus)(nil), // 19: daemon.FullStatus - (*ListRoutesRequest)(nil), // 20: daemon.ListRoutesRequest - (*ListRoutesResponse)(nil), // 21: daemon.ListRoutesResponse - (*SelectRoutesRequest)(nil), // 22: daemon.SelectRoutesRequest - (*SelectRoutesResponse)(nil), // 23: daemon.SelectRoutesResponse - (*IPList)(nil), // 24: daemon.IPList - (*Route)(nil), // 25: daemon.Route - (*DebugBundleRequest)(nil), // 26: daemon.DebugBundleRequest - (*DebugBundleResponse)(nil), // 27: daemon.DebugBundleResponse - (*GetLogLevelRequest)(nil), // 28: daemon.GetLogLevelRequest - (*GetLogLevelResponse)(nil), // 29: daemon.GetLogLevelResponse - (*SetLogLevelRequest)(nil), // 30: daemon.SetLogLevelRequest - (*SetLogLevelResponse)(nil), // 31: daemon.SetLogLevelResponse - nil, // 32: daemon.Route.ResolvedIPsEntry - (*durationpb.Duration)(nil), // 33: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 34: google.protobuf.Timestamp + (LogLevel)(0), // 0: daemon.LogLevel + (*LoginRequest)(nil), // 1: daemon.LoginRequest + (*LoginResponse)(nil), // 2: daemon.LoginResponse + (*WaitSSOLoginRequest)(nil), // 3: daemon.WaitSSOLoginRequest + (*WaitSSOLoginResponse)(nil), // 4: daemon.WaitSSOLoginResponse + (*UpRequest)(nil), // 5: daemon.UpRequest + (*UpResponse)(nil), // 6: daemon.UpResponse + (*StatusRequest)(nil), // 7: daemon.StatusRequest + (*StatusResponse)(nil), // 8: daemon.StatusResponse + (*DownRequest)(nil), // 9: daemon.DownRequest + (*DownResponse)(nil), // 10: daemon.DownResponse + (*GetConfigRequest)(nil), // 11: daemon.GetConfigRequest + (*GetConfigResponse)(nil), // 12: daemon.GetConfigResponse + (*PeerState)(nil), // 13: daemon.PeerState + (*LocalPeerState)(nil), // 14: daemon.LocalPeerState + (*SignalState)(nil), // 15: daemon.SignalState + (*ManagementState)(nil), // 16: daemon.ManagementState + (*RelayState)(nil), // 17: daemon.RelayState + (*NSGroupState)(nil), // 18: daemon.NSGroupState + (*FullStatus)(nil), // 19: daemon.FullStatus + (*ListRoutesRequest)(nil), // 20: daemon.ListRoutesRequest + (*ListRoutesResponse)(nil), // 21: daemon.ListRoutesResponse + (*SelectRoutesRequest)(nil), // 22: daemon.SelectRoutesRequest + (*SelectRoutesResponse)(nil), // 23: daemon.SelectRoutesResponse + (*IPList)(nil), // 24: daemon.IPList + (*Route)(nil), // 25: daemon.Route + (*DebugBundleRequest)(nil), // 26: daemon.DebugBundleRequest + (*DebugBundleResponse)(nil), // 27: daemon.DebugBundleResponse + (*GetLogLevelRequest)(nil), // 28: daemon.GetLogLevelRequest + (*GetLogLevelResponse)(nil), // 29: daemon.GetLogLevelResponse + (*SetLogLevelRequest)(nil), // 30: daemon.SetLogLevelRequest + (*SetLogLevelResponse)(nil), // 31: daemon.SetLogLevelResponse + (*SetNetworkMapPersistenceRequest)(nil), // 32: daemon.SetNetworkMapPersistenceRequest + (*SetNetworkMapPersistenceResponse)(nil), // 33: daemon.SetNetworkMapPersistenceResponse + nil, // 34: daemon.Route.ResolvedIPsEntry + (*durationpb.Duration)(nil), // 35: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 36: google.protobuf.Timestamp } var file_daemon_proto_depIdxs = []int32{ - 33, // 0: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration + 35, // 0: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration 19, // 1: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus - 34, // 2: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp - 34, // 3: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp - 33, // 4: daemon.PeerState.latency:type_name -> google.protobuf.Duration + 36, // 2: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp + 36, // 3: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp + 35, // 4: daemon.PeerState.latency:type_name -> google.protobuf.Duration 16, // 5: daemon.FullStatus.managementState:type_name -> daemon.ManagementState 15, // 6: daemon.FullStatus.signalState:type_name -> daemon.SignalState 14, // 7: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState @@ -2525,7 +2625,7 @@ var file_daemon_proto_depIdxs = []int32{ 17, // 9: daemon.FullStatus.relays:type_name -> daemon.RelayState 18, // 10: daemon.FullStatus.dns_servers:type_name -> daemon.NSGroupState 25, // 11: daemon.ListRoutesResponse.routes:type_name -> daemon.Route - 32, // 12: daemon.Route.resolvedIPs:type_name -> daemon.Route.ResolvedIPsEntry + 34, // 12: daemon.Route.resolvedIPs:type_name -> daemon.Route.ResolvedIPsEntry 0, // 13: daemon.GetLogLevelResponse.level:type_name -> daemon.LogLevel 0, // 14: daemon.SetLogLevelRequest.level:type_name -> daemon.LogLevel 24, // 15: daemon.Route.ResolvedIPsEntry.value:type_name -> daemon.IPList @@ -2541,20 +2641,22 @@ var file_daemon_proto_depIdxs = []int32{ 26, // 25: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest 28, // 26: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest 30, // 27: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest - 2, // 28: daemon.DaemonService.Login:output_type -> daemon.LoginResponse - 4, // 29: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse - 6, // 30: daemon.DaemonService.Up:output_type -> daemon.UpResponse - 8, // 31: daemon.DaemonService.Status:output_type -> daemon.StatusResponse - 10, // 32: daemon.DaemonService.Down:output_type -> daemon.DownResponse - 12, // 33: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse - 21, // 34: daemon.DaemonService.ListRoutes:output_type -> daemon.ListRoutesResponse - 23, // 35: daemon.DaemonService.SelectRoutes:output_type -> daemon.SelectRoutesResponse - 23, // 36: daemon.DaemonService.DeselectRoutes:output_type -> daemon.SelectRoutesResponse - 27, // 37: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse - 29, // 38: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse - 31, // 39: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse - 28, // [28:40] is the sub-list for method output_type - 16, // [16:28] is the sub-list for method input_type + 32, // 28: daemon.DaemonService.SetNetworkMapPersistence:input_type -> daemon.SetNetworkMapPersistenceRequest + 2, // 29: daemon.DaemonService.Login:output_type -> daemon.LoginResponse + 4, // 30: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse + 6, // 31: daemon.DaemonService.Up:output_type -> daemon.UpResponse + 8, // 32: daemon.DaemonService.Status:output_type -> daemon.StatusResponse + 10, // 33: daemon.DaemonService.Down:output_type -> daemon.DownResponse + 12, // 34: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse + 21, // 35: daemon.DaemonService.ListRoutes:output_type -> daemon.ListRoutesResponse + 23, // 36: daemon.DaemonService.SelectRoutes:output_type -> daemon.SelectRoutesResponse + 23, // 37: daemon.DaemonService.DeselectRoutes:output_type -> daemon.SelectRoutesResponse + 27, // 38: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse + 29, // 39: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse + 31, // 40: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse + 33, // 41: daemon.DaemonService.SetNetworkMapPersistence:output_type -> daemon.SetNetworkMapPersistenceResponse + 29, // [29:42] is the sub-list for method output_type + 16, // [16:29] is the sub-list for method input_type 16, // [16:16] is the sub-list for extension type_name 16, // [16:16] is the sub-list for extension extendee 0, // [0:16] is the sub-list for field type_name @@ -2938,6 +3040,30 @@ func file_daemon_proto_init() { return nil } } + file_daemon_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetNetworkMapPersistenceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_daemon_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetNetworkMapPersistenceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_daemon_proto_msgTypes[0].OneofWrappers = []interface{}{} type x struct{} @@ -2946,7 +3072,7 @@ func file_daemon_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_daemon_proto_rawDesc, NumEnums: 1, - NumMessages: 32, + NumMessages: 34, NumExtensions: 0, NumServices: 1, }, diff --git a/client/proto/daemon.proto b/client/proto/daemon.proto index 384bc0e6206..04aa6688262 100644 --- a/client/proto/daemon.proto +++ b/client/proto/daemon.proto @@ -45,7 +45,11 @@ service DaemonService { // SetLogLevel sets the log level of the daemon rpc SetLogLevel(SetLogLevelRequest) returns (SetLogLevelResponse) {} -}; + + // SetNetworkMapPersistence enables or disables network map persistence + rpc SetNetworkMapPersistence(SetNetworkMapPersistenceRequest) returns (SetNetworkMapPersistenceResponse) {} +} + message LoginRequest { // setupKey wiretrustee setup key. @@ -293,4 +297,10 @@ message SetLogLevelRequest { } message SetLogLevelResponse { -} \ No newline at end of file +} + +message SetNetworkMapPersistenceRequest { + bool enabled = 1; +} + +message SetNetworkMapPersistenceResponse {} diff --git a/client/proto/daemon_grpc.pb.go b/client/proto/daemon_grpc.pb.go index e0bc117e52d..31645763e0c 100644 --- a/client/proto/daemon_grpc.pb.go +++ b/client/proto/daemon_grpc.pb.go @@ -43,6 +43,8 @@ type DaemonServiceClient interface { GetLogLevel(ctx context.Context, in *GetLogLevelRequest, opts ...grpc.CallOption) (*GetLogLevelResponse, error) // SetLogLevel sets the log level of the daemon SetLogLevel(ctx context.Context, in *SetLogLevelRequest, opts ...grpc.CallOption) (*SetLogLevelResponse, error) + // SetNetworkMapPersistence enables or disables network map persistence + SetNetworkMapPersistence(ctx context.Context, in *SetNetworkMapPersistenceRequest, opts ...grpc.CallOption) (*SetNetworkMapPersistenceResponse, error) } type daemonServiceClient struct { @@ -161,6 +163,15 @@ func (c *daemonServiceClient) SetLogLevel(ctx context.Context, in *SetLogLevelRe return out, nil } +func (c *daemonServiceClient) SetNetworkMapPersistence(ctx context.Context, in *SetNetworkMapPersistenceRequest, opts ...grpc.CallOption) (*SetNetworkMapPersistenceResponse, error) { + out := new(SetNetworkMapPersistenceResponse) + err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetNetworkMapPersistence", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // DaemonServiceServer is the server API for DaemonService service. // All implementations must embed UnimplementedDaemonServiceServer // for forward compatibility @@ -190,6 +201,8 @@ type DaemonServiceServer interface { GetLogLevel(context.Context, *GetLogLevelRequest) (*GetLogLevelResponse, error) // SetLogLevel sets the log level of the daemon SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error) + // SetNetworkMapPersistence enables or disables network map persistence + SetNetworkMapPersistence(context.Context, *SetNetworkMapPersistenceRequest) (*SetNetworkMapPersistenceResponse, error) mustEmbedUnimplementedDaemonServiceServer() } @@ -233,6 +246,9 @@ func (UnimplementedDaemonServiceServer) GetLogLevel(context.Context, *GetLogLeve func (UnimplementedDaemonServiceServer) SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SetLogLevel not implemented") } +func (UnimplementedDaemonServiceServer) SetNetworkMapPersistence(context.Context, *SetNetworkMapPersistenceRequest) (*SetNetworkMapPersistenceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetNetworkMapPersistence not implemented") +} func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {} // UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service. @@ -462,6 +478,24 @@ func _DaemonService_SetLogLevel_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _DaemonService_SetNetworkMapPersistence_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetNetworkMapPersistenceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).SetNetworkMapPersistence(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/daemon.DaemonService/SetNetworkMapPersistence", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).SetNetworkMapPersistence(ctx, req.(*SetNetworkMapPersistenceRequest)) + } + return interceptor(ctx, in, info, handler) +} + // DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -517,6 +551,10 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{ MethodName: "SetLogLevel", Handler: _DaemonService_SetLogLevel_Handler, }, + { + MethodName: "SetNetworkMapPersistence", + Handler: _DaemonService_SetNetworkMapPersistence_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "daemon.proto", diff --git a/client/server/debug.go b/client/server/debug.go index d87fc9b6a2d..c12fd99dbf2 100644 --- a/client/server/debug.go +++ b/client/server/debug.go @@ -21,12 +21,14 @@ import ( "time" log "github.com/sirupsen/logrus" + "google.golang.org/protobuf/encoding/protojson" "github.com/netbirdio/netbird/client/anonymize" "github.com/netbirdio/netbird/client/internal/peer" "github.com/netbirdio/netbird/client/internal/routemanager/systemops" "github.com/netbirdio/netbird/client/internal/statemanager" "github.com/netbirdio/netbird/client/proto" + mgmProto "github.com/netbirdio/netbird/management/proto" ) const readmeContent = `Netbird debug bundle @@ -39,6 +41,7 @@ netbird.out: Most recent, anonymized stdout log file of the NetBird client. routes.txt: Anonymized system routes, if --system-info flag was provided. interfaces.txt: Anonymized network interface information, if --system-info flag was provided. config.txt: Anonymized configuration information of the NetBird client. +network_map.json: Anonymized network map containing peer configurations, routes, DNS settings, and firewall rules. state.json: Anonymized client state dump containing netbird states. @@ -59,6 +62,16 @@ Domains All domain names (except for the netbird domains) are replaced with randomly generated strings ending in ".domain". Anonymized domains are consistent across all files in the bundle. Reoccuring domain names are replaced with the same anonymized domain. +Network Map +The network_map.json file contains the following anonymized information: +- Peer configurations (addresses, FQDNs, DNS settings) +- Remote and offline peer information (allowed IPs, FQDNs) +- Routes (network ranges, associated domains) +- DNS configuration (nameservers, domains, custom zones) +- Firewall rules (peer IPs, source/destination ranges) + +SSH keys in the network map are replaced with a placeholder value. All IP addresses and domains in the network map follow the same anonymization rules as described above. + State File The state.json file contains anonymized internal state information of the NetBird client, including: - DNS settings and configuration @@ -148,19 +161,23 @@ func (s *Server) createArchive(bundlePath *os.File, req *proto.DebugBundleReques seedFromStatus(anonymizer, &status) if err := s.addConfig(req, anonymizer, archive); err != nil { - return fmt.Errorf("add config: %w", err) + log.Errorf("Failed to add config to debug bundle: %v", err) } if req.GetSystemInfo() { if err := s.addRoutes(req, anonymizer, archive); err != nil { - return fmt.Errorf("add routes: %w", err) + log.Errorf("Failed to add routes to debug bundle: %v", err) } if err := s.addInterfaces(req, anonymizer, archive); err != nil { - return fmt.Errorf("add interfaces: %w", err) + log.Errorf("Failed to add interfaces to debug bundle: %v", err) } } + if err := s.addNetworkMap(req, anonymizer, archive); err != nil { + return fmt.Errorf("add network map: %w", err) + } + if err := s.addStateFile(req, anonymizer, archive); err != nil { log.Errorf("Failed to add state file to debug bundle: %v", err) } @@ -253,15 +270,16 @@ func (s *Server) addCommonConfigFields(configContent *strings.Builder) { } func (s *Server) addRoutes(req *proto.DebugBundleRequest, anonymizer *anonymize.Anonymizer, archive *zip.Writer) error { - if routes, err := systemops.GetRoutesFromTable(); err != nil { - log.Errorf("Failed to get routes: %v", err) - } else { - // TODO: get routes including nexthop - routesContent := formatRoutes(routes, req.GetAnonymize(), anonymizer) - routesReader := strings.NewReader(routesContent) - if err := addFileToZip(archive, routesReader, "routes.txt"); err != nil { - return fmt.Errorf("add routes file to zip: %w", err) - } + routes, err := systemops.GetRoutesFromTable() + if err != nil { + return fmt.Errorf("get routes: %w", err) + } + + // TODO: get routes including nexthop + routesContent := formatRoutes(routes, req.GetAnonymize(), anonymizer) + routesReader := strings.NewReader(routesContent) + if err := addFileToZip(archive, routesReader, "routes.txt"); err != nil { + return fmt.Errorf("add routes file to zip: %w", err) } return nil } @@ -281,6 +299,39 @@ func (s *Server) addInterfaces(req *proto.DebugBundleRequest, anonymizer *anonym return nil } +func (s *Server) addNetworkMap(req *proto.DebugBundleRequest, anonymizer *anonymize.Anonymizer, archive *zip.Writer) error { + networkMap, err := s.getLatestNetworkMap() + if err != nil { + // Skip if network map is not available, but log it + log.Debugf("skipping empty network map in debug bundle: %v", err) + return nil + } + + if req.GetAnonymize() { + if err := anonymizeNetworkMap(networkMap, anonymizer); err != nil { + return fmt.Errorf("anonymize network map: %w", err) + } + } + + options := protojson.MarshalOptions{ + EmitUnpopulated: true, + UseProtoNames: true, + Indent: " ", + AllowPartial: true, + } + + jsonBytes, err := options.Marshal(networkMap) + if err != nil { + return fmt.Errorf("generate json: %w", err) + } + + if err := addFileToZip(archive, bytes.NewReader(jsonBytes), "network_map.json"); err != nil { + return fmt.Errorf("add network map to zip: %w", err) + } + + return nil +} + func (s *Server) addStateFile(req *proto.DebugBundleRequest, anonymizer *anonymize.Anonymizer, archive *zip.Writer) error { path := statemanager.GetDefaultStatePath() if path == "" { @@ -368,14 +419,43 @@ func (s *Server) addSingleLogfile(logPath, targetName string, req *proto.DebugBu return nil } +// getLatestNetworkMap returns the latest network map from the engine if network map persistence is enabled +func (s *Server) getLatestNetworkMap() (*mgmProto.NetworkMap, error) { + if s.connectClient == nil { + return nil, errors.New("connect client is not initialized") + } + + engine := s.connectClient.Engine() + if engine == nil { + return nil, errors.New("engine is not initialized") + } + + networkMap, err := engine.GetLatestNetworkMap() + if err != nil { + return nil, fmt.Errorf("get latest network map: %w", err) + } + + if networkMap == nil { + return nil, errors.New("network map is not available") + } + + return networkMap, nil +} + // GetLogLevel gets the current logging level for the server. func (s *Server) GetLogLevel(_ context.Context, _ *proto.GetLogLevelRequest) (*proto.GetLogLevelResponse, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + level := ParseLogLevel(log.GetLevel().String()) return &proto.GetLogLevelResponse{Level: level}, nil } // SetLogLevel sets the logging level for the server. func (s *Server) SetLogLevel(_ context.Context, req *proto.SetLogLevelRequest) (*proto.SetLogLevelResponse, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + level, err := log.ParseLevel(req.Level.String()) if err != nil { return nil, fmt.Errorf("invalid log level: %w", err) @@ -386,6 +466,20 @@ func (s *Server) SetLogLevel(_ context.Context, req *proto.SetLogLevelRequest) ( return &proto.SetLogLevelResponse{}, nil } +// SetNetworkMapPersistence sets the network map persistence for the server. +func (s *Server) SetNetworkMapPersistence(_ context.Context, req *proto.SetNetworkMapPersistenceRequest) (*proto.SetNetworkMapPersistenceResponse, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + enabled := req.GetEnabled() + s.persistNetworkMap = enabled + if s.connectClient != nil { + s.connectClient.SetNetworkMapPersistence(enabled) + } + + return &proto.SetNetworkMapPersistenceResponse{}, nil +} + func addFileToZip(archive *zip.Writer, reader io.Reader, filename string) error { header := &zip.FileHeader{ Name: filename, @@ -578,6 +672,177 @@ func anonymizeNATExternalIPs(ips []string, anonymizer *anonymize.Anonymizer) []s return anonymizedIPs } +func anonymizeNetworkMap(networkMap *mgmProto.NetworkMap, anonymizer *anonymize.Anonymizer) error { + if networkMap.PeerConfig != nil { + anonymizePeerConfig(networkMap.PeerConfig, anonymizer) + } + + for _, peer := range networkMap.RemotePeers { + anonymizeRemotePeer(peer, anonymizer) + } + + for _, peer := range networkMap.OfflinePeers { + anonymizeRemotePeer(peer, anonymizer) + } + + for _, r := range networkMap.Routes { + anonymizeRoute(r, anonymizer) + } + + if networkMap.DNSConfig != nil { + anonymizeDNSConfig(networkMap.DNSConfig, anonymizer) + } + + for _, rule := range networkMap.FirewallRules { + anonymizeFirewallRule(rule, anonymizer) + } + + for _, rule := range networkMap.RoutesFirewallRules { + anonymizeRouteFirewallRule(rule, anonymizer) + } + + return nil +} + +func anonymizePeerConfig(config *mgmProto.PeerConfig, anonymizer *anonymize.Anonymizer) { + if config == nil { + return + } + + if addr, err := netip.ParseAddr(config.Address); err == nil { + config.Address = anonymizer.AnonymizeIP(addr).String() + } + + if config.SshConfig != nil && len(config.SshConfig.SshPubKey) > 0 { + config.SshConfig.SshPubKey = []byte("ssh-placeholder-key") + } + + config.Dns = anonymizer.AnonymizeString(config.Dns) + config.Fqdn = anonymizer.AnonymizeDomain(config.Fqdn) +} + +func anonymizeRemotePeer(peer *mgmProto.RemotePeerConfig, anonymizer *anonymize.Anonymizer) { + if peer == nil { + return + } + + for i, ip := range peer.AllowedIps { + // Try to parse as prefix first (CIDR) + if prefix, err := netip.ParsePrefix(ip); err == nil { + anonIP := anonymizer.AnonymizeIP(prefix.Addr()) + peer.AllowedIps[i] = fmt.Sprintf("%s/%d", anonIP, prefix.Bits()) + } else if addr, err := netip.ParseAddr(ip); err == nil { + peer.AllowedIps[i] = anonymizer.AnonymizeIP(addr).String() + } + } + + peer.Fqdn = anonymizer.AnonymizeDomain(peer.Fqdn) + + if peer.SshConfig != nil && len(peer.SshConfig.SshPubKey) > 0 { + peer.SshConfig.SshPubKey = []byte("ssh-placeholder-key") + } +} + +func anonymizeRoute(route *mgmProto.Route, anonymizer *anonymize.Anonymizer) { + if route == nil { + return + } + + if prefix, err := netip.ParsePrefix(route.Network); err == nil { + anonIP := anonymizer.AnonymizeIP(prefix.Addr()) + route.Network = fmt.Sprintf("%s/%d", anonIP, prefix.Bits()) + } + + for i, domain := range route.Domains { + route.Domains[i] = anonymizer.AnonymizeDomain(domain) + } + + route.NetID = anonymizer.AnonymizeString(route.NetID) +} + +func anonymizeDNSConfig(config *mgmProto.DNSConfig, anonymizer *anonymize.Anonymizer) { + if config == nil { + return + } + + anonymizeNameServerGroups(config.NameServerGroups, anonymizer) + anonymizeCustomZones(config.CustomZones, anonymizer) +} + +func anonymizeNameServerGroups(groups []*mgmProto.NameServerGroup, anonymizer *anonymize.Anonymizer) { + for _, group := range groups { + anonymizeServers(group.NameServers, anonymizer) + anonymizeDomains(group.Domains, anonymizer) + } +} + +func anonymizeServers(servers []*mgmProto.NameServer, anonymizer *anonymize.Anonymizer) { + for _, server := range servers { + if addr, err := netip.ParseAddr(server.IP); err == nil { + server.IP = anonymizer.AnonymizeIP(addr).String() + } + } +} + +func anonymizeDomains(domains []string, anonymizer *anonymize.Anonymizer) { + for i, domain := range domains { + domains[i] = anonymizer.AnonymizeDomain(domain) + } +} + +func anonymizeCustomZones(zones []*mgmProto.CustomZone, anonymizer *anonymize.Anonymizer) { + for _, zone := range zones { + zone.Domain = anonymizer.AnonymizeDomain(zone.Domain) + anonymizeRecords(zone.Records, anonymizer) + } +} + +func anonymizeRecords(records []*mgmProto.SimpleRecord, anonymizer *anonymize.Anonymizer) { + for _, record := range records { + record.Name = anonymizer.AnonymizeDomain(record.Name) + anonymizeRData(record, anonymizer) + } +} + +func anonymizeRData(record *mgmProto.SimpleRecord, anonymizer *anonymize.Anonymizer) { + switch record.Type { + case 1, 28: // A or AAAA record + if addr, err := netip.ParseAddr(record.RData); err == nil { + record.RData = anonymizer.AnonymizeIP(addr).String() + } + default: + record.RData = anonymizer.AnonymizeString(record.RData) + } +} + +func anonymizeFirewallRule(rule *mgmProto.FirewallRule, anonymizer *anonymize.Anonymizer) { + if rule == nil { + return + } + + if addr, err := netip.ParseAddr(rule.PeerIP); err == nil { + rule.PeerIP = anonymizer.AnonymizeIP(addr).String() + } +} + +func anonymizeRouteFirewallRule(rule *mgmProto.RouteFirewallRule, anonymizer *anonymize.Anonymizer) { + if rule == nil { + return + } + + for i, sourceRange := range rule.SourceRanges { + if prefix, err := netip.ParsePrefix(sourceRange); err == nil { + anonIP := anonymizer.AnonymizeIP(prefix.Addr()) + rule.SourceRanges[i] = fmt.Sprintf("%s/%d", anonIP, prefix.Bits()) + } + } + + if prefix, err := netip.ParsePrefix(rule.Destination); err == nil { + anonIP := anonymizer.AnonymizeIP(prefix.Addr()) + rule.Destination = fmt.Sprintf("%s/%d", anonIP, prefix.Bits()) + } +} + func anonymizeStateFile(rawStates *map[string]json.RawMessage, anonymizer *anonymize.Anonymizer) error { for name, rawState := range *rawStates { if string(rawState) == "null" { diff --git a/client/server/debug_test.go b/client/server/debug_test.go index 1515036bd02..c8f7bae5d38 100644 --- a/client/server/debug_test.go +++ b/client/server/debug_test.go @@ -2,6 +2,7 @@ package server import ( "encoding/json" + "net" "strings" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/client/anonymize" + mgmProto "github.com/netbirdio/netbird/management/proto" ) func TestAnonymizeStateFile(t *testing.T) { @@ -263,3 +265,166 @@ func mustMarshal(v any) json.RawMessage { } return data } + +func TestAnonymizeNetworkMap(t *testing.T) { + networkMap := &mgmProto.NetworkMap{ + PeerConfig: &mgmProto.PeerConfig{ + Address: "203.0.113.5", + Dns: "1.2.3.4", + Fqdn: "peer1.corp.example.com", + SshConfig: &mgmProto.SSHConfig{ + SshPubKey: []byte("ssh-rsa AAAAB3NzaC1..."), + }, + }, + RemotePeers: []*mgmProto.RemotePeerConfig{ + { + AllowedIps: []string{ + "203.0.113.1/32", + "2001:db8:1234::1/128", + "192.168.1.1/32", + "100.64.0.1/32", + "10.0.0.1/32", + }, + Fqdn: "peer2.corp.example.com", + SshConfig: &mgmProto.SSHConfig{ + SshPubKey: []byte("ssh-rsa AAAAB3NzaC2..."), + }, + }, + }, + Routes: []*mgmProto.Route{ + { + Network: "197.51.100.0/24", + Domains: []string{"prod.example.com", "staging.example.com"}, + NetID: "net-123abc", + }, + }, + DNSConfig: &mgmProto.DNSConfig{ + NameServerGroups: []*mgmProto.NameServerGroup{ + { + NameServers: []*mgmProto.NameServer{ + {IP: "8.8.8.8"}, + {IP: "1.1.1.1"}, + {IP: "203.0.113.53"}, + }, + Domains: []string{"example.com", "internal.example.com"}, + }, + }, + CustomZones: []*mgmProto.CustomZone{ + { + Domain: "custom.example.com", + Records: []*mgmProto.SimpleRecord{ + { + Name: "www.custom.example.com", + Type: 1, + RData: "203.0.113.10", + }, + { + Name: "internal.custom.example.com", + Type: 1, + RData: "192.168.1.10", + }, + }, + }, + }, + }, + } + + // Create anonymizer with test addresses + anonymizer := anonymize.NewAnonymizer(anonymize.DefaultAddresses()) + + // Anonymize the network map + err := anonymizeNetworkMap(networkMap, anonymizer) + require.NoError(t, err) + + // Test PeerConfig anonymization + peerCfg := networkMap.PeerConfig + require.NotEqual(t, "203.0.113.5", peerCfg.Address) + + // Verify DNS and FQDN are properly anonymized + require.NotEqual(t, "1.2.3.4", peerCfg.Dns) + require.NotEqual(t, "peer1.corp.example.com", peerCfg.Fqdn) + require.True(t, strings.HasSuffix(peerCfg.Fqdn, ".domain")) + + // Verify SSH key is replaced + require.Equal(t, []byte("ssh-placeholder-key"), peerCfg.SshConfig.SshPubKey) + + // Test RemotePeers anonymization + remotePeer := networkMap.RemotePeers[0] + + // Verify FQDN is anonymized + require.NotEqual(t, "peer2.corp.example.com", remotePeer.Fqdn) + require.True(t, strings.HasSuffix(remotePeer.Fqdn, ".domain")) + + // Check that public IPs are anonymized but private IPs are preserved + for _, allowedIP := range remotePeer.AllowedIps { + ip, _, err := net.ParseCIDR(allowedIP) + require.NoError(t, err) + + if ip.IsPrivate() || isInCGNATRange(ip) { + require.Contains(t, []string{ + "192.168.1.1/32", + "100.64.0.1/32", + "10.0.0.1/32", + }, allowedIP) + } else { + require.NotContains(t, []string{ + "203.0.113.1/32", + "2001:db8:1234::1/128", + }, allowedIP) + } + } + + // Test Routes anonymization + route := networkMap.Routes[0] + require.NotEqual(t, "197.51.100.0/24", route.Network) + for _, domain := range route.Domains { + require.True(t, strings.HasSuffix(domain, ".domain")) + require.NotContains(t, domain, "example.com") + } + + // Test DNS config anonymization + dnsConfig := networkMap.DNSConfig + nameServerGroup := dnsConfig.NameServerGroups[0] + + // Verify well-known DNS servers are preserved + require.Equal(t, "8.8.8.8", nameServerGroup.NameServers[0].IP) + require.Equal(t, "1.1.1.1", nameServerGroup.NameServers[1].IP) + + // Verify public DNS server is anonymized + require.NotEqual(t, "203.0.113.53", nameServerGroup.NameServers[2].IP) + + // Verify domains are anonymized + for _, domain := range nameServerGroup.Domains { + require.True(t, strings.HasSuffix(domain, ".domain")) + require.NotContains(t, domain, "example.com") + } + + // Test CustomZones anonymization + customZone := dnsConfig.CustomZones[0] + require.True(t, strings.HasSuffix(customZone.Domain, ".domain")) + require.NotContains(t, customZone.Domain, "example.com") + + // Verify records are properly anonymized + for _, record := range customZone.Records { + require.True(t, strings.HasSuffix(record.Name, ".domain")) + require.NotContains(t, record.Name, "example.com") + + ip := net.ParseIP(record.RData) + if ip != nil { + if !ip.IsPrivate() { + require.NotEqual(t, "203.0.113.10", record.RData) + } else { + require.Equal(t, "192.168.1.10", record.RData) + } + } + } +} + +// Helper function to check if IP is in CGNAT range +func isInCGNATRange(ip net.IP) bool { + cgnat := net.IPNet{ + IP: net.ParseIP("100.64.0.0"), + Mask: net.CIDRMask(10, 32), + } + return cgnat.Contains(ip) +} diff --git a/client/server/server.go b/client/server/server.go index 106bdf32bbf..71eb58a66bc 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -68,6 +68,8 @@ type Server struct { relayProbe *internal.Probe wgProbe *internal.Probe lastProbe time.Time + + persistNetworkMap bool } type oauthAuthFlow struct { @@ -196,6 +198,7 @@ func (s *Server) connectWithRetryRuns(ctx context.Context, config *internal.Conf runOperation := func() error { log.Tracef("running client connection") s.connectClient = internal.NewConnectClient(ctx, config, statusRecorder) + s.connectClient.SetNetworkMapPersistence(s.persistNetworkMap) probes := internal.ProbeHolder{ MgmProbe: s.mgmProbe, diff --git a/go.mod b/go.mod index e8c65542280..c08713f2bd4 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 golang.zx2c4.com/wireguard/windows v0.5.3 google.golang.org/grpc v1.64.1 - google.golang.org/protobuf v1.34.1 + google.golang.org/protobuf v1.34.2 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) @@ -224,7 +224,7 @@ require ( golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect diff --git a/go.sum b/go.sum index 47975d4eab4..5519d6ded99 100644 --- a/go.sum +++ b/go.sum @@ -1151,8 +1151,8 @@ google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaE google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434 h1:OpXbo8JnN8+jZGPrL4SSfaDjSCjupr8lXyBAbexEm/U= google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1189,8 +1189,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=