Repository: netbirdio/netbird

Issue:
docker-compose.yml should not contain version
**Describe the problem**

*docker-compose.yml* created from *getting-started-with-zitadel.sh* via function `renderDockerCompose()` should not contain version - it causes warning from `docker compose`:

```
WARN[0000] docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion 
```




Hints from issue comments:
@Silex: I addressed this in #3981 

Solution patch:
diff --git a/.github/workflows/git-town.yml b/.github/workflows/git-town.yml
index c54fcb44..699ed7d9 100644
--- a/.github/workflows/git-town.yml
+++ b/.github/workflows/git-town.yml
@@ -16,6 +16,6 @@ jobs:
 
     steps:
       - uses: actions/checkout@v4
-      - uses: git-town/action@v1
+      - uses: git-town/action@v1.2.1
         with:
-          skip-single-stacks: true
\ No newline at end of file
+          skip-single-stacks: true
diff --git a/.github/workflows/golang-test-linux.yml b/.github/workflows/golang-test-linux.yml
index cbce3e6e..0c3862e3 100644
--- a/.github/workflows/golang-test-linux.yml
+++ b/.github/workflows/golang-test-linux.yml
@@ -211,7 +211,11 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        arch: [ '386','amd64' ]
+        include:
+          - arch: "386"
+            raceFlag: ""
+          - arch: "amd64"
+            raceFlag: "-race"
     runs-on: ubuntu-22.04
     steps:
       - name: Install Go
@@ -251,9 +255,9 @@ jobs:
       - name: Test
         run: |
           CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \
-          go test \
+          go test ${{ matrix.raceFlag }} \
             -exec 'sudo' \
-            -timeout 10m ./signal/...
+            -timeout 10m ./relay/...
 
   test_signal:
     name: "Signal / Unit"
diff --git a/.github/workflows/mobile-build-validation.yml b/.github/workflows/mobile-build-validation.yml
index 569956a5..c7d43695 100644
--- a/.github/workflows/mobile-build-validation.yml
+++ b/.github/workflows/mobile-build-validation.yml
@@ -43,7 +43,7 @@ jobs:
       - name: gomobile init
         run: gomobile init
       - name: build android netbird lib
-        run: PATH=$PATH:$(go env GOPATH) gomobile bind -o $GITHUB_WORKSPACE/netbird.aar -javapkg=io.netbird.gomobile -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/io.netbird.client/cache/wireguard -X github.com/netbirdio/netbird/version.version=buildtest"  $GITHUB_WORKSPACE/client/android
+        run: PATH=$PATH:$(go env GOPATH) gomobile bind -o $GITHUB_WORKSPACE/netbird.aar -javapkg=io.netbird.gomobile -ldflags="-checklinkname=0 -X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/io.netbird.client/cache/wireguard -X github.com/netbirdio/netbird/version.version=buildtest"  $GITHUB_WORKSPACE/client/android
         env:
           CGO_ENABLED: 0
           ANDROID_NDK_HOME: /usr/local/lib/android/sdk/ndk/23.1.7779620
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4fc7f315..44e02f45 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,7 +9,7 @@ on:
   pull_request:
 
 env:
-  SIGN_PIPE_VER: "v0.0.18"
+  SIGN_PIPE_VER: "v0.0.21"
   GORELEASER_VER: "v2.3.2"
   PRODUCT_NAME: "NetBird"
   COPYRIGHT: "NetBird GmbH"
@@ -231,3 +231,17 @@ jobs:
           ref: ${{ env.SIGN_PIPE_VER }}
           token: ${{ secrets.SIGN_GITHUB_TOKEN }}
           inputs: '{ "tag": "${{ github.ref }}", "skipRelease": false }'
+
+  post_on_forum:
+    runs-on: ubuntu-latest
+    continue-on-error: true
+    needs: [trigger_signer]
+    steps:
+      - uses: Codixer/discourse-topic-github-release-action@v2.0.1
+        with:
+          discourse-api-key: ${{ secrets.DISCOURSE_RELEASES_API_KEY }}
+          discourse-base-url: https://forum.netbird.io
+          discourse-author-username: NetBird
+          discourse-category: 17
+          discourse-tags:
+            releases          
diff --git a/.github/workflows/test-infrastructure-files.yml b/.github/workflows/test-infrastructure-files.yml
index e2f9e40c..bd37f65c 100644
--- a/.github/workflows/test-infrastructure-files.yml
+++ b/.github/workflows/test-infrastructure-files.yml
@@ -134,6 +134,7 @@ jobs:
           NETBIRD_STORE_ENGINE_MYSQL_DSN: '${{ env.NETBIRD_STORE_ENGINE_MYSQL_DSN }}$'
           CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false
           CI_NETBIRD_TURN_EXTERNAL_IP: "1.2.3.4"
+          CI_NETBIRD_MGMT_DISABLE_DEFAULT_POLICY: false
 
         run: |
           set -x
@@ -180,6 +181,7 @@ jobs:
           grep -A 7 Relay management.json | egrep '"Secret": ".+"'
           grep DisablePromptLogin management.json | grep 'true'
           grep LoginFlag management.json | grep 0
+          grep DisableDefaultPolicy management.json | grep "$CI_NETBIRD_MGMT_DISABLE_DEFAULT_POLICY"
 
       - name: Install modules
         run: go mod tidy
diff --git a/README.md b/README.md
index 1d2a976c..d5469c28 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,9 @@
     <br>
     <a href="https://docs.netbird.io/slack-url">
         <img src="https://img.shields.io/badge/slack-@netbird-red.svg?logo=slack"/>
+     </a>
+    <a href="https://forum.netbird.io">
+        <img src="https://img.shields.io/badge/community forum-@netbird-red.svg?logo=discourse"/>
      </a>  
      <br>
     <a href="https://gurubase.io/g/netbird">
@@ -29,13 +32,13 @@
   <br/>
   See <a href="https://netbird.io/docs/">Documentation</a>
   <br/>
-   Join our <a href="https://docs.netbird.io/slack-url">Slack channel</a>
+   Join our <a href="https://docs.netbird.io/slack-url">Slack channel</a> or our <a href="https://forum.netbird.io">Community forum</a>
   <br/>
  
 </strong>
 <br>
-<a href="https://github.com/netbirdio/kubernetes-operator">
-    New: NetBird Kubernetes Operator
+<a href="https://registry.terraform.io/providers/netbirdio/netbird/latest">
+    New: NetBird terraform provider
   </a> 
 </p>
 
@@ -47,10 +50,9 @@
 
 **Secure.** NetBird enables secure remote access by applying granular access policies while allowing you to manage them intuitively from a single place. Works universally on any infrastructure.
 
-### Open-Source Network Security in a Single Platform
+### Open Source Network Security in a Single Platform
 
-
-![netbird_2](https://github.com/netbirdio/netbird/assets/700848/46bc3b73-508d-4a0e-bb9a-f465d68646ab)
+<img width="1188" alt="centralized-network-management 1" src="https://github.com/user-attachments/assets/c28cc8e4-15d2-4d2f-bb97-a6433db39d56" />
 
 ### NetBird on Lawrence Systems (Video)
 [![Watch the video](https://img.youtube.com/vi/Kwrff6h0rEw/0.jpg)](https://www.youtube.com/watch?v=Kwrff6h0rEw)
diff --git a/client/android/client.go b/client/android/client.go
index 3b8a5bd0..0d0c7654 100644
--- a/client/android/client.go
+++ b/client/android/client.go
@@ -64,7 +64,9 @@ type Client struct {
 }
 
 // NewClient instantiate a new Client
-func NewClient(cfgFile, deviceName string, uiVersion string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client {
+func NewClient(cfgFile string, androidSDKVersion int, deviceName string, uiVersion string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client {
+	execWorkaround(androidSDKVersion)
+
 	net.SetAndroidProtectSocketFn(tunAdapter.ProtectSocket)
 	return &Client{
 		cfgFile:               cfgFile,
@@ -203,8 +205,10 @@ func (c *Client) Networks() *NetworkArray {
 			continue
 		}
 
-		if routes[0].IsDynamic() {
-			continue
+		r := routes[0]
+		netStr := r.Network.String()
+		if r.IsDynamic() {
+			netStr = r.Domains.SafeString()
 		}
 
 		peer, err := c.recorder.GetPeer(routes[0].Peer)
@@ -214,7 +218,7 @@ func (c *Client) Networks() *NetworkArray {
 		}
 		network := Network{
 			Name:    string(id),
-			Network: routes[0].Network.String(),
+			Network: netStr,
 			Peer:    peer.FQDN,
 			Status:  peer.ConnStatus.String(),
 		}
diff --git a/client/android/exec.go b/client/android/exec.go
new file mode 100644
index 00000000..805d3129
--- /dev/null
+++ b/client/android/exec.go
@@ -0,0 +1,26 @@
+//go:build android
+
+package android
+
+import (
+	"fmt"
+	_ "unsafe"
+)
+
+// https://github.com/golang/go/pull/69543/commits/aad6b3b32c81795f86bc4a9e81aad94899daf520
+// In Android version 11 and earlier, pidfd-related system calls
+// are not allowed by the seccomp policy, which causes crashes due
+// to SIGSYS signals.
+
+//go:linkname checkPidfdOnce os.checkPidfdOnce
+var checkPidfdOnce func() error
+
+func execWorkaround(androidSDKVersion int) {
+	if androidSDKVersion > 30 { // above Android 11
+		return
+	}
+
+	checkPidfdOnce = func() error {
+		return fmt.Errorf("unsupported Android version")
+	}
+}
diff --git a/client/cmd/debug.go b/client/cmd/debug.go
index 385bd95f..3f13a0c3 100644
--- a/client/cmd/debug.go
+++ b/client/cmd/debug.go
@@ -17,10 +17,18 @@ import (
 	"github.com/netbirdio/netbird/client/server"
 	nbstatus "github.com/netbirdio/netbird/client/status"
 	mgmProto "github.com/netbirdio/netbird/management/proto"
+	"github.com/netbirdio/netbird/upload-server/types"
 )
 
 const errCloseConnection = "Failed to close connection: %v"
 
+var (
+	logFileCount        uint32
+	systemInfoFlag      bool
+	uploadBundleFlag    bool
+	uploadBundleURLFlag string
+)
+
 var debugCmd = &cobra.Command{
 	Use:   "debug",
 	Short: "Debugging commands",
@@ -88,12 +96,13 @@ func debugBundle(cmd *cobra.Command, _ []string) error {
 
 	client := proto.NewDaemonServiceClient(conn)
 	request := &proto.DebugBundleRequest{
-		Anonymize:  anonymizeFlag,
-		Status:     getStatusOutput(cmd, anonymizeFlag),
-		SystemInfo: debugSystemInfoFlag,
+		Anonymize:    anonymizeFlag,
+		Status:       getStatusOutput(cmd, anonymizeFlag),
+		SystemInfo:   systemInfoFlag,
+		LogFileCount: logFileCount,
 	}
-	if debugUploadBundle {
-		request.UploadURL = debugUploadBundleURL
+	if uploadBundleFlag {
+		request.UploadURL = uploadBundleURLFlag
 	}
 	resp, err := client.DebugBundle(cmd.Context(), request)
 	if err != nil {
@@ -105,7 +114,7 @@ func debugBundle(cmd *cobra.Command, _ []string) error {
 		return fmt.Errorf("upload failed: %s", resp.GetUploadFailureReason())
 	}
 
-	if debugUploadBundle {
+	if uploadBundleFlag {
 		cmd.Printf("Upload file key:\n%s\n", resp.GetUploadedKey())
 	}
 
@@ -223,12 +232,13 @@ func runForDuration(cmd *cobra.Command, args []string) error {
 	headerPreDown := fmt.Sprintf("----- Netbird pre-down - Timestamp: %s - Duration: %s", time.Now().Format(time.RFC3339), duration)
 	statusOutput = fmt.Sprintf("%s\n%s\n%s", statusOutput, headerPreDown, getStatusOutput(cmd, anonymizeFlag))
 	request := &proto.DebugBundleRequest{
-		Anonymize:  anonymizeFlag,
-		Status:     statusOutput,
-		SystemInfo: debugSystemInfoFlag,
+		Anonymize:    anonymizeFlag,
+		Status:       statusOutput,
+		SystemInfo:   systemInfoFlag,
+		LogFileCount: logFileCount,
 	}
-	if debugUploadBundle {
-		request.UploadURL = debugUploadBundleURL
+	if uploadBundleFlag {
+		request.UploadURL = uploadBundleURLFlag
 	}
 	resp, err := client.DebugBundle(cmd.Context(), request)
 	if err != nil {
@@ -255,7 +265,7 @@ func runForDuration(cmd *cobra.Command, args []string) error {
 		return fmt.Errorf("upload failed: %s", resp.GetUploadFailureReason())
 	}
 
-	if debugUploadBundle {
+	if uploadBundleFlag {
 		cmd.Printf("Upload file key:\n%s\n", resp.GetUploadedKey())
 	}
 
@@ -297,7 +307,7 @@ func getStatusOutput(cmd *cobra.Command, anon bool) string {
 		cmd.PrintErrf("Failed to get status: %v\n", err)
 	} else {
 		statusOutputString = nbstatus.ParseToFullDetailSummary(
-			nbstatus.ConvertToStatusOutputOverview(statusResp, anon, "", nil, nil, nil),
+			nbstatus.ConvertToStatusOutputOverview(statusResp, anon, "", nil, nil, nil, ""),
 		)
 	}
 	return statusOutputString
@@ -375,3 +385,15 @@ func generateDebugBundle(config *internal.Config, recorder *peer.Status, connect
 	}
 	log.Infof("Generated debug bundle from SIGUSR1 at: %s", path)
 }
+
+func init() {
+	debugBundleCmd.Flags().Uint32VarP(&logFileCount, "log-file-count", "C", 1, "Number of rotated log files to include in debug bundle")
+	debugBundleCmd.Flags().BoolVarP(&systemInfoFlag, "system-info", "S", true, "Adds system information to the debug bundle")
+	debugBundleCmd.Flags().BoolVarP(&uploadBundleFlag, "upload-bundle", "U", false, "Uploads the debug bundle to a server")
+	debugBundleCmd.Flags().StringVar(&uploadBundleURLFlag, "upload-bundle-url", types.DefaultBundleURL, "Service URL to get an URL to upload the debug bundle")
+
+	forCmd.Flags().Uint32VarP(&logFileCount, "log-file-count", "C", 1, "Number of rotated log files to include in debug bundle")
+	forCmd.Flags().BoolVarP(&systemInfoFlag, "system-info", "S", true, "Adds system information to the debug bundle")
+	forCmd.Flags().BoolVarP(&uploadBundleFlag, "upload-bundle", "U", false, "Uploads the debug bundle to a server")
+	forCmd.Flags().StringVar(&uploadBundleURLFlag, "upload-bundle-url", types.DefaultBundleURL, "Service URL to get an URL to upload the debug bundle")
+}
diff --git a/client/cmd/root.go b/client/cmd/root.go
index 16e445f4..fa4bd4d4 100644
--- a/client/cmd/root.go
+++ b/client/cmd/root.go
@@ -22,7 +22,6 @@ import (
 	"google.golang.org/grpc/credentials/insecure"
 
 	"github.com/netbirdio/netbird/client/internal"
-	"github.com/netbirdio/netbird/upload-server/types"
 )
 
 const (
@@ -38,10 +37,7 @@ const (
 	serverSSHAllowedFlag     = "allow-server-ssh"
 	extraIFaceBlackListFlag  = "extra-iface-blacklist"
 	dnsRouteIntervalFlag     = "dns-router-interval"
-	systemInfoFlag           = "system-info"
 	enableLazyConnectionFlag = "enable-lazy-connection"
-	uploadBundle             = "upload-bundle"
-	uploadBundleURL          = "upload-bundle-url"
 )
 
 var (
@@ -75,10 +71,7 @@ var (
 	autoConnectDisabled     bool
 	extraIFaceBlackList     []string
 	anonymizeFlag           bool
-	debugSystemInfoFlag     bool
 	dnsRouteInterval        time.Duration
-	debugUploadBundle       bool
-	debugUploadBundleURL    string
 	lazyConnEnabled         bool
 
 	rootCmd = &cobra.Command{
@@ -184,11 +177,8 @@ 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(&serverSSHAllowed, serverSSHAllowedFlag, false, "Allow SSH server on peer. If enabled, the SSH server will be permitted")
 	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.")
+	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.")
 
-	debugCmd.PersistentFlags().BoolVarP(&debugSystemInfoFlag, systemInfoFlag, "S", true, "Adds system information to the debug bundle")
-	debugCmd.PersistentFlags().BoolVarP(&debugUploadBundle, uploadBundle, "U", false, fmt.Sprintf("Uploads the debug bundle to a server from URL defined by %s", uploadBundleURL))
-	debugCmd.PersistentFlags().StringVar(&debugUploadBundleURL, uploadBundleURL, types.DefaultBundleURL, "Service URL to get an URL to upload the debug bundle")
 }
 
 // SetupCloseHandler handles SIGTERM signal and exits with success
diff --git a/client/cmd/status.go b/client/cmd/status.go
index a85ee925..2d6e41bc 100644
--- a/client/cmd/status.go
+++ b/client/cmd/status.go
@@ -26,6 +26,7 @@ var (
 	statusFilter         string
 	ipsFilterMap         map[string]struct{}
 	prefixNamesFilterMap map[string]struct{}
+	connectionTypeFilter string
 )
 
 var statusCmd = &cobra.Command{
@@ -45,6 +46,7 @@ func init() {
 	statusCmd.PersistentFlags().StringSliceVar(&ipsFilter, "filter-by-ips", []string{}, "filters the detailed output by a list of one or more IPs, e.g., --filter-by-ips 100.64.0.100,100.64.0.200")
 	statusCmd.PersistentFlags().StringSliceVar(&prefixNamesFilter, "filter-by-names", []string{}, "filters the detailed output by a list of one or more peer FQDN or hostnames, e.g., --filter-by-names peer-a,peer-b.netbird.cloud")
 	statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(idle|connecting|connected), e.g., --filter-by-status connected")
+	statusCmd.PersistentFlags().StringVar(&connectionTypeFilter, "filter-by-connection-type", "", "filters the detailed output by connection type (P2P|Relayed), e.g., --filter-by-connection-type P2P")
 }
 
 func statusFunc(cmd *cobra.Command, args []string) error {
@@ -89,7 +91,7 @@ func statusFunc(cmd *cobra.Command, args []string) error {
 		return nil
 	}
 
-	var outputInformationHolder = nbstatus.ConvertToStatusOutputOverview(resp, anonymizeFlag, statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilterMap)
+	var outputInformationHolder = nbstatus.ConvertToStatusOutputOverview(resp, anonymizeFlag, statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilterMap, connectionTypeFilter)
 	var statusOutputString string
 	switch {
 	case detailFlag:
@@ -120,7 +122,7 @@ func getStatus(ctx context.Context) (*proto.StatusResponse, error) {
 	}
 	defer conn.Close()
 
-	resp, err := proto.NewDaemonServiceClient(conn).Status(ctx, &proto.StatusRequest{GetFullPeerStatus: true})
+	resp, err := proto.NewDaemonServiceClient(conn).Status(ctx, &proto.StatusRequest{GetFullPeerStatus: true, ShouldRunProbes: true})
 	if err != nil {
 		return nil, fmt.Errorf("status failed: %v", status.Convert(err).Message())
 	}
@@ -156,6 +158,15 @@ func parseFilters() error {
 		enableDetailFlagWhenFilterFlag()
 	}
 
+	switch strings.ToLower(connectionTypeFilter) {
+	case "", "p2p", "relayed":
+		if strings.ToLower(connectionTypeFilter) != "" {
+			enableDetailFlagWhenFilterFlag()
+		}
+	default:
+		return fmt.Errorf("wrong connection-type filter, should be one of P2P|Relayed, got: %s", connectionTypeFilter)
+	}
+
 	return nil
 }
 
diff --git a/client/firewall/uspfilter/uspfilter.go b/client/firewall/uspfilter/filter.go
similarity index 96%
rename from client/firewall/uspfilter/uspfilter.go
rename to client/firewall/uspfilter/filter.go
index dcff92c6..7120d7d6 100644
--- a/client/firewall/uspfilter/uspfilter.go
+++ b/client/firewall/uspfilter/filter.go
@@ -104,6 +104,12 @@ type Manager struct {
 	flowLogger  nftypes.FlowLogger
 
 	blockRule firewall.Rule
+
+	// Internal 1:1 DNAT
+	dnatEnabled  atomic.Bool
+	dnatMappings map[netip.Addr]netip.Addr
+	dnatMutex    sync.RWMutex
+	dnatBiMap    *biDNATMap
 }
 
 // decoder for packages
@@ -189,6 +195,7 @@ func create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
 		flowLogger:          flowLogger,
 		netstack:            netstack.IsEnabled(),
 		localForwarding:     enableLocalForwarding,
+		dnatMappings:        make(map[netip.Addr]netip.Addr),
 	}
 	m.routingEnabled.Store(false)
 
@@ -519,22 +526,6 @@ func (m *Manager) SetLegacyManagement(isLegacy bool) error {
 // Flush doesn't need to be implemented for this manager
 func (m *Manager) Flush() error { return nil }
 
-// AddDNATRule adds a DNAT rule
-func (m *Manager) AddDNATRule(rule firewall.ForwardRule) (firewall.Rule, error) {
-	if m.nativeFirewall == nil {
-		return nil, errNatNotSupported
-	}
-	return m.nativeFirewall.AddDNATRule(rule)
-}
-
-// DeleteDNATRule deletes a DNAT rule
-func (m *Manager) DeleteDNATRule(rule firewall.Rule) error {
-	if m.nativeFirewall == nil {
-		return errNatNotSupported
-	}
-	return m.nativeFirewall.DeleteDNATRule(rule)
-}
-
 // UpdateSet updates the rule destinations associated with the given set
 // by merging the existing prefixes with the new ones, then deduplicating.
 func (m *Manager) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {
@@ -581,14 +572,14 @@ func (m *Manager) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {
 	return nil
 }
 
-// DropOutgoing filter outgoing packets
-func (m *Manager) DropOutgoing(packetData []byte, size int) bool {
-	return m.processOutgoingHooks(packetData, size)
+// FilterOutBound filters outgoing packets
+func (m *Manager) FilterOutbound(packetData []byte, size int) bool {
+	return m.filterOutbound(packetData, size)
 }
 
-// DropIncoming filter incoming packets
-func (m *Manager) DropIncoming(packetData []byte, size int) bool {
-	return m.dropFilter(packetData, size)
+// FilterInbound filters incoming packets
+func (m *Manager) FilterInbound(packetData []byte, size int) bool {
+	return m.filterInbound(packetData, size)
 }
 
 // UpdateLocalIPs updates the list of local IPs
@@ -596,7 +587,7 @@ func (m *Manager) UpdateLocalIPs() error {
 	return m.localipmanager.UpdateLocalIPs(m.wgIface)
 }
 
-func (m *Manager) processOutgoingHooks(packetData []byte, size int) bool {
+func (m *Manager) filterOutbound(packetData []byte, size int) bool {
 	d := m.decoders.Get().(*decoder)
 	defer m.decoders.Put(d)
 
@@ -618,8 +609,8 @@ func (m *Manager) processOutgoingHooks(packetData []byte, size int) bool {
 		return true
 	}
 
-	// for netflow we keep track even if the firewall is stateless
 	m.trackOutbound(d, srcIP, dstIP, size)
+	m.translateOutboundDNAT(packetData, d)
 
 	return false
 }
@@ -723,9 +714,9 @@ func (m *Manager) udpHooksDrop(dport uint16, dstIP netip.Addr, packetData []byte
 	return false
 }
 
-// dropFilter implements filtering logic for incoming packets.
+// filterInbound implements filtering logic for incoming packets.
 // If it returns true, the packet should be dropped.
-func (m *Manager) dropFilter(packetData []byte, size int) bool {
+func (m *Manager) filterInbound(packetData []byte, size int) bool {
 	d := m.decoders.Get().(*decoder)
 	defer m.decoders.Put(d)
 
@@ -747,8 +738,15 @@ func (m *Manager) dropFilter(packetData []byte, size int) bool {
 		return false
 	}
 
-	// For all inbound traffic, first check if it matches a tracked connection.
-	// This must happen before any other filtering because the packets are statefully tracked.
+	if translated := m.translateInboundReverse(packetData, d); translated {
+		// Re-decode after translation to get original addresses
+		if err := d.parser.DecodeLayers(packetData, &d.decoded); err != nil {
+			m.logger.Error("Failed to re-decode packet after reverse DNAT: %v", err)
+			return true
+		}
+		srcIP, dstIP = m.extractIPs(d)
+	}
+
 	if m.stateful && m.isValidTrackedConnection(d, srcIP, dstIP, size) {
 		return false
 	}
diff --git a/client/firewall/uspfilter/nat.go b/client/firewall/uspfilter/nat.go
new file mode 100644
index 00000000..4539f7da
--- /dev/null
+++ b/client/firewall/uspfilter/nat.go
@@ -0,0 +1,408 @@
+package uspfilter
+
+import (
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"net/netip"
+
+	"github.com/google/gopacket/layers"
+
+	firewall "github.com/netbirdio/netbird/client/firewall/manager"
+)
+
+var ErrIPv4Only = errors.New("only IPv4 is supported for DNAT")
+
+func ipv4Checksum(header []byte) uint16 {
+	if len(header) < 20 {
+		return 0
+	}
+
+	var sum1, sum2 uint32
+
+	// Parallel processing - unroll and compute two sums simultaneously
+	sum1 += uint32(binary.BigEndian.Uint16(header[0:2]))
+	sum2 += uint32(binary.BigEndian.Uint16(header[2:4]))
+	sum1 += uint32(binary.BigEndian.Uint16(header[4:6]))
+	sum2 += uint32(binary.BigEndian.Uint16(header[6:8]))
+	sum1 += uint32(binary.BigEndian.Uint16(header[8:10]))
+	// Skip checksum field at [10:12]
+	sum2 += uint32(binary.BigEndian.Uint16(header[12:14]))
+	sum1 += uint32(binary.BigEndian.Uint16(header[14:16]))
+	sum2 += uint32(binary.BigEndian.Uint16(header[16:18]))
+	sum1 += uint32(binary.BigEndian.Uint16(header[18:20]))
+
+	sum := sum1 + sum2
+
+	// Handle remaining bytes for headers > 20 bytes
+	for i := 20; i < len(header)-1; i += 2 {
+		sum += uint32(binary.BigEndian.Uint16(header[i : i+2]))
+	}
+
+	if len(header)%2 == 1 {
+		sum += uint32(header[len(header)-1]) << 8
+	}
+
+	// Optimized carry fold - single iteration handles most cases
+	sum = (sum & 0xFFFF) + (sum >> 16)
+	if sum > 0xFFFF {
+		sum++
+	}
+
+	return ^uint16(sum)
+}
+
+func icmpChecksum(data []byte) uint16 {
+	var sum1, sum2, sum3, sum4 uint32
+	i := 0
+
+	// Process 16 bytes at once with 4 parallel accumulators
+	for i <= len(data)-16 {
+		sum1 += uint32(binary.BigEndian.Uint16(data[i : i+2]))
+		sum2 += uint32(binary.BigEndian.Uint16(data[i+2 : i+4]))
+		sum3 += uint32(binary.BigEndian.Uint16(data[i+4 : i+6]))
+		sum4 += uint32(binary.BigEndian.Uint16(data[i+6 : i+8]))
+		sum1 += uint32(binary.BigEndian.Uint16(data[i+8 : i+10]))
+		sum2 += uint32(binary.BigEndian.Uint16(data[i+10 : i+12]))
+		sum3 += uint32(binary.BigEndian.Uint16(data[i+12 : i+14]))
+		sum4 += uint32(binary.BigEndian.Uint16(data[i+14 : i+16]))
+		i += 16
+	}
+
+	sum := sum1 + sum2 + sum3 + sum4
+
+	// Handle remaining bytes
+	for i < len(data)-1 {
+		sum += uint32(binary.BigEndian.Uint16(data[i : i+2]))
+		i += 2
+	}
+
+	if len(data)%2 == 1 {
+		sum += uint32(data[len(data)-1]) << 8
+	}
+
+	sum = (sum & 0xFFFF) + (sum >> 16)
+	if sum > 0xFFFF {
+		sum++
+	}
+
+	return ^uint16(sum)
+}
+
+type biDNATMap struct {
+	forward map[netip.Addr]netip.Addr
+	reverse map[netip.Addr]netip.Addr
+}
+
+func newBiDNATMap() *biDNATMap {
+	return &biDNATMap{
+		forward: make(map[netip.Addr]netip.Addr),
+		reverse: make(map[netip.Addr]netip.Addr),
+	}
+}
+
+func (b *biDNATMap) set(original, translated netip.Addr) {
+	b.forward[original] = translated
+	b.reverse[translated] = original
+}
+
+func (b *biDNATMap) delete(original netip.Addr) {
+	if translated, exists := b.forward[original]; exists {
+		delete(b.forward, original)
+		delete(b.reverse, translated)
+	}
+}
+
+func (b *biDNATMap) getTranslated(original netip.Addr) (netip.Addr, bool) {
+	translated, exists := b.forward[original]
+	return translated, exists
+}
+
+func (b *biDNATMap) getOriginal(translated netip.Addr) (netip.Addr, bool) {
+	original, exists := b.reverse[translated]
+	return original, exists
+}
+
+func (m *Manager) AddInternalDNATMapping(originalAddr, translatedAddr netip.Addr) error {
+	if !originalAddr.IsValid() || !translatedAddr.IsValid() {
+		return fmt.Errorf("invalid IP addresses")
+	}
+
+	if m.localipmanager.IsLocalIP(translatedAddr) {
+		return fmt.Errorf("cannot map to local IP: %s", translatedAddr)
+	}
+
+	m.dnatMutex.Lock()
+	defer m.dnatMutex.Unlock()
+
+	// Initialize both maps together if either is nil
+	if m.dnatMappings == nil || m.dnatBiMap == nil {
+		m.dnatMappings = make(map[netip.Addr]netip.Addr)
+		m.dnatBiMap = newBiDNATMap()
+	}
+
+	m.dnatMappings[originalAddr] = translatedAddr
+	m.dnatBiMap.set(originalAddr, translatedAddr)
+
+	if len(m.dnatMappings) == 1 {
+		m.dnatEnabled.Store(true)
+	}
+
+	return nil
+}
+
+// RemoveInternalDNATMapping removes a 1:1 IP address mapping
+func (m *Manager) RemoveInternalDNATMapping(originalAddr netip.Addr) error {
+	m.dnatMutex.Lock()
+	defer m.dnatMutex.Unlock()
+
+	if _, exists := m.dnatMappings[originalAddr]; !exists {
+		return fmt.Errorf("mapping not found for: %s", originalAddr)
+	}
+
+	delete(m.dnatMappings, originalAddr)
+	m.dnatBiMap.delete(originalAddr)
+	if len(m.dnatMappings) == 0 {
+		m.dnatEnabled.Store(false)
+	}
+
+	return nil
+}
+
+// getDNATTranslation returns the translated address if a mapping exists
+func (m *Manager) getDNATTranslation(addr netip.Addr) (netip.Addr, bool) {
+	if !m.dnatEnabled.Load() {
+		return addr, false
+	}
+
+	m.dnatMutex.RLock()
+	translated, exists := m.dnatBiMap.getTranslated(addr)
+	m.dnatMutex.RUnlock()
+	return translated, exists
+}
+
+// findReverseDNATMapping finds original address for return traffic
+func (m *Manager) findReverseDNATMapping(translatedAddr netip.Addr) (netip.Addr, bool) {
+	if !m.dnatEnabled.Load() {
+		return translatedAddr, false
+	}
+
+	m.dnatMutex.RLock()
+	original, exists := m.dnatBiMap.getOriginal(translatedAddr)
+	m.dnatMutex.RUnlock()
+	return original, exists
+}
+
+// translateOutboundDNAT applies DNAT translation to outbound packets
+func (m *Manager) translateOutboundDNAT(packetData []byte, d *decoder) bool {
+	if !m.dnatEnabled.Load() {
+		return false
+	}
+
+	if len(packetData) < 20 || d.decoded[0] != layers.LayerTypeIPv4 {
+		return false
+	}
+
+	dstIP := netip.AddrFrom4([4]byte{packetData[16], packetData[17], packetData[18], packetData[19]})
+
+	translatedIP, exists := m.getDNATTranslation(dstIP)
+	if !exists {
+		return false
+	}
+
+	if err := m.rewritePacketDestination(packetData, d, translatedIP); err != nil {
+		m.logger.Error("Failed to rewrite packet destination: %v", err)
+		return false
+	}
+
+	m.logger.Trace("DNAT: %s -> %s", dstIP, translatedIP)
+	return true
+}
+
+// translateInboundReverse applies reverse DNAT to inbound return traffic
+func (m *Manager) translateInboundReverse(packetData []byte, d *decoder) bool {
+	if !m.dnatEnabled.Load() {
+		return false
+	}
+
+	if len(packetData) < 20 || d.decoded[0] != layers.LayerTypeIPv4 {
+		return false
+	}
+
+	srcIP := netip.AddrFrom4([4]byte{packetData[12], packetData[13], packetData[14], packetData[15]})
+
+	originalIP, exists := m.findReverseDNATMapping(srcIP)
+	if !exists {
+		return false
+	}
+
+	if err := m.rewritePacketSource(packetData, d, originalIP); err != nil {
+		m.logger.Error("Failed to rewrite packet source: %v", err)
+		return false
+	}
+
+	m.logger.Trace("Reverse DNAT: %s -> %s", srcIP, originalIP)
+	return true
+}
+
+// rewritePacketDestination replaces destination IP in the packet
+func (m *Manager) rewritePacketDestination(packetData []byte, d *decoder, newIP netip.Addr) error {
+	if len(packetData) < 20 || d.decoded[0] != layers.LayerTypeIPv4 || !newIP.Is4() {
+		return ErrIPv4Only
+	}
+
+	var oldDst [4]byte
+	copy(oldDst[:], packetData[16:20])
+	newDst := newIP.As4()
+
+	copy(packetData[16:20], newDst[:])
+
+	ipHeaderLen := int(d.ip4.IHL) * 4
+	if ipHeaderLen < 20 || ipHeaderLen > len(packetData) {
+		return fmt.Errorf("invalid IP header length")
+	}
+
+	binary.BigEndian.PutUint16(packetData[10:12], 0)
+	ipChecksum := ipv4Checksum(packetData[:ipHeaderLen])
+	binary.BigEndian.PutUint16(packetData[10:12], ipChecksum)
+
+	if len(d.decoded) > 1 {
+		switch d.decoded[1] {
+		case layers.LayerTypeTCP:
+			m.updateTCPChecksum(packetData, ipHeaderLen, oldDst[:], newDst[:])
+		case layers.LayerTypeUDP:
+			m.updateUDPChecksum(packetData, ipHeaderLen, oldDst[:], newDst[:])
+		case layers.LayerTypeICMPv4:
+			m.updateICMPChecksum(packetData, ipHeaderLen)
+		}
+	}
+
+	return nil
+}
+
+// rewritePacketSource replaces the source IP address in the packet
+func (m *Manager) rewritePacketSource(packetData []byte, d *decoder, newIP netip.Addr) error {
+	if len(packetData) < 20 || d.decoded[0] != layers.LayerTypeIPv4 || !newIP.Is4() {
+		return ErrIPv4Only
+	}
+
+	var oldSrc [4]byte
+	copy(oldSrc[:], packetData[12:16])
+	newSrc := newIP.As4()
+
+	copy(packetData[12:16], newSrc[:])
+
+	ipHeaderLen := int(d.ip4.IHL) * 4
+	if ipHeaderLen < 20 || ipHeaderLen > len(packetData) {
+		return fmt.Errorf("invalid IP header length")
+	}
+
+	binary.BigEndian.PutUint16(packetData[10:12], 0)
+	ipChecksum := ipv4Checksum(packetData[:ipHeaderLen])
+	binary.BigEndian.PutUint16(packetData[10:12], ipChecksum)
+
+	if len(d.decoded) > 1 {
+		switch d.decoded[1] {
+		case layers.LayerTypeTCP:
+			m.updateTCPChecksum(packetData, ipHeaderLen, oldSrc[:], newSrc[:])
+		case layers.LayerTypeUDP:
+			m.updateUDPChecksum(packetData, ipHeaderLen, oldSrc[:], newSrc[:])
+		case layers.LayerTypeICMPv4:
+			m.updateICMPChecksum(packetData, ipHeaderLen)
+		}
+	}
+
+	return nil
+}
+
+func (m *Manager) updateTCPChecksum(packetData []byte, ipHeaderLen int, oldIP, newIP []byte) {
+	tcpStart := ipHeaderLen
+	if len(packetData) < tcpStart+18 {
+		return
+	}
+
+	checksumOffset := tcpStart + 16
+	oldChecksum := binary.BigEndian.Uint16(packetData[checksumOffset : checksumOffset+2])
+	newChecksum := incrementalUpdate(oldChecksum, oldIP, newIP)
+	binary.BigEndian.PutUint16(packetData[checksumOffset:checksumOffset+2], newChecksum)
+}
+
+func (m *Manager) updateUDPChecksum(packetData []byte, ipHeaderLen int, oldIP, newIP []byte) {
+	udpStart := ipHeaderLen
+	if len(packetData) < udpStart+8 {
+		return
+	}
+
+	checksumOffset := udpStart + 6
+	oldChecksum := binary.BigEndian.Uint16(packetData[checksumOffset : checksumOffset+2])
+
+	if oldChecksum == 0 {
+		return
+	}
+
+	newChecksum := incrementalUpdate(oldChecksum, oldIP, newIP)
+	binary.BigEndian.PutUint16(packetData[checksumOffset:checksumOffset+2], newChecksum)
+}
+
+func (m *Manager) updateICMPChecksum(packetData []byte, ipHeaderLen int) {
+	icmpStart := ipHeaderLen
+	if len(packetData) < icmpStart+8 {
+		return
+	}
+
+	icmpData := packetData[icmpStart:]
+	binary.BigEndian.PutUint16(icmpData[2:4], 0)
+	checksum := icmpChecksum(icmpData)
+	binary.BigEndian.PutUint16(icmpData[2:4], checksum)
+}
+
+// incrementalUpdate performs incremental checksum update per RFC 1624
+func incrementalUpdate(oldChecksum uint16, oldBytes, newBytes []byte) uint16 {
+	sum := uint32(^oldChecksum)
+
+	// Fast path for IPv4 addresses (4 bytes) - most common case
+	if len(oldBytes) == 4 && len(newBytes) == 4 {
+		sum += uint32(^binary.BigEndian.Uint16(oldBytes[0:2]))
+		sum += uint32(^binary.BigEndian.Uint16(oldBytes[2:4]))
+		sum += uint32(binary.BigEndian.Uint16(newBytes[0:2]))
+		sum += uint32(binary.BigEndian.Uint16(newBytes[2:4]))
+	} else {
+		// Fallback for other lengths
+		for i := 0; i < len(oldBytes)-1; i += 2 {
+			sum += uint32(^binary.BigEndian.Uint16(oldBytes[i : i+2]))
+		}
+		if len(oldBytes)%2 == 1 {
+			sum += uint32(^oldBytes[len(oldBytes)-1]) << 8
+		}
+
+		for i := 0; i < len(newBytes)-1; i += 2 {
+			sum += uint32(binary.BigEndian.Uint16(newBytes[i : i+2]))
+		}
+		if len(newBytes)%2 == 1 {
+			sum += uint32(newBytes[len(newBytes)-1]) << 8
+		}
+	}
+
+	sum = (sum & 0xFFFF) + (sum >> 16)
+	if sum > 0xFFFF {
+		sum++
+	}
+
+	return ^uint16(sum)
+}
+
+// AddDNATRule adds a DNAT rule (delegates to native firewall for port forwarding)
+func (m *Manager) AddDNATRule(rule firewall.ForwardRule) (firewall.Rule, error) {
+	if m.nativeFirewall == nil {
+		return nil, errNatNotSupported
+	}
+	return m.nativeFirewall.AddDNATRule(rule)
+}
+
+// DeleteDNATRule deletes a DNAT rule (delegates to native firewall)
+func (m *Manager) DeleteDNATRule(rule firewall.Rule) error {
+	if m.nativeFirewall == nil {
+		return errNatNotSupported
+	}
+	return m.nativeFirewall.DeleteDNATRule(rule)
+}
diff --git a/client/firewall/uspfilter/tracer.go b/client/firewall/uspfilter/tracer.go
index 53350797..ef04f270 100644
--- a/client/firewall/uspfilter/tracer.go
+++ b/client/firewall/uspfilter/tracer.go
@@ -401,7 +401,7 @@ func (m *Manager) addForwardingResult(trace *PacketTrace, action, remoteAddr str
 
 func (m *Manager) traceOutbound(packetData []byte, trace *PacketTrace) *PacketTrace {
 	// will create or update the connection state
-	dropped := m.processOutgoingHooks(packetData, 0)
+	dropped := m.filterOutbound(packetData, 0)
 	if dropped {
 		trace.AddResult(StageCompleted, "Packet dropped by outgoing hook", false)
 	} else {
diff --git a/client/iface/bind/activity.go b/client/iface/bind/activity.go
new file mode 100644
index 00000000..57862e3d
--- /dev/null
+++ b/client/iface/bind/activity.go
@@ -0,0 +1,96 @@
+package bind
+
+import (
+	"net/netip"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	log "github.com/sirupsen/logrus"
+
+	"github.com/netbirdio/netbird/monotime"
+)
+
+const (
+	saveFrequency = int64(5 * time.Second)
+)
+
+type PeerRecord struct {
+	Address      netip.AddrPort
+	LastActivity atomic.Int64 // UnixNano timestamp
+}
+
+type ActivityRecorder struct {
+	mu         sync.RWMutex
+	peers      map[string]*PeerRecord         // publicKey to PeerRecord map
+	addrToPeer map[netip.AddrPort]*PeerRecord // address to PeerRecord map
+}
+
+func NewActivityRecorder() *ActivityRecorder {
+	return &ActivityRecorder{
+		peers:      make(map[string]*PeerRecord),
+		addrToPeer: make(map[netip.AddrPort]*PeerRecord),
+	}
+}
+
+// GetLastActivities returns a snapshot of peer last activity
+func (r *ActivityRecorder) GetLastActivities() map[string]monotime.Time {
+	r.mu.RLock()
+	defer r.mu.RUnlock()
+
+	activities := make(map[string]monotime.Time, len(r.peers))
+	for key, record := range r.peers {
+		monoTime := record.LastActivity.Load()
+		activities[key] = monotime.Time(monoTime)
+	}
+	return activities
+}
+
+// UpsertAddress adds or updates the address for a publicKey
+func (r *ActivityRecorder) UpsertAddress(publicKey string, address netip.AddrPort) {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	var record *PeerRecord
+	record, exists := r.peers[publicKey]
+	if exists {
+		delete(r.addrToPeer, record.Address)
+		record.Address = address
+	} else {
+		record = &PeerRecord{
+			Address: address,
+		}
+		record.LastActivity.Store(int64(monotime.Now()))
+		r.peers[publicKey] = record
+	}
+
+	r.addrToPeer[address] = record
+}
+
+func (r *ActivityRecorder) Remove(publicKey string) {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	if record, exists := r.peers[publicKey]; exists {
+		delete(r.addrToPeer, record.Address)
+		delete(r.peers, publicKey)
+	}
+}
+
+// record updates LastActivity for the given address using atomic store
+func (r *ActivityRecorder) record(address netip.AddrPort) {
+	r.mu.RLock()
+	record, ok := r.addrToPeer[address]
+	r.mu.RUnlock()
+	if !ok {
+		log.Warnf("could not find record for address %s", address)
+		return
+	}
+
+	now := int64(monotime.Now())
+	last := record.LastActivity.Load()
+	if now-last < saveFrequency {
+		return
+	}
+
+	_ = record.LastActivity.CompareAndSwap(last, now)
+}
diff --git a/client/iface/bind/control.go b/client/iface/bind/control.go
new file mode 100644
index 00000000..89bddf12
--- /dev/null
+++ b/client/iface/bind/control.go
@@ -0,0 +1,15 @@
+package bind
+
+import (
+	wireguard "golang.zx2c4.com/wireguard/conn"
+
+	nbnet "github.com/netbirdio/netbird/util/net"
+)
+
+// TODO: This is most likely obsolete since the control fns should be called by the wrapped udpconn (ice_bind.go)
+func init() {
+	listener := nbnet.NewListener()
+	if listener.ListenConfig.Control != nil {
+		*wireguard.ControlFns = append(*wireguard.ControlFns, listener.ListenConfig.Control)
+	}
+}
diff --git a/client/iface/bind/control_android.go b/client/iface/bind/control_android.go
deleted file mode 100644
index b8a865e3..00000000
--- a/client/iface/bind/control_android.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package bind
-
-import (
-	wireguard "golang.zx2c4.com/wireguard/conn"
-
-	nbnet "github.com/netbirdio/netbird/util/net"
-)
-
-func init() {
-	// ControlFns is not thread safe and should only be modified during init.
-	*wireguard.ControlFns = append(*wireguard.ControlFns, nbnet.ControlProtectSocket)
-}
diff --git a/client/iface/bind/ice_bind.go b/client/iface/bind/ice_bind.go
index 66ec6a00..c3d5ef37 100644
--- a/client/iface/bind/ice_bind.go
+++ b/client/iface/bind/ice_bind.go
@@ -1,6 +1,7 @@
 package bind
 
 import (
+	"encoding/binary"
 	"fmt"
 	"net"
 	"net/netip"
@@ -15,6 +16,7 @@ import (
 	wgConn "golang.zx2c4.com/wireguard/conn"
 
 	"github.com/netbirdio/netbird/client/iface/wgaddr"
+	nbnet "github.com/netbirdio/netbird/util/net"
 )
 
 type RecvMessage struct {
@@ -51,22 +53,24 @@ type ICEBind struct {
 	closedChanMu sync.RWMutex // protect the closeChan recreation from reading from it.
 	closed       bool
 
-	muUDPMux sync.Mutex
-	udpMux   *UniversalUDPMuxDefault
-	address  wgaddr.Address
+	muUDPMux         sync.Mutex
+	udpMux           *UniversalUDPMuxDefault
+	address          wgaddr.Address
+	activityRecorder *ActivityRecorder
 }
 
 func NewICEBind(transportNet transport.Net, filterFn FilterFn, address wgaddr.Address) *ICEBind {
 	b, _ := wgConn.NewStdNetBind().(*wgConn.StdNetBind)
 	ib := &ICEBind{
-		StdNetBind:   b,
-		RecvChan:     make(chan RecvMessage, 1),
-		transportNet: transportNet,
-		filterFn:     filterFn,
-		endpoints:    make(map[netip.Addr]net.Conn),
-		closedChan:   make(chan struct{}),
-		closed:       true,
-		address:      address,
+		StdNetBind:       b,
+		RecvChan:         make(chan RecvMessage, 1),
+		transportNet:     transportNet,
+		filterFn:         filterFn,
+		endpoints:        make(map[netip.Addr]net.Conn),
+		closedChan:       make(chan struct{}),
+		closed:           true,
+		address:          address,
+		activityRecorder: NewActivityRecorder(),
 	}
 
 	rc := receiverCreator{
@@ -100,6 +104,10 @@ func (s *ICEBind) Close() error {
 	return s.StdNetBind.Close()
 }
 
+func (s *ICEBind) ActivityRecorder() *ActivityRecorder {
+	return s.activityRecorder
+}
+
 // GetICEMux returns the ICE UDPMux that was created and used by ICEBind
 func (s *ICEBind) GetICEMux() (*UniversalUDPMuxDefault, error) {
 	s.muUDPMux.Lock()
@@ -146,7 +154,7 @@ func (s *ICEBind) createIPv4ReceiverFn(pc *ipv4.PacketConn, conn *net.UDPConn, r
 
 	s.udpMux = NewUniversalUDPMuxDefault(
 		UniversalUDPMuxParams{
-			UDPConn:   conn,
+			UDPConn:   nbnet.WrapUDPConn(conn),
 			Net:       s.transportNet,
 			FilterFn:  s.filterFn,
 			WGAddress: s.address,
@@ -199,6 +207,11 @@ func (s *ICEBind) createIPv4ReceiverFn(pc *ipv4.PacketConn, conn *net.UDPConn, r
 				continue
 			}
 			addrPort := msg.Addr.(*net.UDPAddr).AddrPort()
+
+			if isTransportPkg(msg.Buffers, msg.N) {
+				s.activityRecorder.record(addrPort)
+			}
+
 			ep := &wgConn.StdNetEndpoint{AddrPort: addrPort} // TODO: remove allocation
 			wgConn.GetSrcFromControl(msg.OOB[:msg.NN], ep)
 			eps[i] = ep
@@ -257,6 +270,13 @@ func (c *ICEBind) receiveRelayed(buffs [][]byte, sizes []int, eps []wgConn.Endpo
 		copy(buffs[0], msg.Buffer)
 		sizes[0] = len(msg.Buffer)
 		eps[0] = wgConn.Endpoint(msg.Endpoint)
+
+		if isTransportPkg(buffs, sizes[0]) {
+			if ep, ok := eps[0].(*Endpoint); ok {
+				c.activityRecorder.record(ep.AddrPort)
+			}
+		}
+
 		return 1, nil
 	}
 }
@@ -272,3 +292,19 @@ func putMessages(msgs *[]ipv6.Message, msgsPool *sync.Pool) {
 	}
 	msgsPool.Put(msgs)
 }
+
+func isTransportPkg(buffers [][]byte, n int) bool {
+	// The first buffer should contain at least 4 bytes for type
+	if len(buffers[0]) < 4 {
+		return true
+	}
+
+	// WireGuard packet type is a little-endian uint32 at start
+	packetType := binary.LittleEndian.Uint32(buffers[0][:4])
+
+	// Check if packetType matches known WireGuard message types
+	if packetType == 4 && n > 32 {
+		return true
+	}
+	return false
+}
diff --git a/client/iface/bind/udp_mux.go b/client/iface/bind/udp_mux.go
index 0e58499a..29e5d793 100644
--- a/client/iface/bind/udp_mux.go
+++ b/client/iface/bind/udp_mux.go
@@ -296,14 +296,20 @@ func (m *UDPMuxDefault) RemoveConnByUfrag(ufrag string) {
 		return
 	}
 
-	m.addressMapMu.Lock()
-	defer m.addressMapMu.Unlock()
-
+	var allAddresses []string
 	for _, c := range removedConns {
 		addresses := c.getAddresses()
-		for _, addr := range addresses {
-			delete(m.addressMap, addr)
-		}
+		allAddresses = append(allAddresses, addresses...)
+	}
+
+	m.addressMapMu.Lock()
+	for _, addr := range allAddresses {
+		delete(m.addressMap, addr)
+	}
+	m.addressMapMu.Unlock()
+
+	for _, addr := range allAddresses {
+		m.notifyAddressRemoval(addr)
 	}
 }
 
@@ -351,14 +357,13 @@ func (m *UDPMuxDefault) registerConnForAddress(conn *udpMuxedConn, addr string)
 	}
 
 	m.addressMapMu.Lock()
-	defer m.addressMapMu.Unlock()
-
 	existing, ok := m.addressMap[addr]
 	if !ok {
 		existing = []*udpMuxedConn{}
 	}
 	existing = append(existing, conn)
 	m.addressMap[addr] = existing
+	m.addressMapMu.Unlock()
 
 	log.Debugf("ICE: registered %s for %s", addr, conn.params.Key)
 }
@@ -386,12 +391,12 @@ func (m *UDPMuxDefault) HandleSTUNMessage(msg *stun.Message, addr net.Addr) erro
 	// If you are using the same socket for the Host and SRFLX candidates, it might be that there are more than one
 	// muxed connection - one for the SRFLX candidate and the other one for the HOST one.
 	// We will then forward STUN packets to each of these connections.
-	m.addressMapMu.Lock()
+	m.addressMapMu.RLock()
 	var destinationConnList []*udpMuxedConn
 	if storedConns, ok := m.addressMap[addr.String()]; ok {
 		destinationConnList = append(destinationConnList, storedConns...)
 	}
-	m.addressMapMu.Unlock()
+	m.addressMapMu.RUnlock()
 
 	var isIPv6 bool
 	if udpAddr, _ := addr.(*net.UDPAddr); udpAddr != nil && udpAddr.IP.To4() == nil {
diff --git a/client/iface/bind/udp_mux_generic.go b/client/iface/bind/udp_mux_generic.go
new file mode 100644
index 00000000..e42d2546
--- /dev/null
+++ b/client/iface/bind/udp_mux_generic.go
@@ -0,0 +1,21 @@
+//go:build !ios
+
+package bind
+
+import (
+	nbnet "github.com/netbirdio/netbird/util/net"
+)
+
+func (m *UDPMuxDefault) notifyAddressRemoval(addr string) {
+	wrapped, ok := m.params.UDPConn.(*UDPConn)
+	if !ok {
+		return
+	}
+
+	nbnetConn, ok := wrapped.GetPacketConn().(*nbnet.UDPConn)
+	if !ok {
+		return
+	}
+
+	nbnetConn.RemoveAddress(addr)
+}
diff --git a/client/iface/bind/udp_mux_ios.go b/client/iface/bind/udp_mux_ios.go
new file mode 100644
index 00000000..15e26d02
--- /dev/null
+++ b/client/iface/bind/udp_mux_ios.go
@@ -0,0 +1,7 @@
+//go:build ios
+
+package bind
+
+func (m *UDPMuxDefault) notifyAddressRemoval(addr string) {
+	// iOS doesn't support nbnet hooks, so this is a no-op
+}
\ No newline at end of file
diff --git a/client/iface/bind/udp_mux_universal.go b/client/iface/bind/udp_mux_universal.go
index 5cc63495..b755a782 100644
--- a/client/iface/bind/udp_mux_universal.go
+++ b/client/iface/bind/udp_mux_universal.go
@@ -62,7 +62,7 @@ func NewUniversalUDPMuxDefault(params UniversalUDPMuxParams) *UniversalUDPMuxDef
 
 	// wrap UDP connection, process server reflexive messages
 	// before they are passed to the UDPMux connection handler (connWorker)
-	m.params.UDPConn = &udpConn{
+	m.params.UDPConn = &UDPConn{
 		PacketConn: params.UDPConn,
 		mux:        m,
 		logger:     params.Logger,
@@ -70,7 +70,6 @@ func NewUniversalUDPMuxDefault(params UniversalUDPMuxParams) *UniversalUDPMuxDef
 		address:    params.WGAddress,
 	}
 
-	// embed UDPMux
 	udpMuxParams := UDPMuxParams{
 		Logger:  params.Logger,
 		UDPConn: m.params.UDPConn,
@@ -114,8 +113,8 @@ func (m *UniversalUDPMuxDefault) ReadFromConn(ctx context.Context) {
 	}
 }
 
-// udpConn is a wrapper around UDPMux conn that overrides ReadFrom and handles STUN/TURN packets
-type udpConn struct {
+// UDPConn is a wrapper around UDPMux conn that overrides ReadFrom and handles STUN/TURN packets
+type UDPConn struct {
 	net.PacketConn
 	mux      *UniversalUDPMuxDefault
 	logger   logging.LeveledLogger
@@ -125,7 +124,12 @@ type udpConn struct {
 	address   wgaddr.Address
 }
 
-func (u *udpConn) WriteTo(b []byte, addr net.Addr) (int, error) {
+// GetPacketConn returns the underlying PacketConn
+func (u *UDPConn) GetPacketConn() net.PacketConn {
+	return u.PacketConn
+}
+
+func (u *UDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
 	if u.filterFn == nil {
 		return u.PacketConn.WriteTo(b, addr)
 	}
@@ -137,21 +141,21 @@ func (u *udpConn) WriteTo(b []byte, addr net.Addr) (int, error) {
 	return u.handleUncachedAddress(b, addr)
 }
 
-func (u *udpConn) handleCachedAddress(isRouted bool, b []byte, addr net.Addr) (int, error) {
+func (u *UDPConn) handleCachedAddress(isRouted bool, b []byte, addr net.Addr) (int, error) {
 	if isRouted {
 		return 0, fmt.Errorf("address %s is part of a routed network, refusing to write", addr)
 	}
 	return u.PacketConn.WriteTo(b, addr)
 }
 
-func (u *udpConn) handleUncachedAddress(b []byte, addr net.Addr) (int, error) {
+func (u *UDPConn) handleUncachedAddress(b []byte, addr net.Addr) (int, error) {
 	if err := u.performFilterCheck(addr); err != nil {
 		return 0, err
 	}
 	return u.PacketConn.WriteTo(b, addr)
 }
 
-func (u *udpConn) performFilterCheck(addr net.Addr) error {
+func (u *UDPConn) performFilterCheck(addr net.Addr) error {
 	host, err := getHostFromAddr(addr)
 	if err != nil {
 		log.Errorf("Failed to get host from address %s: %v", addr, err)
diff --git a/client/iface/configurer/kernel_unix.go b/client/iface/configurer/kernel_unix.go
index 4922a54f..84afc38f 100644
--- a/client/iface/configurer/kernel_unix.go
+++ b/client/iface/configurer/kernel_unix.go
@@ -11,6 +11,8 @@ import (
 	log "github.com/sirupsen/logrus"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+
+	"github.com/netbirdio/netbird/monotime"
 )
 
 var zeroKey wgtypes.Key
@@ -276,3 +278,7 @@ func (c *KernelConfigurer) GetStats() (map[string]WGStats, error) {
 	}
 	return stats, nil
 }
+
+func (c *KernelConfigurer) LastActivities() map[string]monotime.Time {
+	return nil
+}
diff --git a/client/iface/configurer/usp.go b/client/iface/configurer/usp.go
index 79ce91ee..1ff4d839 100644
--- a/client/iface/configurer/usp.go
+++ b/client/iface/configurer/usp.go
@@ -16,6 +16,8 @@ import (
 	"golang.zx2c4.com/wireguard/device"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 
+	"github.com/netbirdio/netbird/client/iface/bind"
+	"github.com/netbirdio/netbird/monotime"
 	nbnet "github.com/netbirdio/netbird/util/net"
 )
 
@@ -36,16 +38,18 @@ const (
 var ErrAllowedIPNotFound = fmt.Errorf("allowed IP not found")
 
 type WGUSPConfigurer struct {
-	device     *device.Device
-	deviceName string
+	device           *device.Device
+	deviceName       string
+	activityRecorder *bind.ActivityRecorder
 
 	uapiListener net.Listener
 }
 
-func NewUSPConfigurer(device *device.Device, deviceName string) *WGUSPConfigurer {
+func NewUSPConfigurer(device *device.Device, deviceName string, activityRecorder *bind.ActivityRecorder) *WGUSPConfigurer {
 	wgCfg := &WGUSPConfigurer{
-		device:     device,
-		deviceName: deviceName,
+		device:           device,
+		deviceName:       deviceName,
+		activityRecorder: activityRecorder,
 	}
 	wgCfg.startUAPI()
 	return wgCfg
@@ -87,7 +91,19 @@ func (c *WGUSPConfigurer) UpdatePeer(peerKey string, allowedIps []netip.Prefix,
 		Peers: []wgtypes.PeerConfig{peer},
 	}
 
-	return c.device.IpcSet(toWgUserspaceString(config))
+	if ipcErr := c.device.IpcSet(toWgUserspaceString(config)); ipcErr != nil {
+		return ipcErr
+	}
+
+	if endpoint != nil {
+		addr, err := netip.ParseAddr(endpoint.IP.String())
+		if err != nil {
+			return fmt.Errorf("failed to parse endpoint address: %w", err)
+		}
+		addrPort := netip.AddrPortFrom(addr, uint16(endpoint.Port))
+		c.activityRecorder.UpsertAddress(peerKey, addrPort)
+	}
+	return nil
 }
 
 func (c *WGUSPConfigurer) RemovePeer(peerKey string) error {
@@ -104,7 +120,10 @@ func (c *WGUSPConfigurer) RemovePeer(peerKey string) error {
 	config := wgtypes.Config{
 		Peers: []wgtypes.PeerConfig{peer},
 	}
-	return c.device.IpcSet(toWgUserspaceString(config))
+	ipcErr := c.device.IpcSet(toWgUserspaceString(config))
+
+	c.activityRecorder.Remove(peerKey)
+	return ipcErr
 }
 
 func (c *WGUSPConfigurer) AddAllowedIP(peerKey string, allowedIP netip.Prefix) error {
@@ -205,6 +224,10 @@ func (c *WGUSPConfigurer) FullStats() (*Stats, error) {
 	return parseStatus(c.deviceName, ipcStr)
 }
 
+func (c *WGUSPConfigurer) LastActivities() map[string]monotime.Time {
+	return c.activityRecorder.GetLastActivities()
+}
+
 // startUAPI starts the UAPI listener for managing the WireGuard interface via external tool
 func (t *WGUSPConfigurer) startUAPI() {
 	var err error
diff --git a/client/iface/device/device_android.go b/client/iface/device/device_android.go
index ae9e29bd..4fe6e466 100644
--- a/client/iface/device/device_android.go
+++ b/client/iface/device/device_android.go
@@ -79,7 +79,7 @@ func (t *WGTunDevice) Create(routes []string, dns string, searchDomains []string
 	// this helps with support for the older NetBird clients that had a hardcoded direct mode
 	// t.device.DisableSomeRoamingForBrokenMobileSemantics()
 
-	t.configurer = configurer.NewUSPConfigurer(t.device, t.name)
+	t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.iceBind.ActivityRecorder())
 	err = t.configurer.ConfigureInterface(t.key, t.port)
 	if err != nil {
 		t.device.Close()
diff --git a/client/iface/device/device_darwin.go b/client/iface/device/device_darwin.go
index 01bfbf38..81de0e36 100644
--- a/client/iface/device/device_darwin.go
+++ b/client/iface/device/device_darwin.go
@@ -61,7 +61,7 @@ func (t *TunDevice) Create() (WGConfigurer, error) {
 		return nil, fmt.Errorf("error assigning ip: %s", err)
 	}
 
-	t.configurer = configurer.NewUSPConfigurer(t.device, t.name)
+	t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.iceBind.ActivityRecorder())
 	err = t.configurer.ConfigureInterface(t.key, t.port)
 	if err != nil {
 		t.device.Close()
diff --git a/client/iface/device/device_filter.go b/client/iface/device/device_filter.go
index 5a1a0e96..015f71ff 100644
--- a/client/iface/device/device_filter.go
+++ b/client/iface/device/device_filter.go
@@ -9,11 +9,11 @@ import (
 
 // PacketFilter interface for firewall abilities
 type PacketFilter interface {
-	// DropOutgoing filter outgoing packets from host to external destinations
-	DropOutgoing(packetData []byte, size int) bool
+	// FilterOutbound filter outgoing packets from host to external destinations
+	FilterOutbound(packetData []byte, size int) bool
 
-	// DropIncoming filter incoming packets from external sources to host
-	DropIncoming(packetData []byte, size int) bool
+	// FilterInbound filter incoming packets from external sources to host
+	FilterInbound(packetData []byte, size int) bool
 
 	// AddUDPPacketHook calls hook when UDP packet from given direction matched
 	//
@@ -54,7 +54,7 @@ func (d *FilteredDevice) Read(bufs [][]byte, sizes []int, offset int) (n int, er
 	}
 
 	for i := 0; i < n; i++ {
-		if filter.DropOutgoing(bufs[i][offset:offset+sizes[i]], sizes[i]) {
+		if filter.FilterOutbound(bufs[i][offset:offset+sizes[i]], sizes[i]) {
 			bufs = append(bufs[:i], bufs[i+1:]...)
 			sizes = append(sizes[:i], sizes[i+1:]...)
 			n--
@@ -78,7 +78,7 @@ func (d *FilteredDevice) Write(bufs [][]byte, offset int) (int, error) {
 	filteredBufs := make([][]byte, 0, len(bufs))
 	dropped := 0
 	for _, buf := range bufs {
-		if !filter.DropIncoming(buf[offset:], len(buf)) {
+		if !filter.FilterInbound(buf[offset:], len(buf)) {
 			filteredBufs = append(filteredBufs, buf)
 			dropped++
 		}
diff --git a/client/iface/device/device_ios.go b/client/iface/device/device_ios.go
index 56d44d68..4613762c 100644
--- a/client/iface/device/device_ios.go
+++ b/client/iface/device/device_ios.go
@@ -71,7 +71,7 @@ func (t *TunDevice) Create() (WGConfigurer, error) {
 	// this helps with support for the older NetBird clients that had a hardcoded direct mode
 	// t.device.DisableSomeRoamingForBrokenMobileSemantics()
 
-	t.configurer = configurer.NewUSPConfigurer(t.device, t.name)
+	t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.iceBind.ActivityRecorder())
 	err = t.configurer.ConfigureInterface(t.key, t.port)
 	if err != nil {
 		t.device.Close()
diff --git a/client/iface/device/device_netstack.go b/client/iface/device/device_netstack.go
index d2f2c87a..fc3cb021 100644
--- a/client/iface/device/device_netstack.go
+++ b/client/iface/device/device_netstack.go
@@ -72,7 +72,7 @@ func (t *TunNetstackDevice) Create() (WGConfigurer, error) {
 		device.NewLogger(wgLogLevel(), "[netbird] "),
 	)
 
-	t.configurer = configurer.NewUSPConfigurer(t.device, t.name)
+	t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.iceBind.ActivityRecorder())
 	err = t.configurer.ConfigureInterface(t.key, t.port)
 	if err != nil {
 		_ = tunIface.Close()
diff --git a/client/iface/device/device_usp_unix.go b/client/iface/device/device_usp_unix.go
index c45ae967..e781f600 100644
--- a/client/iface/device/device_usp_unix.go
+++ b/client/iface/device/device_usp_unix.go
@@ -64,7 +64,7 @@ func (t *USPDevice) Create() (WGConfigurer, error) {
 		return nil, fmt.Errorf("error assigning ip: %s", err)
 	}
 
-	t.configurer = configurer.NewUSPConfigurer(t.device, t.name)
+	t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.iceBind.ActivityRecorder())
 	err = t.configurer.ConfigureInterface(t.key, t.port)
 	if err != nil {
 		t.device.Close()
diff --git a/client/iface/device/device_windows.go b/client/iface/device/device_windows.go
index 41e615bc..0316c4b8 100644
--- a/client/iface/device/device_windows.go
+++ b/client/iface/device/device_windows.go
@@ -94,7 +94,7 @@ func (t *TunDevice) Create() (WGConfigurer, error) {
 		return nil, fmt.Errorf("error assigning ip: %s", err)
 	}
 
-	t.configurer = configurer.NewUSPConfigurer(t.device, t.name)
+	t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.iceBind.ActivityRecorder())
 	err = t.configurer.ConfigureInterface(t.key, t.port)
 	if err != nil {
 		t.device.Close()
diff --git a/client/iface/device/interface.go b/client/iface/device/interface.go
index 296eb7dd..1f40b0d4 100644
--- a/client/iface/device/interface.go
+++ b/client/iface/device/interface.go
@@ -8,6 +8,7 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 
 	"github.com/netbirdio/netbird/client/iface/configurer"
+	"github.com/netbirdio/netbird/monotime"
 )
 
 type WGConfigurer interface {
@@ -19,4 +20,5 @@ type WGConfigurer interface {
 	Close()
 	GetStats() (map[string]configurer.WGStats, error)
 	FullStats() (*configurer.Stats, error)
+	LastActivities() map[string]monotime.Time
 }
diff --git a/client/iface/iface.go b/client/iface/iface.go
index 006dfe4e..0e41f8e6 100644
--- a/client/iface/iface.go
+++ b/client/iface/iface.go
@@ -21,6 +21,7 @@ import (
 	"github.com/netbirdio/netbird/client/iface/device"
 	"github.com/netbirdio/netbird/client/iface/wgaddr"
 	"github.com/netbirdio/netbird/client/iface/wgproxy"
+	"github.com/netbirdio/netbird/monotime"
 )
 
 const (
@@ -29,6 +30,11 @@ const (
 	WgInterfaceDefault = configurer.WgInterfaceDefault
 )
 
+var (
+	// ErrIfaceNotFound is returned when the WireGuard interface is not found
+	ErrIfaceNotFound = fmt.Errorf("wireguard interface not found")
+)
+
 type wgProxyFactory interface {
 	GetProxy() wgproxy.Proxy
 	Free() error
@@ -117,6 +123,9 @@ func (w *WGIface) UpdateAddr(newAddr string) error {
 func (w *WGIface) UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
 	w.mu.Lock()
 	defer w.mu.Unlock()
+	if w.configurer == nil {
+		return ErrIfaceNotFound
+	}
 
 	log.Debugf("updating interface %s peer %s, endpoint %s, allowedIPs %v", w.tun.DeviceName(), peerKey, endpoint, allowedIps)
 	return w.configurer.UpdatePeer(peerKey, allowedIps, keepAlive, endpoint, preSharedKey)
@@ -126,6 +135,9 @@ func (w *WGIface) UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAliv
 func (w *WGIface) RemovePeer(peerKey string) error {
 	w.mu.Lock()
 	defer w.mu.Unlock()
+	if w.configurer == nil {
+		return ErrIfaceNotFound
+	}
 
 	log.Debugf("Removing peer %s from interface %s ", peerKey, w.tun.DeviceName())
 	return w.configurer.RemovePeer(peerKey)
@@ -135,6 +147,9 @@ func (w *WGIface) RemovePeer(peerKey string) error {
 func (w *WGIface) AddAllowedIP(peerKey string, allowedIP netip.Prefix) error {
 	w.mu.Lock()
 	defer w.mu.Unlock()
+	if w.configurer == nil {
+		return ErrIfaceNotFound
+	}
 
 	log.Debugf("Adding allowed IP to interface %s and peer %s: allowed IP %s ", w.tun.DeviceName(), peerKey, allowedIP)
 	return w.configurer.AddAllowedIP(peerKey, allowedIP)
@@ -144,6 +159,9 @@ func (w *WGIface) AddAllowedIP(peerKey string, allowedIP netip.Prefix) error {
 func (w *WGIface) RemoveAllowedIP(peerKey string, allowedIP netip.Prefix) error {
 	w.mu.Lock()
 	defer w.mu.Unlock()
+	if w.configurer == nil {
+		return ErrIfaceNotFound
+	}
 
 	log.Debugf("Removing allowed IP from interface %s and peer %s: allowed IP %s ", w.tun.DeviceName(), peerKey, allowedIP)
 	return w.configurer.RemoveAllowedIP(peerKey, allowedIP)
@@ -214,10 +232,29 @@ func (w *WGIface) GetWGDevice() *wgdevice.Device {
 
 // GetStats returns the last handshake time, rx and tx bytes
 func (w *WGIface) GetStats() (map[string]configurer.WGStats, error) {
+	if w.configurer == nil {
+		return nil, ErrIfaceNotFound
+	}
 	return w.configurer.GetStats()
 }
 
+func (w *WGIface) LastActivities() map[string]monotime.Time {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	if w.configurer == nil {
+		return nil
+	}
+
+	return w.configurer.LastActivities()
+
+}
+
 func (w *WGIface) FullStats() (*configurer.Stats, error) {
+	if w.configurer == nil {
+		return nil, ErrIfaceNotFound
+	}
+
 	return w.configurer.FullStats()
 }
 
diff --git a/client/iface/mocks/filter.go b/client/iface/mocks/filter.go
index 8cd2a123..566068aa 100644
--- a/client/iface/mocks/filter.go
+++ b/client/iface/mocks/filter.go
@@ -48,32 +48,32 @@ func (mr *MockPacketFilterMockRecorder) AddUDPPacketHook(arg0, arg1, arg2, arg3
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUDPPacketHook", reflect.TypeOf((*MockPacketFilter)(nil).AddUDPPacketHook), arg0, arg1, arg2, arg3)
 }
 
-// DropIncoming mocks base method.
-func (m *MockPacketFilter) DropIncoming(arg0 []byte, arg1 int) bool {
+// FilterInbound mocks base method.
+func (m *MockPacketFilter) FilterInbound(arg0 []byte, arg1 int) bool {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "DropIncoming", arg0, arg1)
+	ret := m.ctrl.Call(m, "FilterInbound", arg0, arg1)
 	ret0, _ := ret[0].(bool)
 	return ret0
 }
 
-// DropIncoming indicates an expected call of DropIncoming.
-func (mr *MockPacketFilterMockRecorder) DropIncoming(arg0 interface{}, arg1 any) *gomock.Call {
+// FilterInbound indicates an expected call of FilterInbound.
+func (mr *MockPacketFilterMockRecorder) FilterInbound(arg0 interface{}, arg1 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropIncoming", reflect.TypeOf((*MockPacketFilter)(nil).DropIncoming), arg0, arg1)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilterInbound", reflect.TypeOf((*MockPacketFilter)(nil).FilterInbound), arg0, arg1)
 }
 
-// DropOutgoing mocks base method.
-func (m *MockPacketFilter) DropOutgoing(arg0 []byte, arg1 int) bool {
+// FilterOutbound mocks base method.
+func (m *MockPacketFilter) FilterOutbound(arg0 []byte, arg1 int) bool {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "DropOutgoing", arg0, arg1)
+	ret := m.ctrl.Call(m, "FilterOutbound", arg0, arg1)
 	ret0, _ := ret[0].(bool)
 	return ret0
 }
 
-// DropOutgoing indicates an expected call of DropOutgoing.
-func (mr *MockPacketFilterMockRecorder) DropOutgoing(arg0 interface{}, arg1 any) *gomock.Call {
+// FilterOutbound indicates an expected call of FilterOutbound.
+func (mr *MockPacketFilterMockRecorder) FilterOutbound(arg0 interface{}, arg1 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropOutgoing", reflect.TypeOf((*MockPacketFilter)(nil).DropOutgoing), arg0, arg1)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilterOutbound", reflect.TypeOf((*MockPacketFilter)(nil).FilterOutbound), arg0, arg1)
 }
 
 // RemovePacketHook mocks base method.
diff --git a/client/iface/mocks/iface/mocks/filter.go b/client/iface/mocks/iface/mocks/filter.go
index 17e123ab..291ab9ab 100644
--- a/client/iface/mocks/iface/mocks/filter.go
+++ b/client/iface/mocks/iface/mocks/filter.go
@@ -46,32 +46,32 @@ func (mr *MockPacketFilterMockRecorder) AddUDPPacketHook(arg0, arg1, arg2, arg3
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUDPPacketHook", reflect.TypeOf((*MockPacketFilter)(nil).AddUDPPacketHook), arg0, arg1, arg2, arg3)
 }
 
-// DropIncoming mocks base method.
-func (m *MockPacketFilter) DropIncoming(arg0 []byte) bool {
+// FilterInbound mocks base method.
+func (m *MockPacketFilter) FilterInbound(arg0 []byte) bool {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "DropIncoming", arg0)
+	ret := m.ctrl.Call(m, "FilterInbound", arg0)
 	ret0, _ := ret[0].(bool)
 	return ret0
 }
 
-// DropIncoming indicates an expected call of DropIncoming.
-func (mr *MockPacketFilterMockRecorder) DropIncoming(arg0 interface{}) *gomock.Call {
+// FilterInbound indicates an expected call of FilterInbound.
+func (mr *MockPacketFilterMockRecorder) FilterInbound(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropIncoming", reflect.TypeOf((*MockPacketFilter)(nil).DropIncoming), arg0)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilterInbound", reflect.TypeOf((*MockPacketFilter)(nil).FilterInbound), arg0)
 }
 
-// DropOutgoing mocks base method.
-func (m *MockPacketFilter) DropOutgoing(arg0 []byte) bool {
+// FilterOutbound mocks base method.
+func (m *MockPacketFilter) FilterOutbound(arg0 []byte) bool {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "DropOutgoing", arg0)
+	ret := m.ctrl.Call(m, "FilterOutbound", arg0)
 	ret0, _ := ret[0].(bool)
 	return ret0
 }
 
-// DropOutgoing indicates an expected call of DropOutgoing.
-func (mr *MockPacketFilterMockRecorder) DropOutgoing(arg0 interface{}) *gomock.Call {
+// FilterOutbound indicates an expected call of FilterOutbound.
+func (mr *MockPacketFilterMockRecorder) FilterOutbound(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropOutgoing", reflect.TypeOf((*MockPacketFilter)(nil).DropOutgoing), arg0)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilterOutbound", reflect.TypeOf((*MockPacketFilter)(nil).FilterOutbound), arg0)
 }
 
 // SetNetwork mocks base method.
diff --git a/client/iface/netstack/tun.go b/client/iface/netstack/tun.go
index aec9d4fa..b2506b50 100644
--- a/client/iface/netstack/tun.go
+++ b/client/iface/netstack/tun.go
@@ -41,9 +41,12 @@ func (t *NetStackTun) Create() (tun.Device, *netstack.Net, error) {
 	}
 	t.tundev = nsTunDev
 
-	skipProxy, err := strconv.ParseBool(os.Getenv(EnvSkipProxy))
-	if err != nil {
-		log.Errorf("failed to parse %s: %s", EnvSkipProxy, err)
+	var skipProxy bool
+	if val := os.Getenv(EnvSkipProxy); val != "" {
+		skipProxy, err = strconv.ParseBool(val)
+		if err != nil {
+			log.Errorf("failed to parse %s: %s", EnvSkipProxy, err)
+		}
 	}
 	if skipProxy {
 		return nsTunDev, tunNet, nil
diff --git a/client/iface/wgproxy/bind/proxy.go b/client/iface/wgproxy/bind/proxy.go
index 614787e1..179ac0b7 100644
--- a/client/iface/wgproxy/bind/proxy.go
+++ b/client/iface/wgproxy/bind/proxy.go
@@ -12,6 +12,7 @@ import (
 	log "github.com/sirupsen/logrus"
 
 	"github.com/netbirdio/netbird/client/iface/bind"
+	"github.com/netbirdio/netbird/client/iface/wgproxy/listener"
 )
 
 type ProxyBind struct {
@@ -28,6 +29,17 @@ type ProxyBind struct {
 	pausedMu  sync.Mutex
 	paused    bool
 	isStarted bool
+
+	closeListener *listener.CloseListener
+}
+
+func NewProxyBind(bind *bind.ICEBind) *ProxyBind {
+	p := &ProxyBind{
+		Bind:          bind,
+		closeListener: listener.NewCloseListener(),
+	}
+
+	return p
 }
 
 // AddTurnConn adds a new connection to the bind.
@@ -54,6 +66,10 @@ func (p *ProxyBind) EndpointAddr() *net.UDPAddr {
 	}
 }
 
+func (p *ProxyBind) SetDisconnectListener(disconnected func()) {
+	p.closeListener.SetCloseListener(disconnected)
+}
+
 func (p *ProxyBind) Work() {
 	if p.remoteConn == nil {
 		return
@@ -96,6 +112,9 @@ func (p *ProxyBind) close() error {
 	if p.closed {
 		return nil
 	}
+
+	p.closeListener.SetCloseListener(nil)
+
 	p.closed = true
 
 	p.cancel()
@@ -122,6 +141,7 @@ func (p *ProxyBind) proxyToLocal(ctx context.Context) {
 			if ctx.Err() != nil {
 				return
 			}
+			p.closeListener.Notify()
 			log.Errorf("failed to read from remote conn: %s, %s", p.remoteConn.RemoteAddr(), err)
 			return
 		}
diff --git a/client/iface/wgproxy/ebpf/wrapper.go b/client/iface/wgproxy/ebpf/wrapper.go
index 54cab4e1..dbf9128a 100644
--- a/client/iface/wgproxy/ebpf/wrapper.go
+++ b/client/iface/wgproxy/ebpf/wrapper.go
@@ -11,6 +11,8 @@ import (
 	"sync"
 
 	log "github.com/sirupsen/logrus"
+
+	"github.com/netbirdio/netbird/client/iface/wgproxy/listener"
 )
 
 // ProxyWrapper help to keep the remoteConn instance for net.Conn.Close function call
@@ -26,6 +28,15 @@ type ProxyWrapper struct {
 	pausedMu  sync.Mutex
 	paused    bool
 	isStarted bool
+
+	closeListener *listener.CloseListener
+}
+
+func NewProxyWrapper(WgeBPFProxy *WGEBPFProxy) *ProxyWrapper {
+	return &ProxyWrapper{
+		WgeBPFProxy:   WgeBPFProxy,
+		closeListener: listener.NewCloseListener(),
+	}
 }
 
 func (p *ProxyWrapper) AddTurnConn(ctx context.Context, endpoint *net.UDPAddr, remoteConn net.Conn) error {
@@ -43,6 +54,10 @@ func (p *ProxyWrapper) EndpointAddr() *net.UDPAddr {
 	return p.wgEndpointAddr
 }
 
+func (p *ProxyWrapper) SetDisconnectListener(disconnected func()) {
+	p.closeListener.SetCloseListener(disconnected)
+}
+
 func (p *ProxyWrapper) Work() {
 	if p.remoteConn == nil {
 		return
@@ -77,6 +92,8 @@ func (e *ProxyWrapper) CloseConn() error {
 
 	e.cancel()
 
+	e.closeListener.SetCloseListener(nil)
+
 	if err := e.remoteConn.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
 		return fmt.Errorf("failed to close remote conn: %w", err)
 	}
@@ -117,6 +134,7 @@ func (p *ProxyWrapper) readFromRemote(ctx context.Context, buf []byte) (int, err
 		if ctx.Err() != nil {
 			return 0, ctx.Err()
 		}
+		p.closeListener.Notify()
 		if !errors.Is(err, io.EOF) {
 			log.Errorf("failed to read from turn conn (endpoint: :%d): %s", p.wgEndpointAddr.Port, err)
 		}
diff --git a/client/iface/wgproxy/factory_kernel.go b/client/iface/wgproxy/factory_kernel.go
index 3ad7dc59..e62cd97b 100644
--- a/client/iface/wgproxy/factory_kernel.go
+++ b/client/iface/wgproxy/factory_kernel.go
@@ -36,9 +36,8 @@ func (w *KernelFactory) GetProxy() Proxy {
 		return udpProxy.NewWGUDPProxy(w.wgPort)
 	}
 
-	return &ebpf.ProxyWrapper{
-		WgeBPFProxy: w.ebpfProxy,
-	}
+	return ebpf.NewProxyWrapper(w.ebpfProxy)
+
 }
 
 func (w *KernelFactory) Free() error {
diff --git a/client/iface/wgproxy/factory_usp.go b/client/iface/wgproxy/factory_usp.go
index e2d47933..141b4c1f 100644
--- a/client/iface/wgproxy/factory_usp.go
+++ b/client/iface/wgproxy/factory_usp.go
@@ -20,9 +20,7 @@ func NewUSPFactory(iceBind *bind.ICEBind) *USPFactory {
 }
 
 func (w *USPFactory) GetProxy() Proxy {
-	return &proxyBind.ProxyBind{
-		Bind: w.bind,
-	}
+	return proxyBind.NewProxyBind(w.bind)
 }
 
 func (w *USPFactory) Free() error {
diff --git a/client/iface/wgproxy/listener/listener.go b/client/iface/wgproxy/listener/listener.go
new file mode 100644
index 00000000..bfd65154
--- /dev/null
+++ b/client/iface/wgproxy/listener/listener.go
@@ -0,0 +1,19 @@
+package listener
+
+type CloseListener struct {
+	listener func()
+}
+
+func NewCloseListener() *CloseListener {
+	return &CloseListener{}
+}
+
+func (c *CloseListener) SetCloseListener(listener func()) {
+	c.listener = listener
+}
+
+func (c *CloseListener) Notify() {
+	if c.listener != nil {
+		c.listener()
+	}
+}
diff --git a/client/iface/wgproxy/proxy.go b/client/iface/wgproxy/proxy.go
index 243aa2bd..c2879877 100644
--- a/client/iface/wgproxy/proxy.go
+++ b/client/iface/wgproxy/proxy.go
@@ -12,4 +12,5 @@ type Proxy interface {
 	Work()                      // Work start or resume the proxy
 	Pause()                     // Pause to forward the packages from remote connection to WireGuard. The opposite way still works.
 	CloseConn() error
+	SetDisconnectListener(disconnected func())
 }
diff --git a/client/iface/wgproxy/udp/proxy.go b/client/iface/wgproxy/udp/proxy.go
index ba0004b8..df45d8ca 100644
--- a/client/iface/wgproxy/udp/proxy.go
+++ b/client/iface/wgproxy/udp/proxy.go
@@ -12,6 +12,7 @@ import (
 	log "github.com/sirupsen/logrus"
 
 	cerrors "github.com/netbirdio/netbird/client/errors"
+	"github.com/netbirdio/netbird/client/iface/wgproxy/listener"
 )
 
 // WGUDPProxy proxies
@@ -28,6 +29,8 @@ type WGUDPProxy struct {
 	pausedMu  sync.Mutex
 	paused    bool
 	isStarted bool
+
+	closeListener *listener.CloseListener
 }
 
 // NewWGUDPProxy instantiate a UDP based WireGuard proxy. This is not a thread safe implementation
@@ -35,6 +38,7 @@ func NewWGUDPProxy(wgPort int) *WGUDPProxy {
 	log.Debugf("Initializing new user space proxy with port %d", wgPort)
 	p := &WGUDPProxy{
 		localWGListenPort: wgPort,
+		closeListener:     listener.NewCloseListener(),
 	}
 	return p
 }
@@ -67,6 +71,10 @@ func (p *WGUDPProxy) EndpointAddr() *net.UDPAddr {
 	return endpointUdpAddr
 }
 
+func (p *WGUDPProxy) SetDisconnectListener(disconnected func()) {
+	p.closeListener.SetCloseListener(disconnected)
+}
+
 // Work starts the proxy or resumes it if it was paused
 func (p *WGUDPProxy) Work() {
 	if p.remoteConn == nil {
@@ -111,6 +119,8 @@ func (p *WGUDPProxy) close() error {
 	if p.closed {
 		return nil
 	}
+
+	p.closeListener.SetCloseListener(nil)
 	p.closed = true
 
 	p.cancel()
@@ -141,6 +151,7 @@ func (p *WGUDPProxy) proxyToRemote(ctx context.Context) {
 			if ctx.Err() != nil {
 				return
 			}
+			p.closeListener.Notify()
 			log.Debugf("failed to read from wg interface conn: %s", err)
 			return
 		}
diff --git a/client/internal/config.go b/client/internal/config.go
index 37ee1e1b..add702cd 100644
--- a/client/internal/config.go
+++ b/client/internal/config.go
@@ -319,10 +319,6 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
 			*input.WireguardPort, config.WgPort)
 		config.WgPort = *input.WireguardPort
 		updated = true
-	} else if config.WgPort == 0 {
-		config.WgPort = iface.DefaultWgPort
-		log.Infof("using default Wireguard port %d", config.WgPort)
-		updated = true
 	}
 
 	if input.InterfaceName != nil && *input.InterfaceName != config.WgIface {
diff --git a/client/internal/conn_mgr.go b/client/internal/conn_mgr.go
index c630d305..11255913 100644
--- a/client/internal/conn_mgr.go
+++ b/client/internal/conn_mgr.go
@@ -12,7 +12,6 @@ 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/dispatcher"
 	"github.com/netbirdio/netbird/client/internal/peerstore"
 	"github.com/netbirdio/netbird/route"
 )
@@ -26,11 +25,11 @@ import (
 //
 // The implementation is not thread-safe; it is protected by engine.syncMsgMux.
 type ConnMgr struct {
-	peerStore      *peerstore.Store
-	statusRecorder *peer.Status
-	iface          lazyconn.WGIface
-	dispatcher     *dispatcher.ConnectionDispatcher
-	enabledLocally bool
+	peerStore        *peerstore.Store
+	statusRecorder   *peer.Status
+	iface            lazyconn.WGIface
+	enabledLocally   bool
+	rosenpassEnabled bool
 
 	lazyConnMgr *manager.Manager
 
@@ -39,12 +38,12 @@ type ConnMgr struct {
 	lazyCtxCancel context.CancelFunc
 }
 
-func NewConnMgr(engineConfig *EngineConfig, statusRecorder *peer.Status, peerStore *peerstore.Store, iface lazyconn.WGIface, dispatcher *dispatcher.ConnectionDispatcher) *ConnMgr {
+func NewConnMgr(engineConfig *EngineConfig, statusRecorder *peer.Status, peerStore *peerstore.Store, iface lazyconn.WGIface) *ConnMgr {
 	e := &ConnMgr{
-		peerStore:      peerStore,
-		statusRecorder: statusRecorder,
-		iface:          iface,
-		dispatcher:     dispatcher,
+		peerStore:        peerStore,
+		statusRecorder:   statusRecorder,
+		iface:            iface,
+		rosenpassEnabled: engineConfig.RosenpassEnabled,
 	}
 	if engineConfig.LazyConnectionEnabled || lazyconn.IsLazyConnEnabledByEnv() {
 		e.enabledLocally = true
@@ -64,6 +63,11 @@ func (e *ConnMgr) Start(ctx context.Context) {
 		return
 	}
 
+	if e.rosenpassEnabled {
+		log.Warnf("rosenpass connection manager is enabled, lazy connection manager will not be started")
+		return
+	}
+
 	e.initLazyManager(ctx)
 	e.statusRecorder.UpdateLazyConnection(true)
 }
@@ -83,7 +87,12 @@ func (e *ConnMgr) UpdatedRemoteFeatureFlag(ctx context.Context, enabled bool) er
 			return nil
 		}
 
-		log.Infof("lazy connection manager is enabled by management feature flag")
+		if e.rosenpassEnabled {
+			log.Infof("rosenpass connection manager is enabled, lazy connection manager will not be started")
+			return nil
+		}
+
+		log.Warnf("lazy connection manager is enabled by management feature flag")
 		e.initLazyManager(ctx)
 		e.statusRecorder.UpdateLazyConnection(true)
 		return e.addPeersToLazyConnManager()
@@ -133,7 +142,7 @@ func (e *ConnMgr) SetExcludeList(ctx context.Context, peerIDs map[string]bool) {
 		excludedPeers = append(excludedPeers, lazyPeerCfg)
 	}
 
-	added := e.lazyConnMgr.ExcludePeer(e.lazyCtx, excludedPeers)
+	added := e.lazyConnMgr.ExcludePeer(excludedPeers)
 	for _, peerID := range added {
 		var peerConn *peer.Conn
 		var exists bool
@@ -175,7 +184,7 @@ func (e *ConnMgr) AddPeerConn(ctx context.Context, peerKey string, conn *peer.Co
 		PeerConnID: conn.ConnID(),
 		Log:        conn.Log,
 	}
-	excluded, err := e.lazyConnMgr.AddPeer(e.lazyCtx, lazyPeerCfg)
+	excluded, err := e.lazyConnMgr.AddPeer(lazyPeerCfg)
 	if err != nil {
 		conn.Log.Errorf("failed to add peer to lazyconn manager: %v", err)
 		if err := conn.Open(ctx); err != nil {
@@ -201,7 +210,7 @@ func (e *ConnMgr) RemovePeerConn(peerKey string) {
 	if !ok {
 		return
 	}
-	defer conn.Close()
+	defer conn.Close(false)
 
 	if !e.isStartedWithLazyMgr() {
 		return
@@ -211,23 +220,27 @@ func (e *ConnMgr) RemovePeerConn(peerKey string) {
 	conn.Log.Infof("removed peer from lazy conn manager")
 }
 
-func (e *ConnMgr) OnSignalMsg(ctx context.Context, peerKey string) (*peer.Conn, bool) {
-	conn, ok := e.peerStore.PeerConn(peerKey)
-	if !ok {
-		return nil, false
-	}
-
+func (e *ConnMgr) ActivatePeer(ctx context.Context, conn *peer.Conn) {
 	if !e.isStartedWithLazyMgr() {
-		return conn, true
+		return
 	}
 
-	if found := e.lazyConnMgr.ActivatePeer(e.lazyCtx, peerKey); found {
-		conn.Log.Infof("activated peer from inactive state")
+	if found := e.lazyConnMgr.ActivatePeer(conn.GetKey()); found {
 		if err := conn.Open(ctx); err != nil {
 			conn.Log.Errorf("failed to open connection: %v", err)
 		}
 	}
-	return conn, true
+}
+
+// DeactivatePeer deactivates a peer connection in the lazy connection manager.
+// If locally the lazy connection is disabled, we force the peer connection open.
+func (e *ConnMgr) DeactivatePeer(conn *peer.Conn) {
+	if !e.isStartedWithLazyMgr() {
+		return
+	}
+
+	conn.Log.Infof("closing peer connection: remote peer initiated inactive, idle lazy state and sent GOAWAY")
+	e.lazyConnMgr.DeactivatePeer(conn.ConnID())
 }
 
 func (e *ConnMgr) Close() {
@@ -244,7 +257,7 @@ func (e *ConnMgr) initLazyManager(engineCtx context.Context) {
 	cfg := manager.Config{
 		InactivityThreshold: inactivityThresholdEnv(),
 	}
-	e.lazyConnMgr = manager.NewManager(cfg, engineCtx, e.peerStore, e.iface, e.dispatcher)
+	e.lazyConnMgr = manager.NewManager(cfg, engineCtx, e.peerStore, e.iface)
 
 	e.lazyCtx, e.lazyCtxCancel = context.WithCancel(engineCtx)
 
@@ -275,7 +288,7 @@ func (e *ConnMgr) addPeersToLazyConnManager() error {
 		lazyPeerCfgs = append(lazyPeerCfgs, lazyPeerCfg)
 	}
 
-	return e.lazyConnMgr.AddActivePeers(e.lazyCtx, lazyPeerCfgs)
+	return e.lazyConnMgr.AddActivePeers(lazyPeerCfgs)
 }
 
 func (e *ConnMgr) closeManager(ctx context.Context) {
diff --git a/client/internal/connect.go b/client/internal/connect.go
index f59fbe34..7b49fa3a 100644
--- a/client/internal/connect.go
+++ b/client/internal/connect.go
@@ -17,7 +17,6 @@ import (
 	"google.golang.org/grpc/codes"
 	gstatus "google.golang.org/grpc/status"
 
-	"github.com/netbirdio/netbird/client/iface"
 	"github.com/netbirdio/netbird/client/iface/device"
 	"github.com/netbirdio/netbird/client/internal/dns"
 	"github.com/netbirdio/netbird/client/internal/listener"
@@ -526,17 +525,13 @@ func statusRecorderToSignalConnStateNotifier(statusRecorder *peer.Status) signal
 
 // freePort attempts to determine if the provided port is available, if not it will ask the system for a free port.
 func freePort(initPort int) (int, error) {
-	addr := net.UDPAddr{}
-	if initPort == 0 {
-		initPort = iface.DefaultWgPort
-	}
-
-	addr.Port = initPort
+	addr := net.UDPAddr{Port: initPort}
 
 	conn, err := net.ListenUDP("udp", &addr)
 	if err == nil {
+		returnPort := conn.LocalAddr().(*net.UDPAddr).Port
 		closeConnWithLog(conn)
-		return initPort, nil
+		return returnPort, nil
 	}
 
 	// if the port is already in use, ask the system for a free port
diff --git a/client/internal/debug/debug.go b/client/internal/debug/debug.go
index dfed47f0..6455b3aa 100644
--- a/client/internal/debug/debug.go
+++ b/client/internal/debug/debug.go
@@ -167,6 +167,7 @@ type BundleGenerator struct {
 	anonymize         bool
 	clientStatus      string
 	includeSystemInfo bool
+	logFileCount      uint32
 
 	archive *zip.Writer
 }
@@ -175,6 +176,7 @@ type BundleConfig struct {
 	Anonymize         bool
 	ClientStatus      string
 	IncludeSystemInfo bool
+	LogFileCount      uint32
 }
 
 type GeneratorDependencies struct {
@@ -185,6 +187,12 @@ type GeneratorDependencies struct {
 }
 
 func NewBundleGenerator(deps GeneratorDependencies, cfg BundleConfig) *BundleGenerator {
+	// Default to 1 log file for backward compatibility when 0 is provided
+	logFileCount := cfg.LogFileCount
+	if logFileCount == 0 {
+		logFileCount = 1
+	}
+
 	return &BundleGenerator{
 		anonymizer: anonymize.NewAnonymizer(anonymize.DefaultAddresses()),
 
@@ -196,6 +204,7 @@ func NewBundleGenerator(deps GeneratorDependencies, cfg BundleConfig) *BundleGen
 		anonymize:         cfg.Anonymize,
 		clientStatus:      cfg.ClientStatus,
 		includeSystemInfo: cfg.IncludeSystemInfo,
+		logFileCount:      logFileCount,
 	}
 }
 
@@ -561,32 +570,8 @@ func (g *BundleGenerator) addLogfile() error {
 		return fmt.Errorf("add client log file to zip: %w", err)
 	}
 
-	// add latest rotated log file
-	pattern := filepath.Join(logDir, "client-*.log.gz")
-	files, err := filepath.Glob(pattern)
-	if err != nil {
-		log.Warnf("failed to glob rotated logs: %v", err)
-	} else if len(files) > 0 {
-		// pick the file with the latest ModTime
-		sort.Slice(files, func(i, j int) bool {
-			fi, err := os.Stat(files[i])
-			if err != nil {
-				log.Warnf("failed to stat rotated log %s: %v", files[i], err)
-				return false
-			}
-			fj, err := os.Stat(files[j])
-			if err != nil {
-				log.Warnf("failed to stat rotated log %s: %v", files[j], err)
-				return false
-			}
-			return fi.ModTime().Before(fj.ModTime())
-		})
-		latest := files[len(files)-1]
-		name := filepath.Base(latest)
-		if err := g.addSingleLogFileGz(latest, name); err != nil {
-			log.Warnf("failed to add rotated log %s: %v", name, err)
-		}
-	}
+	// add rotated log files based on logFileCount
+	g.addRotatedLogFiles(logDir)
 
 	stdErrLogPath := filepath.Join(logDir, errorLogFile)
 	stdoutLogPath := filepath.Join(logDir, stdoutLogFile)
@@ -670,6 +655,52 @@ func (g *BundleGenerator) addSingleLogFileGz(logPath, targetName string) error {
 	return nil
 }
 
+// addRotatedLogFiles adds rotated log files to the bundle based on logFileCount
+func (g *BundleGenerator) addRotatedLogFiles(logDir string) {
+	if g.logFileCount == 0 {
+		return
+	}
+
+	pattern := filepath.Join(logDir, "client-*.log.gz")
+	files, err := filepath.Glob(pattern)
+	if err != nil {
+		log.Warnf("failed to glob rotated logs: %v", err)
+		return
+	}
+
+	if len(files) == 0 {
+		return
+	}
+
+	// sort files by modification time (newest first)
+	sort.Slice(files, func(i, j int) bool {
+		fi, err := os.Stat(files[i])
+		if err != nil {
+			log.Warnf("failed to stat rotated log %s: %v", files[i], err)
+			return false
+		}
+		fj, err := os.Stat(files[j])
+		if err != nil {
+			log.Warnf("failed to stat rotated log %s: %v", files[j], err)
+			return false
+		}
+		return fi.ModTime().After(fj.ModTime())
+	})
+
+	// include up to logFileCount rotated files
+	maxFiles := int(g.logFileCount)
+	if maxFiles > len(files) {
+		maxFiles = len(files)
+	}
+
+	for i := 0; i < maxFiles; i++ {
+		name := filepath.Base(files[i])
+		if err := g.addSingleLogFileGz(files[i], name); err != nil {
+			log.Warnf("failed to add rotated log %s: %v", name, err)
+		}
+	}
+}
+
 func (g *BundleGenerator) addFileToZip(reader io.Reader, filename string) error {
 	header := &zip.FileHeader{
 		Name:     filename,
diff --git a/client/internal/engine.go b/client/internal/engine.go
index 4ea6fbd9..1abb8163 100644
--- a/client/internal/engine.go
+++ b/client/internal/engine.go
@@ -38,7 +38,6 @@ 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/dispatcher"
 	"github.com/netbirdio/netbird/client/internal/peer/guard"
 	icemaker "github.com/netbirdio/netbird/client/internal/peer/ice"
 	"github.com/netbirdio/netbird/client/internal/peerstore"
@@ -62,7 +61,6 @@ import (
 	signal "github.com/netbirdio/netbird/signal/client"
 	sProto "github.com/netbirdio/netbird/signal/proto"
 	"github.com/netbirdio/netbird/util"
-	nbnet "github.com/netbirdio/netbird/util/net"
 )
 
 // PeerConnectionTimeoutMax is a timeout of an initial connection attempt to a remote peer.
@@ -139,9 +137,6 @@ type Engine struct {
 
 	connMgr *ConnMgr
 
-	beforePeerHook nbnet.AddHookFunc
-	afterPeerHook  nbnet.RemoveHookFunc
-
 	// rpManager is a Rosenpass manager
 	rpManager *rosenpass.Manager
 
@@ -175,8 +170,7 @@ type Engine struct {
 	sshServerFunc func(hostKeyPEM []byte, addr string) (nbssh.Server, error)
 	sshServer     nbssh.Server
 
-	statusRecorder     *peer.Status
-	peerConnDispatcher *dispatcher.ConnectionDispatcher
+	statusRecorder *peer.Status
 
 	firewall          firewallManager.Manager
 	routeManager      routemanager.Manager
@@ -383,7 +377,13 @@ func (e *Engine) Start() error {
 	}
 	e.stateManager.Start()
 
-	initialRoutes, dnsServer, err := e.newDnsServer()
+	initialRoutes, dnsConfig, dnsFeatureFlag, err := e.readInitialSettings()
+	if err != nil {
+		e.close()
+		return fmt.Errorf("read initial settings: %w", err)
+	}
+
+	dnsServer, err := e.newDnsServer(dnsConfig)
 	if err != nil {
 		e.close()
 		return fmt.Errorf("create dns server: %w", err)
@@ -400,16 +400,13 @@ func (e *Engine) Start() error {
 		InitialRoutes:       initialRoutes,
 		StateManager:        e.stateManager,
 		DNSServer:           dnsServer,
+		DNSFeatureFlag:      dnsFeatureFlag,
 		PeerStore:           e.peerStore,
 		DisableClientRoutes: e.config.DisableClientRoutes,
 		DisableServerRoutes: e.config.DisableServerRoutes,
 	})
-	beforePeerHook, afterPeerHook, err := e.routeManager.Init()
-	if err != nil {
+	if err := e.routeManager.Init(); err != nil {
 		log.Errorf("Failed to initialize route manager: %s", err)
-	} else {
-		e.beforePeerHook = beforePeerHook
-		e.afterPeerHook = afterPeerHook
 	}
 
 	e.routeManager.SetRouteChangeListener(e.mobileDep.NetworkChangeListener)
@@ -451,9 +448,7 @@ func (e *Engine) Start() error {
 		NATExternalIPs:       e.parseNATExternalIPMappings(),
 	}
 
-	e.peerConnDispatcher = dispatcher.NewConnectionDispatcher()
-
-	e.connMgr = NewConnMgr(e.config, e.statusRecorder, e.peerStore, wgIface, e.peerConnDispatcher)
+	e.connMgr = NewConnMgr(e.config, e.statusRecorder, e.peerStore, wgIface)
 	e.connMgr.Start(e.ctx)
 
 	e.srWatcher = guard.NewSRWatcher(e.signal, e.relayManager, e.mobileDep.IFaceDiscover, iceCfg)
@@ -488,9 +483,9 @@ func (e *Engine) createFirewall() error {
 }
 
 func (e *Engine) initFirewall() error {
-	if err := e.routeManager.EnableServerRouter(e.firewall); err != nil {
+	if err := e.routeManager.SetFirewall(e.firewall); err != nil {
 		e.close()
-		return fmt.Errorf("enable server router: %w", err)
+		return fmt.Errorf("set firewall: %w", err)
 	}
 
 	if e.config.BlockLANAccess {
@@ -1009,8 +1004,6 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
 		log.Errorf("failed to update dns server, err: %v", err)
 	}
 
-	dnsRouteFeatureFlag := toDNSFeatureFlag(networkMap)
-
 	// apply routes first, route related actions might depend on routing being enabled
 	routes := toRoutes(networkMap.GetRoutes())
 	serverRoutes, clientRoutes := e.routeManager.ClassifyRoutes(routes)
@@ -1021,6 +1014,7 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
 		log.Debugf("updated lazy connection manager with %d HA groups", len(clientRoutes))
 	}
 
+	dnsRouteFeatureFlag := toDNSFeatureFlag(networkMap)
 	if err := e.routeManager.UpdateRoutes(serial, serverRoutes, clientRoutes, dnsRouteFeatureFlag); err != nil {
 		log.Errorf("failed to update routes: %v", err)
 	}
@@ -1255,14 +1249,10 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {
 	}
 
 	if exists := e.connMgr.AddPeerConn(e.ctx, peerKey, conn); exists {
-		conn.Close()
+		conn.Close(false)
 		return fmt.Errorf("peer already exists: %s", peerKey)
 	}
 
-	if e.beforePeerHook != nil && e.afterPeerHook != nil {
-		conn.AddBeforeAddPeerHook(e.beforePeerHook)
-		conn.AddAfterRemovePeerHook(e.afterPeerHook)
-	}
 	return nil
 }
 
@@ -1302,13 +1292,12 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs []netip.Prefix, agentV
 	}
 
 	serviceDependencies := peer.ServiceDependencies{
-		StatusRecorder:     e.statusRecorder,
-		Signaler:           e.signaler,
-		IFaceDiscover:      e.mobileDep.IFaceDiscover,
-		RelayManager:       e.relayManager,
-		SrWatcher:          e.srWatcher,
-		Semaphore:          e.connSemaphore,
-		PeerConnDispatcher: e.peerConnDispatcher,
+		StatusRecorder: e.statusRecorder,
+		Signaler:       e.signaler,
+		IFaceDiscover:  e.mobileDep.IFaceDiscover,
+		RelayManager:   e.relayManager,
+		SrWatcher:      e.srWatcher,
+		Semaphore:      e.connSemaphore,
 	}
 	peerConn, err := peer.NewConn(config, serviceDependencies)
 	if err != nil {
@@ -1331,11 +1320,16 @@ func (e *Engine) receiveSignalEvents() {
 			e.syncMsgMux.Lock()
 			defer e.syncMsgMux.Unlock()
 
-			conn, ok := e.connMgr.OnSignalMsg(e.ctx, msg.Key)
+			conn, ok := e.peerStore.PeerConn(msg.Key)
 			if !ok {
 				return fmt.Errorf("wrongly addressed message %s", msg.Key)
 			}
 
+			msgType := msg.GetBody().GetType()
+			if msgType != sProto.Body_GO_IDLE {
+				e.connMgr.ActivatePeer(e.ctx, conn)
+			}
+
 			switch msg.GetBody().Type {
 			case sProto.Body_OFFER:
 				remoteCred, err := signal.UnMarshalCredential(msg)
@@ -1392,6 +1386,8 @@ func (e *Engine) receiveSignalEvents() {
 
 				go conn.OnRemoteCandidate(candidate, e.routeManager.GetClientRoutes())
 			case sProto.Body_MODE:
+			case sProto.Body_GO_IDLE:
+				e.connMgr.DeactivatePeer(conn)
 			}
 
 			return nil
@@ -1489,7 +1485,12 @@ func (e *Engine) close() {
 	}
 }
 
-func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, error) {
+func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, bool, error) {
+	if runtime.GOOS != "android" {
+		// nolint:nilnil
+		return nil, nil, false, nil
+	}
+
 	info := system.GetInfo(e.ctx)
 	info.SetFlags(
 		e.config.RosenpassEnabled,
@@ -1506,11 +1507,12 @@ func (e *Engine) readInitialSettings() ([]*route.Route, *nbdns.Config, error) {
 
 	netMap, err := e.mgmClient.GetNetworkMap(info)
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, false, err
 	}
 	routes := toRoutes(netMap.GetRoutes())
 	dnsCfg := toDNSConfig(netMap.GetDNSConfig(), e.wgInterface.Address().Network)
-	return routes, &dnsCfg, nil
+	dnsFeatureFlag := toDNSFeatureFlag(netMap)
+	return routes, &dnsCfg, dnsFeatureFlag, nil
 }
 
 func (e *Engine) newWgIface() (*iface.WGIface, error) {
@@ -1558,18 +1560,14 @@ func (e *Engine) wgInterfaceCreate() (err error) {
 	return err
 }
 
-func (e *Engine) newDnsServer() ([]*route.Route, dns.Server, error) {
+func (e *Engine) newDnsServer(dnsConfig *nbdns.Config) (dns.Server, error) {
 	// due to tests where we are using a mocked version of the DNS server
 	if e.dnsServer != nil {
-		return nil, e.dnsServer, nil
+		return e.dnsServer, nil
 	}
 
 	switch runtime.GOOS {
 	case "android":
-		routes, dnsConfig, err := e.readInitialSettings()
-		if err != nil {
-			return nil, nil, err
-		}
 		dnsServer := dns.NewDefaultServerPermanentUpstream(
 			e.ctx,
 			e.wgInterface,
@@ -1580,19 +1578,19 @@ func (e *Engine) newDnsServer() ([]*route.Route, dns.Server, error) {
 			e.config.DisableDNS,
 		)
 		go e.mobileDep.DnsReadyListener.OnReady()
-		return routes, dnsServer, nil
+		return dnsServer, nil
 
 	case "ios":
 		dnsServer := dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager, e.statusRecorder, e.config.DisableDNS)
-		return nil, dnsServer, nil
+		return dnsServer, nil
 
 	default:
 		dnsServer, err := dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress, e.statusRecorder, e.stateManager, e.config.DisableDNS)
 		if err != nil {
-			return nil, nil, err
+			return nil, err
 		}
 
-		return nil, dnsServer, nil
+		return dnsServer, nil
 	}
 }
 
diff --git a/client/internal/iface_common.go b/client/internal/iface_common.go
index 99947241..bf96153e 100644
--- a/client/internal/iface_common.go
+++ b/client/internal/iface_common.go
@@ -14,6 +14,7 @@ import (
 	"github.com/netbirdio/netbird/client/iface/device"
 	"github.com/netbirdio/netbird/client/iface/wgaddr"
 	"github.com/netbirdio/netbird/client/iface/wgproxy"
+	"github.com/netbirdio/netbird/monotime"
 )
 
 type wgIfaceBase interface {
@@ -38,4 +39,5 @@ type wgIfaceBase interface {
 	GetStats() (map[string]configurer.WGStats, error)
 	GetNet() *netstack.Net
 	FullStats() (*configurer.Stats, error)
+	LastActivities() map[string]monotime.Time
 }
diff --git a/client/internal/lazyconn/activity/listener.go b/client/internal/lazyconn/activity/listener.go
index 1ef48416..817ff00c 100644
--- a/client/internal/lazyconn/activity/listener.go
+++ b/client/internal/lazyconn/activity/listener.go
@@ -13,7 +13,7 @@ import (
 
 // Listener it is not a thread safe implementation, do not call Close before ReadPackets. It will cause blocking
 type Listener struct {
-	wgIface  lazyconn.WGIface
+	wgIface  WgInterface
 	peerCfg  lazyconn.PeerConfig
 	conn     *net.UDPConn
 	endpoint *net.UDPAddr
@@ -22,7 +22,7 @@ type Listener struct {
 	isClosed atomic.Bool // use to avoid error log when closing the listener
 }
 
-func NewListener(wgIface lazyconn.WGIface, cfg lazyconn.PeerConfig) (*Listener, error) {
+func NewListener(wgIface WgInterface, cfg lazyconn.PeerConfig) (*Listener, error) {
 	d := &Listener{
 		wgIface: wgIface,
 		peerCfg: cfg,
@@ -48,7 +48,7 @@ func (d *Listener) ReadPackets() {
 		n, remoteAddr, err := d.conn.ReadFromUDP(make([]byte, 1))
 		if err != nil {
 			if d.isClosed.Load() {
-				d.peerCfg.Log.Debugf("exit from activity listener")
+				d.peerCfg.Log.Infof("exit from activity listener")
 			} else {
 				d.peerCfg.Log.Errorf("failed to read from activity listener: %s", err)
 			}
@@ -59,9 +59,11 @@ func (d *Listener) ReadPackets() {
 			d.peerCfg.Log.Warnf("received %d bytes from %s, too short", n, remoteAddr)
 			continue
 		}
+		d.peerCfg.Log.Infof("activity detected")
 		break
 	}
 
+	d.peerCfg.Log.Debugf("removing lazy endpoint: %s", d.endpoint.String())
 	if err := d.removeEndpoint(); err != nil {
 		d.peerCfg.Log.Errorf("failed to remove endpoint: %s", err)
 	}
@@ -71,7 +73,7 @@ func (d *Listener) ReadPackets() {
 }
 
 func (d *Listener) Close() {
-	d.peerCfg.Log.Infof("closing listener: %s", d.conn.LocalAddr().String())
+	d.peerCfg.Log.Infof("closing activity listener: %s", d.conn.LocalAddr().String())
 	d.isClosed.Store(true)
 
 	if err := d.conn.Close(); err != nil {
@@ -81,7 +83,6 @@ func (d *Listener) Close() {
 }
 
 func (d *Listener) removeEndpoint() error {
-	d.peerCfg.Log.Debugf("removing lazy endpoint: %s", d.endpoint.String())
 	return d.wgIface.RemovePeer(d.peerCfg.PublicKey)
 }
 
diff --git a/client/internal/lazyconn/activity/manager.go b/client/internal/lazyconn/activity/manager.go
index e18b9646..915fb9cb 100644
--- a/client/internal/lazyconn/activity/manager.go
+++ b/client/internal/lazyconn/activity/manager.go
@@ -1,18 +1,27 @@
 package activity
 
 import (
+	"net"
+	"net/netip"
 	"sync"
+	"time"
 
 	log "github.com/sirupsen/logrus"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 
 	"github.com/netbirdio/netbird/client/internal/lazyconn"
 	peerid "github.com/netbirdio/netbird/client/internal/peer/id"
 )
 
+type WgInterface interface {
+	RemovePeer(peerKey string) error
+	UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
+}
+
 type Manager struct {
 	OnActivityChan chan peerid.ConnID
 
-	wgIface lazyconn.WGIface
+	wgIface WgInterface
 
 	peers map[peerid.ConnID]*Listener
 	done  chan struct{}
@@ -20,7 +29,7 @@ type Manager struct {
 	mu sync.Mutex
 }
 
-func NewManager(wgIface lazyconn.WGIface) *Manager {
+func NewManager(wgIface WgInterface) *Manager {
 	m := &Manager{
 		OnActivityChan: make(chan peerid.ConnID, 1),
 		wgIface:        wgIface,
diff --git a/client/internal/lazyconn/inactivity/inactivity.go b/client/internal/lazyconn/inactivity/inactivity.go
deleted file mode 100644
index 9b7c8511..00000000
--- a/client/internal/lazyconn/inactivity/inactivity.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package inactivity
-
-import (
-	"context"
-	"time"
-
-	peer "github.com/netbirdio/netbird/client/internal/peer/id"
-)
-
-const (
-	DefaultInactivityThreshold = 60 * time.Minute // idle after 1 hour inactivity
-	MinimumInactivityThreshold = 3 * time.Minute
-)
-
-type Monitor struct {
-	id                  peer.ConnID
-	timer               *time.Timer
-	cancel              context.CancelFunc
-	inactivityThreshold time.Duration
-}
-
-func NewInactivityMonitor(peerID peer.ConnID, threshold time.Duration) *Monitor {
-	i := &Monitor{
-		id:                  peerID,
-		timer:               time.NewTimer(0),
-		inactivityThreshold: threshold,
-	}
-	i.timer.Stop()
-	return i
-}
-
-func (i *Monitor) Start(ctx context.Context, timeoutChan chan peer.ConnID) {
-	i.timer.Reset(i.inactivityThreshold)
-	defer i.timer.Stop()
-
-	ctx, i.cancel = context.WithCancel(ctx)
-	defer func() {
-		defer i.cancel()
-		select {
-		case <-i.timer.C:
-		default:
-		}
-	}()
-
-	select {
-	case <-i.timer.C:
-		select {
-		case timeoutChan <- i.id:
-		case <-ctx.Done():
-			return
-		}
-	case <-ctx.Done():
-		return
-	}
-}
-
-func (i *Monitor) Stop() {
-	if i.cancel == nil {
-		return
-	}
-	i.cancel()
-}
-
-func (i *Monitor) PauseTimer() {
-	i.timer.Stop()
-}
-
-func (i *Monitor) ResetTimer() {
-	i.timer.Reset(i.inactivityThreshold)
-}
-
-func (i *Monitor) ResetMonitor(ctx context.Context, timeoutChan chan peer.ConnID) {
-	i.Stop()
-	go i.Start(ctx, timeoutChan)
-}
diff --git a/client/internal/lazyconn/inactivity/manager.go b/client/internal/lazyconn/inactivity/manager.go
new file mode 100644
index 00000000..0120f443
--- /dev/null
+++ b/client/internal/lazyconn/inactivity/manager.go
@@ -0,0 +1,155 @@
+package inactivity
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	log "github.com/sirupsen/logrus"
+
+	"github.com/netbirdio/netbird/client/internal/lazyconn"
+	"github.com/netbirdio/netbird/monotime"
+)
+
+const (
+	checkInterval = 1 * time.Minute
+
+	DefaultInactivityThreshold = 15 * time.Minute
+	MinimumInactivityThreshold = 1 * time.Minute
+)
+
+type WgInterface interface {
+	LastActivities() map[string]monotime.Time
+}
+
+type Manager struct {
+	inactivePeersChan chan map[string]struct{}
+
+	iface               WgInterface
+	interestedPeers     map[string]*lazyconn.PeerConfig
+	inactivityThreshold time.Duration
+}
+
+func NewManager(iface WgInterface, configuredThreshold *time.Duration) *Manager {
+	inactivityThreshold, err := validateInactivityThreshold(configuredThreshold)
+	if err != nil {
+		inactivityThreshold = DefaultInactivityThreshold
+		log.Warnf("invalid inactivity threshold configured: %v, using default: %v", err, DefaultInactivityThreshold)
+	}
+
+	log.Infof("inactivity threshold configured: %v", inactivityThreshold)
+	return &Manager{
+		inactivePeersChan:   make(chan map[string]struct{}, 1),
+		iface:               iface,
+		interestedPeers:     make(map[string]*lazyconn.PeerConfig),
+		inactivityThreshold: inactivityThreshold,
+	}
+}
+
+func (m *Manager) InactivePeersChan() chan map[string]struct{} {
+	if m == nil {
+		// return a nil channel that blocks forever
+		return nil
+	}
+
+	return m.inactivePeersChan
+}
+
+func (m *Manager) AddPeer(peerCfg *lazyconn.PeerConfig) {
+	if m == nil {
+		return
+	}
+
+	if _, exists := m.interestedPeers[peerCfg.PublicKey]; exists {
+		return
+	}
+
+	peerCfg.Log.Infof("adding peer to inactivity manager")
+	m.interestedPeers[peerCfg.PublicKey] = peerCfg
+}
+
+func (m *Manager) RemovePeer(peer string) {
+	if m == nil {
+		return
+	}
+
+	pi, ok := m.interestedPeers[peer]
+	if !ok {
+		return
+	}
+
+	pi.Log.Debugf("remove peer from inactivity manager")
+	delete(m.interestedPeers, peer)
+}
+
+func (m *Manager) Start(ctx context.Context) {
+	if m == nil {
+		return
+	}
+
+	ticker := newTicker(checkInterval)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-ticker.C():
+			idlePeers, err := m.checkStats()
+			if err != nil {
+				log.Errorf("error checking stats: %v", err)
+				return
+			}
+
+			if len(idlePeers) == 0 {
+				continue
+			}
+
+			m.notifyInactivePeers(ctx, idlePeers)
+		}
+	}
+}
+
+func (m *Manager) notifyInactivePeers(ctx context.Context, inactivePeers map[string]struct{}) {
+	select {
+	case m.inactivePeersChan <- inactivePeers:
+	case <-ctx.Done():
+		return
+	default:
+		return
+	}
+}
+
+func (m *Manager) checkStats() (map[string]struct{}, error) {
+	lastActivities := m.iface.LastActivities()
+
+	idlePeers := make(map[string]struct{})
+
+	checkTime := time.Now()
+	for peerID, peerCfg := range m.interestedPeers {
+		lastActive, ok := lastActivities[peerID]
+		if !ok {
+			// when peer is in connecting state
+			peerCfg.Log.Warnf("peer not found in wg stats")
+			continue
+		}
+
+		since := monotime.Since(lastActive)
+		if since > m.inactivityThreshold {
+			peerCfg.Log.Infof("peer is inactive since time: %s", checkTime.Add(-since).String())
+			idlePeers[peerID] = struct{}{}
+		}
+	}
+
+	return idlePeers, nil
+}
+
+func validateInactivityThreshold(configuredThreshold *time.Duration) (time.Duration, error) {
+	if configuredThreshold == nil {
+		return DefaultInactivityThreshold, nil
+	}
+	if *configuredThreshold < MinimumInactivityThreshold {
+		return 0, fmt.Errorf("configured inactivity threshold %v is too low, using %v", *configuredThreshold, MinimumInactivityThreshold)
+	}
+	return *configuredThreshold, nil
+}
diff --git a/client/internal/lazyconn/inactivity/ticker.go b/client/internal/lazyconn/inactivity/ticker.go
new file mode 100644
index 00000000..12b64bd5
--- /dev/null
+++ b/client/internal/lazyconn/inactivity/ticker.go
@@ -0,0 +1,24 @@
+package inactivity
+
+import "time"
+
+var newTicker = func(d time.Duration) Ticker {
+	return &realTicker{t: time.NewTicker(d)}
+}
+
+type Ticker interface {
+	C() <-chan time.Time
+	Stop()
+}
+
+type realTicker struct {
+	t *time.Ticker
+}
+
+func (r *realTicker) C() <-chan time.Time {
+	return r.t.C
+}
+
+func (r *realTicker) Stop() {
+	r.t.Stop()
+}
diff --git a/client/internal/lazyconn/manager/manager.go b/client/internal/lazyconn/manager/manager.go
index 74ede50a..b6b3c609 100644
--- a/client/internal/lazyconn/manager/manager.go
+++ b/client/internal/lazyconn/manager/manager.go
@@ -11,7 +11,6 @@ import (
 	"github.com/netbirdio/netbird/client/internal/lazyconn"
 	"github.com/netbirdio/netbird/client/internal/lazyconn/activity"
 	"github.com/netbirdio/netbird/client/internal/lazyconn/inactivity"
-	"github.com/netbirdio/netbird/client/internal/peer/dispatcher"
 	peerid "github.com/netbirdio/netbird/client/internal/peer/id"
 	"github.com/netbirdio/netbird/client/internal/peerstore"
 	"github.com/netbirdio/netbird/route"
@@ -43,60 +42,46 @@ type Config struct {
 type Manager struct {
 	engineCtx           context.Context
 	peerStore           *peerstore.Store
-	connStateDispatcher *dispatcher.ConnectionDispatcher
 	inactivityThreshold time.Duration
 
-	connStateListener    *dispatcher.ConnectionListener
 	managedPeers         map[string]*lazyconn.PeerConfig
 	managedPeersByConnID map[peerid.ConnID]*managedPeer
 	excludes             map[string]lazyconn.PeerConfig
 	managedPeersMu       sync.Mutex
 
-	activityManager    *activity.Manager
-	inactivityMonitors map[peerid.ConnID]*inactivity.Monitor
+	activityManager   *activity.Manager
+	inactivityManager *inactivity.Manager
 
 	// Route HA group management
+	// If any peer in the same HA group is active, all peers in that group should prevent going idle
 	peerToHAGroups map[string][]route.HAUniqueID // peer ID -> HA groups they belong to
 	haGroupToPeers map[route.HAUniqueID][]string // HA group -> peer IDs in the group
 	routesMu       sync.RWMutex
-
-	onInactive chan peerid.ConnID
 }
 
 // NewManager creates a new lazy connection manager
 // engineCtx is the context for creating peer Connection
-func NewManager(config Config, engineCtx context.Context, peerStore *peerstore.Store, wgIface lazyconn.WGIface, connStateDispatcher *dispatcher.ConnectionDispatcher) *Manager {
+func NewManager(config Config, engineCtx context.Context, peerStore *peerstore.Store, wgIface lazyconn.WGIface) *Manager {
 	log.Infof("setup lazy connection service")
+
 	m := &Manager{
 		engineCtx:            engineCtx,
 		peerStore:            peerStore,
-		connStateDispatcher:  connStateDispatcher,
 		inactivityThreshold:  inactivity.DefaultInactivityThreshold,
 		managedPeers:         make(map[string]*lazyconn.PeerConfig),
 		managedPeersByConnID: make(map[peerid.ConnID]*managedPeer),
 		excludes:             make(map[string]lazyconn.PeerConfig),
 		activityManager:      activity.NewManager(wgIface),
-		inactivityMonitors:   make(map[peerid.ConnID]*inactivity.Monitor),
 		peerToHAGroups:       make(map[string][]route.HAUniqueID),
 		haGroupToPeers:       make(map[route.HAUniqueID][]string),
-		onInactive:           make(chan peerid.ConnID),
-	}
-
-	if config.InactivityThreshold != nil {
-		if *config.InactivityThreshold >= inactivity.MinimumInactivityThreshold {
-			m.inactivityThreshold = *config.InactivityThreshold
-		} else {
-			log.Warnf("inactivity threshold is too low, using %v", m.inactivityThreshold)
-		}
 	}
 
-	m.connStateListener = &dispatcher.ConnectionListener{
-		OnConnected:    m.onPeerConnected,
-		OnDisconnected: m.onPeerDisconnected,
+	if wgIface.IsUserspaceBind() {
+		m.inactivityManager = inactivity.NewManager(wgIface, config.InactivityThreshold)
+	} else {
+		log.Warnf("inactivity manager not supported for kernel mode, wait for remote peer to close the connection")
 	}
 
-	connStateDispatcher.AddListener(m.connStateListener)
-
 	return m
 }
 
@@ -131,24 +116,28 @@ func (m *Manager) UpdateRouteHAMap(haMap route.HAMap) {
 		}
 	}
 
-	log.Debugf("updated route HA mappings: %d HA groups, %d peers with routes",
-		len(m.haGroupToPeers), len(m.peerToHAGroups))
+	log.Debugf("updated route HA mappings: %d HA groups, %d peers with routes", len(m.haGroupToPeers), len(m.peerToHAGroups))
 }
 
 // Start starts the manager and listens for peer activity and inactivity events
 func (m *Manager) Start(ctx context.Context) {
 	defer m.close()
 
+	if m.inactivityManager != nil {
+		go m.inactivityManager.Start(ctx)
+	}
+
 	for {
 		select {
 		case <-ctx.Done():
 			return
 		case peerConnID := <-m.activityManager.OnActivityChan:
-			m.onPeerActivity(ctx, peerConnID)
-		case peerConnID := <-m.onInactive:
-			m.onPeerInactivityTimedOut(ctx, peerConnID)
+			m.onPeerActivity(peerConnID)
+		case peerIDs := <-m.inactivityManager.InactivePeersChan():
+			m.onPeerInactivityTimedOut(peerIDs)
 		}
 	}
+
 }
 
 // ExcludePeer marks peers for a permanent connection
@@ -156,7 +145,7 @@ func (m *Manager) Start(ctx context.Context) {
 // Adds them back to the managed list and start the inactivity listener if they are removed from the exclude list. In
 // this case, we suppose that the connection status is connected or connecting.
 // If the peer is not exists yet in the managed list then the responsibility is the upper layer to call the AddPeer function
-func (m *Manager) ExcludePeer(ctx context.Context, peerConfigs []lazyconn.PeerConfig) []string {
+func (m *Manager) ExcludePeer(peerConfigs []lazyconn.PeerConfig) []string {
 	m.managedPeersMu.Lock()
 	defer m.managedPeersMu.Unlock()
 
@@ -187,7 +176,7 @@ func (m *Manager) ExcludePeer(ctx context.Context, peerConfigs []lazyconn.PeerCo
 
 		peerCfg.Log.Infof("peer removed from lazy connection exclude list")
 
-		if err := m.addActivePeer(ctx, peerCfg); err != nil {
+		if err := m.addActivePeer(&peerCfg); err != nil {
 			log.Errorf("failed to add peer to lazy connection manager: %s", err)
 			continue
 		}
@@ -197,7 +186,7 @@ func (m *Manager) ExcludePeer(ctx context.Context, peerConfigs []lazyconn.PeerCo
 	return added
 }
 
-func (m *Manager) AddPeer(ctx context.Context, peerCfg lazyconn.PeerConfig) (bool, error) {
+func (m *Manager) AddPeer(peerCfg lazyconn.PeerConfig) (bool, error) {
 	m.managedPeersMu.Lock()
 	defer m.managedPeersMu.Unlock()
 
@@ -217,9 +206,6 @@ func (m *Manager) AddPeer(ctx context.Context, peerCfg lazyconn.PeerConfig) (boo
 		return false, err
 	}
 
-	im := inactivity.NewInactivityMonitor(peerCfg.PeerConnID, m.inactivityThreshold)
-	m.inactivityMonitors[peerCfg.PeerConnID] = im
-
 	m.managedPeers[peerCfg.PublicKey] = &peerCfg
 	m.managedPeersByConnID[peerCfg.PeerConnID] = &managedPeer{
 		peerCfg:         &peerCfg,
@@ -229,7 +215,7 @@ func (m *Manager) AddPeer(ctx context.Context, peerCfg lazyconn.PeerConfig) (boo
 	// Check if this peer should be activated because its HA group peers are active
 	if group, ok := m.shouldActivateNewPeer(peerCfg.PublicKey); ok {
 		peerCfg.Log.Debugf("peer belongs to active HA group %s, will activate immediately", group)
-		m.activateNewPeerInActiveGroup(ctx, peerCfg)
+		m.activateNewPeerInActiveGroup(peerCfg)
 	}
 
 	return false, nil
@@ -237,7 +223,7 @@ func (m *Manager) AddPeer(ctx context.Context, peerCfg lazyconn.PeerConfig) (boo
 
 // AddActivePeers adds a list of peers to the lazy connection manager
 // suppose these peers was in connected or in connecting states
-func (m *Manager) AddActivePeers(ctx context.Context, peerCfg []lazyconn.PeerConfig) error {
+func (m *Manager) AddActivePeers(peerCfg []lazyconn.PeerConfig) error {
 	m.managedPeersMu.Lock()
 	defer m.managedPeersMu.Unlock()
 
@@ -247,7 +233,7 @@ func (m *Manager) AddActivePeers(ctx context.Context, peerCfg []lazyconn.PeerCon
 			continue
 		}
 
-		if err := m.addActivePeer(ctx, cfg); err != nil {
+		if err := m.addActivePeer(&cfg); err != nil {
 			cfg.Log.Errorf("failed to add peer to lazy connection manager: %v", err)
 			return err
 		}
@@ -264,7 +250,7 @@ func (m *Manager) RemovePeer(peerID string) {
 
 // ActivatePeer activates a peer connection when a signal message is received
 // Also activates all peers in the same HA groups as this peer
-func (m *Manager) ActivatePeer(ctx context.Context, peerID string) (found bool) {
+func (m *Manager) ActivatePeer(peerID string) (found bool) {
 	m.managedPeersMu.Lock()
 	defer m.managedPeersMu.Unlock()
 	cfg, mp := m.getPeerForActivation(peerID)
@@ -272,15 +258,43 @@ func (m *Manager) ActivatePeer(ctx context.Context, peerID string) (found bool)
 		return false
 	}
 
-	if !m.activateSinglePeer(ctx, cfg, mp) {
+	cfg.Log.Infof("activate peer from inactive state by remote signal message")
+
+	if !m.activateSinglePeer(cfg, mp) {
 		return false
 	}
 
-	m.activateHAGroupPeers(ctx, peerID)
-
+	m.activateHAGroupPeers(cfg)
 	return true
 }
 
+func (m *Manager) DeactivatePeer(peerID peerid.ConnID) {
+	m.managedPeersMu.Lock()
+	defer m.managedPeersMu.Unlock()
+
+	mp, ok := m.managedPeersByConnID[peerID]
+	if !ok {
+		return
+	}
+
+	if mp.expectedWatcher != watcherInactivity {
+		return
+	}
+
+	m.peerStore.PeerConnClose(mp.peerCfg.PublicKey)
+
+	mp.peerCfg.Log.Infof("start activity monitor")
+
+	mp.expectedWatcher = watcherActivity
+
+	m.inactivityManager.RemovePeer(mp.peerCfg.PublicKey)
+
+	if err := m.activityManager.MonitorPeerActivity(*mp.peerCfg); err != nil {
+		mp.peerCfg.Log.Errorf("failed to create activity monitor: %v", err)
+		return
+	}
+}
+
 // getPeerForActivation checks if a peer can be activated and returns the necessary structs
 // Returns nil values if the peer should be skipped
 func (m *Manager) getPeerForActivation(peerID string) (*lazyconn.PeerConfig, *managedPeer) {
@@ -302,41 +316,36 @@ func (m *Manager) getPeerForActivation(peerID string) (*lazyconn.PeerConfig, *ma
 	return cfg, mp
 }
 
-// activateSinglePeer activates a single peer (internal method)
-func (m *Manager) activateSinglePeer(ctx context.Context, cfg *lazyconn.PeerConfig, mp *managedPeer) bool {
-	mp.expectedWatcher = watcherInactivity
-
-	m.activityManager.RemovePeer(cfg.Log, cfg.PeerConnID)
-
-	im, ok := m.inactivityMonitors[cfg.PeerConnID]
-	if !ok {
-		cfg.Log.Errorf("inactivity monitor not found for peer")
+// activateSinglePeer activates a single peer
+// return true if the peer was activated, false if it was already active
+func (m *Manager) activateSinglePeer(cfg *lazyconn.PeerConfig, mp *managedPeer) bool {
+	if mp.expectedWatcher == watcherInactivity {
 		return false
 	}
 
-	cfg.Log.Infof("starting inactivity monitor")
-	go im.Start(ctx, m.onInactive)
-
+	mp.expectedWatcher = watcherInactivity
+	m.activityManager.RemovePeer(cfg.Log, cfg.PeerConnID)
+	m.inactivityManager.AddPeer(cfg)
 	return true
 }
 
 // activateHAGroupPeers activates all peers in HA groups that the given peer belongs to
-func (m *Manager) activateHAGroupPeers(ctx context.Context, triggerPeerID string) {
+func (m *Manager) activateHAGroupPeers(triggeredPeerCfg *lazyconn.PeerConfig) {
 	var peersToActivate []string
 
 	m.routesMu.RLock()
-	haGroups := m.peerToHAGroups[triggerPeerID]
+	haGroups := m.peerToHAGroups[triggeredPeerCfg.PublicKey]
 
 	if len(haGroups) == 0 {
 		m.routesMu.RUnlock()
-		log.Debugf("peer %s is not part of any HA groups", triggerPeerID)
+		triggeredPeerCfg.Log.Debugf("peer is not part of any HA groups")
 		return
 	}
 
 	for _, haGroup := range haGroups {
 		peers := m.haGroupToPeers[haGroup]
 		for _, peerID := range peers {
-			if peerID != triggerPeerID {
+			if peerID != triggeredPeerCfg.PublicKey {
 				peersToActivate = append(peersToActivate, peerID)
 			}
 		}
@@ -350,16 +359,16 @@ func (m *Manager) activateHAGroupPeers(ctx context.Context, triggerPeerID string
 			continue
 		}
 
-		if m.activateSinglePeer(ctx, cfg, mp) {
+		if m.activateSinglePeer(cfg, mp) {
 			activatedCount++
-			cfg.Log.Infof("activated peer as part of HA group (triggered by %s)", triggerPeerID)
+			cfg.Log.Infof("activated peer as part of HA group (triggered by %s)", triggeredPeerCfg.PublicKey)
 			m.peerStore.PeerConnOpen(m.engineCtx, cfg.PublicKey)
 		}
 	}
 
 	if activatedCount > 0 {
 		log.Infof("activated %d additional peers in HA groups for peer %s (groups: %v)",
-			activatedCount, triggerPeerID, haGroups)
+			activatedCount, triggeredPeerCfg.PublicKey, haGroups)
 	}
 }
 
@@ -394,13 +403,13 @@ func (m *Manager) shouldActivateNewPeer(peerID string) (route.HAUniqueID, bool)
 }
 
 // activateNewPeerInActiveGroup activates a newly added peer that should be active due to HA group
-func (m *Manager) activateNewPeerInActiveGroup(ctx context.Context, peerCfg lazyconn.PeerConfig) {
+func (m *Manager) activateNewPeerInActiveGroup(peerCfg lazyconn.PeerConfig) {
 	mp, ok := m.managedPeersByConnID[peerCfg.PeerConnID]
 	if !ok {
 		return
 	}
 
-	if !m.activateSinglePeer(ctx, &peerCfg, mp) {
+	if !m.activateSinglePeer(&peerCfg, mp) {
 		return
 	}
 
@@ -408,23 +417,19 @@ func (m *Manager) activateNewPeerInActiveGroup(ctx context.Context, peerCfg lazy
 	m.peerStore.PeerConnOpen(m.engineCtx, peerCfg.PublicKey)
 }
 
-func (m *Manager) addActivePeer(ctx context.Context, peerCfg lazyconn.PeerConfig) error {
+func (m *Manager) addActivePeer(peerCfg *lazyconn.PeerConfig) error {
 	if _, ok := m.managedPeers[peerCfg.PublicKey]; ok {
 		peerCfg.Log.Warnf("peer already managed")
 		return nil
 	}
 
-	im := inactivity.NewInactivityMonitor(peerCfg.PeerConnID, m.inactivityThreshold)
-	m.inactivityMonitors[peerCfg.PeerConnID] = im
-
-	m.managedPeers[peerCfg.PublicKey] = &peerCfg
+	m.managedPeers[peerCfg.PublicKey] = peerCfg
 	m.managedPeersByConnID[peerCfg.PeerConnID] = &managedPeer{
-		peerCfg:         &peerCfg,
+		peerCfg:         peerCfg,
 		expectedWatcher: watcherInactivity,
 	}
 
-	peerCfg.Log.Infof("starting inactivity monitor on peer that has been removed from exclude list")
-	go im.Start(ctx, m.onInactive)
+	m.inactivityManager.AddPeer(peerCfg)
 	return nil
 }
 
@@ -436,12 +441,7 @@ func (m *Manager) removePeer(peerID string) {
 
 	cfg.Log.Infof("removing lazy peer")
 
-	if im, ok := m.inactivityMonitors[cfg.PeerConnID]; ok {
-		im.Stop()
-		delete(m.inactivityMonitors, cfg.PeerConnID)
-		cfg.Log.Debugf("inactivity monitor stopped")
-	}
-
+	m.inactivityManager.RemovePeer(cfg.PublicKey)
 	m.activityManager.RemovePeer(cfg.Log, cfg.PeerConnID)
 	delete(m.managedPeers, peerID)
 	delete(m.managedPeersByConnID, cfg.PeerConnID)
@@ -451,12 +451,8 @@ func (m *Manager) close() {
 	m.managedPeersMu.Lock()
 	defer m.managedPeersMu.Unlock()
 
-	m.connStateDispatcher.RemoveListener(m.connStateListener)
 	m.activityManager.Close()
-	for _, iw := range m.inactivityMonitors {
-		iw.Stop()
-	}
-	m.inactivityMonitors = make(map[peerid.ConnID]*inactivity.Monitor)
+
 	m.managedPeers = make(map[string]*lazyconn.PeerConfig)
 	m.managedPeersByConnID = make(map[peerid.ConnID]*managedPeer)
 
@@ -470,7 +466,7 @@ func (m *Manager) close() {
 }
 
 // shouldDeferIdleForHA checks if peer should stay connected due to HA group requirements
-func (m *Manager) shouldDeferIdleForHA(peerID string) bool {
+func (m *Manager) shouldDeferIdleForHA(inactivePeers map[string]struct{}, peerID string) bool {
 	m.routesMu.RLock()
 	defer m.routesMu.RUnlock()
 
@@ -480,38 +476,45 @@ func (m *Manager) shouldDeferIdleForHA(peerID string) bool {
 	}
 
 	for _, haGroup := range haGroups {
-		groupPeers := m.haGroupToPeers[haGroup]
+		if active := m.checkHaGroupActivity(haGroup, peerID, inactivePeers); active {
+			return true
+		}
+	}
 
-		for _, groupPeerID := range groupPeers {
-			if groupPeerID == peerID {
-				continue
-			}
+	return false
+}
 
-			cfg, ok := m.managedPeers[groupPeerID]
-			if !ok {
-				continue
-			}
+func (m *Manager) checkHaGroupActivity(haGroup route.HAUniqueID, peerID string, inactivePeers map[string]struct{}) bool {
+	groupPeers := m.haGroupToPeers[haGroup]
+	for _, groupPeerID := range groupPeers {
 
-			groupMp, ok := m.managedPeersByConnID[cfg.PeerConnID]
-			if !ok {
-				continue
-			}
+		if groupPeerID == peerID {
+			continue
+		}
 
-			if groupMp.expectedWatcher != watcherInactivity {
-				continue
-			}
+		cfg, ok := m.managedPeers[groupPeerID]
+		if !ok {
+			continue
+		}
 
-			// Other member is still connected, defer idle
-			if peer, ok := m.peerStore.PeerConn(groupPeerID); ok && peer.IsConnected() {
-				return true
-			}
+		groupMp, ok := m.managedPeersByConnID[cfg.PeerConnID]
+		if !ok {
+			continue
 		}
-	}
 
+		if groupMp.expectedWatcher != watcherInactivity {
+			continue
+		}
+
+		// If any peer in the group is active, do defer idle
+		if _, isInactive := inactivePeers[groupPeerID]; !isInactive {
+			return true
+		}
+	}
 	return false
 }
 
-func (m *Manager) onPeerActivity(ctx context.Context, peerConnID peerid.ConnID) {
+func (m *Manager) onPeerActivity(peerConnID peerid.ConnID) {
 	m.managedPeersMu.Lock()
 	defer m.managedPeersMu.Unlock()
 
@@ -528,100 +531,56 @@ func (m *Manager) onPeerActivity(ctx context.Context, peerConnID peerid.ConnID)
 
 	mp.peerCfg.Log.Infof("detected peer activity")
 
-	if !m.activateSinglePeer(ctx, mp.peerCfg, mp) {
+	if !m.activateSinglePeer(mp.peerCfg, mp) {
 		return
 	}
 
-	m.activateHAGroupPeers(ctx, mp.peerCfg.PublicKey)
+	m.activateHAGroupPeers(mp.peerCfg)
 
 	m.peerStore.PeerConnOpen(m.engineCtx, mp.peerCfg.PublicKey)
 }
 
-func (m *Manager) onPeerInactivityTimedOut(ctx context.Context, peerConnID peerid.ConnID) {
+func (m *Manager) onPeerInactivityTimedOut(peerIDs map[string]struct{}) {
 	m.managedPeersMu.Lock()
 	defer m.managedPeersMu.Unlock()
 
-	mp, ok := m.managedPeersByConnID[peerConnID]
-	if !ok {
-		log.Errorf("peer not found by id: %v", peerConnID)
-		return
-	}
-
-	if mp.expectedWatcher != watcherInactivity {
-		mp.peerCfg.Log.Warnf("ignore inactivity event")
-		return
-	}
-
-	if m.shouldDeferIdleForHA(mp.peerCfg.PublicKey) {
-		iw, ok := m.inactivityMonitors[peerConnID]
-		if ok {
-			mp.peerCfg.Log.Debugf("resetting inactivity timer due to HA group requirements")
-			iw.ResetMonitor(ctx, m.onInactive)
-		} else {
-			mp.peerCfg.Log.Errorf("inactivity monitor not found for HA defer reset")
+	for peerID := range peerIDs {
+		peerCfg, ok := m.managedPeers[peerID]
+		if !ok {
+			log.Errorf("peer not found by peerId: %v", peerID)
+			continue
 		}
-		return
-	}
-
-	mp.peerCfg.Log.Infof("connection timed out")
-
-	// this is blocking operation, potentially can be optimized
-	m.peerStore.PeerConnClose(mp.peerCfg.PublicKey)
-
-	mp.peerCfg.Log.Infof("start activity monitor")
 
-	mp.expectedWatcher = watcherActivity
-
-	// just in case free up
-	m.inactivityMonitors[peerConnID].PauseTimer()
-
-	if err := m.activityManager.MonitorPeerActivity(*mp.peerCfg); err != nil {
-		mp.peerCfg.Log.Errorf("failed to create activity monitor: %v", err)
-		return
-	}
-}
-
-func (m *Manager) onPeerConnected(peerConnID peerid.ConnID) {
-	m.managedPeersMu.Lock()
-	defer m.managedPeersMu.Unlock()
+		mp, ok := m.managedPeersByConnID[peerCfg.PeerConnID]
+		if !ok {
+			log.Errorf("peer not found by conn id: %v", peerCfg.PeerConnID)
+			continue
+		}
 
-	mp, ok := m.managedPeersByConnID[peerConnID]
-	if !ok {
-		return
-	}
+		if mp.expectedWatcher != watcherInactivity {
+			mp.peerCfg.Log.Warnf("ignore inactivity event")
+			continue
+		}
 
-	if mp.expectedWatcher != watcherInactivity {
-		return
-	}
+		if m.shouldDeferIdleForHA(peerIDs, mp.peerCfg.PublicKey) {
+			mp.peerCfg.Log.Infof("defer inactivity due to active HA group peers")
+			continue
+		}
 
-	iw, ok := m.inactivityMonitors[mp.peerCfg.PeerConnID]
-	if !ok {
-		mp.peerCfg.Log.Warnf("inactivity monitor not found for peer")
-		return
-	}
+		mp.peerCfg.Log.Infof("connection timed out")
 
-	mp.peerCfg.Log.Infof("peer connected, pausing inactivity monitor while connection is not disconnected")
-	iw.PauseTimer()
-}
+		// this is blocking operation, potentially can be optimized
+		m.peerStore.PeerConnIdle(mp.peerCfg.PublicKey)
 
-func (m *Manager) onPeerDisconnected(peerConnID peerid.ConnID) {
-	m.managedPeersMu.Lock()
-	defer m.managedPeersMu.Unlock()
+		mp.expectedWatcher = watcherActivity
 
-	mp, ok := m.managedPeersByConnID[peerConnID]
-	if !ok {
-		return
-	}
+		m.inactivityManager.RemovePeer(mp.peerCfg.PublicKey)
 
-	if mp.expectedWatcher != watcherInactivity {
-		return
-	}
+		mp.peerCfg.Log.Infof("start activity monitor")
 
-	iw, ok := m.inactivityMonitors[mp.peerCfg.PeerConnID]
-	if !ok {
-		return
+		if err := m.activityManager.MonitorPeerActivity(*mp.peerCfg); err != nil {
+			mp.peerCfg.Log.Errorf("failed to create activity monitor: %v", err)
+			continue
+		}
 	}
-
-	mp.peerCfg.Log.Infof("reset inactivity monitor timer")
-	iw.ResetTimer()
 }
diff --git a/client/internal/lazyconn/wgiface.go b/client/internal/lazyconn/wgiface.go
index 090a9319..0351904f 100644
--- a/client/internal/lazyconn/wgiface.go
+++ b/client/internal/lazyconn/wgiface.go
@@ -6,9 +6,13 @@ import (
 	"time"
 
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+
+	"github.com/netbirdio/netbird/monotime"
 )
 
 type WGIface interface {
 	RemovePeer(peerKey string) error
 	UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
+	IsUserspaceBind() bool
+	LastActivities() map[string]monotime.Time
 }
diff --git a/client/internal/login.go b/client/internal/login.go
index bbf844eb..53fa17d9 100644
--- a/client/internal/login.go
+++ b/client/internal/login.go
@@ -148,7 +148,7 @@ func registerPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.
 	)
 	loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info, pubSSHKey, config.DNSLabels)
 	if err != nil {
-		log.Errorf("failed registering peer %v,%s", err, validSetupKey.String())
+		log.Errorf("failed registering peer %v", err)
 		return nil, err
 	}
 
diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go
index c3f44cc7..ddd90450 100644
--- a/client/internal/peer/conn.go
+++ b/client/internal/peer/conn.go
@@ -26,7 +26,6 @@ import (
 	"github.com/netbirdio/netbird/client/internal/stdnet"
 	relayClient "github.com/netbirdio/netbird/relay/client"
 	"github.com/netbirdio/netbird/route"
-	nbnet "github.com/netbirdio/netbird/util/net"
 	semaphoregroup "github.com/netbirdio/netbird/util/semaphore-group"
 )
 
@@ -106,10 +105,6 @@ type Conn struct {
 	workerRelay *WorkerRelay
 	wgWatcherWg sync.WaitGroup
 
-	connIDRelay          nbnet.ConnectionID
-	connIDICE            nbnet.ConnectionID
-	beforeAddPeerHooks   []nbnet.AddHookFunc
-	afterRemovePeerHooks []nbnet.RemoveHookFunc
 	// used to store the remote Rosenpass key for Relayed connection in case of connection update from ice
 	rosenpassRemoteKey []byte
 
@@ -117,10 +112,9 @@ type Conn struct {
 	wgProxyRelay wgproxy.Proxy
 	handshaker   *Handshaker
 
-	guard              *guard.Guard
-	semaphore          *semaphoregroup.SemaphoreGroup
-	peerConnDispatcher *dispatcher.ConnectionDispatcher
-	wg                 sync.WaitGroup
+	guard     *guard.Guard
+	semaphore *semaphoregroup.SemaphoreGroup
+	wg        sync.WaitGroup
 
 	// debug purpose
 	dumpState *stateDump
@@ -136,18 +130,17 @@ func NewConn(config ConnConfig, services ServiceDependencies) (*Conn, error) {
 	connLog := log.WithField("peer", config.Key)
 
 	var conn = &Conn{
-		Log:                connLog,
-		config:             config,
-		statusRecorder:     services.StatusRecorder,
-		signaler:           services.Signaler,
-		iFaceDiscover:      services.IFaceDiscover,
-		relayManager:       services.RelayManager,
-		srWatcher:          services.SrWatcher,
-		semaphore:          services.Semaphore,
-		peerConnDispatcher: services.PeerConnDispatcher,
-		statusRelay:        worker.NewAtomicStatus(),
-		statusICE:          worker.NewAtomicStatus(),
-		dumpState:          newStateDump(config.Key, connLog, services.StatusRecorder),
+		Log:            connLog,
+		config:         config,
+		statusRecorder: services.StatusRecorder,
+		signaler:       services.Signaler,
+		iFaceDiscover:  services.IFaceDiscover,
+		relayManager:   services.RelayManager,
+		srWatcher:      services.SrWatcher,
+		semaphore:      services.Semaphore,
+		statusRelay:    worker.NewAtomicStatus(),
+		statusICE:      worker.NewAtomicStatus(),
+		dumpState:      newStateDump(config.Key, connLog, services.StatusRecorder),
 	}
 
 	return conn, nil
@@ -169,7 +162,7 @@ func (conn *Conn) Open(engineCtx context.Context) error {
 
 	conn.ctx, conn.ctxCancel = context.WithCancel(engineCtx)
 
-	conn.workerRelay = NewWorkerRelay(conn.Log, isController(conn.config), conn.config, conn, conn.relayManager, conn.dumpState)
+	conn.workerRelay = NewWorkerRelay(conn.ctx, conn.Log, isController(conn.config), conn.config, conn, conn.relayManager, conn.dumpState)
 
 	relayIsSupportedLocally := conn.workerRelay.RelayIsSupportedLocally()
 	workerICE, err := NewWorkerICE(conn.ctx, conn.Log, conn.config, conn, conn.signaler, conn.iFaceDiscover, conn.statusRecorder, relayIsSupportedLocally)
@@ -226,7 +219,7 @@ func (conn *Conn) Open(engineCtx context.Context) error {
 }
 
 // Close closes this peer Conn issuing a close event to the Conn closeCh
-func (conn *Conn) Close() {
+func (conn *Conn) Close(signalToRemote bool) {
 	conn.mu.Lock()
 	defer conn.wgWatcherWg.Wait()
 	defer conn.mu.Unlock()
@@ -236,6 +229,12 @@ func (conn *Conn) Close() {
 		return
 	}
 
+	if signalToRemote {
+		if err := conn.signaler.SignalIdle(conn.config.Key); err != nil {
+			conn.Log.Errorf("failed to signal idle state to peer: %v", err)
+		}
+	}
+
 	conn.Log.Infof("close peer connection")
 	conn.ctxCancel()
 
@@ -263,8 +262,6 @@ func (conn *Conn) Close() {
 		conn.Log.Errorf("failed to remove wg endpoint: %v", err)
 	}
 
-	conn.freeUpConnID()
-
 	if conn.evalStatus() == StatusConnected && conn.onDisconnected != nil {
 		conn.onDisconnected(conn.config.WgConfig.RemoteKey)
 	}
@@ -289,13 +286,6 @@ func (conn *Conn) OnRemoteCandidate(candidate ice.Candidate, haRoutes route.HAMa
 	conn.workerICE.OnRemoteCandidate(candidate, haRoutes)
 }
 
-func (conn *Conn) AddBeforeAddPeerHook(hook nbnet.AddHookFunc) {
-	conn.beforeAddPeerHooks = append(conn.beforeAddPeerHooks, hook)
-}
-func (conn *Conn) AddAfterRemovePeerHook(hook nbnet.RemoveHookFunc) {
-	conn.afterRemovePeerHooks = append(conn.afterRemovePeerHooks, hook)
-}
-
 // SetOnConnected sets a handler function to be triggered by Conn when a new connection to a remote peer established
 func (conn *Conn) SetOnConnected(handler func(remoteWireGuardKey string, remoteRosenpassPubKey []byte, wireGuardIP string, remoteRosenpassAddr string)) {
 	conn.onConnected = handler
@@ -383,10 +373,6 @@ func (conn *Conn) onICEConnectionIsReady(priority conntype.ConnPriority, iceConn
 		ep = directEp
 	}
 
-	if err := conn.runBeforeAddPeerHooks(ep.IP); err != nil {
-		conn.Log.Errorf("Before add peer hook failed: %v", err)
-	}
-
 	conn.workerRelay.DisableWgWatcher()
 	// todo consider to run conn.wgWatcherWg.Wait() here
 
@@ -404,15 +390,10 @@ func (conn *Conn) onICEConnectionIsReady(priority conntype.ConnPriority, iceConn
 	}
 	wgConfigWorkaround()
 
-	oldState := conn.currentConnPriority
 	conn.currentConnPriority = priority
 	conn.statusICE.SetConnected()
 	conn.updateIceState(iceConnInfo)
 	conn.doOnConnected(iceConnInfo.RosenpassPubKey, iceConnInfo.RosenpassAddr)
-
-	if oldState == conntype.None {
-		conn.peerConnDispatcher.NotifyConnected(conn.ConnID())
-	}
 }
 
 func (conn *Conn) onICEStateDisconnected() {
@@ -450,7 +431,6 @@ func (conn *Conn) onICEStateDisconnected() {
 	} else {
 		conn.Log.Infof("ICE disconnected, do not switch to Relay. Reset priority to: %s", conntype.None.String())
 		conn.currentConnPriority = conntype.None
-		conn.peerConnDispatcher.NotifyDisconnected(conn.ConnID())
 	}
 
 	changed := conn.statusICE.Get() != worker.StatusDisconnected
@@ -491,6 +471,8 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
 		conn.Log.Errorf("failed to add relayed net.Conn to local proxy: %v", err)
 		return
 	}
+	wgProxy.SetDisconnectListener(conn.onRelayDisconnected)
+
 	conn.dumpState.NewLocalProxy()
 
 	conn.Log.Infof("created new wgProxy for relay connection: %s", wgProxy.EndpointAddr().String())
@@ -503,10 +485,6 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
 		return
 	}
 
-	if err := conn.runBeforeAddPeerHooks(wgProxy.EndpointAddr().IP); err != nil {
-		conn.Log.Errorf("Before add peer hook failed: %v", err)
-	}
-
 	wgProxy.Work()
 	if err := conn.configureWGEndpoint(wgProxy.EndpointAddr(), rci.rosenpassPubKey); err != nil {
 		if err := wgProxy.CloseConn(); err != nil {
@@ -530,7 +508,6 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
 	conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey)
 	conn.Log.Infof("start to communicate with peer via relay")
 	conn.doOnConnected(rci.rosenpassPubKey, rci.rosenpassAddr)
-	conn.peerConnDispatcher.NotifyConnected(conn.ConnID())
 }
 
 func (conn *Conn) onRelayDisconnected() {
@@ -545,11 +522,7 @@ func (conn *Conn) onRelayDisconnected() {
 
 	if conn.currentConnPriority == conntype.Relay {
 		conn.Log.Debugf("clean up WireGuard config")
-		if err := conn.removeWgPeer(); err != nil {
-			conn.Log.Errorf("failed to remove wg endpoint: %v", err)
-		}
 		conn.currentConnPriority = conntype.None
-		conn.peerConnDispatcher.NotifyDisconnected(conn.ConnID())
 	}
 
 	if conn.wgProxyRelay != nil {
@@ -712,36 +685,6 @@ func (conn *Conn) isConnectedOnAllWay() (connected bool) {
 	return true
 }
 
-func (conn *Conn) runBeforeAddPeerHooks(ip net.IP) error {
-	conn.connIDICE = nbnet.GenerateConnID()
-	for _, hook := range conn.beforeAddPeerHooks {
-		if err := hook(conn.connIDICE, ip); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (conn *Conn) freeUpConnID() {
-	if conn.connIDRelay != "" {
-		for _, hook := range conn.afterRemovePeerHooks {
-			if err := hook(conn.connIDRelay); err != nil {
-				conn.Log.Errorf("After remove peer hook failed: %v", err)
-			}
-		}
-		conn.connIDRelay = ""
-	}
-
-	if conn.connIDICE != "" {
-		for _, hook := range conn.afterRemovePeerHooks {
-			if err := hook(conn.connIDICE); err != nil {
-				conn.Log.Errorf("After remove peer hook failed: %v", err)
-			}
-		}
-		conn.connIDICE = ""
-	}
-}
-
 func (conn *Conn) newProxy(remoteConn net.Conn) (wgproxy.Proxy, error) {
 	conn.Log.Debugf("setup proxied WireGuard connection")
 	udpAddr := &net.UDPAddr{
diff --git a/client/internal/peer/signaler.go b/client/internal/peer/signaler.go
index 713123e5..9022e029 100644
--- a/client/internal/peer/signaler.go
+++ b/client/internal/peer/signaler.go
@@ -68,3 +68,13 @@ func (s *Signaler) signalOfferAnswer(offerAnswer OfferAnswer, remoteKey string,
 
 	return nil
 }
+
+func (s *Signaler) SignalIdle(remoteKey string) error {
+	return s.signal.Send(&sProto.Message{
+		Key:       s.wgPrivateKey.PublicKey().String(),
+		RemoteKey: remoteKey,
+		Body: &sProto.Body{
+			Type: sProto.Body_GO_IDLE,
+		},
+	})
+}
diff --git a/client/internal/peer/worker_relay.go b/client/internal/peer/worker_relay.go
index aa8f7d63..ef9f24a2 100644
--- a/client/internal/peer/worker_relay.go
+++ b/client/internal/peer/worker_relay.go
@@ -19,11 +19,12 @@ type RelayConnInfo struct {
 }
 
 type WorkerRelay struct {
+	peerCtx      context.Context
 	log          *log.Entry
 	isController bool
 	config       ConnConfig
 	conn         *Conn
-	relayManager relayClient.ManagerService
+	relayManager *relayClient.Manager
 
 	relayedConn net.Conn
 	relayLock   sync.Mutex
@@ -33,8 +34,9 @@ type WorkerRelay struct {
 	wgWatcher *WGWatcher
 }
 
-func NewWorkerRelay(log *log.Entry, ctrl bool, config ConnConfig, conn *Conn, relayManager relayClient.ManagerService, stateDump *stateDump) *WorkerRelay {
+func NewWorkerRelay(ctx context.Context, log *log.Entry, ctrl bool, config ConnConfig, conn *Conn, relayManager *relayClient.Manager, stateDump *stateDump) *WorkerRelay {
 	r := &WorkerRelay{
+		peerCtx:      ctx,
 		log:          log,
 		isController: ctrl,
 		config:       config,
@@ -62,7 +64,7 @@ func (w *WorkerRelay) OnNewOffer(remoteOfferAnswer *OfferAnswer) {
 
 	srv := w.preferredRelayServer(currentRelayAddress, remoteOfferAnswer.RelaySrvAddress)
 
-	relayedConn, err := w.relayManager.OpenConn(srv, w.config.Key)
+	relayedConn, err := w.relayManager.OpenConn(w.peerCtx, srv, w.config.Key)
 	if err != nil {
 		if errors.Is(err, relayClient.ErrConnAlreadyExists) {
 			w.log.Debugf("handled offer by reusing existing relay connection")
diff --git a/client/internal/peerstore/store.go b/client/internal/peerstore/store.go
index 81ac7a5b..099fe452 100644
--- a/client/internal/peerstore/store.go
+++ b/client/internal/peerstore/store.go
@@ -95,6 +95,17 @@ func (s *Store) PeerConnOpen(ctx context.Context, pubKey string) {
 
 }
 
+func (s *Store) PeerConnIdle(pubKey string) {
+	s.peerConnsMu.RLock()
+	defer s.peerConnsMu.RUnlock()
+
+	p, ok := s.peerConns[pubKey]
+	if !ok {
+		return
+	}
+	p.Close(true)
+}
+
 func (s *Store) PeerConnClose(pubKey string) {
 	s.peerConnsMu.RLock()
 	defer s.peerConnsMu.RUnlock()
@@ -103,7 +114,7 @@ func (s *Store) PeerConnClose(pubKey string) {
 	if !ok {
 		return
 	}
-	p.Close()
+	p.Close(false)
 }
 
 func (s *Store) PeersPubKey() []string {
diff --git a/client/internal/routemanager/client/client.go b/client/internal/routemanager/client/client.go
index 46bff96d..0b8e161d 100644
--- a/client/internal/routemanager/client/client.go
+++ b/client/internal/routemanager/client/client.go
@@ -10,11 +10,10 @@ import (
 
 	nbdns "github.com/netbirdio/netbird/client/internal/dns"
 	"github.com/netbirdio/netbird/client/internal/peer"
-	"github.com/netbirdio/netbird/client/internal/peerstore"
+	"github.com/netbirdio/netbird/client/internal/routemanager/common"
 	"github.com/netbirdio/netbird/client/internal/routemanager/dnsinterceptor"
 	"github.com/netbirdio/netbird/client/internal/routemanager/dynamic"
 	"github.com/netbirdio/netbird/client/internal/routemanager/iface"
-	"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
 	"github.com/netbirdio/netbird/client/internal/routemanager/static"
 	"github.com/netbirdio/netbird/client/proto"
 	"github.com/netbirdio/netbird/route"
@@ -553,41 +552,16 @@ func (w *Watcher) Stop() {
 	w.currentChosenStatus = nil
 }
 
-func HandlerFromRoute(
-	rt *route.Route,
-	routeRefCounter *refcounter.RouteRefCounter,
-	allowedIPsRefCounter *refcounter.AllowedIPsRefCounter,
-	dnsRouterInteval time.Duration,
-	statusRecorder *peer.Status,
-	wgInterface iface.WGIface,
-	dnsServer nbdns.Server,
-	peerStore *peerstore.Store,
-	useNewDNSRoute bool,
-) RouteHandler {
-	switch handlerType(rt, useNewDNSRoute) {
+func HandlerFromRoute(params common.HandlerParams) RouteHandler {
+	switch handlerType(params.Route, params.UseNewDNSRoute) {
 	case handlerTypeDnsInterceptor:
-		return dnsinterceptor.New(
-			rt,
-			routeRefCounter,
-			allowedIPsRefCounter,
-			statusRecorder,
-			dnsServer,
-			wgInterface,
-			peerStore,
-		)
+		return dnsinterceptor.New(params)
 	case handlerTypeDynamic:
-		dns := nbdns.NewServiceViaMemory(wgInterface)
-		return dynamic.NewRoute(
-			rt,
-			routeRefCounter,
-			allowedIPsRefCounter,
-			dnsRouterInteval,
-			statusRecorder,
-			wgInterface,
-			fmt.Sprintf("%s:%d", dns.RuntimeIP(), dns.RuntimePort()),
-		)
+		dns := nbdns.NewServiceViaMemory(params.WgInterface)
+		dnsAddr := fmt.Sprintf("%s:%d", dns.RuntimeIP(), dns.RuntimePort())
+		return dynamic.NewRoute(params, dnsAddr)
 	default:
-		return static.NewRoute(rt, routeRefCounter, allowedIPsRefCounter)
+		return static.NewRoute(params)
 	}
 }
 
diff --git a/client/internal/routemanager/common/params.go b/client/internal/routemanager/common/params.go
new file mode 100644
index 00000000..def18411
--- /dev/null
+++ b/client/internal/routemanager/common/params.go
@@ -0,0 +1,28 @@
+package common
+
+import (
+	"time"
+
+	"github.com/netbirdio/netbird/client/firewall/manager"
+	"github.com/netbirdio/netbird/client/internal/dns"
+	"github.com/netbirdio/netbird/client/internal/peer"
+	"github.com/netbirdio/netbird/client/internal/peerstore"
+	"github.com/netbirdio/netbird/client/internal/routemanager/fakeip"
+	"github.com/netbirdio/netbird/client/internal/routemanager/iface"
+	"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
+	"github.com/netbirdio/netbird/route"
+)
+
+type HandlerParams struct {
+	Route                *route.Route
+	RouteRefCounter      *refcounter.RouteRefCounter
+	AllowedIPsRefCounter *refcounter.AllowedIPsRefCounter
+	DnsRouterInterval    time.Duration
+	StatusRecorder       *peer.Status
+	WgInterface          iface.WGIface
+	DnsServer            dns.Server
+	PeerStore            *peerstore.Store
+	UseNewDNSRoute       bool
+	Firewall             manager.Manager
+	FakeIPManager        *fakeip.Manager
+}
diff --git a/client/internal/routemanager/dnsinterceptor/handler.go b/client/internal/routemanager/dnsinterceptor/handler.go
index 66557e88..c7c3aeb0 100644
--- a/client/internal/routemanager/dnsinterceptor/handler.go
+++ b/client/internal/routemanager/dnsinterceptor/handler.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"net/netip"
+	"runtime"
 	"strings"
 	"sync"
 
@@ -12,11 +13,14 @@ import (
 	log "github.com/sirupsen/logrus"
 
 	nberrors "github.com/netbirdio/netbird/client/errors"
+	firewall "github.com/netbirdio/netbird/client/firewall/manager"
 	"github.com/netbirdio/netbird/client/iface/wgaddr"
 	nbdns "github.com/netbirdio/netbird/client/internal/dns"
 	"github.com/netbirdio/netbird/client/internal/dnsfwd"
 	"github.com/netbirdio/netbird/client/internal/peer"
 	"github.com/netbirdio/netbird/client/internal/peerstore"
+	"github.com/netbirdio/netbird/client/internal/routemanager/common"
+	"github.com/netbirdio/netbird/client/internal/routemanager/fakeip"
 	"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
 	"github.com/netbirdio/netbird/management/domain"
 	"github.com/netbirdio/netbird/route"
@@ -24,6 +28,11 @@ import (
 
 type domainMap map[domain.Domain][]netip.Prefix
 
+type internalDNATer interface {
+	RemoveInternalDNATMapping(netip.Addr) error
+	AddInternalDNATMapping(netip.Addr, netip.Addr) error
+}
+
 type wgInterface interface {
 	Name() string
 	Address() wgaddr.Address
@@ -40,26 +49,22 @@ type DnsInterceptor struct {
 	interceptedDomains   domainMap
 	wgInterface          wgInterface
 	peerStore            *peerstore.Store
+	firewall             firewall.Manager
+	fakeIPManager        *fakeip.Manager
 }
 
-func New(
-	rt *route.Route,
-	routeRefCounter *refcounter.RouteRefCounter,
-	allowedIPsRefCounter *refcounter.AllowedIPsRefCounter,
-	statusRecorder *peer.Status,
-	dnsServer nbdns.Server,
-	wgInterface wgInterface,
-	peerStore *peerstore.Store,
-) *DnsInterceptor {
+func New(params common.HandlerParams) *DnsInterceptor {
 	return &DnsInterceptor{
-		route:                rt,
-		routeRefCounter:      routeRefCounter,
-		allowedIPsRefcounter: allowedIPsRefCounter,
-		statusRecorder:       statusRecorder,
-		dnsServer:            dnsServer,
-		wgInterface:          wgInterface,
+		route:                params.Route,
+		routeRefCounter:      params.RouteRefCounter,
+		allowedIPsRefcounter: params.AllowedIPsRefCounter,
+		statusRecorder:       params.StatusRecorder,
+		dnsServer:            params.DnsServer,
+		wgInterface:          params.WgInterface,
+		peerStore:            params.PeerStore,
+		firewall:             params.Firewall,
+		fakeIPManager:        params.FakeIPManager,
 		interceptedDomains:   make(domainMap),
-		peerStore:            peerStore,
 	}
 }
 
@@ -78,9 +83,13 @@ func (d *DnsInterceptor) RemoveRoute() error {
 	var merr *multierror.Error
 	for domain, prefixes := range d.interceptedDomains {
 		for _, prefix := range prefixes {
-			if _, err := d.routeRefCounter.Decrement(prefix); err != nil {
-				merr = multierror.Append(merr, fmt.Errorf("remove dynamic route for IP %s: %v", prefix, err))
+			// Routes should use fake IPs
+			routePrefix := d.transformRealToFakePrefix(prefix)
+			if _, err := d.routeRefCounter.Decrement(routePrefix); err != nil {
+				merr = multierror.Append(merr, fmt.Errorf("remove dynamic route for IP %s: %v", routePrefix, err))
 			}
+
+			// AllowedIPs should use real IPs
 			if d.currentPeerKey != "" {
 				if _, err := d.allowedIPsRefcounter.Decrement(prefix); err != nil {
 					merr = multierror.Append(merr, fmt.Errorf("remove allowed IP %s: %v", prefix, err))
@@ -88,8 +97,10 @@ func (d *DnsInterceptor) RemoveRoute() error {
 			}
 		}
 		log.Debugf("removed dynamic route(s) for [%s]: %s", domain.SafeString(), strings.ReplaceAll(fmt.Sprintf("%s", prefixes), " ", ", "))
-
 	}
+
+	d.cleanupDNATMappings()
+
 	for _, domain := range d.route.Domains {
 		d.statusRecorder.DeleteResolvedDomainsStates(domain)
 	}
@@ -102,6 +113,68 @@ func (d *DnsInterceptor) RemoveRoute() error {
 	return nberrors.FormatErrorOrNil(merr)
 }
 
+// transformRealToFakePrefix returns fake IP prefix for routes (if DNAT enabled)
+func (d *DnsInterceptor) transformRealToFakePrefix(realPrefix netip.Prefix) netip.Prefix {
+	if _, hasDNAT := d.internalDnatFw(); !hasDNAT {
+		return realPrefix
+	}
+
+	if fakeIP, ok := d.fakeIPManager.GetFakeIP(realPrefix.Addr()); ok {
+		return netip.PrefixFrom(fakeIP, realPrefix.Bits())
+	}
+
+	return realPrefix
+}
+
+// addAllowedIPForPrefix handles the AllowedIPs logic for a single prefix (uses real IPs)
+func (d *DnsInterceptor) addAllowedIPForPrefix(realPrefix netip.Prefix, peerKey string, domain domain.Domain) error {
+	// AllowedIPs always use real IPs
+	ref, err := d.allowedIPsRefcounter.Increment(realPrefix, peerKey)
+	if err != nil {
+		return fmt.Errorf("add allowed IP %s: %v", realPrefix, err)
+	}
+
+	if ref.Count > 1 && ref.Out != peerKey {
+		log.Warnf("IP [%s] for domain [%s] is already routed by peer [%s]. HA routing disabled",
+			realPrefix.Addr(),
+			domain.SafeString(),
+			ref.Out,
+		)
+	}
+
+	return nil
+}
+
+// addRouteAndAllowedIP handles both route and AllowedIPs addition for a prefix
+func (d *DnsInterceptor) addRouteAndAllowedIP(realPrefix netip.Prefix, domain domain.Domain) error {
+	// Routes use fake IPs (so traffic to fake IPs gets routed to interface)
+	routePrefix := d.transformRealToFakePrefix(realPrefix)
+	if _, err := d.routeRefCounter.Increment(routePrefix, struct{}{}); err != nil {
+		return fmt.Errorf("add route for IP %s: %v", routePrefix, err)
+	}
+
+	// Add to AllowedIPs if we have a current peer (uses real IPs)
+	if d.currentPeerKey == "" {
+		return nil
+	}
+
+	return d.addAllowedIPForPrefix(realPrefix, d.currentPeerKey, domain)
+}
+
+// removeAllowedIP handles AllowedIPs removal for a prefix (uses real IPs)
+func (d *DnsInterceptor) removeAllowedIP(realPrefix netip.Prefix) error {
+	if d.currentPeerKey == "" {
+		return nil
+	}
+
+	// AllowedIPs use real IPs
+	if _, err := d.allowedIPsRefcounter.Decrement(realPrefix); err != nil {
+		return fmt.Errorf("remove allowed IP %s: %v", realPrefix, err)
+	}
+
+	return nil
+}
+
 func (d *DnsInterceptor) AddAllowedIPs(peerKey string) error {
 	d.mu.Lock()
 	defer d.mu.Unlock()
@@ -109,14 +182,9 @@ func (d *DnsInterceptor) AddAllowedIPs(peerKey string) error {
 	var merr *multierror.Error
 	for domain, prefixes := range d.interceptedDomains {
 		for _, prefix := range prefixes {
-			if ref, err := d.allowedIPsRefcounter.Increment(prefix, peerKey); err != nil {
-				merr = multierror.Append(merr, fmt.Errorf("add allowed IP %s: %v", prefix, err))
-			} else if ref.Count > 1 && ref.Out != peerKey {
-				log.Warnf("IP [%s] for domain [%s] is already routed by peer [%s]. HA routing disabled",
-					prefix.Addr(),
-					domain.SafeString(),
-					ref.Out,
-				)
+			// AllowedIPs use real IPs
+			if err := d.addAllowedIPForPrefix(prefix, peerKey, domain); err != nil {
+				merr = multierror.Append(merr, err)
 			}
 		}
 	}
@@ -132,6 +200,7 @@ func (d *DnsInterceptor) RemoveAllowedIPs() error {
 	var merr *multierror.Error
 	for _, prefixes := range d.interceptedDomains {
 		for _, prefix := range prefixes {
+			// AllowedIPs use real IPs
 			if _, err := d.allowedIPsRefcounter.Decrement(prefix); err != nil {
 				merr = multierror.Append(merr, fmt.Errorf("remove allowed IP %s: %v", prefix, err))
 			}
@@ -287,6 +356,8 @@ func (d *DnsInterceptor) writeMsg(w dns.ResponseWriter, r *dns.Msg) error {
 			if err := d.updateDomainPrefixes(resolvedDomain, originalDomain, newPrefixes); err != nil {
 				log.Errorf("failed to update domain prefixes: %v", err)
 			}
+
+			d.replaceIPsInDNSResponse(r, newPrefixes)
 		}
 	}
 
@@ -297,6 +368,22 @@ func (d *DnsInterceptor) writeMsg(w dns.ResponseWriter, r *dns.Msg) error {
 	return nil
 }
 
+// logPrefixChanges handles the logging for prefix changes
+func (d *DnsInterceptor) logPrefixChanges(resolvedDomain, originalDomain domain.Domain, toAdd, toRemove []netip.Prefix) {
+	if len(toAdd) > 0 {
+		log.Debugf("added dynamic route(s) for domain=%s (pattern: domain=%s): %s",
+			resolvedDomain.SafeString(),
+			originalDomain.SafeString(),
+			toAdd)
+	}
+	if len(toRemove) > 0 && !d.route.KeepRoute {
+		log.Debugf("removed dynamic route(s) for domain=%s (pattern: domain=%s): %s",
+			resolvedDomain.SafeString(),
+			originalDomain.SafeString(),
+			toRemove)
+	}
+}
+
 func (d *DnsInterceptor) updateDomainPrefixes(resolvedDomain, originalDomain domain.Domain, newPrefixes []netip.Prefix) error {
 	d.mu.Lock()
 	defer d.mu.Unlock()
@@ -305,68 +392,161 @@ func (d *DnsInterceptor) updateDomainPrefixes(resolvedDomain, originalDomain dom
 	toAdd, toRemove := determinePrefixChanges(oldPrefixes, newPrefixes)
 
 	var merr *multierror.Error
+	var dnatMappings map[netip.Addr]netip.Addr
+
+	// Handle DNAT mappings for new prefixes
+	if _, hasDNAT := d.internalDnatFw(); hasDNAT {
+		dnatMappings = make(map[netip.Addr]netip.Addr)
+		for _, prefix := range toAdd {
+			realIP := prefix.Addr()
+			if fakeIP, err := d.fakeIPManager.AllocateFakeIP(realIP); err == nil {
+				dnatMappings[fakeIP] = realIP
+				log.Tracef("allocated fake IP %s for real IP %s", fakeIP, realIP)
+			} else {
+				log.Errorf("Failed to allocate fake IP for %s: %v", realIP, err)
+			}
+		}
+	}
 
 	// Add new prefixes
 	for _, prefix := range toAdd {
-		if _, err := d.routeRefCounter.Increment(prefix, struct{}{}); err != nil {
-			merr = multierror.Append(merr, fmt.Errorf("add route for IP %s: %v", prefix, err))
-			continue
-		}
-
-		if d.currentPeerKey == "" {
-			continue
-		}
-		if ref, err := d.allowedIPsRefcounter.Increment(prefix, d.currentPeerKey); err != nil {
-			merr = multierror.Append(merr, fmt.Errorf("add allowed IP %s: %v", prefix, err))
-		} else if ref.Count > 1 && ref.Out != d.currentPeerKey {
-			log.Warnf("IP [%s] for domain [%s] is already routed by peer [%s]. HA routing disabled",
-				prefix.Addr(),
-				resolvedDomain.SafeString(),
-				ref.Out,
-			)
+		if err := d.addRouteAndAllowedIP(prefix, resolvedDomain); err != nil {
+			merr = multierror.Append(merr, err)
 		}
 	}
 
+	d.addDNATMappings(dnatMappings)
+
 	if !d.route.KeepRoute {
 		// Remove old prefixes
 		for _, prefix := range toRemove {
-			if _, err := d.routeRefCounter.Decrement(prefix); err != nil {
-				merr = multierror.Append(merr, fmt.Errorf("remove route for IP %s: %v", prefix, err))
+			// Routes use fake IPs
+			routePrefix := d.transformRealToFakePrefix(prefix)
+			if _, err := d.routeRefCounter.Decrement(routePrefix); err != nil {
+				merr = multierror.Append(merr, fmt.Errorf("remove route for IP %s: %v", routePrefix, err))
 			}
-			if d.currentPeerKey != "" {
-				if _, err := d.allowedIPsRefcounter.Decrement(prefix); err != nil {
-					merr = multierror.Append(merr, fmt.Errorf("remove allowed IP %s: %v", prefix, err))
-				}
+			// AllowedIPs use real IPs
+			if err := d.removeAllowedIP(prefix); err != nil {
+				merr = multierror.Append(merr, err)
 			}
 		}
+
+		d.removeDNATMappings(toRemove)
 	}
 
-	// Update domain prefixes using resolved domain as key
+	// Update domain prefixes using resolved domain as key - store real IPs
 	if len(toAdd) > 0 || len(toRemove) > 0 {
 		if d.route.KeepRoute {
-			// replace stored prefixes with old + added
 			// nolint:gocritic
 			newPrefixes = append(oldPrefixes, toAdd...)
 		}
 		d.interceptedDomains[resolvedDomain] = newPrefixes
 		originalDomain = domain.Domain(strings.TrimSuffix(string(originalDomain), "."))
+
+		// Store real IPs for status (user-facing), not fake IPs
 		d.statusRecorder.UpdateResolvedDomainsStates(originalDomain, resolvedDomain, newPrefixes, d.route.GetResourceID())
 
-		if len(toAdd) > 0 {
-			log.Debugf("added dynamic route(s) for domain=%s (pattern: domain=%s): %s",
-				resolvedDomain.SafeString(),
-				originalDomain.SafeString(),
-				toAdd)
+		d.logPrefixChanges(resolvedDomain, originalDomain, toAdd, toRemove)
+	}
+
+	return nberrors.FormatErrorOrNil(merr)
+}
+
+// removeDNATMappings removes DNAT mappings from the firewall for real IP prefixes
+func (d *DnsInterceptor) removeDNATMappings(realPrefixes []netip.Prefix) {
+	if len(realPrefixes) == 0 {
+		return
+	}
+
+	dnatFirewall, ok := d.internalDnatFw()
+	if !ok {
+		return
+	}
+
+	for _, prefix := range realPrefixes {
+		realIP := prefix.Addr()
+		if fakeIP, exists := d.fakeIPManager.GetFakeIP(realIP); exists {
+			if err := dnatFirewall.RemoveInternalDNATMapping(fakeIP); err != nil {
+				log.Errorf("Failed to remove DNAT mapping for %s: %v", fakeIP, err)
+			} else {
+				log.Debugf("Removed DNAT mapping for: %s -> %s", fakeIP, realIP)
+			}
 		}
-		if len(toRemove) > 0 && !d.route.KeepRoute {
-			log.Debugf("removed dynamic route(s) for domain=%s (pattern: domain=%s): %s",
-				resolvedDomain.SafeString(),
-				originalDomain.SafeString(),
-				toRemove)
+	}
+}
+
+// internalDnatFw checks if the firewall supports internal DNAT
+func (d *DnsInterceptor) internalDnatFw() (internalDNATer, bool) {
+	if d.firewall == nil || runtime.GOOS != "android" {
+		return nil, false
+	}
+	fw, ok := d.firewall.(internalDNATer)
+	return fw, ok
+}
+
+// addDNATMappings adds DNAT mappings to the firewall
+func (d *DnsInterceptor) addDNATMappings(mappings map[netip.Addr]netip.Addr) {
+	if len(mappings) == 0 {
+		return
+	}
+
+	dnatFirewall, ok := d.internalDnatFw()
+	if !ok {
+		return
+	}
+
+	for fakeIP, realIP := range mappings {
+		if err := dnatFirewall.AddInternalDNATMapping(fakeIP, realIP); err != nil {
+			log.Errorf("Failed to add DNAT mapping %s -> %s: %v", fakeIP, realIP, err)
+		} else {
+			log.Debugf("Added DNAT mapping: %s -> %s", fakeIP, realIP)
 		}
 	}
+}
 
-	return nberrors.FormatErrorOrNil(merr)
+// cleanupDNATMappings removes all DNAT mappings for this interceptor
+func (d *DnsInterceptor) cleanupDNATMappings() {
+	if _, ok := d.internalDnatFw(); !ok {
+		return
+	}
+
+	for _, prefixes := range d.interceptedDomains {
+		d.removeDNATMappings(prefixes)
+	}
+}
+
+// replaceIPsInDNSResponse replaces real IPs with fake IPs in the DNS response
+func (d *DnsInterceptor) replaceIPsInDNSResponse(reply *dns.Msg, realPrefixes []netip.Prefix) {
+	if _, ok := d.internalDnatFw(); !ok {
+		return
+	}
+
+	// Replace A and AAAA records with fake IPs
+	for _, answer := range reply.Answer {
+		switch rr := answer.(type) {
+		case *dns.A:
+			realIP, ok := netip.AddrFromSlice(rr.A)
+			if !ok {
+				continue
+			}
+
+			if fakeIP, exists := d.fakeIPManager.GetFakeIP(realIP); exists {
+				rr.A = fakeIP.AsSlice()
+				log.Tracef("Replaced real IP %s with fake IP %s in DNS response", realIP, fakeIP)
+			}
+
+		case *dns.AAAA:
+			realIP, ok := netip.AddrFromSlice(rr.AAAA)
+			if !ok {
+				continue
+			}
+
+			if fakeIP, exists := d.fakeIPManager.GetFakeIP(realIP); exists {
+				rr.AAAA = fakeIP.AsSlice()
+				log.Tracef("Replaced real IP %s with fake IP %s in DNS response", realIP, fakeIP)
+			}
+		}
+	}
 }
 
 func determinePrefixChanges(oldPrefixes, newPrefixes []netip.Prefix) (toAdd, toRemove []netip.Prefix) {
diff --git a/client/internal/routemanager/dynamic/route.go b/client/internal/routemanager/dynamic/route.go
index 47511d4a..5d561f0c 100644
--- a/client/internal/routemanager/dynamic/route.go
+++ b/client/internal/routemanager/dynamic/route.go
@@ -14,6 +14,7 @@ import (
 
 	nberrors "github.com/netbirdio/netbird/client/errors"
 	"github.com/netbirdio/netbird/client/internal/peer"
+	"github.com/netbirdio/netbird/client/internal/routemanager/common"
 	"github.com/netbirdio/netbird/client/internal/routemanager/iface"
 	"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
 	"github.com/netbirdio/netbird/client/internal/routemanager/util"
@@ -52,24 +53,16 @@ type Route struct {
 	resolverAddr         string
 }
 
-func NewRoute(
-	rt *route.Route,
-	routeRefCounter *refcounter.RouteRefCounter,
-	allowedIPsRefCounter *refcounter.AllowedIPsRefCounter,
-	interval time.Duration,
-	statusRecorder *peer.Status,
-	wgInterface iface.WGIface,
-	resolverAddr string,
-) *Route {
+func NewRoute(params common.HandlerParams, resolverAddr string) *Route {
 	return &Route{
-		route:                rt,
-		routeRefCounter:      routeRefCounter,
-		allowedIPsRefcounter: allowedIPsRefCounter,
-		interval:             interval,
-		dynamicDomains:       domainMap{},
-		statusRecorder:       statusRecorder,
-		wgInterface:          wgInterface,
+		route:                params.Route,
+		routeRefCounter:      params.RouteRefCounter,
+		allowedIPsRefcounter: params.AllowedIPsRefCounter,
+		interval:             params.DnsRouterInterval,
+		statusRecorder:       params.StatusRecorder,
+		wgInterface:          params.WgInterface,
 		resolverAddr:         resolverAddr,
+		dynamicDomains:       domainMap{},
 	}
 }
 
diff --git a/client/internal/routemanager/fakeip/fakeip.go b/client/internal/routemanager/fakeip/fakeip.go
new file mode 100644
index 00000000..1592045d
--- /dev/null
+++ b/client/internal/routemanager/fakeip/fakeip.go
@@ -0,0 +1,93 @@
+package fakeip
+
+import (
+	"fmt"
+	"net/netip"
+	"sync"
+)
+
+// Manager manages allocation of fake IPs from the 240.0.0.0/8 block
+type Manager struct {
+	mu         sync.Mutex
+	nextIP     netip.Addr                // Next IP to allocate
+	allocated  map[netip.Addr]netip.Addr // real IP -> fake IP
+	fakeToReal map[netip.Addr]netip.Addr // fake IP -> real IP
+	baseIP     netip.Addr                // First usable IP: 240.0.0.1
+	maxIP      netip.Addr                // Last usable IP: 240.255.255.254
+}
+
+// NewManager creates a new fake IP manager using 240.0.0.0/8 block
+func NewManager() *Manager {
+	baseIP := netip.AddrFrom4([4]byte{240, 0, 0, 1})
+	maxIP := netip.AddrFrom4([4]byte{240, 255, 255, 254})
+
+	return &Manager{
+		nextIP:     baseIP,
+		allocated:  make(map[netip.Addr]netip.Addr),
+		fakeToReal: make(map[netip.Addr]netip.Addr),
+		baseIP:     baseIP,
+		maxIP:      maxIP,
+	}
+}
+
+// AllocateFakeIP allocates a fake IP for the given real IP
+// Returns the fake IP, or existing fake IP if already allocated
+func (m *Manager) AllocateFakeIP(realIP netip.Addr) (netip.Addr, error) {
+	if !realIP.Is4() {
+		return netip.Addr{}, fmt.Errorf("only IPv4 addresses supported")
+	}
+
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	if fakeIP, exists := m.allocated[realIP]; exists {
+		return fakeIP, nil
+	}
+
+	startIP := m.nextIP
+	for {
+		currentIP := m.nextIP
+
+		// Advance to next IP, wrapping at boundary
+		if m.nextIP.Compare(m.maxIP) >= 0 {
+			m.nextIP = m.baseIP
+		} else {
+			m.nextIP = m.nextIP.Next()
+		}
+
+		// Check if current IP is available
+		if _, inUse := m.fakeToReal[currentIP]; !inUse {
+			m.allocated[realIP] = currentIP
+			m.fakeToReal[currentIP] = realIP
+			return currentIP, nil
+		}
+
+		// Prevent infinite loop if all IPs exhausted
+		if m.nextIP.Compare(startIP) == 0 {
+			return netip.Addr{}, fmt.Errorf("no more fake IPs available in 240.0.0.0/8 block")
+		}
+	}
+}
+
+// GetFakeIP returns the fake IP for a real IP if it exists
+func (m *Manager) GetFakeIP(realIP netip.Addr) (netip.Addr, bool) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	fakeIP, exists := m.allocated[realIP]
+	return fakeIP, exists
+}
+
+// GetRealIP returns the real IP for a fake IP if it exists, otherwise false
+func (m *Manager) GetRealIP(fakeIP netip.Addr) (netip.Addr, bool) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	realIP, exists := m.fakeToReal[fakeIP]
+	return realIP, exists
+}
+
+// GetFakeIPBlock returns the fake IP block used by this manager
+func (m *Manager) GetFakeIPBlock() netip.Prefix {
+	return netip.MustParsePrefix("240.0.0.0/8")
+}
diff --git a/client/internal/routemanager/manager.go b/client/internal/routemanager/manager.go
index 919bf25e..e5177881 100644
--- a/client/internal/routemanager/manager.go
+++ b/client/internal/routemanager/manager.go
@@ -8,9 +8,11 @@ import (
 	"net/netip"
 	"net/url"
 	"runtime"
+	"slices"
 	"sync"
 	"time"
 
+	"github.com/google/uuid"
 	"github.com/hashicorp/go-multierror"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/exp/maps"
@@ -24,6 +26,8 @@ import (
 	"github.com/netbirdio/netbird/client/internal/peer"
 	"github.com/netbirdio/netbird/client/internal/peerstore"
 	"github.com/netbirdio/netbird/client/internal/routemanager/client"
+	"github.com/netbirdio/netbird/client/internal/routemanager/common"
+	"github.com/netbirdio/netbird/client/internal/routemanager/fakeip"
 	"github.com/netbirdio/netbird/client/internal/routemanager/iface"
 	"github.com/netbirdio/netbird/client/internal/routemanager/notifier"
 	"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
@@ -40,7 +44,7 @@ import (
 
 // Manager is a route manager interface
 type Manager interface {
-	Init() (nbnet.AddHookFunc, nbnet.RemoveHookFunc, error)
+	Init() error
 	UpdateRoutes(updateSerial uint64, serverRoutes map[route.ID]*route.Route, clientRoutes route.HAMap, useNewDNSRoute bool) error
 	ClassifyRoutes(newRoutes []*route.Route) (map[route.ID]*route.Route, route.HAMap)
 	TriggerSelection(route.HAMap)
@@ -49,7 +53,7 @@ type Manager interface {
 	GetClientRoutesWithNetID() map[route.NetID][]*route.Route
 	SetRouteChangeListener(listener listener.NetworkChangeListener)
 	InitialRouteRange() []string
-	EnableServerRouter(firewall firewall.Manager) error
+	SetFirewall(firewall.Manager) error
 	Stop(stateManager *statemanager.Manager)
 }
 
@@ -63,6 +67,7 @@ type ManagerConfig struct {
 	InitialRoutes       []*route.Route
 	StateManager        *statemanager.Manager
 	DNSServer           dns.Server
+	DNSFeatureFlag      bool
 	PeerStore           *peerstore.Store
 	DisableClientRoutes bool
 	DisableServerRoutes bool
@@ -89,11 +94,13 @@ type DefaultManager struct {
 	// clientRoutes is the most recent list of clientRoutes received from the Management Service
 	clientRoutes        route.HAMap
 	dnsServer           dns.Server
+	firewall            firewall.Manager
 	peerStore           *peerstore.Store
 	useNewDNSRoute      bool
 	disableClientRoutes bool
 	disableServerRoutes bool
 	activeRoutes        map[route.HAUniqueID]client.RouteHandler
+	fakeIPManager       *fakeip.Manager
 }
 
 func NewManager(config ManagerConfig) *DefaultManager {
@@ -129,11 +136,31 @@ func NewManager(config ManagerConfig) *DefaultManager {
 	}
 
 	if runtime.GOOS == "android" {
-		cr := dm.initialClientRoutes(config.InitialRoutes)
-		dm.notifier.SetInitialClientRoutes(cr)
+		dm.setupAndroidRoutes(config)
 	}
 	return dm
 }
+func (m *DefaultManager) setupAndroidRoutes(config ManagerConfig) {
+	cr := m.initialClientRoutes(config.InitialRoutes)
+
+	routesForComparison := slices.Clone(cr)
+
+	if config.DNSFeatureFlag {
+		m.fakeIPManager = fakeip.NewManager()
+
+		id := uuid.NewString()
+		fakeIPRoute := &route.Route{
+			ID:          route.ID(id),
+			Network:     m.fakeIPManager.GetFakeIPBlock(),
+			NetID:       route.NetID(id),
+			Peer:        m.pubKey,
+			NetworkType: route.IPv4Network,
+		}
+		cr = append(cr, fakeIPRoute)
+	}
+
+	m.notifier.SetInitialClientRoutes(cr, routesForComparison)
+}
 
 func (m *DefaultManager) setupRefCounters(useNoop bool) {
 	m.routeRefCounter = refcounter.New(
@@ -174,11 +201,11 @@ func (m *DefaultManager) setupRefCounters(useNoop bool) {
 }
 
 // Init sets up the routing
-func (m *DefaultManager) Init() (nbnet.AddHookFunc, nbnet.RemoveHookFunc, error) {
+func (m *DefaultManager) Init() error {
 	m.routeSelector = m.initSelector()
 
 	if nbnet.CustomRoutingDisabled() || m.disableClientRoutes {
-		return nil, nil, nil
+		return nil
 	}
 
 	if err := m.sysOps.CleanupRouting(nil); err != nil {
@@ -192,13 +219,12 @@ func (m *DefaultManager) Init() (nbnet.AddHookFunc, nbnet.RemoveHookFunc, error)
 
 	ips := resolveURLsToIPs(initialAddresses)
 
-	beforePeerHook, afterPeerHook, err := m.sysOps.SetupRouting(ips, m.stateManager)
-	if err != nil {
-		return nil, nil, fmt.Errorf("setup routing: %w", err)
+	if err := m.sysOps.SetupRouting(ips, m.stateManager); err != nil {
+		return fmt.Errorf("setup routing: %w", err)
 	}
 
 	log.Info("Routing setup complete")
-	return beforePeerHook, afterPeerHook, nil
+	return nil
 }
 
 func (m *DefaultManager) initSelector() *routeselector.RouteSelector {
@@ -222,16 +248,16 @@ func (m *DefaultManager) initSelector() *routeselector.RouteSelector {
 	return routeselector.NewRouteSelector()
 }
 
-func (m *DefaultManager) EnableServerRouter(firewall firewall.Manager) error {
-	if m.disableServerRoutes {
+// SetFirewall sets the firewall manager for the DefaultManager
+// Not thread-safe, should be called before starting the manager
+func (m *DefaultManager) SetFirewall(firewall firewall.Manager) error {
+	m.firewall = firewall
+
+	if m.disableServerRoutes || firewall == nil {
 		log.Info("server routes are disabled")
 		return nil
 	}
 
-	if firewall == nil {
-		return errors.New("firewall manager is not set")
-	}
-
 	var err error
 	m.serverRouter, err = server.NewRouter(m.ctx, m.wgInterface, firewall, m.statusRecorder)
 	if err != nil {
@@ -299,17 +325,20 @@ func (m *DefaultManager) updateSystemRoutes(newRoutes route.HAMap) error {
 	}
 
 	for id, route := range toAdd {
-		handler := client.HandlerFromRoute(
-			route,
-			m.routeRefCounter,
-			m.allowedIPsRefCounter,
-			m.dnsRouteInterval,
-			m.statusRecorder,
-			m.wgInterface,
-			m.dnsServer,
-			m.peerStore,
-			m.useNewDNSRoute,
-		)
+		params := common.HandlerParams{
+			Route:                route,
+			RouteRefCounter:      m.routeRefCounter,
+			AllowedIPsRefCounter: m.allowedIPsRefCounter,
+			DnsRouterInterval:    m.dnsRouteInterval,
+			StatusRecorder:       m.statusRecorder,
+			WgInterface:          m.wgInterface,
+			DnsServer:            m.dnsServer,
+			PeerStore:            m.peerStore,
+			UseNewDNSRoute:       m.useNewDNSRoute,
+			Firewall:             m.firewall,
+			FakeIPManager:        m.fakeIPManager,
+		}
+		handler := client.HandlerFromRoute(params)
 		if err := handler.AddRoute(m.ctx); err != nil {
 			merr = multierror.Append(merr, fmt.Errorf("add route %s: %w", handler.String(), err))
 			continue
@@ -517,6 +546,7 @@ func (m *DefaultManager) initialClientRoutes(initialRoutes []*route.Route) []*ro
 	for _, routes := range crMap {
 		rs = append(rs, routes...)
 	}
+
 	return rs
 }
 
diff --git a/client/internal/routemanager/mock.go b/client/internal/routemanager/mock.go
index 742294cd..be633c3f 100644
--- a/client/internal/routemanager/mock.go
+++ b/client/internal/routemanager/mock.go
@@ -9,7 +9,6 @@ import (
 	"github.com/netbirdio/netbird/client/internal/routeselector"
 	"github.com/netbirdio/netbird/client/internal/statemanager"
 	"github.com/netbirdio/netbird/route"
-	"github.com/netbirdio/netbird/util/net"
 )
 
 // MockManager is the mock instance of a route manager
@@ -23,8 +22,8 @@ type MockManager struct {
 	StopFunc                     func(manager *statemanager.Manager)
 }
 
-func (m *MockManager) Init() (net.AddHookFunc, net.RemoveHookFunc, error) {
-	return nil, nil, nil
+func (m *MockManager) Init() error {
+	return nil
 }
 
 // InitialRouteRange mock implementation of InitialRouteRange from Manager interface
@@ -87,7 +86,7 @@ func (m *MockManager) SetRouteChangeListener(listener listener.NetworkChangeList
 
 }
 
-func (m *MockManager) EnableServerRouter(firewall firewall.Manager) error {
+func (m *MockManager) SetFirewall(firewall.Manager) error {
 	panic("implement me")
 }
 
diff --git a/client/internal/routemanager/notifier/notifier.go b/client/internal/routemanager/notifier/notifier.go
deleted file mode 100644
index 3cc7c330..00000000
--- a/client/internal/routemanager/notifier/notifier.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package notifier
-
-import (
-	"net/netip"
-	"runtime"
-	"sort"
-	"strings"
-	"sync"
-
-	"github.com/netbirdio/netbird/client/internal/listener"
-	"github.com/netbirdio/netbird/route"
-)
-
-type Notifier struct {
-	initialRouteRanges []string
-	routeRanges        []string
-
-	listener    listener.NetworkChangeListener
-	listenerMux sync.Mutex
-}
-
-func NewNotifier() *Notifier {
-	return &Notifier{}
-}
-
-func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
-	n.listenerMux.Lock()
-	defer n.listenerMux.Unlock()
-	n.listener = listener
-}
-
-func (n *Notifier) SetInitialClientRoutes(clientRoutes []*route.Route) {
-	nets := make([]string, 0)
-	for _, r := range clientRoutes {
-		if r.IsDynamic() {
-			continue
-		}
-		nets = append(nets, r.Network.String())
-	}
-	sort.Strings(nets)
-	n.initialRouteRanges = nets
-}
-
-func (n *Notifier) OnNewRoutes(idMap route.HAMap) {
-	if runtime.GOOS != "android" {
-		return
-	}
-
-	var newNets []string
-	for _, routes := range idMap {
-		for _, r := range routes {
-			if r.IsDynamic() {
-				continue
-			}
-			newNets = append(newNets, r.Network.String())
-		}
-	}
-
-	sort.Strings(newNets)
-	if !n.hasDiff(n.initialRouteRanges, newNets) {
-		return
-	}
-
-	n.routeRanges = newNets
-	n.notify()
-}
-
-// OnNewPrefixes is called from iOS only
-func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
-	newNets := make([]string, 0)
-	for _, prefix := range prefixes {
-		newNets = append(newNets, prefix.String())
-	}
-
-	sort.Strings(newNets)
-	if !n.hasDiff(n.routeRanges, newNets) {
-		return
-	}
-
-	n.routeRanges = newNets
-	n.notify()
-}
-
-func (n *Notifier) notify() {
-	n.listenerMux.Lock()
-	defer n.listenerMux.Unlock()
-	if n.listener == nil {
-		return
-	}
-
-	go func(l listener.NetworkChangeListener) {
-		l.OnNetworkChanged(strings.Join(addIPv6RangeIfNeeded(n.routeRanges), ","))
-	}(n.listener)
-}
-
-func (n *Notifier) hasDiff(a []string, b []string) bool {
-	if len(a) != len(b) {
-		return true
-	}
-	for i, v := range a {
-		if v != b[i] {
-			return true
-		}
-	}
-	return false
-}
-
-func (n *Notifier) GetInitialRouteRanges() []string {
-	return addIPv6RangeIfNeeded(n.initialRouteRanges)
-}
-
-// addIPv6RangeIfNeeded returns the input ranges with the default IPv6 range when there is an IPv4 default route.
-func addIPv6RangeIfNeeded(inputRanges []string) []string {
-	ranges := inputRanges
-	for _, r := range inputRanges {
-		// we are intentionally adding the ipv6 default range in case of ipv4 default range
-		// to ensure that all traffic is managed by the tunnel interface on android
-		if r == "0.0.0.0/0" {
-			ranges = append(ranges, "::/0")
-			break
-		}
-	}
-	return ranges
-}
diff --git a/client/internal/routemanager/notifier/notifier_android.go b/client/internal/routemanager/notifier/notifier_android.go
new file mode 100644
index 00000000..dec0af87
--- /dev/null
+++ b/client/internal/routemanager/notifier/notifier_android.go
@@ -0,0 +1,127 @@
+//go:build android
+
+package notifier
+
+import (
+	"net/netip"
+	"slices"
+	"sort"
+	"strings"
+	"sync"
+
+	"github.com/netbirdio/netbird/client/internal/listener"
+	"github.com/netbirdio/netbird/route"
+)
+
+type Notifier struct {
+	initialRoutes []*route.Route
+	currentRoutes []*route.Route
+
+	listener    listener.NetworkChangeListener
+	listenerMux sync.Mutex
+}
+
+func NewNotifier() *Notifier {
+	return &Notifier{}
+}
+
+func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
+	n.listenerMux.Lock()
+	defer n.listenerMux.Unlock()
+	n.listener = listener
+}
+
+func (n *Notifier) SetInitialClientRoutes(initialRoutes []*route.Route, routesForComparison []*route.Route) {
+	// initialRoutes contains fake IP block for interface configuration
+	filteredInitial := make([]*route.Route, 0)
+	for _, r := range initialRoutes {
+		if r.IsDynamic() {
+			continue
+		}
+		filteredInitial = append(filteredInitial, r)
+	}
+	n.initialRoutes = filteredInitial
+
+	// routesForComparison excludes fake IP block for comparison with new routes
+	filteredComparison := make([]*route.Route, 0)
+	for _, r := range routesForComparison {
+		if r.IsDynamic() {
+			continue
+		}
+		filteredComparison = append(filteredComparison, r)
+	}
+	n.currentRoutes = filteredComparison
+}
+
+func (n *Notifier) OnNewRoutes(idMap route.HAMap) {
+	var newRoutes []*route.Route
+	for _, routes := range idMap {
+		for _, r := range routes {
+			if r.IsDynamic() {
+				continue
+			}
+			newRoutes = append(newRoutes, r)
+		}
+	}
+
+	if !n.hasRouteDiff(n.currentRoutes, newRoutes) {
+		return
+	}
+
+	n.currentRoutes = newRoutes
+	n.notify()
+}
+
+func (n *Notifier) OnNewPrefixes([]netip.Prefix) {
+	// Not used on Android
+}
+
+func (n *Notifier) notify() {
+	n.listenerMux.Lock()
+	defer n.listenerMux.Unlock()
+	if n.listener == nil {
+		return
+	}
+
+	routeStrings := n.routesToStrings(n.currentRoutes)
+	sort.Strings(routeStrings)
+	go func(l listener.NetworkChangeListener) {
+		l.OnNetworkChanged(strings.Join(n.addIPv6RangeIfNeeded(routeStrings, n.currentRoutes), ","))
+	}(n.listener)
+}
+
+func (n *Notifier) routesToStrings(routes []*route.Route) []string {
+	nets := make([]string, 0, len(routes))
+	for _, r := range routes {
+		nets = append(nets, r.NetString())
+	}
+	return nets
+}
+
+func (n *Notifier) hasRouteDiff(a []*route.Route, b []*route.Route) bool {
+	slices.SortFunc(a, func(x, y *route.Route) int {
+		return strings.Compare(x.NetString(), y.NetString())
+	})
+	slices.SortFunc(b, func(x, y *route.Route) int {
+		return strings.Compare(x.NetString(), y.NetString())
+	})
+
+	return !slices.EqualFunc(a, b, func(x, y *route.Route) bool {
+		return x.NetString() == y.NetString()
+	})
+}
+
+func (n *Notifier) GetInitialRouteRanges() []string {
+	initialStrings := n.routesToStrings(n.initialRoutes)
+	sort.Strings(initialStrings)
+	return n.addIPv6RangeIfNeeded(initialStrings, n.initialRoutes)
+}
+
+func (n *Notifier) addIPv6RangeIfNeeded(inputRanges []string, routes []*route.Route) []string {
+	for _, r := range routes {
+		if r.Network.Addr().Is4() && r.Network.Bits() == 0 {
+			return append(slices.Clone(inputRanges), "::/0")
+		}
+	}
+	return inputRanges
+}
diff --git a/client/internal/routemanager/notifier/notifier_ios.go b/client/internal/routemanager/notifier/notifier_ios.go
new file mode 100644
index 00000000..bb125cfa
--- /dev/null
+++ b/client/internal/routemanager/notifier/notifier_ios.go
@@ -0,0 +1,80 @@
+//go:build ios
+
+package notifier
+
+import (
+	"net/netip"
+	"slices"
+	"sort"
+	"strings"
+	"sync"
+
+	"github.com/netbirdio/netbird/client/internal/listener"
+	"github.com/netbirdio/netbird/route"
+)
+
+type Notifier struct {
+	currentPrefixes []string
+
+	listener    listener.NetworkChangeListener
+	listenerMux sync.Mutex
+}
+
+func NewNotifier() *Notifier {
+	return &Notifier{}
+}
+
+func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
+	n.listenerMux.Lock()
+	defer n.listenerMux.Unlock()
+	n.listener = listener
+}
+
+func (n *Notifier) SetInitialClientRoutes([]*route.Route, []*route.Route) {
+	// iOS doesn't care about initial routes
+}
+
+func (n *Notifier) OnNewRoutes(route.HAMap) {
+	// Not used on iOS
+}
+
+func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
+	newNets := make([]string, 0)
+	for _, prefix := range prefixes {
+		newNets = append(newNets, prefix.String())
+	}
+
+	sort.Strings(newNets)
+
+	if slices.Equal(n.currentPrefixes, newNets) {
+		return
+	}
+
+	n.currentPrefixes = newNets
+	n.notify()
+}
+
+func (n *Notifier) notify() {
+	n.listenerMux.Lock()
+	defer n.listenerMux.Unlock()
+	if n.listener == nil {
+		return
+	}
+
+	go func(l listener.NetworkChangeListener) {
+		l.OnNetworkChanged(strings.Join(n.addIPv6RangeIfNeeded(n.currentPrefixes), ","))
+	}(n.listener)
+}
+
+func (n *Notifier) GetInitialRouteRanges() []string {
+	return nil
+}
+
+func (n *Notifier) addIPv6RangeIfNeeded(inputRanges []string) []string {
+	for _, r := range inputRanges {
+		if r == "0.0.0.0/0" {
+			return append(slices.Clone(inputRanges), "::/0")
+		}
+	}
+	return inputRanges
+}
diff --git a/client/internal/routemanager/notifier/notifier_other.go b/client/internal/routemanager/notifier/notifier_other.go
new file mode 100644
index 00000000..0521e3dc
--- /dev/null
+++ b/client/internal/routemanager/notifier/notifier_other.go
@@ -0,0 +1,36 @@
+//go:build !android && !ios
+
+package notifier
+
+import (
+	"net/netip"
+
+	"github.com/netbirdio/netbird/client/internal/listener"
+	"github.com/netbirdio/netbird/route"
+)
+
+type Notifier struct{}
+
+func NewNotifier() *Notifier {
+	return &Notifier{}
+}
+
+func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
+	// Not used on non-mobile platforms
+}
+
+func (n *Notifier) SetInitialClientRoutes([]*route.Route, []*route.Route) {
+	// Not used on non-mobile platforms
+}
+
+func (n *Notifier) OnNewRoutes(idMap route.HAMap) {
+	// Not used on non-mobile platforms
+}
+
+func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
+	// Not used on non-mobile platforms
+}
+
+func (n *Notifier) GetInitialRouteRanges() []string {
+	return []string{}
+}
diff --git a/client/internal/routemanager/static/route.go b/client/internal/routemanager/static/route.go
index c8b9338e..d480fdf0 100644
--- a/client/internal/routemanager/static/route.go
+++ b/client/internal/routemanager/static/route.go
@@ -6,6 +6,7 @@ import (
 
 	log "github.com/sirupsen/logrus"
 
+	"github.com/netbirdio/netbird/client/internal/routemanager/common"
 	"github.com/netbirdio/netbird/client/internal/routemanager/refcounter"
 	"github.com/netbirdio/netbird/route"
 )
@@ -16,11 +17,11 @@ type Route struct {
 	allowedIPsRefcounter *refcounter.AllowedIPsRefCounter
 }
 
-func NewRoute(rt *route.Route, routeRefCounter *refcounter.RouteRefCounter, allowedIPsRefCounter *refcounter.AllowedIPsRefCounter) *Route {
+func NewRoute(params common.HandlerParams) *Route {
 	return &Route{
-		route:                rt,
-		routeRefCounter:      routeRefCounter,
-		allowedIPsRefcounter: allowedIPsRefCounter,
+		route:                params.Route,
+		routeRefCounter:      params.RouteRefCounter,
+		allowedIPsRefcounter: params.AllowedIPsRefCounter,
 	}
 }
 
diff --git a/client/internal/routemanager/systemops/systemops.go b/client/internal/routemanager/systemops/systemops.go
index 261567dc..b91348e9 100644
--- a/client/internal/routemanager/systemops/systemops.go
+++ b/client/internal/routemanager/systemops/systemops.go
@@ -5,6 +5,8 @@ import (
 	"net"
 	"net/netip"
 	"sync"
+	"sync/atomic"
+	"time"
 
 	"github.com/netbirdio/netbird/client/iface/wgaddr"
 	"github.com/netbirdio/netbird/client/internal/routemanager/notifier"
@@ -28,7 +30,10 @@ func (n Nexthop) String() string {
 	if n.Intf == nil {
 		return n.IP.String()
 	}
-	return fmt.Sprintf("%s @ %d (%s)", n.IP.String(), n.Intf.Index, n.Intf.Name)
+	if n.IP.IsValid() {
+		return fmt.Sprintf("%s @ %d (%s)", n.IP.String(), n.Intf.Index, n.Intf.Name)
+	}
+	return fmt.Sprintf("no-ip @ %d (%s)", n.Intf.Index, n.Intf.Name)
 }
 
 type wgIface interface {
@@ -49,6 +54,13 @@ type SysOps struct {
 	mu sync.Mutex
 	// notifier is used to notify the system of route changes (also used on mobile)
 	notifier *notifier.Notifier
+	// seq is an atomic counter for generating unique sequence numbers for route messages
+	//nolint:unused // only used on BSD systems
+	seq atomic.Uint32
+
+	localSubnetsCache     []*net.IPNet
+	localSubnetsCacheMu   sync.RWMutex
+	localSubnetsCacheTime time.Time
 }
 
 func NewSysOps(wgInterface wgIface, notifier *notifier.Notifier) *SysOps {
@@ -58,6 +70,11 @@ func NewSysOps(wgInterface wgIface, notifier *notifier.Notifier) *SysOps {
 	}
 }
 
+//nolint:unused // only used on BSD systems
+func (r *SysOps) getSeq() int {
+	return int(r.seq.Add(1))
+}
+
 func (r *SysOps) validateRoute(prefix netip.Prefix) error {
 	addr := prefix.Addr()
 
diff --git a/client/internal/routemanager/systemops/systemops_android.go b/client/internal/routemanager/systemops/systemops_android.go
index ca8aea3f..a375ce83 100644
--- a/client/internal/routemanager/systemops/systemops_android.go
+++ b/client/internal/routemanager/systemops/systemops_android.go
@@ -10,11 +10,10 @@ import (
 	log "github.com/sirupsen/logrus"
 
 	"github.com/netbirdio/netbird/client/internal/statemanager"
-	nbnet "github.com/netbirdio/netbird/util/net"
 )
 
-func (r *SysOps) SetupRouting([]net.IP, *statemanager.Manager) (nbnet.AddHookFunc, nbnet.RemoveHookFunc, error) {
-	return nil, nil, nil
+func (r *SysOps) SetupRouting([]net.IP, *statemanager.Manager) error {
+	return nil
 }
 
 func (r *SysOps) CleanupRouting(*statemanager.Manager) error {
diff --git a/client/internal/routemanager/systemops/systemops_generic.go b/client/internal/routemanager/systemops/systemops_generic.go
index d223a27b..128afa2a 100644
--- a/client/internal/routemanager/systemops/systemops_generic.go
+++ b/client/internal/routemanager/systemops/systemops_generic.go
@@ -10,6 +10,7 @@ import (
 	"net/netip"
 	"runtime"
 	"strconv"
+	"time"
 
 	"github.com/hashicorp/go-multierror"
 	"github.com/libp2p/go-netroute"
@@ -24,6 +25,8 @@ import (
 	nbnet "github.com/netbirdio/netbird/util/net"
 )
 
+const localSubnetsCacheTTL = 15 * time.Minute
+
 var splitDefaultv4_1 = netip.PrefixFrom(netip.IPv4Unspecified(), 1)
 var splitDefaultv4_2 = netip.PrefixFrom(netip.AddrFrom4([4]byte{128}), 1)
 var splitDefaultv6_1 = netip.PrefixFrom(netip.IPv6Unspecified(), 1)
@@ -31,7 +34,7 @@ var splitDefaultv6_2 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1)
 
 var ErrRoutingIsSeparate = errors.New("routing is separate")
 
-func (r *SysOps) setupRefCounter(initAddresses []net.IP, stateManager *statemanager.Manager) (nbnet.AddHookFunc, nbnet.RemoveHookFunc, error) {
+func (r *SysOps) setupRefCounter(initAddresses []net.IP, stateManager *statemanager.Manager) error {
 	stateManager.RegisterState(&ShutdownState{})
 
 	initialNextHopV4, err := GetNextHop(netip.IPv4Unspecified())
@@ -75,7 +78,10 @@ func (r *SysOps) setupRefCounter(initAddresses []net.IP, stateManager *statemana
 
 	r.refCounter = refCounter
 
-	return r.setupHooks(initAddresses, stateManager)
+	if err := r.setupHooks(initAddresses, stateManager); err != nil {
+		return fmt.Errorf("setup hooks: %w", err)
+	}
+	return nil
 }
 
 // updateState updates state on every change so it will be persisted regularly
@@ -128,18 +134,14 @@ func (r *SysOps) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf wgIface, init
 		return Nexthop{}, fmt.Errorf("get next hop: %w", err)
 	}
 
-	log.Debugf("Found next hop %s for prefix %s with interface %v", nexthop.IP, prefix, nexthop.IP)
-	exitNextHop := Nexthop{
-		IP:   nexthop.IP,
-		Intf: nexthop.Intf,
-	}
+	log.Debugf("Found next hop %s for prefix %s with interface %v", nexthop.IP, prefix, nexthop.Intf)
+	exitNextHop := nexthop
 
 	vpnAddr := vpnIntf.Address().IP
 
 	// if next hop is the VPN address or the interface is the VPN interface, we should use the initial values
 	if exitNextHop.IP == vpnAddr || exitNextHop.Intf != nil && exitNextHop.Intf.Name == vpnIntf.Name() {
 		log.Debugf("Route for prefix %s is pointing to the VPN interface, using initial next hop %v", prefix, initialNextHop)
-
 		exitNextHop = initialNextHop
 	}
 
@@ -152,12 +154,37 @@ func (r *SysOps) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf wgIface, init
 }
 
 func (r *SysOps) isPrefixInLocalSubnets(prefix netip.Prefix) (bool, *net.IPNet) {
+	r.localSubnetsCacheMu.RLock()
+	cacheAge := time.Since(r.localSubnetsCacheTime)
+	subnets := r.localSubnetsCache
+	r.localSubnetsCacheMu.RUnlock()
+
+	if cacheAge > localSubnetsCacheTTL || subnets == nil {
+		r.localSubnetsCacheMu.Lock()
+		if time.Since(r.localSubnetsCacheTime) > localSubnetsCacheTTL || r.localSubnetsCache == nil {
+			r.refreshLocalSubnetsCache()
+		}
+		subnets = r.localSubnetsCache
+		r.localSubnetsCacheMu.Unlock()
+	}
+
+	for _, subnet := range subnets {
+		if subnet.Contains(prefix.Addr().AsSlice()) {
+			return true, subnet
+		}
+	}
+
+	return false, nil
+}
+
+func (r *SysOps) refreshLocalSubnetsCache() {
 	localInterfaces, err := net.Interfaces()
 	if err != nil {
 		log.Errorf("Failed to get local interfaces: %v", err)
-		return false, nil
+		return
 	}
 
+	var newSubnets []*net.IPNet
 	for _, intf := range localInterfaces {
 		addrs, err := intf.Addrs()
 		if err != nil {
@@ -171,14 +198,12 @@ func (r *SysOps) isPrefixInLocalSubnets(prefix netip.Prefix) (bool, *net.IPNet)
 				log.Errorf("Failed to convert address to IPNet: %v", addr)
 				continue
 			}
-
-			if ipnet.Contains(prefix.Addr().AsSlice()) {
-				return true, ipnet
-			}
+			newSubnets = append(newSubnets, ipnet)
 		}
 	}
 
-	return false, nil
+	r.localSubnetsCache = newSubnets
+	r.localSubnetsCacheTime = time.Now()
 }
 
 // genericAddVPNRoute adds a new route to the vpn interface, it splits the default prefix
@@ -264,7 +289,7 @@ func (r *SysOps) genericRemoveVPNRoute(prefix netip.Prefix, intf *net.Interface)
 	return r.removeFromRouteTable(prefix, nextHop)
 }
 
-func (r *SysOps) setupHooks(initAddresses []net.IP, stateManager *statemanager.Manager) (nbnet.AddHookFunc, nbnet.RemoveHookFunc, error) {
+func (r *SysOps) setupHooks(initAddresses []net.IP, stateManager *statemanager.Manager) error {
 	beforeHook := func(connID nbnet.ConnectionID, ip net.IP) error {
 		prefix, err := util.GetPrefixFromIP(ip)
 		if err != nil {
@@ -289,9 +314,11 @@ func (r *SysOps) setupHooks(initAddresses []net.IP, stateManager *statemanager.M
 		return nil
 	}
 
+	var merr *multierror.Error
+
 	for _, ip := range initAddresses {
 		if err := beforeHook("init", ip); err != nil {
-			log.Errorf("Failed to add route reference: %v", err)
+			merr = multierror.Append(merr, fmt.Errorf("add initial route for %s: %w", ip, err))
 		}
 	}
 
@@ -300,11 +327,11 @@ func (r *SysOps) setupHooks(initAddresses []net.IP, stateManager *statemanager.M
 			return ctx.Err()
 		}
 
-		var result *multierror.Error
+		var merr *multierror.Error
 		for _, ip := range resolvedIPs {
-			result = multierror.Append(result, beforeHook(connID, ip.IP))
+			merr = multierror.Append(merr, beforeHook(connID, ip.IP))
 		}
-		return nberrors.FormatErrorOrNil(result)
+		return nberrors.FormatErrorOrNil(merr)
 	})
 
 	nbnet.AddDialerCloseHook(func(connID nbnet.ConnectionID, conn *net.Conn) error {
@@ -319,7 +346,16 @@ func (r *SysOps) setupHooks(initAddresses []net.IP, stateManager *statemanager.M
 		return afterHook(connID)
 	})
 
-	return beforeHook, afterHook, nil
+	nbnet.AddListenerAddressRemoveHook(func(connID nbnet.ConnectionID, prefix netip.Prefix) error {
+		if _, err := r.refCounter.Decrement(prefix); err != nil {
+			return fmt.Errorf("remove route reference: %w", err)
+		}
+
+		r.updateState(stateManager)
+		return nil
+	})
+
+	return nberrors.FormatErrorOrNil(merr)
 }
 
 func GetNextHop(ip netip.Addr) (Nexthop, error) {
diff --git a/client/internal/routemanager/systemops/systemops_ios.go b/client/internal/routemanager/systemops/systemops_ios.go
index bf06f373..10356eae 100644
--- a/client/internal/routemanager/systemops/systemops_ios.go
+++ b/client/internal/routemanager/systemops/systemops_ios.go
@@ -10,14 +10,13 @@ import (
 	log "github.com/sirupsen/logrus"
 
 	"github.com/netbirdio/netbird/client/internal/statemanager"
-	nbnet "github.com/netbirdio/netbird/util/net"
 )
 
-func (r *SysOps) SetupRouting([]net.IP, *statemanager.Manager) (nbnet.AddHookFunc, nbnet.RemoveHookFunc, error) {
+func (r *SysOps) SetupRouting([]net.IP, *statemanager.Manager) error {
 	r.mu.Lock()
 	defer r.mu.Unlock()
 	r.prefixes = make(map[netip.Prefix]struct{})
-	return nil, nil, nil
+	return nil
 }
 
 func (r *SysOps) CleanupRouting(*statemanager.Manager) error {
diff --git a/client/internal/routemanager/systemops/systemops_linux.go b/client/internal/routemanager/systemops/systemops_linux.go
index b48cfa24..711f1d75 100644
--- a/client/internal/routemanager/systemops/systemops_linux.go
+++ b/client/internal/routemanager/systemops/systemops_linux.go
@@ -72,7 +72,7 @@ func getSetupRules() []ruleParams {
 // Rule 2 (VPN Traffic Routing): Directs all remaining traffic to the 'NetbirdVPNTableID' custom routing table.
 // This table is where a default route or other specific routes received from the management server are configured,
 // enabling VPN connectivity.
-func (r *SysOps) SetupRouting(initAddresses []net.IP, stateManager *statemanager.Manager) (_ nbnet.AddHookFunc, _ nbnet.RemoveHookFunc, err error) {
+func (r *SysOps) SetupRouting(initAddresses []net.IP, stateManager *statemanager.Manager) (err error) {
 	if !nbnet.AdvancedRouting() {
 		log.Infof("Using legacy routing setup")
 		return r.setupRefCounter(initAddresses, stateManager)
@@ -89,7 +89,7 @@ func (r *SysOps) SetupRouting(initAddresses []net.IP, stateManager *statemanager
 	rules := getSetupRules()
 	for _, rule := range rules {
 		if err := addRule(rule); err != nil {
-			return nil, nil, fmt.Errorf("%s: %w", rule.description, err)
+			return fmt.Errorf("%s: %w", rule.description, err)
 		}
 	}
 
@@ -104,7 +104,7 @@ func (r *SysOps) SetupRouting(initAddresses []net.IP, stateManager *statemanager
 	}
 	originalSysctl = originalValues
 
-	return nil, nil, nil
+	return nil
 }
 
 // CleanupRouting performs a thorough cleanup of the routing configuration established by 'setupRouting'.
diff --git a/client/internal/routemanager/systemops/systemops_unix.go b/client/internal/routemanager/systemops/systemops_unix.go
index f284e131..f165f777 100644
--- a/client/internal/routemanager/systemops/systemops_unix.go
+++ b/client/internal/routemanager/systemops/systemops_unix.go
@@ -18,10 +18,9 @@ import (
 	"golang.org/x/sys/unix"
 
 	"github.com/netbirdio/netbird/client/internal/statemanager"
-	nbnet "github.com/netbirdio/netbird/util/net"
 )
 
-func (r *SysOps) SetupRouting(initAddresses []net.IP, stateManager *statemanager.Manager) (nbnet.AddHookFunc, nbnet.RemoveHookFunc, error) {
+func (r *SysOps) SetupRouting(initAddresses []net.IP, stateManager *statemanager.Manager) error {
 	return r.setupRefCounter(initAddresses, stateManager)
 }
 
@@ -108,7 +107,7 @@ func (r *SysOps) buildRouteMessage(action int, prefix netip.Prefix, nexthop Next
 		Type:    action,
 		Flags:   unix.RTF_UP,
 		Version: unix.RTM_VERSION,
-		Seq:     1,
+		Seq:     r.getSeq(),
 	}
 
 	const numAddrs = unix.RTAX_NETMASK + 1
diff --git a/client/internal/routemanager/systemops/systemops_windows.go b/client/internal/routemanager/systemops/systemops_windows.go
index 11eaa435..7afac9ae 100644
--- a/client/internal/routemanager/systemops/systemops_windows.go
+++ b/client/internal/routemanager/systemops/systemops_windows.go
@@ -19,7 +19,6 @@ import (
 	"golang.org/x/sys/windows"
 
 	"github.com/netbirdio/netbird/client/internal/statemanager"
-	nbnet "github.com/netbirdio/netbird/util/net"
 )
 
 const InfiniteLifetime = 0xffffffff
@@ -137,7 +136,7 @@ const (
 	RouteDeleted
 )
 
-func (r *SysOps) SetupRouting(initAddresses []net.IP, stateManager *statemanager.Manager) (nbnet.AddHookFunc, nbnet.RemoveHookFunc, error) {
+func (r *SysOps) SetupRouting(initAddresses []net.IP, stateManager *statemanager.Manager) error {
 	return r.setupRefCounter(initAddresses, stateManager)
 }
 
diff --git a/client/netbird.wxs b/client/netbird.wxs
index 5e03a014..91595539 100644
--- a/client/netbird.wxs
+++ b/client/netbird.wxs
@@ -1,8 +1,10 @@
 <Wix
-	xmlns="http://wixtoolset.org/schemas/v4/wxs">
+	xmlns="http://wixtoolset.org/schemas/v4/wxs"
+	xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
 	<Package Name="NetBird" Version="$(env.NETBIRD_VERSION)" Manufacturer="NetBird GmbH" Language="1033" UpgradeCode="6456ec4e-3ad6-4b9b-a2be-98e81cb21ccf"
         InstallerVersion="500" Compressed="yes"  Codepage="utf-8" >
 
+
 		<MediaTemplate EmbedCab="yes" />
 
 		<Feature Id="NetbirdFeature" Title="Netbird" Level="1">
@@ -46,29 +48,10 @@
 			<ComponentRef Id="NetbirdFiles" />
 		</ComponentGroup>
 
-		<Property Id="cmd" Value="cmd.exe"/>
-
-		<CustomAction Id="KillDaemon"
-                      ExeCommand='/c "taskkill /im netbird.exe"'
-                      Execute="deferred"
-                      Property="cmd"
-                      Impersonate="no"
-                      Return="ignore"
-            />
+		<util:CloseApplication Id="CloseNetBird" CloseMessage="no" Target="netbird.exe" RebootPrompt="no" />
+		<util:CloseApplication Id="CloseNetBirdUI" CloseMessage="no" Target="netbird-ui.exe" RebootPrompt="no" />
 
-		<CustomAction Id="KillUI"
-                      ExeCommand='/c "taskkill /im netbird-ui.exe"'
-                      Execute="deferred"
-                      Property="cmd"
-                      Impersonate="no"
-                      Return="ignore"
-            />
 
-		<InstallExecuteSequence>
-			<!-- For Uninstallation -->
-			<Custom Action="KillDaemon" Before="RemoveFiles" Condition="Installed"/>
-			<Custom Action="KillUI" After="KillDaemon" Condition="Installed"/>
-		</InstallExecuteSequence>
 
 		<!-- Icons -->
 		<Icon Id="NetbirdIcon" SourceFile=".\client\ui\assets\netbird.ico" />
diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go
index 6c4c2dc9..753aa62d 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-gen-go v1.36.6
+// 	protoc        v5.29.3
 // source: daemon.proto
 
 package proto
@@ -14,6 +14,7 @@ import (
 	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
 	reflect "reflect"
 	sync "sync"
+	unsafe "unsafe"
 )
 
 const (
@@ -195,18 +196,16 @@ func (SystemEvent_Category) EnumDescriptor() ([]byte, []int) {
 }
 
 type EmptyRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *EmptyRequest) Reset() {
 	*x = EmptyRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[0]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *EmptyRequest) String() string {
@@ -217,7 +216,7 @@ func (*EmptyRequest) ProtoMessage() {}
 
 func (x *EmptyRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[0]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -233,16 +232,13 @@ func (*EmptyRequest) Descriptor() ([]byte, []int) {
 }
 
 type LoginRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
+	state protoimpl.MessageState `protogen:"open.v1"`
 	// setupKey netbird setup key.
 	SetupKey string `protobuf:"bytes,1,opt,name=setupKey,proto3" json:"setupKey,omitempty"`
 	// This is the old PreSharedKey field which will be deprecated in favor of optionalPreSharedKey field that is defined as optional
 	// to allow clearing of preshared key while being able to persist in the config file.
 	//
-	// Deprecated: Do not use.
+	// Deprecated: Marked as deprecated in daemon.proto.
 	PreSharedKey string `protobuf:"bytes,2,opt,name=preSharedKey,proto3" json:"preSharedKey,omitempty"`
 	// managementUrl to authenticate.
 	ManagementUrl string `protobuf:"bytes,3,opt,name=managementUrl,proto3" json:"managementUrl,omitempty"`
@@ -280,15 +276,15 @@ type LoginRequest struct {
 	CleanDNSLabels        bool  `protobuf:"varint,27,opt,name=cleanDNSLabels,proto3" json:"cleanDNSLabels,omitempty"`
 	LazyConnectionEnabled *bool `protobuf:"varint,28,opt,name=lazyConnectionEnabled,proto3,oneof" json:"lazyConnectionEnabled,omitempty"`
 	BlockInbound          *bool `protobuf:"varint,29,opt,name=block_inbound,json=blockInbound,proto3,oneof" json:"block_inbound,omitempty"`
+	unknownFields         protoimpl.UnknownFields
+	sizeCache             protoimpl.SizeCache
 }
 
 func (x *LoginRequest) Reset() {
 	*x = LoginRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[1]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *LoginRequest) String() string {
@@ -299,7 +295,7 @@ func (*LoginRequest) ProtoMessage() {}
 
 func (x *LoginRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[1]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -321,7 +317,7 @@ func (x *LoginRequest) GetSetupKey() string {
 	return ""
 }
 
-// Deprecated: Do not use.
+// Deprecated: Marked as deprecated in daemon.proto.
 func (x *LoginRequest) GetPreSharedKey() string {
 	if x != nil {
 		return x.PreSharedKey
@@ -519,23 +515,20 @@ func (x *LoginRequest) GetBlockInbound() bool {
 }
 
 type LoginResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	NeedsSSOLogin           bool   `protobuf:"varint,1,opt,name=needsSSOLogin,proto3" json:"needsSSOLogin,omitempty"`
-	UserCode                string `protobuf:"bytes,2,opt,name=userCode,proto3" json:"userCode,omitempty"`
-	VerificationURI         string `protobuf:"bytes,3,opt,name=verificationURI,proto3" json:"verificationURI,omitempty"`
-	VerificationURIComplete string `protobuf:"bytes,4,opt,name=verificationURIComplete,proto3" json:"verificationURIComplete,omitempty"`
+	state                   protoimpl.MessageState `protogen:"open.v1"`
+	NeedsSSOLogin           bool                   `protobuf:"varint,1,opt,name=needsSSOLogin,proto3" json:"needsSSOLogin,omitempty"`
+	UserCode                string                 `protobuf:"bytes,2,opt,name=userCode,proto3" json:"userCode,omitempty"`
+	VerificationURI         string                 `protobuf:"bytes,3,opt,name=verificationURI,proto3" json:"verificationURI,omitempty"`
+	VerificationURIComplete string                 `protobuf:"bytes,4,opt,name=verificationURIComplete,proto3" json:"verificationURIComplete,omitempty"`
+	unknownFields           protoimpl.UnknownFields
+	sizeCache               protoimpl.SizeCache
 }
 
 func (x *LoginResponse) Reset() {
 	*x = LoginResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[2]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[2]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *LoginResponse) String() string {
@@ -546,7 +539,7 @@ func (*LoginResponse) ProtoMessage() {}
 
 func (x *LoginResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[2]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -590,21 +583,18 @@ func (x *LoginResponse) GetVerificationURIComplete() string {
 }
 
 type WaitSSOLoginRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	UserCode      string                 `protobuf:"bytes,1,opt,name=userCode,proto3" json:"userCode,omitempty"`
+	Hostname      string                 `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	UserCode string `protobuf:"bytes,1,opt,name=userCode,proto3" json:"userCode,omitempty"`
-	Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *WaitSSOLoginRequest) Reset() {
 	*x = WaitSSOLoginRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[3]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[3]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *WaitSSOLoginRequest) String() string {
@@ -615,7 +605,7 @@ func (*WaitSSOLoginRequest) ProtoMessage() {}
 
 func (x *WaitSSOLoginRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[3]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -645,18 +635,16 @@ func (x *WaitSSOLoginRequest) GetHostname() string {
 }
 
 type WaitSSOLoginResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *WaitSSOLoginResponse) Reset() {
 	*x = WaitSSOLoginResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[4]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[4]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *WaitSSOLoginResponse) String() string {
@@ -667,7 +655,7 @@ func (*WaitSSOLoginResponse) ProtoMessage() {}
 
 func (x *WaitSSOLoginResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[4]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -683,18 +671,16 @@ func (*WaitSSOLoginResponse) Descriptor() ([]byte, []int) {
 }
 
 type UpRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *UpRequest) Reset() {
 	*x = UpRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[5]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[5]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *UpRequest) String() string {
@@ -705,7 +691,7 @@ func (*UpRequest) ProtoMessage() {}
 
 func (x *UpRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[5]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -721,18 +707,16 @@ func (*UpRequest) Descriptor() ([]byte, []int) {
 }
 
 type UpResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *UpResponse) Reset() {
 	*x = UpResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[6]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[6]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *UpResponse) String() string {
@@ -743,7 +727,7 @@ func (*UpResponse) ProtoMessage() {}
 
 func (x *UpResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[6]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -759,20 +743,18 @@ func (*UpResponse) Descriptor() ([]byte, []int) {
 }
 
 type StatusRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	GetFullPeerStatus bool `protobuf:"varint,1,opt,name=getFullPeerStatus,proto3" json:"getFullPeerStatus,omitempty"`
+	state             protoimpl.MessageState `protogen:"open.v1"`
+	GetFullPeerStatus bool                   `protobuf:"varint,1,opt,name=getFullPeerStatus,proto3" json:"getFullPeerStatus,omitempty"`
+	ShouldRunProbes   bool                   `protobuf:"varint,2,opt,name=shouldRunProbes,proto3" json:"shouldRunProbes,omitempty"`
+	unknownFields     protoimpl.UnknownFields
+	sizeCache         protoimpl.SizeCache
 }
 
 func (x *StatusRequest) Reset() {
 	*x = StatusRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[7]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[7]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *StatusRequest) String() string {
@@ -783,7 +765,7 @@ func (*StatusRequest) ProtoMessage() {}
 
 func (x *StatusRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[7]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -805,25 +787,29 @@ func (x *StatusRequest) GetGetFullPeerStatus() bool {
 	return false
 }
 
-type StatusResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
+func (x *StatusRequest) GetShouldRunProbes() bool {
+	if x != nil {
+		return x.ShouldRunProbes
+	}
+	return false
+}
 
+type StatusResponse struct {
+	state protoimpl.MessageState `protogen:"open.v1"`
 	// status of the server.
 	Status     string      `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
 	FullStatus *FullStatus `protobuf:"bytes,2,opt,name=fullStatus,proto3" json:"fullStatus,omitempty"`
 	// NetBird daemon version
 	DaemonVersion string `protobuf:"bytes,3,opt,name=daemonVersion,proto3" json:"daemonVersion,omitempty"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *StatusResponse) Reset() {
 	*x = StatusResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[8]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[8]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *StatusResponse) String() string {
@@ -834,7 +820,7 @@ func (*StatusResponse) ProtoMessage() {}
 
 func (x *StatusResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[8]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -871,18 +857,16 @@ func (x *StatusResponse) GetDaemonVersion() string {
 }
 
 type DownRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *DownRequest) Reset() {
 	*x = DownRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[9]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[9]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *DownRequest) String() string {
@@ -893,7 +877,7 @@ func (*DownRequest) ProtoMessage() {}
 
 func (x *DownRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[9]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -909,18 +893,16 @@ func (*DownRequest) Descriptor() ([]byte, []int) {
 }
 
 type DownResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *DownResponse) Reset() {
 	*x = DownResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[10]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[10]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *DownResponse) String() string {
@@ -931,7 +913,7 @@ func (*DownResponse) ProtoMessage() {}
 
 func (x *DownResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[10]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -947,18 +929,16 @@ func (*DownResponse) Descriptor() ([]byte, []int) {
 }
 
 type GetConfigRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *GetConfigRequest) Reset() {
 	*x = GetConfigRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[11]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[11]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *GetConfigRequest) String() string {
@@ -969,7 +949,7 @@ func (*GetConfigRequest) ProtoMessage() {}
 
 func (x *GetConfigRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[11]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -985,10 +965,7 @@ func (*GetConfigRequest) Descriptor() ([]byte, []int) {
 }
 
 type GetConfigResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
+	state protoimpl.MessageState `protogen:"open.v1"`
 	// managementUrl settings value.
 	ManagementUrl string `protobuf:"bytes,1,opt,name=managementUrl,proto3" json:"managementUrl,omitempty"`
 	// configFile settings value.
@@ -1013,15 +990,15 @@ type GetConfigResponse struct {
 	DisableClientRoutes   bool   `protobuf:"varint,18,opt,name=disable_client_routes,json=disableClientRoutes,proto3" json:"disable_client_routes,omitempty"`
 	DisableServerRoutes   bool   `protobuf:"varint,19,opt,name=disable_server_routes,json=disableServerRoutes,proto3" json:"disable_server_routes,omitempty"`
 	BlockLanAccess        bool   `protobuf:"varint,20,opt,name=block_lan_access,json=blockLanAccess,proto3" json:"block_lan_access,omitempty"`
+	unknownFields         protoimpl.UnknownFields
+	sizeCache             protoimpl.SizeCache
 }
 
 func (x *GetConfigResponse) Reset() {
 	*x = GetConfigResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[12]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[12]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *GetConfigResponse) String() string {
@@ -1032,7 +1009,7 @@ func (*GetConfigResponse) ProtoMessage() {}
 
 func (x *GetConfigResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[12]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1182,10 +1159,7 @@ func (x *GetConfigResponse) GetBlockLanAccess() bool {
 
 // PeerState contains the latest state of a peer
 type PeerState struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
+	state                      protoimpl.MessageState `protogen:"open.v1"`
 	IP                         string                 `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
 	PubKey                     string                 `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
 	ConnStatus                 string                 `protobuf:"bytes,3,opt,name=connStatus,proto3" json:"connStatus,omitempty"`
@@ -1203,15 +1177,15 @@ type PeerState struct {
 	Networks                   []string               `protobuf:"bytes,16,rep,name=networks,proto3" json:"networks,omitempty"`
 	Latency                    *durationpb.Duration   `protobuf:"bytes,17,opt,name=latency,proto3" json:"latency,omitempty"`
 	RelayAddress               string                 `protobuf:"bytes,18,opt,name=relayAddress,proto3" json:"relayAddress,omitempty"`
+	unknownFields              protoimpl.UnknownFields
+	sizeCache                  protoimpl.SizeCache
 }
 
 func (x *PeerState) Reset() {
 	*x = PeerState{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[13]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[13]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *PeerState) String() string {
@@ -1222,7 +1196,7 @@ func (*PeerState) ProtoMessage() {}
 
 func (x *PeerState) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[13]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1356,28 +1330,32 @@ func (x *PeerState) GetRelayAddress() string {
 	return ""
 }
 
+func (x *PeerState) GetConnectionType() string {
+	if x.Relayed {
+		return "Relayed"
+	}
+	return "P2P"
+}
+
 // LocalPeerState contains the latest state of the local peer
 type LocalPeerState struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	IP                  string   `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
-	PubKey              string   `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
-	KernelInterface     bool     `protobuf:"varint,3,opt,name=kernelInterface,proto3" json:"kernelInterface,omitempty"`
-	Fqdn                string   `protobuf:"bytes,4,opt,name=fqdn,proto3" json:"fqdn,omitempty"`
-	RosenpassEnabled    bool     `protobuf:"varint,5,opt,name=rosenpassEnabled,proto3" json:"rosenpassEnabled,omitempty"`
-	RosenpassPermissive bool     `protobuf:"varint,6,opt,name=rosenpassPermissive,proto3" json:"rosenpassPermissive,omitempty"`
-	Networks            []string `protobuf:"bytes,7,rep,name=networks,proto3" json:"networks,omitempty"`
+	state               protoimpl.MessageState `protogen:"open.v1"`
+	IP                  string                 `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
+	PubKey              string                 `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
+	KernelInterface     bool                   `protobuf:"varint,3,opt,name=kernelInterface,proto3" json:"kernelInterface,omitempty"`
+	Fqdn                string                 `protobuf:"bytes,4,opt,name=fqdn,proto3" json:"fqdn,omitempty"`
+	RosenpassEnabled    bool                   `protobuf:"varint,5,opt,name=rosenpassEnabled,proto3" json:"rosenpassEnabled,omitempty"`
+	RosenpassPermissive bool                   `protobuf:"varint,6,opt,name=rosenpassPermissive,proto3" json:"rosenpassPermissive,omitempty"`
+	Networks            []string               `protobuf:"bytes,7,rep,name=networks,proto3" json:"networks,omitempty"`
+	unknownFields       protoimpl.UnknownFields
+	sizeCache           protoimpl.SizeCache
 }
 
 func (x *LocalPeerState) Reset() {
 	*x = LocalPeerState{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[14]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[14]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *LocalPeerState) String() string {
@@ -1388,7 +1366,7 @@ func (*LocalPeerState) ProtoMessage() {}
 
 func (x *LocalPeerState) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[14]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1454,22 +1432,19 @@ func (x *LocalPeerState) GetNetworks() []string {
 
 // SignalState contains the latest state of a signal connection
 type SignalState struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	URL           string                 `protobuf:"bytes,1,opt,name=URL,proto3" json:"URL,omitempty"`
+	Connected     bool                   `protobuf:"varint,2,opt,name=connected,proto3" json:"connected,omitempty"`
+	Error         string                 `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	URL       string `protobuf:"bytes,1,opt,name=URL,proto3" json:"URL,omitempty"`
-	Connected bool   `protobuf:"varint,2,opt,name=connected,proto3" json:"connected,omitempty"`
-	Error     string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *SignalState) Reset() {
 	*x = SignalState{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[15]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[15]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *SignalState) String() string {
@@ -1480,7 +1455,7 @@ func (*SignalState) ProtoMessage() {}
 
 func (x *SignalState) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[15]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1518,22 +1493,19 @@ func (x *SignalState) GetError() string {
 
 // ManagementState contains the latest state of a management connection
 type ManagementState struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	URL           string                 `protobuf:"bytes,1,opt,name=URL,proto3" json:"URL,omitempty"`
+	Connected     bool                   `protobuf:"varint,2,opt,name=connected,proto3" json:"connected,omitempty"`
+	Error         string                 `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	URL       string `protobuf:"bytes,1,opt,name=URL,proto3" json:"URL,omitempty"`
-	Connected bool   `protobuf:"varint,2,opt,name=connected,proto3" json:"connected,omitempty"`
-	Error     string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *ManagementState) Reset() {
 	*x = ManagementState{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[16]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[16]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *ManagementState) String() string {
@@ -1544,7 +1516,7 @@ func (*ManagementState) ProtoMessage() {}
 
 func (x *ManagementState) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[16]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1582,22 +1554,19 @@ func (x *ManagementState) GetError() string {
 
 // RelayState contains the latest state of the relay
 type RelayState struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	URI           string                 `protobuf:"bytes,1,opt,name=URI,proto3" json:"URI,omitempty"`
+	Available     bool                   `protobuf:"varint,2,opt,name=available,proto3" json:"available,omitempty"`
+	Error         string                 `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	URI       string `protobuf:"bytes,1,opt,name=URI,proto3" json:"URI,omitempty"`
-	Available bool   `protobuf:"varint,2,opt,name=available,proto3" json:"available,omitempty"`
-	Error     string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *RelayState) Reset() {
 	*x = RelayState{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[17]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[17]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *RelayState) String() string {
@@ -1608,7 +1577,7 @@ func (*RelayState) ProtoMessage() {}
 
 func (x *RelayState) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[17]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1645,23 +1614,20 @@ func (x *RelayState) GetError() string {
 }
 
 type NSGroupState struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Servers       []string               `protobuf:"bytes,1,rep,name=servers,proto3" json:"servers,omitempty"`
+	Domains       []string               `protobuf:"bytes,2,rep,name=domains,proto3" json:"domains,omitempty"`
+	Enabled       bool                   `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"`
+	Error         string                 `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Servers []string `protobuf:"bytes,1,rep,name=servers,proto3" json:"servers,omitempty"`
-	Domains []string `protobuf:"bytes,2,rep,name=domains,proto3" json:"domains,omitempty"`
-	Enabled bool     `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"`
-	Error   string   `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *NSGroupState) Reset() {
 	*x = NSGroupState{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[18]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[18]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *NSGroupState) String() string {
@@ -1672,7 +1638,7 @@ func (*NSGroupState) ProtoMessage() {}
 
 func (x *NSGroupState) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[18]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1717,28 +1683,25 @@ func (x *NSGroupState) GetError() string {
 
 // FullStatus contains the full state held by the Status instance
 type FullStatus struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	ManagementState         *ManagementState `protobuf:"bytes,1,opt,name=managementState,proto3" json:"managementState,omitempty"`
-	SignalState             *SignalState     `protobuf:"bytes,2,opt,name=signalState,proto3" json:"signalState,omitempty"`
-	LocalPeerState          *LocalPeerState  `protobuf:"bytes,3,opt,name=localPeerState,proto3" json:"localPeerState,omitempty"`
-	Peers                   []*PeerState     `protobuf:"bytes,4,rep,name=peers,proto3" json:"peers,omitempty"`
-	Relays                  []*RelayState    `protobuf:"bytes,5,rep,name=relays,proto3" json:"relays,omitempty"`
-	DnsServers              []*NSGroupState  `protobuf:"bytes,6,rep,name=dns_servers,json=dnsServers,proto3" json:"dns_servers,omitempty"`
-	NumberOfForwardingRules int32            `protobuf:"varint,8,opt,name=NumberOfForwardingRules,proto3" json:"NumberOfForwardingRules,omitempty"`
-	Events                  []*SystemEvent   `protobuf:"bytes,7,rep,name=events,proto3" json:"events,omitempty"`
-	LazyConnectionEnabled   bool             `protobuf:"varint,9,opt,name=lazyConnectionEnabled,proto3" json:"lazyConnectionEnabled,omitempty"`
+	state                   protoimpl.MessageState `protogen:"open.v1"`
+	ManagementState         *ManagementState       `protobuf:"bytes,1,opt,name=managementState,proto3" json:"managementState,omitempty"`
+	SignalState             *SignalState           `protobuf:"bytes,2,opt,name=signalState,proto3" json:"signalState,omitempty"`
+	LocalPeerState          *LocalPeerState        `protobuf:"bytes,3,opt,name=localPeerState,proto3" json:"localPeerState,omitempty"`
+	Peers                   []*PeerState           `protobuf:"bytes,4,rep,name=peers,proto3" json:"peers,omitempty"`
+	Relays                  []*RelayState          `protobuf:"bytes,5,rep,name=relays,proto3" json:"relays,omitempty"`
+	DnsServers              []*NSGroupState        `protobuf:"bytes,6,rep,name=dns_servers,json=dnsServers,proto3" json:"dns_servers,omitempty"`
+	NumberOfForwardingRules int32                  `protobuf:"varint,8,opt,name=NumberOfForwardingRules,proto3" json:"NumberOfForwardingRules,omitempty"`
+	Events                  []*SystemEvent         `protobuf:"bytes,7,rep,name=events,proto3" json:"events,omitempty"`
+	LazyConnectionEnabled   bool                   `protobuf:"varint,9,opt,name=lazyConnectionEnabled,proto3" json:"lazyConnectionEnabled,omitempty"`
+	unknownFields           protoimpl.UnknownFields
+	sizeCache               protoimpl.SizeCache
 }
 
 func (x *FullStatus) Reset() {
 	*x = FullStatus{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[19]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[19]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *FullStatus) String() string {
@@ -1749,7 +1712,7 @@ func (*FullStatus) ProtoMessage() {}
 
 func (x *FullStatus) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[19]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1829,18 +1792,16 @@ func (x *FullStatus) GetLazyConnectionEnabled() bool {
 
 // Networks
 type ListNetworksRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *ListNetworksRequest) Reset() {
 	*x = ListNetworksRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[20]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[20]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *ListNetworksRequest) String() string {
@@ -1851,7 +1812,7 @@ func (*ListNetworksRequest) ProtoMessage() {}
 
 func (x *ListNetworksRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[20]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1867,20 +1828,17 @@ func (*ListNetworksRequest) Descriptor() ([]byte, []int) {
 }
 
 type ListNetworksResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Routes        []*Network             `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Routes []*Network `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *ListNetworksResponse) Reset() {
 	*x = ListNetworksResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[21]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[21]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *ListNetworksResponse) String() string {
@@ -1891,7 +1849,7 @@ func (*ListNetworksResponse) ProtoMessage() {}
 
 func (x *ListNetworksResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[21]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1914,22 +1872,19 @@ func (x *ListNetworksResponse) GetRoutes() []*Network {
 }
 
 type SelectNetworksRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	NetworkIDs    []string               `protobuf:"bytes,1,rep,name=networkIDs,proto3" json:"networkIDs,omitempty"`
+	Append        bool                   `protobuf:"varint,2,opt,name=append,proto3" json:"append,omitempty"`
+	All           bool                   `protobuf:"varint,3,opt,name=all,proto3" json:"all,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	NetworkIDs []string `protobuf:"bytes,1,rep,name=networkIDs,proto3" json:"networkIDs,omitempty"`
-	Append     bool     `protobuf:"varint,2,opt,name=append,proto3" json:"append,omitempty"`
-	All        bool     `protobuf:"varint,3,opt,name=all,proto3" json:"all,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *SelectNetworksRequest) Reset() {
 	*x = SelectNetworksRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[22]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[22]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *SelectNetworksRequest) String() string {
@@ -1940,7 +1895,7 @@ func (*SelectNetworksRequest) ProtoMessage() {}
 
 func (x *SelectNetworksRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[22]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -1977,18 +1932,16 @@ func (x *SelectNetworksRequest) GetAll() bool {
 }
 
 type SelectNetworksResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *SelectNetworksResponse) Reset() {
 	*x = SelectNetworksResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[23]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[23]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *SelectNetworksResponse) String() string {
@@ -1999,7 +1952,7 @@ func (*SelectNetworksResponse) ProtoMessage() {}
 
 func (x *SelectNetworksResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[23]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2015,20 +1968,17 @@ func (*SelectNetworksResponse) Descriptor() ([]byte, []int) {
 }
 
 type IPList struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Ips           []string               `protobuf:"bytes,1,rep,name=ips,proto3" json:"ips,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Ips []string `protobuf:"bytes,1,rep,name=ips,proto3" json:"ips,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *IPList) Reset() {
 	*x = IPList{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[24]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[24]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *IPList) String() string {
@@ -2039,7 +1989,7 @@ func (*IPList) ProtoMessage() {}
 
 func (x *IPList) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[24]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2062,24 +2012,21 @@ func (x *IPList) GetIps() []string {
 }
 
 type Network struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	ID            string                 `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
+	Range         string                 `protobuf:"bytes,2,opt,name=range,proto3" json:"range,omitempty"`
+	Selected      bool                   `protobuf:"varint,3,opt,name=selected,proto3" json:"selected,omitempty"`
+	Domains       []string               `protobuf:"bytes,4,rep,name=domains,proto3" json:"domains,omitempty"`
+	ResolvedIPs   map[string]*IPList     `protobuf:"bytes,5,rep,name=resolvedIPs,proto3" json:"resolvedIPs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
 	unknownFields protoimpl.UnknownFields
-
-	ID          string             `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
-	Range       string             `protobuf:"bytes,2,opt,name=range,proto3" json:"range,omitempty"`
-	Selected    bool               `protobuf:"varint,3,opt,name=selected,proto3" json:"selected,omitempty"`
-	Domains     []string           `protobuf:"bytes,4,rep,name=domains,proto3" json:"domains,omitempty"`
-	ResolvedIPs map[string]*IPList `protobuf:"bytes,5,rep,name=resolvedIPs,proto3" json:"resolvedIPs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *Network) Reset() {
 	*x = Network{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[25]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[25]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *Network) String() string {
@@ -2090,7 +2037,7 @@ func (*Network) ProtoMessage() {}
 
 func (x *Network) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[25]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2142,24 +2089,21 @@ func (x *Network) GetResolvedIPs() map[string]*IPList {
 
 // ForwardingRules
 type PortInfo struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// Types that are assignable to PortSelection:
+	state protoimpl.MessageState `protogen:"open.v1"`
+	// Types that are valid to be assigned to PortSelection:
 	//
 	//	*PortInfo_Port
 	//	*PortInfo_Range_
 	PortSelection isPortInfo_PortSelection `protobuf_oneof:"portSelection"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *PortInfo) Reset() {
 	*x = PortInfo{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[26]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[26]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *PortInfo) String() string {
@@ -2170,7 +2114,7 @@ func (*PortInfo) ProtoMessage() {}
 
 func (x *PortInfo) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[26]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2185,23 +2129,27 @@ func (*PortInfo) Descriptor() ([]byte, []int) {
 	return file_daemon_proto_rawDescGZIP(), []int{26}
 }
 
-func (m *PortInfo) GetPortSelection() isPortInfo_PortSelection {
-	if m != nil {
-		return m.PortSelection
+func (x *PortInfo) GetPortSelection() isPortInfo_PortSelection {
+	if x != nil {
+		return x.PortSelection
 	}
 	return nil
 }
 
 func (x *PortInfo) GetPort() uint32 {
-	if x, ok := x.GetPortSelection().(*PortInfo_Port); ok {
-		return x.Port
+	if x != nil {
+		if x, ok := x.PortSelection.(*PortInfo_Port); ok {
+			return x.Port
+		}
 	}
 	return 0
 }
 
 func (x *PortInfo) GetRange() *PortInfo_Range {
-	if x, ok := x.GetPortSelection().(*PortInfo_Range_); ok {
-		return x.Range
+	if x != nil {
+		if x, ok := x.PortSelection.(*PortInfo_Range_); ok {
+			return x.Range
+		}
 	}
 	return nil
 }
@@ -2223,24 +2171,21 @@ func (*PortInfo_Port) isPortInfo_PortSelection() {}
 func (*PortInfo_Range_) isPortInfo_PortSelection() {}
 
 type ForwardingRule struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Protocol           string    `protobuf:"bytes,1,opt,name=protocol,proto3" json:"protocol,omitempty"`
-	DestinationPort    *PortInfo `protobuf:"bytes,2,opt,name=destinationPort,proto3" json:"destinationPort,omitempty"`
-	TranslatedAddress  string    `protobuf:"bytes,3,opt,name=translatedAddress,proto3" json:"translatedAddress,omitempty"`
-	TranslatedHostname string    `protobuf:"bytes,4,opt,name=translatedHostname,proto3" json:"translatedHostname,omitempty"`
-	TranslatedPort     *PortInfo `protobuf:"bytes,5,opt,name=translatedPort,proto3" json:"translatedPort,omitempty"`
+	state              protoimpl.MessageState `protogen:"open.v1"`
+	Protocol           string                 `protobuf:"bytes,1,opt,name=protocol,proto3" json:"protocol,omitempty"`
+	DestinationPort    *PortInfo              `protobuf:"bytes,2,opt,name=destinationPort,proto3" json:"destinationPort,omitempty"`
+	TranslatedAddress  string                 `protobuf:"bytes,3,opt,name=translatedAddress,proto3" json:"translatedAddress,omitempty"`
+	TranslatedHostname string                 `protobuf:"bytes,4,opt,name=translatedHostname,proto3" json:"translatedHostname,omitempty"`
+	TranslatedPort     *PortInfo              `protobuf:"bytes,5,opt,name=translatedPort,proto3" json:"translatedPort,omitempty"`
+	unknownFields      protoimpl.UnknownFields
+	sizeCache          protoimpl.SizeCache
 }
 
 func (x *ForwardingRule) Reset() {
 	*x = ForwardingRule{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[27]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[27]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *ForwardingRule) String() string {
@@ -2251,7 +2196,7 @@ func (*ForwardingRule) ProtoMessage() {}
 
 func (x *ForwardingRule) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[27]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2302,20 +2247,17 @@ func (x *ForwardingRule) GetTranslatedPort() *PortInfo {
 }
 
 type ForwardingRulesResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Rules         []*ForwardingRule      `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Rules []*ForwardingRule `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *ForwardingRulesResponse) Reset() {
 	*x = ForwardingRulesResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[28]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[28]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *ForwardingRulesResponse) String() string {
@@ -2326,7 +2268,7 @@ func (*ForwardingRulesResponse) ProtoMessage() {}
 
 func (x *ForwardingRulesResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[28]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2350,23 +2292,21 @@ func (x *ForwardingRulesResponse) GetRules() []*ForwardingRule {
 
 // DebugBundler
 type DebugBundleRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Anonymize     bool                   `protobuf:"varint,1,opt,name=anonymize,proto3" json:"anonymize,omitempty"`
+	Status        string                 `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
+	SystemInfo    bool                   `protobuf:"varint,3,opt,name=systemInfo,proto3" json:"systemInfo,omitempty"`
+	UploadURL     string                 `protobuf:"bytes,4,opt,name=uploadURL,proto3" json:"uploadURL,omitempty"`
+	LogFileCount  uint32                 `protobuf:"varint,5,opt,name=logFileCount,proto3" json:"logFileCount,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Anonymize  bool   `protobuf:"varint,1,opt,name=anonymize,proto3" json:"anonymize,omitempty"`
-	Status     string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
-	SystemInfo bool   `protobuf:"varint,3,opt,name=systemInfo,proto3" json:"systemInfo,omitempty"`
-	UploadURL  string `protobuf:"bytes,4,opt,name=uploadURL,proto3" json:"uploadURL,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *DebugBundleRequest) Reset() {
 	*x = DebugBundleRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[29]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[29]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *DebugBundleRequest) String() string {
@@ -2377,7 +2317,7 @@ func (*DebugBundleRequest) ProtoMessage() {}
 
 func (x *DebugBundleRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[29]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2420,23 +2360,27 @@ func (x *DebugBundleRequest) GetUploadURL() string {
 	return ""
 }
 
-type DebugBundleResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
+func (x *DebugBundleRequest) GetLogFileCount() uint32 {
+	if x != nil {
+		return x.LogFileCount
+	}
+	return 0
+}
 
-	Path                string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
-	UploadedKey         string `protobuf:"bytes,2,opt,name=uploadedKey,proto3" json:"uploadedKey,omitempty"`
-	UploadFailureReason string `protobuf:"bytes,3,opt,name=uploadFailureReason,proto3" json:"uploadFailureReason,omitempty"`
+type DebugBundleResponse struct {
+	state               protoimpl.MessageState `protogen:"open.v1"`
+	Path                string                 `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+	UploadedKey         string                 `protobuf:"bytes,2,opt,name=uploadedKey,proto3" json:"uploadedKey,omitempty"`
+	UploadFailureReason string                 `protobuf:"bytes,3,opt,name=uploadFailureReason,proto3" json:"uploadFailureReason,omitempty"`
+	unknownFields       protoimpl.UnknownFields
+	sizeCache           protoimpl.SizeCache
 }
 
 func (x *DebugBundleResponse) Reset() {
 	*x = DebugBundleResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[30]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[30]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *DebugBundleResponse) String() string {
@@ -2447,7 +2391,7 @@ func (*DebugBundleResponse) ProtoMessage() {}
 
 func (x *DebugBundleResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[30]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2484,18 +2428,16 @@ func (x *DebugBundleResponse) GetUploadFailureReason() string {
 }
 
 type GetLogLevelRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *GetLogLevelRequest) Reset() {
 	*x = GetLogLevelRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[31]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[31]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *GetLogLevelRequest) String() string {
@@ -2506,7 +2448,7 @@ func (*GetLogLevelRequest) ProtoMessage() {}
 
 func (x *GetLogLevelRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[31]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2522,20 +2464,17 @@ func (*GetLogLevelRequest) Descriptor() ([]byte, []int) {
 }
 
 type GetLogLevelResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Level         LogLevel               `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *GetLogLevelResponse) Reset() {
 	*x = GetLogLevelResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[32]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[32]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *GetLogLevelResponse) String() string {
@@ -2546,7 +2485,7 @@ func (*GetLogLevelResponse) ProtoMessage() {}
 
 func (x *GetLogLevelResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[32]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2569,20 +2508,17 @@ func (x *GetLogLevelResponse) GetLevel() LogLevel {
 }
 
 type SetLogLevelRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Level         LogLevel               `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *SetLogLevelRequest) Reset() {
 	*x = SetLogLevelRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[33]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[33]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *SetLogLevelRequest) String() string {
@@ -2593,7 +2529,7 @@ func (*SetLogLevelRequest) ProtoMessage() {}
 
 func (x *SetLogLevelRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[33]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2616,18 +2552,16 @@ func (x *SetLogLevelRequest) GetLevel() LogLevel {
 }
 
 type SetLogLevelResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *SetLogLevelResponse) Reset() {
 	*x = SetLogLevelResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[34]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[34]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *SetLogLevelResponse) String() string {
@@ -2638,7 +2572,7 @@ func (*SetLogLevelResponse) ProtoMessage() {}
 
 func (x *SetLogLevelResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[34]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2655,20 +2589,17 @@ func (*SetLogLevelResponse) Descriptor() ([]byte, []int) {
 
 // State represents a daemon state entry
 type State struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Name          string                 `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *State) Reset() {
 	*x = State{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[35]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[35]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *State) String() string {
@@ -2679,7 +2610,7 @@ func (*State) ProtoMessage() {}
 
 func (x *State) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[35]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2703,18 +2634,16 @@ func (x *State) GetName() string {
 
 // ListStatesRequest is empty as it requires no parameters
 type ListStatesRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *ListStatesRequest) Reset() {
 	*x = ListStatesRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[36]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[36]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *ListStatesRequest) String() string {
@@ -2725,7 +2654,7 @@ func (*ListStatesRequest) ProtoMessage() {}
 
 func (x *ListStatesRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[36]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2742,20 +2671,17 @@ func (*ListStatesRequest) Descriptor() ([]byte, []int) {
 
 // ListStatesResponse contains a list of states
 type ListStatesResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	States        []*State               `protobuf:"bytes,1,rep,name=states,proto3" json:"states,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	States []*State `protobuf:"bytes,1,rep,name=states,proto3" json:"states,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *ListStatesResponse) Reset() {
 	*x = ListStatesResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[37]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[37]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *ListStatesResponse) String() string {
@@ -2766,7 +2692,7 @@ func (*ListStatesResponse) ProtoMessage() {}
 
 func (x *ListStatesResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[37]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2790,21 +2716,18 @@ func (x *ListStatesResponse) GetStates() []*State {
 
 // CleanStateRequest for cleaning states
 type CleanStateRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	StateName     string                 `protobuf:"bytes,1,opt,name=state_name,json=stateName,proto3" json:"state_name,omitempty"`
+	All           bool                   `protobuf:"varint,2,opt,name=all,proto3" json:"all,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	StateName string `protobuf:"bytes,1,opt,name=state_name,json=stateName,proto3" json:"state_name,omitempty"`
-	All       bool   `protobuf:"varint,2,opt,name=all,proto3" json:"all,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *CleanStateRequest) Reset() {
 	*x = CleanStateRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[38]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[38]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *CleanStateRequest) String() string {
@@ -2815,7 +2738,7 @@ func (*CleanStateRequest) ProtoMessage() {}
 
 func (x *CleanStateRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[38]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2846,20 +2769,17 @@ func (x *CleanStateRequest) GetAll() bool {
 
 // CleanStateResponse contains the result of the clean operation
 type CleanStateResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	CleanedStates int32                  `protobuf:"varint,1,opt,name=cleaned_states,json=cleanedStates,proto3" json:"cleaned_states,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	CleanedStates int32 `protobuf:"varint,1,opt,name=cleaned_states,json=cleanedStates,proto3" json:"cleaned_states,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *CleanStateResponse) Reset() {
 	*x = CleanStateResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[39]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[39]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *CleanStateResponse) String() string {
@@ -2870,7 +2790,7 @@ func (*CleanStateResponse) ProtoMessage() {}
 
 func (x *CleanStateResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[39]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2894,21 +2814,18 @@ func (x *CleanStateResponse) GetCleanedStates() int32 {
 
 // DeleteStateRequest for deleting states
 type DeleteStateRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	StateName     string                 `protobuf:"bytes,1,opt,name=state_name,json=stateName,proto3" json:"state_name,omitempty"`
+	All           bool                   `protobuf:"varint,2,opt,name=all,proto3" json:"all,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	StateName string `protobuf:"bytes,1,opt,name=state_name,json=stateName,proto3" json:"state_name,omitempty"`
-	All       bool   `protobuf:"varint,2,opt,name=all,proto3" json:"all,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *DeleteStateRequest) Reset() {
 	*x = DeleteStateRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[40]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[40]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *DeleteStateRequest) String() string {
@@ -2919,7 +2836,7 @@ func (*DeleteStateRequest) ProtoMessage() {}
 
 func (x *DeleteStateRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[40]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2950,20 +2867,17 @@ func (x *DeleteStateRequest) GetAll() bool {
 
 // DeleteStateResponse contains the result of the delete operation
 type DeleteStateResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	DeletedStates int32                  `protobuf:"varint,1,opt,name=deleted_states,json=deletedStates,proto3" json:"deleted_states,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	DeletedStates int32 `protobuf:"varint,1,opt,name=deleted_states,json=deletedStates,proto3" json:"deleted_states,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *DeleteStateResponse) Reset() {
 	*x = DeleteStateResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[41]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[41]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *DeleteStateResponse) String() string {
@@ -2974,7 +2888,7 @@ func (*DeleteStateResponse) ProtoMessage() {}
 
 func (x *DeleteStateResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[41]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -2997,20 +2911,17 @@ func (x *DeleteStateResponse) GetDeletedStates() int32 {
 }
 
 type SetNetworkMapPersistenceRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Enabled       bool                   `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *SetNetworkMapPersistenceRequest) Reset() {
 	*x = SetNetworkMapPersistenceRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[42]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[42]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *SetNetworkMapPersistenceRequest) String() string {
@@ -3021,7 +2932,7 @@ func (*SetNetworkMapPersistenceRequest) ProtoMessage() {}
 
 func (x *SetNetworkMapPersistenceRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[42]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3044,18 +2955,16 @@ func (x *SetNetworkMapPersistenceRequest) GetEnabled() bool {
 }
 
 type SetNetworkMapPersistenceResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *SetNetworkMapPersistenceResponse) Reset() {
 	*x = SetNetworkMapPersistenceResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[43]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[43]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *SetNetworkMapPersistenceResponse) String() string {
@@ -3066,7 +2975,7 @@ func (*SetNetworkMapPersistenceResponse) ProtoMessage() {}
 
 func (x *SetNetworkMapPersistenceResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[43]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3082,25 +2991,22 @@ func (*SetNetworkMapPersistenceResponse) Descriptor() ([]byte, []int) {
 }
 
 type TCPFlags struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Syn           bool                   `protobuf:"varint,1,opt,name=syn,proto3" json:"syn,omitempty"`
+	Ack           bool                   `protobuf:"varint,2,opt,name=ack,proto3" json:"ack,omitempty"`
+	Fin           bool                   `protobuf:"varint,3,opt,name=fin,proto3" json:"fin,omitempty"`
+	Rst           bool                   `protobuf:"varint,4,opt,name=rst,proto3" json:"rst,omitempty"`
+	Psh           bool                   `protobuf:"varint,5,opt,name=psh,proto3" json:"psh,omitempty"`
+	Urg           bool                   `protobuf:"varint,6,opt,name=urg,proto3" json:"urg,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Syn bool `protobuf:"varint,1,opt,name=syn,proto3" json:"syn,omitempty"`
-	Ack bool `protobuf:"varint,2,opt,name=ack,proto3" json:"ack,omitempty"`
-	Fin bool `protobuf:"varint,3,opt,name=fin,proto3" json:"fin,omitempty"`
-	Rst bool `protobuf:"varint,4,opt,name=rst,proto3" json:"rst,omitempty"`
-	Psh bool `protobuf:"varint,5,opt,name=psh,proto3" json:"psh,omitempty"`
-	Urg bool `protobuf:"varint,6,opt,name=urg,proto3" json:"urg,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *TCPFlags) Reset() {
 	*x = TCPFlags{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[44]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[44]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *TCPFlags) String() string {
@@ -3111,7 +3017,7 @@ func (*TCPFlags) ProtoMessage() {}
 
 func (x *TCPFlags) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[44]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3169,28 +3075,25 @@ func (x *TCPFlags) GetUrg() bool {
 }
 
 type TracePacketRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	SourceIp        string    `protobuf:"bytes,1,opt,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"`
-	DestinationIp   string    `protobuf:"bytes,2,opt,name=destination_ip,json=destinationIp,proto3" json:"destination_ip,omitempty"`
-	Protocol        string    `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"`
-	SourcePort      uint32    `protobuf:"varint,4,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"`
-	DestinationPort uint32    `protobuf:"varint,5,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"`
-	Direction       string    `protobuf:"bytes,6,opt,name=direction,proto3" json:"direction,omitempty"`
-	TcpFlags        *TCPFlags `protobuf:"bytes,7,opt,name=tcp_flags,json=tcpFlags,proto3,oneof" json:"tcp_flags,omitempty"`
-	IcmpType        *uint32   `protobuf:"varint,8,opt,name=icmp_type,json=icmpType,proto3,oneof" json:"icmp_type,omitempty"`
-	IcmpCode        *uint32   `protobuf:"varint,9,opt,name=icmp_code,json=icmpCode,proto3,oneof" json:"icmp_code,omitempty"`
+	state           protoimpl.MessageState `protogen:"open.v1"`
+	SourceIp        string                 `protobuf:"bytes,1,opt,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"`
+	DestinationIp   string                 `protobuf:"bytes,2,opt,name=destination_ip,json=destinationIp,proto3" json:"destination_ip,omitempty"`
+	Protocol        string                 `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"`
+	SourcePort      uint32                 `protobuf:"varint,4,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"`
+	DestinationPort uint32                 `protobuf:"varint,5,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"`
+	Direction       string                 `protobuf:"bytes,6,opt,name=direction,proto3" json:"direction,omitempty"`
+	TcpFlags        *TCPFlags              `protobuf:"bytes,7,opt,name=tcp_flags,json=tcpFlags,proto3,oneof" json:"tcp_flags,omitempty"`
+	IcmpType        *uint32                `protobuf:"varint,8,opt,name=icmp_type,json=icmpType,proto3,oneof" json:"icmp_type,omitempty"`
+	IcmpCode        *uint32                `protobuf:"varint,9,opt,name=icmp_code,json=icmpCode,proto3,oneof" json:"icmp_code,omitempty"`
+	unknownFields   protoimpl.UnknownFields
+	sizeCache       protoimpl.SizeCache
 }
 
 func (x *TracePacketRequest) Reset() {
 	*x = TracePacketRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[45]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[45]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *TracePacketRequest) String() string {
@@ -3201,7 +3104,7 @@ func (*TracePacketRequest) ProtoMessage() {}
 
 func (x *TracePacketRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[45]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3280,23 +3183,20 @@ func (x *TracePacketRequest) GetIcmpCode() uint32 {
 }
 
 type TraceStage struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Name              string  `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
-	Message           string  `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
-	Allowed           bool    `protobuf:"varint,3,opt,name=allowed,proto3" json:"allowed,omitempty"`
-	ForwardingDetails *string `protobuf:"bytes,4,opt,name=forwarding_details,json=forwardingDetails,proto3,oneof" json:"forwarding_details,omitempty"`
+	state             protoimpl.MessageState `protogen:"open.v1"`
+	Name              string                 `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Message           string                 `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
+	Allowed           bool                   `protobuf:"varint,3,opt,name=allowed,proto3" json:"allowed,omitempty"`
+	ForwardingDetails *string                `protobuf:"bytes,4,opt,name=forwarding_details,json=forwardingDetails,proto3,oneof" json:"forwarding_details,omitempty"`
+	unknownFields     protoimpl.UnknownFields
+	sizeCache         protoimpl.SizeCache
 }
 
 func (x *TraceStage) Reset() {
 	*x = TraceStage{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[46]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[46]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *TraceStage) String() string {
@@ -3307,7 +3207,7 @@ func (*TraceStage) ProtoMessage() {}
 
 func (x *TraceStage) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[46]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3351,21 +3251,18 @@ func (x *TraceStage) GetForwardingDetails() string {
 }
 
 type TracePacketResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Stages           []*TraceStage `protobuf:"bytes,1,rep,name=stages,proto3" json:"stages,omitempty"`
-	FinalDisposition bool          `protobuf:"varint,2,opt,name=final_disposition,json=finalDisposition,proto3" json:"final_disposition,omitempty"`
+	state            protoimpl.MessageState `protogen:"open.v1"`
+	Stages           []*TraceStage          `protobuf:"bytes,1,rep,name=stages,proto3" json:"stages,omitempty"`
+	FinalDisposition bool                   `protobuf:"varint,2,opt,name=final_disposition,json=finalDisposition,proto3" json:"final_disposition,omitempty"`
+	unknownFields    protoimpl.UnknownFields
+	sizeCache        protoimpl.SizeCache
 }
 
 func (x *TracePacketResponse) Reset() {
 	*x = TracePacketResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[47]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[47]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *TracePacketResponse) String() string {
@@ -3376,7 +3273,7 @@ func (*TracePacketResponse) ProtoMessage() {}
 
 func (x *TracePacketResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[47]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3406,18 +3303,16 @@ func (x *TracePacketResponse) GetFinalDisposition() bool {
 }
 
 type SubscribeRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *SubscribeRequest) Reset() {
 	*x = SubscribeRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[48]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[48]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *SubscribeRequest) String() string {
@@ -3428,7 +3323,7 @@ func (*SubscribeRequest) ProtoMessage() {}
 
 func (x *SubscribeRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[48]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3444,26 +3339,23 @@ func (*SubscribeRequest) Descriptor() ([]byte, []int) {
 }
 
 type SystemEvent struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Id            string                 `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+	Severity      SystemEvent_Severity   `protobuf:"varint,2,opt,name=severity,proto3,enum=daemon.SystemEvent_Severity" json:"severity,omitempty"`
+	Category      SystemEvent_Category   `protobuf:"varint,3,opt,name=category,proto3,enum=daemon.SystemEvent_Category" json:"category,omitempty"`
+	Message       string                 `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"`
+	UserMessage   string                 `protobuf:"bytes,5,opt,name=userMessage,proto3" json:"userMessage,omitempty"`
+	Timestamp     *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
+	Metadata      map[string]string      `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
 	unknownFields protoimpl.UnknownFields
-
-	Id          string                 `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
-	Severity    SystemEvent_Severity   `protobuf:"varint,2,opt,name=severity,proto3,enum=daemon.SystemEvent_Severity" json:"severity,omitempty"`
-	Category    SystemEvent_Category   `protobuf:"varint,3,opt,name=category,proto3,enum=daemon.SystemEvent_Category" json:"category,omitempty"`
-	Message     string                 `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"`
-	UserMessage string                 `protobuf:"bytes,5,opt,name=userMessage,proto3" json:"userMessage,omitempty"`
-	Timestamp   *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
-	Metadata    map[string]string      `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *SystemEvent) Reset() {
 	*x = SystemEvent{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[49]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[49]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *SystemEvent) String() string {
@@ -3474,7 +3366,7 @@ func (*SystemEvent) ProtoMessage() {}
 
 func (x *SystemEvent) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[49]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3539,18 +3431,16 @@ func (x *SystemEvent) GetMetadata() map[string]string {
 }
 
 type GetEventsRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
 	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *GetEventsRequest) Reset() {
 	*x = GetEventsRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[50]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[50]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *GetEventsRequest) String() string {
@@ -3561,7 +3451,7 @@ func (*GetEventsRequest) ProtoMessage() {}
 
 func (x *GetEventsRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[50]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3577,20 +3467,17 @@ func (*GetEventsRequest) Descriptor() ([]byte, []int) {
 }
 
 type GetEventsResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Events        []*SystemEvent         `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Events []*SystemEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *GetEventsResponse) Reset() {
 	*x = GetEventsResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[51]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[51]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *GetEventsResponse) String() string {
@@ -3601,7 +3488,7 @@ func (*GetEventsResponse) ProtoMessage() {}
 
 func (x *GetEventsResponse) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[51]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3624,21 +3511,18 @@ func (x *GetEventsResponse) GetEvents() []*SystemEvent {
 }
 
 type PortInfo_Range struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Start         uint32                 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"`
+	End           uint32                 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"`
 	unknownFields protoimpl.UnknownFields
-
-	Start uint32 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"`
-	End   uint32 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"`
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *PortInfo_Range) Reset() {
 	*x = PortInfo_Range{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_daemon_proto_msgTypes[53]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
+	mi := &file_daemon_proto_msgTypes[53]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
 }
 
 func (x *PortInfo_Range) String() string {
@@ -3649,7 +3533,7 @@ func (*PortInfo_Range) ProtoMessage() {}
 
 func (x *PortInfo_Range) ProtoReflect() protoreflect.Message {
 	mi := &file_daemon_proto_msgTypes[53]
-	if protoimpl.UnsafeEnabled && x != nil {
+	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 			ms.StoreMessageInfo(mi)
@@ -3680,648 +3564,351 @@ func (x *PortInfo_Range) GetEnd() uint32 {
 
 var File_daemon_proto protoreflect.FileDescriptor
 
-var file_daemon_proto_rawDesc = []byte{
-	0x0a, 0x0c, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
-	0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
-	0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
-	0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
-	0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0e, 0x0a, 0x0c, 0x45, 0x6d, 0x70,
-	0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x0d, 0x0a, 0x0c, 0x4c, 0x6f,
-	0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65,
-	0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65,
-	0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61,
-	0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01,
-	0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x24,
-	0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
-	0x74, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
-	0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x61, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49,
-	0x50, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x61, 0x74, 0x45, 0x78, 0x74,
-	0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x50, 0x73, 0x12, 0x30, 0x0a, 0x13, 0x63, 0x6c, 0x65, 0x61,
-	0x6e, 0x4e, 0x41, 0x54, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x50, 0x73, 0x18,
-	0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x4e, 0x41, 0x54, 0x45,
-	0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x50, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x75,
-	0x73, 0x74, 0x6f, 0x6d, 0x44, 0x4e, 0x53, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07,
-	0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x44, 0x4e, 0x53, 0x41,
-	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x30, 0x0a, 0x13, 0x69, 0x73, 0x55, 0x6e, 0x69, 0x78,
-	0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20,
-	0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x73, 0x55, 0x6e, 0x69, 0x78, 0x44, 0x65, 0x73, 0x6b, 0x74,
-	0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74,
-	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74,
-	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73,
-	0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00,
-	0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c,
-	0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61,
-	0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0d,
-	0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01,
-	0x12, 0x29, 0x0a, 0x0d, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72,
-	0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x48, 0x02, 0x52, 0x0d, 0x77, 0x69, 0x72, 0x65, 0x67,
-	0x75, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x88, 0x01, 0x01, 0x12, 0x37, 0x0a, 0x14, 0x6f,
-	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x50, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64,
-	0x4b, 0x65, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x14, 0x6f, 0x70, 0x74,
-	0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x50, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65,
-	0x79, 0x88, 0x01, 0x01, 0x12, 0x33, 0x0a, 0x12, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x41,
-	0x75, 0x74, 0x6f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08,
-	0x48, 0x04, 0x52, 0x12, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x43,
-	0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x10, 0x73, 0x65, 0x72,
-	0x76, 0x65, 0x72, 0x53, 0x53, 0x48, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x0f, 0x20,
-	0x01, 0x28, 0x08, 0x48, 0x05, 0x52, 0x10, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x53, 0x48,
-	0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x13, 0x72, 0x6f,
-	0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76,
-	0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x48, 0x06, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e,
-	0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x88, 0x01,
-	0x01, 0x12, 0x30, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x72, 0x61, 0x49, 0x46, 0x61, 0x63, 0x65, 0x42,
-	0x6c, 0x61, 0x63, 0x6b, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13,
-	0x65, 0x78, 0x74, 0x72, 0x61, 0x49, 0x46, 0x61, 0x63, 0x65, 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6c,
-	0x69, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x0e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x6f,
-	0x6e, 0x69, 0x74, 0x6f, 0x72, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x48, 0x07, 0x52, 0x0e, 0x6e,
-	0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01,
-	0x12, 0x4a, 0x0a, 0x10, 0x64, 0x6e, 0x73, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65,
-	0x72, 0x76, 0x61, 0x6c, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f,
-	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x08, 0x52, 0x10, 0x64, 0x6e, 0x73, 0x52, 0x6f, 0x75, 0x74,
-	0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x37, 0x0a, 0x15,
-	0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x72,
-	0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x48, 0x09, 0x52, 0x13, 0x64,
-	0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x75, 0x74,
-	0x65, 0x73, 0x88, 0x01, 0x01, 0x12, 0x37, 0x0a, 0x15, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
-	0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x15,
-	0x20, 0x01, 0x28, 0x08, 0x48, 0x0a, 0x52, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53,
-	0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x88, 0x01, 0x01, 0x12, 0x24,
-	0x0a, 0x0b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x6e, 0x73, 0x18, 0x16, 0x20,
-	0x01, 0x28, 0x08, 0x48, 0x0b, 0x52, 0x0a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x44, 0x6e,
-	0x73, 0x88, 0x01, 0x01, 0x12, 0x2e, 0x0a, 0x10, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f,
-	0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x18, 0x17, 0x20, 0x01, 0x28, 0x08, 0x48, 0x0c,
-	0x52, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c,
-	0x6c, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6c, 0x61,
-	0x6e, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x48, 0x0d,
-	0x52, 0x0e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4c, 0x61, 0x6e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73,
-	0x88, 0x01, 0x01, 0x12, 0x38, 0x0a, 0x15, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e,
-	0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x19, 0x20, 0x01,
-	0x28, 0x08, 0x48, 0x0e, 0x52, 0x14, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x6f, 0x74,
-	0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a,
-	0x0a, 0x64, 0x6e, 0x73, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x1a, 0x20, 0x03, 0x28,
-	0x09, 0x52, 0x09, 0x64, 0x6e, 0x73, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x26, 0x0a, 0x0e,
-	0x63, 0x6c, 0x65, 0x61, 0x6e, 0x44, 0x4e, 0x53, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x1b,
-	0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x44, 0x4e, 0x53, 0x4c, 0x61,
-	0x62, 0x65, 0x6c, 0x73, 0x12, 0x39, 0x0a, 0x15, 0x6c, 0x61, 0x7a, 0x79, 0x43, 0x6f, 0x6e, 0x6e,
-	0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x1c, 0x20,
-	0x01, 0x28, 0x08, 0x48, 0x0f, 0x52, 0x15, 0x6c, 0x61, 0x7a, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65,
-	0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x88, 0x01, 0x01, 0x12,
-	0x28, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
-	0x18, 0x1d, 0x20, 0x01, 0x28, 0x08, 0x48, 0x10, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49,
-	0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x88, 0x01, 0x01, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x72, 0x6f,
-	0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x10,
-	0x0a, 0x0e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65,
-	0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x50, 0x6f,
-	0x72, 0x74, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x50,
-	0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x42, 0x15, 0x0a, 0x13, 0x5f,
-	0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x43, 0x6f, 0x6e, 0x6e, 0x65,
-	0x63, 0x74, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x53, 0x48,
-	0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x72, 0x6f, 0x73, 0x65,
-	0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x42,
-	0x11, 0x0a, 0x0f, 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x6f, 0x6e, 0x69, 0x74,
-	0x6f, 0x72, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x64, 0x6e, 0x73, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x49,
-	0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x64, 0x69, 0x73, 0x61,
-	0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65,
-	0x73, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x65,
-	0x72, 0x76, 0x65, 0x72, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x5f,
-	0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x6e, 0x73, 0x42, 0x13, 0x0a, 0x11, 0x5f,
-	0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c,
-	0x42, 0x13, 0x0a, 0x11, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6c, 0x61, 0x6e, 0x5f, 0x61,
-	0x63, 0x63, 0x65, 0x73, 0x73, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c,
-	0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42,
-	0x18, 0x0a, 0x16, 0x5f, 0x6c, 0x61, 0x7a, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,
-	0x6f, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x62, 0x6c,
-	0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xb5, 0x01, 0x0a, 0x0d,
-	0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a,
-	0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f,
-	0x67, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12,
-	0x28, 0x0a, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55,
-	0x52, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69,
-	0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72,
-	0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70,
-	0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69,
-	0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c,
-	0x65, 0x74, 0x65, 0x22, 0x4d, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f,
-	0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73,
-	0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73,
-	0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
-	0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
-	0x6d, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67,
-	0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c,
-	0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x08, 0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74,
-	0x61, 0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
-	0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
-	0x32, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c,
-	0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61,
-	0x74, 0x75, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72,
-	0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d,
-	0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77,
-	0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43,
-	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa3, 0x06, 0x0a,
-	0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
-	0x55, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67,
-	0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46,
-	0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69,
-	0x6c, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b,
-	0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61,
-	0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55,
-	0x52, 0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55,
-	0x52, 0x4c, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4e,
-	0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72,
-	0x66, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x77, 0x69, 0x72, 0x65,
-	0x67, 0x75, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52,
-	0x0d, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x2e,
-	0x0a, 0x12, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x43, 0x6f, 0x6e,
-	0x6e, 0x65, 0x63, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x64, 0x69, 0x73, 0x61,
-	0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x2a,
-	0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x53, 0x48, 0x41, 0x6c, 0x6c, 0x6f, 0x77,
-	0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
-	0x53, 0x53, 0x48, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x6f,
-	0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0b,
-	0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45,
-	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70,
-	0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x18, 0x0c, 0x20,
-	0x01, 0x28, 0x08, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65,
-	0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x12, 0x33, 0x0a, 0x15, 0x64, 0x69, 0x73, 0x61,
-	0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
-	0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x34, 0x0a,
-	0x15, 0x6c, 0x61, 0x7a, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45,
-	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x6c, 0x61,
-	0x7a, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x61, 0x62,
-	0x6c, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x62, 0x6f,
-	0x75, 0x6e, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
-	0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x65, 0x74, 0x77, 0x6f,
-	0x72, 0x6b, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x0e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x12,
-	0x1f, 0x0a, 0x0b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x6e, 0x73, 0x18, 0x11,
-	0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x44, 0x6e, 0x73,
-	0x12, 0x32, 0x0a, 0x15, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x65,
-	0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x6f,
-	0x75, 0x74, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f,
-	0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x13, 0x20,
-	0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76,
-	0x65, 0x72, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x6c, 0x6f, 0x63,
-	0x6b, 0x5f, 0x6c, 0x61, 0x6e, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x14, 0x20, 0x01,
-	0x28, 0x08, 0x52, 0x0e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4c, 0x61, 0x6e, 0x41, 0x63, 0x63, 0x65,
-	0x73, 0x73, 0x22, 0xde, 0x05, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
-	0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50,
-	0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e,
-	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f,
-	0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e,
-	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10,
-	0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
-	0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28,
-	0x08, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f,
-	0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54,
-	0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
-	0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65,
-	0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e,
-	0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69,
-	0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e,
-	0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x3c, 0x0a, 0x19,
-	0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74,
-	0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x19, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61,
-	0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3e, 0x0a, 0x1a, 0x72, 0x65,
-	0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65,
-	0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a,
-	0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61,
-	0x74, 0x65, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x52, 0x0a, 0x16, 0x6c, 0x61,
-	0x73, 0x74, 0x57, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x73,
-	0x68, 0x61, 0x6b, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
-	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
-	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x57, 0x69, 0x72, 0x65,
-	0x67, 0x75, 0x61, 0x72, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x18,
-	0x0a, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x78, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52,
-	0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x79, 0x74, 0x65,
-	0x73, 0x54, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73,
-	0x54, 0x78, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45,
-	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x6f,
-	0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a,
-	0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x09,
-	0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x33, 0x0a, 0x07, 0x6c, 0x61,
-	0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f,
-	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75,
-	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12,
-	0x22, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18,
-	0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x41, 0x64, 0x64, 0x72,
-	0x65, 0x73, 0x73, 0x22, 0xf0, 0x01, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
-	0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28,
-	0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63,
-	0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49,
-	0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x2a, 0x0a, 0x10,
-	0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
-	0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73,
-	0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73, 0x65,
-	0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x18,
-	0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73,
-	0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x65,
-	0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x65,
-	0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x22, 0x53, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c,
-	0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
-	0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e,
-	0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x57, 0x0a, 0x0f, 0x4d,
-	0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10,
-	0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c,
-	0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x14,
-	0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,
-	0x72, 0x72, 0x6f, 0x72, 0x22, 0x52, 0x0a, 0x0a, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x74, 0x61,
-	0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x49, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x03, 0x55, 0x52, 0x49, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62,
-	0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x72, 0x0a, 0x0c, 0x4e, 0x53, 0x47, 0x72,
-	0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76,
-	0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65,
-	0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20,
-	0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07,
-	0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65,
-	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xef, 0x03, 0x0a,
-	0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d,
-	0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61,
-	0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d,
-	0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35,
-	0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67,
-	0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c,
-	0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65,
-	0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
-	0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72,
-	0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72,
-	0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65,
-	0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2a,
-	0x0a, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12,
-	0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x74, 0x61,
-	0x74, 0x65, 0x52, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x12, 0x35, 0x0a, 0x0b, 0x64, 0x6e,
-	0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4e, 0x53, 0x47, 0x72, 0x6f, 0x75, 0x70,
-	0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x64, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
-	0x73, 0x12, 0x38, 0x0a, 0x17, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x66, 0x46, 0x6f, 0x72,
-	0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01,
-	0x28, 0x05, 0x52, 0x17, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x66, 0x46, 0x6f, 0x72, 0x77,
-	0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x06, 0x65,
-	0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61,
-	0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74,
-	0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x61, 0x7a, 0x79,
-	0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
-	0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x6c, 0x61, 0x7a, 0x79, 0x43, 0x6f, 0x6e,
-	0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x15,
-	0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3f, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x74,
-	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a,
-	0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
-	0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x06,
-	0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x61, 0x0a, 0x15, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74,
-	0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
-	0x1e, 0x0a, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20,
-	0x03, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x44, 0x73, 0x12,
-	0x16, 0x0a, 0x06, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x06, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6c, 0x6c, 0x22, 0x18, 0x0a, 0x16, 0x53, 0x65, 0x6c,
-	0x65, 0x63, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x22, 0x1a, 0x0a, 0x06, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x10, 0x0a,
-	0x03, 0x69, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x70, 0x73, 0x22,
-	0xf9, 0x01, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x49,
-	0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x72,
-	0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67,
-	0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x08, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a,
-	0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07,
-	0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c,
-	0x76, 0x65, 0x64, 0x49, 0x50, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64,
-	0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65,
-	0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x49, 0x50, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b,
-	0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x49, 0x50, 0x73, 0x1a, 0x4e, 0x0a, 0x10, 0x52,
-	0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x49, 0x50, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
-	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
-	0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x0e, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74,
-	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x92, 0x01, 0x0a, 0x08,
-	0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2e,
-	0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
-	0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x2e,
-	0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x00, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x1a, 0x2f,
-	0x0a, 0x05, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a,
-	0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x42,
-	0x0f, 0x0a, 0x0d, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
-	0x22, 0x80, 0x02, 0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52,
-	0x75, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12,
-	0x3a, 0x0a, 0x0f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f,
-	0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
-	0x6e, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x74,
-	0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x74,
-	0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74,
-	0x65, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x74, 0x72, 0x61,
-	0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65,
-	0x64, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x0e, 0x74, 0x72, 0x61,
-	0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x10, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49,
-	0x6e, 0x66, 0x6f, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50,
-	0x6f, 0x72, 0x74, 0x22, 0x47, 0x0a, 0x17, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e,
-	0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c,
-	0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e,
-	0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e,
-	0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x88, 0x01, 0x0a,
-	0x12, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x69, 0x7a, 0x65,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x6e, 0x6f, 0x6e, 0x79, 0x6d, 0x69, 0x7a,
-	0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x79, 0x73,
-	0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73,
-	0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x70, 0x6c,
-	0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70,
-	0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x22, 0x7d, 0x0a, 0x13, 0x44, 0x65, 0x62, 0x75, 0x67,
-	0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12,
-	0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61,
-	0x74, 0x68, 0x12, 0x20, 0x0a, 0x0b, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x4b, 0x65,
-	0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65,
-	0x64, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x13, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x61,
-	0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x13, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65,
-	0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x14, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67,
-	0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3d, 0x0a, 0x13,
-	0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x0e, 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, 0x3c, 0x0a, 0x12, 0x53,
-	0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x12, 0x26, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
-	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,
-	0x22, 0x1b, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
-	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x13, 0x0a,
-	0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x22, 0x3b, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74,
-	0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
-	0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x22,
-	0x44, 0x0a, 0x11, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61,
-	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x4e,
-	0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
-	0x52, 0x03, 0x61, 0x6c, 0x6c, 0x22, 0x3b, 0x0a, 0x12, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x53, 0x74,
-	0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63,
-	0x6c, 0x65, 0x61, 0x6e, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x05, 0x52, 0x0d, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74,
-	0x65, 0x73, 0x22, 0x45, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74,
-	0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74,
-	0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74,
-	0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6c, 0x6c, 0x22, 0x3c, 0x0a, 0x13, 0x44, 0x65, 0x6c,
-	0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74,
-	0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65,
-	0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 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, 0x22, 0x76, 0x0a, 0x08, 0x54, 0x43, 0x50, 0x46,
-	0x6c, 0x61, 0x67, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x79, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x08, 0x52, 0x03, 0x73, 0x79, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x63, 0x6b, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x69, 0x6e, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x66, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x73,
-	0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x72, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03,
-	0x70, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x70, 0x73, 0x68, 0x12, 0x10,
-	0x0a, 0x03, 0x75, 0x72, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x75, 0x72, 0x67,
-	0x22, 0x80, 0x03, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63,
-	0x65, 0x5f, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x6f, 0x75, 0x72,
-	0x63, 0x65, 0x49, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x65,
-	0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63,
-	0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x6f,
-	0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x74,
-	0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01,
-	0x28, 0x0d, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50,
-	0x6f, 0x72, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
-	0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f,
-	0x6e, 0x12, 0x32, 0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x07,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x54, 0x43,
-	0x50, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x48, 0x00, 0x52, 0x08, 0x74, 0x63, 0x70, 0x46, 0x6c, 0x61,
-	0x67, 0x73, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x69, 0x63, 0x6d, 0x70, 0x5f, 0x74, 0x79,
-	0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x08, 0x69, 0x63, 0x6d, 0x70,
-	0x54, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x69, 0x63, 0x6d, 0x70, 0x5f,
-	0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x08, 0x69, 0x63,
-	0x6d, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x74, 0x63,
-	0x70, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x69, 0x63, 0x6d, 0x70,
-	0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x69, 0x63, 0x6d, 0x70, 0x5f, 0x63,
-	0x6f, 0x64, 0x65, 0x22, 0x9f, 0x01, 0x0a, 0x0a, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61,
-	0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-	0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x08, 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x12, 0x66, 0x6f,
-	0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72,
-	0x64, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x88, 0x01, 0x01, 0x42, 0x15,
-	0x0a, 0x13, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65,
-	0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x6e, 0x0a, 0x13, 0x54, 0x72, 0x61, 0x63, 0x65, 0x50, 0x61,
-	0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06,
-	0x73, 0x74, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64,
-	0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x67, 0x65,
-	0x52, 0x06, 0x73, 0x74, 0x61, 0x67, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x69, 0x6e, 0x61,
-	0x6c, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x08, 0x52, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x69, 0x73, 0x70, 0x6f, 0x73,
-	0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
-	0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x93, 0x04, 0x0a, 0x0b, 0x53, 0x79,
-	0x73, 0x74, 0x65, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x38, 0x0a, 0x08, 0x73, 0x65, 0x76,
-	0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x64, 0x61,
-	0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74,
-	0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72,
-	0x69, 0x74, 0x79, 0x12, 0x38, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53,
-	0x79, 0x73, 0x74, 0x65, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x61, 0x74, 0x65, 0x67,
-	0x6f, 0x72, 0x79, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x0a,
-	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
-	0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x4d,
-	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x73,
-	0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d,
-	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
-	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
-	0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
-	0x61, 0x6d, 0x70, 0x12, 0x3d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
-	0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53,
-	0x79, 0x73, 0x74, 0x65, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64,
-	0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
-	0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e,
-	0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22,
-	0x3a, 0x0a, 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x49,
-	0x4e, 0x46, 0x4f, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47,
-	0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0c, 0x0a,
-	0x08, 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x03, 0x22, 0x52, 0x0a, 0x08, 0x43,
-	0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x45, 0x54, 0x57, 0x4f,
-	0x52, 0x4b, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x4e, 0x53, 0x10, 0x01, 0x12, 0x12, 0x0a,
-	0x0e, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10,
-	0x02, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x56, 0x49, 0x54,
-	0x59, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x10, 0x04, 0x22,
-	0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x22, 0x40, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e,
-	0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
-	0x6e, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65,
-	0x76, 0x65, 0x6e, 0x74, 0x73, 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, 0xb3, 0x0b, 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, 0x4b, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f,
-	0x72, 0x6b, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73,
-	0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x65,
-	0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
-	0x12, 0x51, 0x0a, 0x0e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
-	0x6b, 0x73, 0x12, 0x1d, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65,
-	0x63, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x1a, 0x1e, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63,
-	0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x10, 0x44, 0x65, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4e,
-	0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x1d, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
-	0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
-	0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x52, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0f, 0x46, 0x6f, 0x72, 0x77,
-	0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x14, 0x2e, 0x64, 0x61,
-	0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x1a, 0x1f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61,
-	0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 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, 0x12, 0x45, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73,
-	0x12, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74,
-	0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61,
-	0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0a, 0x43, 0x6c, 0x65,
-	0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
-	0x2e, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6c, 0x65, 0x61,
-	0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
-	0x12, 0x48, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
-	0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53,
-	0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61,
-	0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65,
-	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, 0x12, 0x48, 0x0a, 0x0b, 0x54,
-	0x72, 0x61, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65,
-	0x6d, 0x6f, 0x6e, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
-	0x54, 0x72, 0x61, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
-	0x62, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
-	0x6e, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x1a, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x79, 0x73, 0x74,
-	0x65, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x09, 0x47,
-	0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
-	0x6e, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x45,
-	0x76, 0x65, 0x6e, 0x74, 0x73, 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,
-}
+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\"\xbf\r\n" +
+	"\fLoginRequest\x12\x1a\n" +
+	"\bsetupKey\x18\x01 \x01(\tR\bsetupKey\x12&\n" +
+	"\fpreSharedKey\x18\x02 \x01(\tB\x02\x18\x01R\fpreSharedKey\x12$\n" +
+	"\rmanagementUrl\x18\x03 \x01(\tR\rmanagementUrl\x12\x1a\n" +
+	"\badminURL\x18\x04 \x01(\tR\badminURL\x12&\n" +
+	"\x0enatExternalIPs\x18\x05 \x03(\tR\x0enatExternalIPs\x120\n" +
+	"\x13cleanNATExternalIPs\x18\x06 \x01(\bR\x13cleanNATExternalIPs\x12*\n" +
+	"\x10customDNSAddress\x18\a \x01(\fR\x10customDNSAddress\x120\n" +
+	"\x13isUnixDesktopClient\x18\b \x01(\bR\x13isUnixDesktopClient\x12\x1a\n" +
+	"\bhostname\x18\t \x01(\tR\bhostname\x12/\n" +
+	"\x10rosenpassEnabled\x18\n" +
+	" \x01(\bH\x00R\x10rosenpassEnabled\x88\x01\x01\x12)\n" +
+	"\rinterfaceName\x18\v \x01(\tH\x01R\rinterfaceName\x88\x01\x01\x12)\n" +
+	"\rwireguardPort\x18\f \x01(\x03H\x02R\rwireguardPort\x88\x01\x01\x127\n" +
+	"\x14optionalPreSharedKey\x18\r \x01(\tH\x03R\x14optionalPreSharedKey\x88\x01\x01\x123\n" +
+	"\x12disableAutoConnect\x18\x0e \x01(\bH\x04R\x12disableAutoConnect\x88\x01\x01\x12/\n" +
+	"\x10serverSSHAllowed\x18\x0f \x01(\bH\x05R\x10serverSSHAllowed\x88\x01\x01\x125\n" +
+	"\x13rosenpassPermissive\x18\x10 \x01(\bH\x06R\x13rosenpassPermissive\x88\x01\x01\x120\n" +
+	"\x13extraIFaceBlacklist\x18\x11 \x03(\tR\x13extraIFaceBlacklist\x12+\n" +
+	"\x0enetworkMonitor\x18\x12 \x01(\bH\aR\x0enetworkMonitor\x88\x01\x01\x12J\n" +
+	"\x10dnsRouteInterval\x18\x13 \x01(\v2\x19.google.protobuf.DurationH\bR\x10dnsRouteInterval\x88\x01\x01\x127\n" +
+	"\x15disable_client_routes\x18\x14 \x01(\bH\tR\x13disableClientRoutes\x88\x01\x01\x127\n" +
+	"\x15disable_server_routes\x18\x15 \x01(\bH\n" +
+	"R\x13disableServerRoutes\x88\x01\x01\x12$\n" +
+	"\vdisable_dns\x18\x16 \x01(\bH\vR\n" +
+	"disableDns\x88\x01\x01\x12.\n" +
+	"\x10disable_firewall\x18\x17 \x01(\bH\fR\x0fdisableFirewall\x88\x01\x01\x12-\n" +
+	"\x10block_lan_access\x18\x18 \x01(\bH\rR\x0eblockLanAccess\x88\x01\x01\x128\n" +
+	"\x15disable_notifications\x18\x19 \x01(\bH\x0eR\x14disableNotifications\x88\x01\x01\x12\x1d\n" +
+	"\n" +
+	"dns_labels\x18\x1a \x03(\tR\tdnsLabels\x12&\n" +
+	"\x0ecleanDNSLabels\x18\x1b \x01(\bR\x0ecleanDNSLabels\x129\n" +
+	"\x15lazyConnectionEnabled\x18\x1c \x01(\bH\x0fR\x15lazyConnectionEnabled\x88\x01\x01\x12(\n" +
+	"\rblock_inbound\x18\x1d \x01(\bH\x10R\fblockInbound\x88\x01\x01B\x13\n" +
+	"\x11_rosenpassEnabledB\x10\n" +
+	"\x0e_interfaceNameB\x10\n" +
+	"\x0e_wireguardPortB\x17\n" +
+	"\x15_optionalPreSharedKeyB\x15\n" +
+	"\x13_disableAutoConnectB\x13\n" +
+	"\x11_serverSSHAllowedB\x16\n" +
+	"\x14_rosenpassPermissiveB\x11\n" +
+	"\x0f_networkMonitorB\x13\n" +
+	"\x11_dnsRouteIntervalB\x18\n" +
+	"\x16_disable_client_routesB\x18\n" +
+	"\x16_disable_server_routesB\x0e\n" +
+	"\f_disable_dnsB\x13\n" +
+	"\x11_disable_firewallB\x13\n" +
+	"\x11_block_lan_accessB\x18\n" +
+	"\x16_disable_notificationsB\x18\n" +
+	"\x16_lazyConnectionEnabledB\x10\n" +
+	"\x0e_block_inbound\"\xb5\x01\n" +
+	"\rLoginResponse\x12$\n" +
+	"\rneedsSSOLogin\x18\x01 \x01(\bR\rneedsSSOLogin\x12\x1a\n" +
+	"\buserCode\x18\x02 \x01(\tR\buserCode\x12(\n" +
+	"\x0fverificationURI\x18\x03 \x01(\tR\x0fverificationURI\x128\n" +
+	"\x17verificationURIComplete\x18\x04 \x01(\tR\x17verificationURIComplete\"M\n" +
+	"\x13WaitSSOLoginRequest\x12\x1a\n" +
+	"\buserCode\x18\x01 \x01(\tR\buserCode\x12\x1a\n" +
+	"\bhostname\x18\x02 \x01(\tR\bhostname\"\x16\n" +
+	"\x14WaitSSOLoginResponse\"\v\n" +
+	"\tUpRequest\"\f\n" +
+	"\n" +
+	"UpResponse\"g\n" +
+	"\rStatusRequest\x12,\n" +
+	"\x11getFullPeerStatus\x18\x01 \x01(\bR\x11getFullPeerStatus\x12(\n" +
+	"\x0fshouldRunProbes\x18\x02 \x01(\bR\x0fshouldRunProbes\"\x82\x01\n" +
+	"\x0eStatusResponse\x12\x16\n" +
+	"\x06status\x18\x01 \x01(\tR\x06status\x122\n" +
+	"\n" +
+	"fullStatus\x18\x02 \x01(\v2\x12.daemon.FullStatusR\n" +
+	"fullStatus\x12$\n" +
+	"\rdaemonVersion\x18\x03 \x01(\tR\rdaemonVersion\"\r\n" +
+	"\vDownRequest\"\x0e\n" +
+	"\fDownResponse\"\x12\n" +
+	"\x10GetConfigRequest\"\xa3\x06\n" +
+	"\x11GetConfigResponse\x12$\n" +
+	"\rmanagementUrl\x18\x01 \x01(\tR\rmanagementUrl\x12\x1e\n" +
+	"\n" +
+	"configFile\x18\x02 \x01(\tR\n" +
+	"configFile\x12\x18\n" +
+	"\alogFile\x18\x03 \x01(\tR\alogFile\x12\"\n" +
+	"\fpreSharedKey\x18\x04 \x01(\tR\fpreSharedKey\x12\x1a\n" +
+	"\badminURL\x18\x05 \x01(\tR\badminURL\x12$\n" +
+	"\rinterfaceName\x18\x06 \x01(\tR\rinterfaceName\x12$\n" +
+	"\rwireguardPort\x18\a \x01(\x03R\rwireguardPort\x12.\n" +
+	"\x12disableAutoConnect\x18\t \x01(\bR\x12disableAutoConnect\x12*\n" +
+	"\x10serverSSHAllowed\x18\n" +
+	" \x01(\bR\x10serverSSHAllowed\x12*\n" +
+	"\x10rosenpassEnabled\x18\v \x01(\bR\x10rosenpassEnabled\x120\n" +
+	"\x13rosenpassPermissive\x18\f \x01(\bR\x13rosenpassPermissive\x123\n" +
+	"\x15disable_notifications\x18\r \x01(\bR\x14disableNotifications\x124\n" +
+	"\x15lazyConnectionEnabled\x18\x0e \x01(\bR\x15lazyConnectionEnabled\x12\"\n" +
+	"\fblockInbound\x18\x0f \x01(\bR\fblockInbound\x12&\n" +
+	"\x0enetworkMonitor\x18\x10 \x01(\bR\x0enetworkMonitor\x12\x1f\n" +
+	"\vdisable_dns\x18\x11 \x01(\bR\n" +
+	"disableDns\x122\n" +
+	"\x15disable_client_routes\x18\x12 \x01(\bR\x13disableClientRoutes\x122\n" +
+	"\x15disable_server_routes\x18\x13 \x01(\bR\x13disableServerRoutes\x12(\n" +
+	"\x10block_lan_access\x18\x14 \x01(\bR\x0eblockLanAccess\"\xde\x05\n" +
+	"\tPeerState\x12\x0e\n" +
+	"\x02IP\x18\x01 \x01(\tR\x02IP\x12\x16\n" +
+	"\x06pubKey\x18\x02 \x01(\tR\x06pubKey\x12\x1e\n" +
+	"\n" +
+	"connStatus\x18\x03 \x01(\tR\n" +
+	"connStatus\x12F\n" +
+	"\x10connStatusUpdate\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\x10connStatusUpdate\x12\x18\n" +
+	"\arelayed\x18\x05 \x01(\bR\arelayed\x124\n" +
+	"\x15localIceCandidateType\x18\a \x01(\tR\x15localIceCandidateType\x126\n" +
+	"\x16remoteIceCandidateType\x18\b \x01(\tR\x16remoteIceCandidateType\x12\x12\n" +
+	"\x04fqdn\x18\t \x01(\tR\x04fqdn\x12<\n" +
+	"\x19localIceCandidateEndpoint\x18\n" +
+	" \x01(\tR\x19localIceCandidateEndpoint\x12>\n" +
+	"\x1aremoteIceCandidateEndpoint\x18\v \x01(\tR\x1aremoteIceCandidateEndpoint\x12R\n" +
+	"\x16lastWireguardHandshake\x18\f \x01(\v2\x1a.google.protobuf.TimestampR\x16lastWireguardHandshake\x12\x18\n" +
+	"\abytesRx\x18\r \x01(\x03R\abytesRx\x12\x18\n" +
+	"\abytesTx\x18\x0e \x01(\x03R\abytesTx\x12*\n" +
+	"\x10rosenpassEnabled\x18\x0f \x01(\bR\x10rosenpassEnabled\x12\x1a\n" +
+	"\bnetworks\x18\x10 \x03(\tR\bnetworks\x123\n" +
+	"\alatency\x18\x11 \x01(\v2\x19.google.protobuf.DurationR\alatency\x12\"\n" +
+	"\frelayAddress\x18\x12 \x01(\tR\frelayAddress\"\xf0\x01\n" +
+	"\x0eLocalPeerState\x12\x0e\n" +
+	"\x02IP\x18\x01 \x01(\tR\x02IP\x12\x16\n" +
+	"\x06pubKey\x18\x02 \x01(\tR\x06pubKey\x12(\n" +
+	"\x0fkernelInterface\x18\x03 \x01(\bR\x0fkernelInterface\x12\x12\n" +
+	"\x04fqdn\x18\x04 \x01(\tR\x04fqdn\x12*\n" +
+	"\x10rosenpassEnabled\x18\x05 \x01(\bR\x10rosenpassEnabled\x120\n" +
+	"\x13rosenpassPermissive\x18\x06 \x01(\bR\x13rosenpassPermissive\x12\x1a\n" +
+	"\bnetworks\x18\a \x03(\tR\bnetworks\"S\n" +
+	"\vSignalState\x12\x10\n" +
+	"\x03URL\x18\x01 \x01(\tR\x03URL\x12\x1c\n" +
+	"\tconnected\x18\x02 \x01(\bR\tconnected\x12\x14\n" +
+	"\x05error\x18\x03 \x01(\tR\x05error\"W\n" +
+	"\x0fManagementState\x12\x10\n" +
+	"\x03URL\x18\x01 \x01(\tR\x03URL\x12\x1c\n" +
+	"\tconnected\x18\x02 \x01(\bR\tconnected\x12\x14\n" +
+	"\x05error\x18\x03 \x01(\tR\x05error\"R\n" +
+	"\n" +
+	"RelayState\x12\x10\n" +
+	"\x03URI\x18\x01 \x01(\tR\x03URI\x12\x1c\n" +
+	"\tavailable\x18\x02 \x01(\bR\tavailable\x12\x14\n" +
+	"\x05error\x18\x03 \x01(\tR\x05error\"r\n" +
+	"\fNSGroupState\x12\x18\n" +
+	"\aservers\x18\x01 \x03(\tR\aservers\x12\x18\n" +
+	"\adomains\x18\x02 \x03(\tR\adomains\x12\x18\n" +
+	"\aenabled\x18\x03 \x01(\bR\aenabled\x12\x14\n" +
+	"\x05error\x18\x04 \x01(\tR\x05error\"\xef\x03\n" +
+	"\n" +
+	"FullStatus\x12A\n" +
+	"\x0fmanagementState\x18\x01 \x01(\v2\x17.daemon.ManagementStateR\x0fmanagementState\x125\n" +
+	"\vsignalState\x18\x02 \x01(\v2\x13.daemon.SignalStateR\vsignalState\x12>\n" +
+	"\x0elocalPeerState\x18\x03 \x01(\v2\x16.daemon.LocalPeerStateR\x0elocalPeerState\x12'\n" +
+	"\x05peers\x18\x04 \x03(\v2\x11.daemon.PeerStateR\x05peers\x12*\n" +
+	"\x06relays\x18\x05 \x03(\v2\x12.daemon.RelayStateR\x06relays\x125\n" +
+	"\vdns_servers\x18\x06 \x03(\v2\x14.daemon.NSGroupStateR\n" +
+	"dnsServers\x128\n" +
+	"\x17NumberOfForwardingRules\x18\b \x01(\x05R\x17NumberOfForwardingRules\x12+\n" +
+	"\x06events\x18\a \x03(\v2\x13.daemon.SystemEventR\x06events\x124\n" +
+	"\x15lazyConnectionEnabled\x18\t \x01(\bR\x15lazyConnectionEnabled\"\x15\n" +
+	"\x13ListNetworksRequest\"?\n" +
+	"\x14ListNetworksResponse\x12'\n" +
+	"\x06routes\x18\x01 \x03(\v2\x0f.daemon.NetworkR\x06routes\"a\n" +
+	"\x15SelectNetworksRequest\x12\x1e\n" +
+	"\n" +
+	"networkIDs\x18\x01 \x03(\tR\n" +
+	"networkIDs\x12\x16\n" +
+	"\x06append\x18\x02 \x01(\bR\x06append\x12\x10\n" +
+	"\x03all\x18\x03 \x01(\bR\x03all\"\x18\n" +
+	"\x16SelectNetworksResponse\"\x1a\n" +
+	"\x06IPList\x12\x10\n" +
+	"\x03ips\x18\x01 \x03(\tR\x03ips\"\xf9\x01\n" +
+	"\aNetwork\x12\x0e\n" +
+	"\x02ID\x18\x01 \x01(\tR\x02ID\x12\x14\n" +
+	"\x05range\x18\x02 \x01(\tR\x05range\x12\x1a\n" +
+	"\bselected\x18\x03 \x01(\bR\bselected\x12\x18\n" +
+	"\adomains\x18\x04 \x03(\tR\adomains\x12B\n" +
+	"\vresolvedIPs\x18\x05 \x03(\v2 .daemon.Network.ResolvedIPsEntryR\vresolvedIPs\x1aN\n" +
+	"\x10ResolvedIPsEntry\x12\x10\n" +
+	"\x03key\x18\x01 \x01(\tR\x03key\x12$\n" +
+	"\x05value\x18\x02 \x01(\v2\x0e.daemon.IPListR\x05value:\x028\x01\"\x92\x01\n" +
+	"\bPortInfo\x12\x14\n" +
+	"\x04port\x18\x01 \x01(\rH\x00R\x04port\x12.\n" +
+	"\x05range\x18\x02 \x01(\v2\x16.daemon.PortInfo.RangeH\x00R\x05range\x1a/\n" +
+	"\x05Range\x12\x14\n" +
+	"\x05start\x18\x01 \x01(\rR\x05start\x12\x10\n" +
+	"\x03end\x18\x02 \x01(\rR\x03endB\x0f\n" +
+	"\rportSelection\"\x80\x02\n" +
+	"\x0eForwardingRule\x12\x1a\n" +
+	"\bprotocol\x18\x01 \x01(\tR\bprotocol\x12:\n" +
+	"\x0fdestinationPort\x18\x02 \x01(\v2\x10.daemon.PortInfoR\x0fdestinationPort\x12,\n" +
+	"\x11translatedAddress\x18\x03 \x01(\tR\x11translatedAddress\x12.\n" +
+	"\x12translatedHostname\x18\x04 \x01(\tR\x12translatedHostname\x128\n" +
+	"\x0etranslatedPort\x18\x05 \x01(\v2\x10.daemon.PortInfoR\x0etranslatedPort\"G\n" +
+	"\x17ForwardingRulesResponse\x12,\n" +
+	"\x05rules\x18\x01 \x03(\v2\x16.daemon.ForwardingRuleR\x05rules\"\xac\x01\n" +
+	"\x12DebugBundleRequest\x12\x1c\n" +
+	"\tanonymize\x18\x01 \x01(\bR\tanonymize\x12\x16\n" +
+	"\x06status\x18\x02 \x01(\tR\x06status\x12\x1e\n" +
+	"\n" +
+	"systemInfo\x18\x03 \x01(\bR\n" +
+	"systemInfo\x12\x1c\n" +
+	"\tuploadURL\x18\x04 \x01(\tR\tuploadURL\x12\"\n" +
+	"\flogFileCount\x18\x05 \x01(\rR\flogFileCount\"}\n" +
+	"\x13DebugBundleResponse\x12\x12\n" +
+	"\x04path\x18\x01 \x01(\tR\x04path\x12 \n" +
+	"\vuploadedKey\x18\x02 \x01(\tR\vuploadedKey\x120\n" +
+	"\x13uploadFailureReason\x18\x03 \x01(\tR\x13uploadFailureReason\"\x14\n" +
+	"\x12GetLogLevelRequest\"=\n" +
+	"\x13GetLogLevelResponse\x12&\n" +
+	"\x05level\x18\x01 \x01(\x0e2\x10.daemon.LogLevelR\x05level\"<\n" +
+	"\x12SetLogLevelRequest\x12&\n" +
+	"\x05level\x18\x01 \x01(\x0e2\x10.daemon.LogLevelR\x05level\"\x15\n" +
+	"\x13SetLogLevelResponse\"\x1b\n" +
+	"\x05State\x12\x12\n" +
+	"\x04name\x18\x01 \x01(\tR\x04name\"\x13\n" +
+	"\x11ListStatesRequest\";\n" +
+	"\x12ListStatesResponse\x12%\n" +
+	"\x06states\x18\x01 \x03(\v2\r.daemon.StateR\x06states\"D\n" +
+	"\x11CleanStateRequest\x12\x1d\n" +
+	"\n" +
+	"state_name\x18\x01 \x01(\tR\tstateName\x12\x10\n" +
+	"\x03all\x18\x02 \x01(\bR\x03all\";\n" +
+	"\x12CleanStateResponse\x12%\n" +
+	"\x0ecleaned_states\x18\x01 \x01(\x05R\rcleanedStates\"E\n" +
+	"\x12DeleteStateRequest\x12\x1d\n" +
+	"\n" +
+	"state_name\x18\x01 \x01(\tR\tstateName\x12\x10\n" +
+	"\x03all\x18\x02 \x01(\bR\x03all\"<\n" +
+	"\x13DeleteStateResponse\x12%\n" +
+	"\x0edeleted_states\x18\x01 \x01(\x05R\rdeletedStates\";\n" +
+	"\x1fSetNetworkMapPersistenceRequest\x12\x18\n" +
+	"\aenabled\x18\x01 \x01(\bR\aenabled\"\"\n" +
+	" SetNetworkMapPersistenceResponse\"v\n" +
+	"\bTCPFlags\x12\x10\n" +
+	"\x03syn\x18\x01 \x01(\bR\x03syn\x12\x10\n" +
+	"\x03ack\x18\x02 \x01(\bR\x03ack\x12\x10\n" +
+	"\x03fin\x18\x03 \x01(\bR\x03fin\x12\x10\n" +
+	"\x03rst\x18\x04 \x01(\bR\x03rst\x12\x10\n" +
+	"\x03psh\x18\x05 \x01(\bR\x03psh\x12\x10\n" +
+	"\x03urg\x18\x06 \x01(\bR\x03urg\"\x80\x03\n" +
+	"\x12TracePacketRequest\x12\x1b\n" +
+	"\tsource_ip\x18\x01 \x01(\tR\bsourceIp\x12%\n" +
+	"\x0edestination_ip\x18\x02 \x01(\tR\rdestinationIp\x12\x1a\n" +
+	"\bprotocol\x18\x03 \x01(\tR\bprotocol\x12\x1f\n" +
+	"\vsource_port\x18\x04 \x01(\rR\n" +
+	"sourcePort\x12)\n" +
+	"\x10destination_port\x18\x05 \x01(\rR\x0fdestinationPort\x12\x1c\n" +
+	"\tdirection\x18\x06 \x01(\tR\tdirection\x122\n" +
+	"\ttcp_flags\x18\a \x01(\v2\x10.daemon.TCPFlagsH\x00R\btcpFlags\x88\x01\x01\x12 \n" +
+	"\ticmp_type\x18\b \x01(\rH\x01R\bicmpType\x88\x01\x01\x12 \n" +
+	"\ticmp_code\x18\t \x01(\rH\x02R\bicmpCode\x88\x01\x01B\f\n" +
+	"\n" +
+	"_tcp_flagsB\f\n" +
+	"\n" +
+	"_icmp_typeB\f\n" +
+	"\n" +
+	"_icmp_code\"\x9f\x01\n" +
+	"\n" +
+	"TraceStage\x12\x12\n" +
+	"\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" +
+	"\amessage\x18\x02 \x01(\tR\amessage\x12\x18\n" +
+	"\aallowed\x18\x03 \x01(\bR\aallowed\x122\n" +
+	"\x12forwarding_details\x18\x04 \x01(\tH\x00R\x11forwardingDetails\x88\x01\x01B\x15\n" +
+	"\x13_forwarding_details\"n\n" +
+	"\x13TracePacketResponse\x12*\n" +
+	"\x06stages\x18\x01 \x03(\v2\x12.daemon.TraceStageR\x06stages\x12+\n" +
+	"\x11final_disposition\x18\x02 \x01(\bR\x10finalDisposition\"\x12\n" +
+	"\x10SubscribeRequest\"\x93\x04\n" +
+	"\vSystemEvent\x12\x0e\n" +
+	"\x02id\x18\x01 \x01(\tR\x02id\x128\n" +
+	"\bseverity\x18\x02 \x01(\x0e2\x1c.daemon.SystemEvent.SeverityR\bseverity\x128\n" +
+	"\bcategory\x18\x03 \x01(\x0e2\x1c.daemon.SystemEvent.CategoryR\bcategory\x12\x18\n" +
+	"\amessage\x18\x04 \x01(\tR\amessage\x12 \n" +
+	"\vuserMessage\x18\x05 \x01(\tR\vuserMessage\x128\n" +
+	"\ttimestamp\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12=\n" +
+	"\bmetadata\x18\a \x03(\v2!.daemon.SystemEvent.MetadataEntryR\bmetadata\x1a;\n" +
+	"\rMetadataEntry\x12\x10\n" +
+	"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+	"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\":\n" +
+	"\bSeverity\x12\b\n" +
+	"\x04INFO\x10\x00\x12\v\n" +
+	"\aWARNING\x10\x01\x12\t\n" +
+	"\x05ERROR\x10\x02\x12\f\n" +
+	"\bCRITICAL\x10\x03\"R\n" +
+	"\bCategory\x12\v\n" +
+	"\aNETWORK\x10\x00\x12\a\n" +
+	"\x03DNS\x10\x01\x12\x12\n" +
+	"\x0eAUTHENTICATION\x10\x02\x12\x10\n" +
+	"\fCONNECTIVITY\x10\x03\x12\n" +
+	"\n" +
+	"\x06SYSTEM\x10\x04\"\x12\n" +
+	"\x10GetEventsRequest\"@\n" +
+	"\x11GetEventsResponse\x12+\n" +
+	"\x06events\x18\x01 \x03(\v2\x13.daemon.SystemEventR\x06events*b\n" +
+	"\bLogLevel\x12\v\n" +
+	"\aUNKNOWN\x10\x00\x12\t\n" +
+	"\x05PANIC\x10\x01\x12\t\n" +
+	"\x05FATAL\x10\x02\x12\t\n" +
+	"\x05ERROR\x10\x03\x12\b\n" +
+	"\x04WARN\x10\x04\x12\b\n" +
+	"\x04INFO\x10\x05\x12\t\n" +
+	"\x05DEBUG\x10\x06\x12\t\n" +
+	"\x05TRACE\x10\a2\xb3\v\n" +
+	"\rDaemonService\x126\n" +
+	"\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\n" +
+	"\fWaitSSOLogin\x12\x1b.daemon.WaitSSOLoginRequest\x1a\x1c.daemon.WaitSSOLoginResponse\"\x00\x12-\n" +
+	"\x02Up\x12\x11.daemon.UpRequest\x1a\x12.daemon.UpResponse\"\x00\x129\n" +
+	"\x06Status\x12\x15.daemon.StatusRequest\x1a\x16.daemon.StatusResponse\"\x00\x123\n" +
+	"\x04Down\x12\x13.daemon.DownRequest\x1a\x14.daemon.DownResponse\"\x00\x12B\n" +
+	"\tGetConfig\x12\x18.daemon.GetConfigRequest\x1a\x19.daemon.GetConfigResponse\"\x00\x12K\n" +
+	"\fListNetworks\x12\x1b.daemon.ListNetworksRequest\x1a\x1c.daemon.ListNetworksResponse\"\x00\x12Q\n" +
+	"\x0eSelectNetworks\x12\x1d.daemon.SelectNetworksRequest\x1a\x1e.daemon.SelectNetworksResponse\"\x00\x12S\n" +
+	"\x10DeselectNetworks\x12\x1d.daemon.SelectNetworksRequest\x1a\x1e.daemon.SelectNetworksResponse\"\x00\x12J\n" +
+	"\x0fForwardingRules\x12\x14.daemon.EmptyRequest\x1a\x1f.daemon.ForwardingRulesResponse\"\x00\x12H\n" +
+	"\vDebugBundle\x12\x1a.daemon.DebugBundleRequest\x1a\x1b.daemon.DebugBundleResponse\"\x00\x12H\n" +
+	"\vGetLogLevel\x12\x1a.daemon.GetLogLevelRequest\x1a\x1b.daemon.GetLogLevelResponse\"\x00\x12H\n" +
+	"\vSetLogLevel\x12\x1a.daemon.SetLogLevelRequest\x1a\x1b.daemon.SetLogLevelResponse\"\x00\x12E\n" +
+	"\n" +
+	"ListStates\x12\x19.daemon.ListStatesRequest\x1a\x1a.daemon.ListStatesResponse\"\x00\x12E\n" +
+	"\n" +
+	"CleanState\x12\x19.daemon.CleanStateRequest\x1a\x1a.daemon.CleanStateResponse\"\x00\x12H\n" +
+	"\vDeleteState\x12\x1a.daemon.DeleteStateRequest\x1a\x1b.daemon.DeleteStateResponse\"\x00\x12o\n" +
+	"\x18SetNetworkMapPersistence\x12'.daemon.SetNetworkMapPersistenceRequest\x1a(.daemon.SetNetworkMapPersistenceResponse\"\x00\x12H\n" +
+	"\vTracePacket\x12\x1a.daemon.TracePacketRequest\x1a\x1b.daemon.TracePacketResponse\"\x00\x12D\n" +
+	"\x0fSubscribeEvents\x12\x18.daemon.SubscribeRequest\x1a\x13.daemon.SystemEvent\"\x000\x01\x12B\n" +
+	"\tGetEvents\x12\x18.daemon.GetEventsRequest\x1a\x19.daemon.GetEventsResponse\"\x00B\bZ\x06/protob\x06proto3"
 
 var (
 	file_daemon_proto_rawDescOnce sync.Once
-	file_daemon_proto_rawDescData = file_daemon_proto_rawDesc
+	file_daemon_proto_rawDescData []byte
 )
 
 func file_daemon_proto_rawDescGZIP() []byte {
 	file_daemon_proto_rawDescOnce.Do(func() {
-		file_daemon_proto_rawDescData = protoimpl.X.CompressGZIP(file_daemon_proto_rawDescData)
+		file_daemon_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_proto_rawDesc), len(file_daemon_proto_rawDesc)))
 	})
 	return file_daemon_proto_rawDescData
 }
 
 var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
 var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 55)
-var file_daemon_proto_goTypes = []interface{}{
+var file_daemon_proto_goTypes = []any{
 	(LogLevel)(0),                            // 0: daemon.LogLevel
 	(SystemEvent_Severity)(0),                // 1: daemon.SystemEvent.Severity
 	(SystemEvent_Category)(0),                // 2: daemon.SystemEvent.Category
@@ -4465,656 +4052,18 @@ func file_daemon_proto_init() {
 	if File_daemon_proto != nil {
 		return
 	}
-	if !protoimpl.UnsafeEnabled {
-		file_daemon_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*EmptyRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*LoginRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*LoginResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*WaitSSOLoginRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*WaitSSOLoginResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*UpRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*UpResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*StatusRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*StatusResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*DownRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*DownResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*GetConfigRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*GetConfigResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PeerState); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*LocalPeerState); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SignalState); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ManagementState); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*RelayState); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*NSGroupState); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*FullStatus); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ListNetworksRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ListNetworksResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SelectNetworksRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SelectNetworksResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*IPList); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Network); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PortInfo); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ForwardingRule); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ForwardingRulesResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*DebugBundleRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*DebugBundleResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*GetLogLevelRequest); 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.(*GetLogLevelResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SetLogLevelRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SetLogLevelResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*State); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ListStatesRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ListStatesResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CleanStateRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CleanStateResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*DeleteStateRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*DeleteStateResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[42].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[43].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[44].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*TCPFlags); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*TracePacketRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*TraceStage); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*TracePacketResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SubscribeRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SystemEvent); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*GetEventsRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*GetEventsResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_daemon_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PortInfo_Range); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-	}
-	file_daemon_proto_msgTypes[1].OneofWrappers = []interface{}{}
-	file_daemon_proto_msgTypes[26].OneofWrappers = []interface{}{
+	file_daemon_proto_msgTypes[1].OneofWrappers = []any{}
+	file_daemon_proto_msgTypes[26].OneofWrappers = []any{
 		(*PortInfo_Port)(nil),
 		(*PortInfo_Range_)(nil),
 	}
-	file_daemon_proto_msgTypes[45].OneofWrappers = []interface{}{}
-	file_daemon_proto_msgTypes[46].OneofWrappers = []interface{}{}
+	file_daemon_proto_msgTypes[45].OneofWrappers = []any{}
+	file_daemon_proto_msgTypes[46].OneofWrappers = []any{}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_daemon_proto_rawDesc,
+			RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_proto_rawDesc), len(file_daemon_proto_rawDesc)),
 			NumEnums:      3,
 			NumMessages:   55,
 			NumExtensions: 0,
@@ -5126,7 +4075,6 @@ func file_daemon_proto_init() {
 		MessageInfos:      file_daemon_proto_msgTypes,
 	}.Build()
 	File_daemon_proto = out.File
-	file_daemon_proto_rawDesc = nil
 	file_daemon_proto_goTypes = nil
 	file_daemon_proto_depIdxs = nil
 }
diff --git a/client/proto/daemon.proto b/client/proto/daemon.proto
index 2a1a49d1..462555c8 100644
--- a/client/proto/daemon.proto
+++ b/client/proto/daemon.proto
@@ -158,6 +158,7 @@ message UpResponse {}
 
 message StatusRequest{
   bool getFullPeerStatus = 1;
+  bool shouldRunProbes   = 2;
 }
 
 message StatusResponse{
@@ -355,6 +356,7 @@ message DebugBundleRequest {
   string status = 2;
   bool systemInfo = 3;
   string uploadURL = 4;
+  uint32 logFileCount = 5;
 }
 
 message DebugBundleResponse {
diff --git a/client/proto/generate.sh b/client/proto/generate.sh
index 52fe23d7..f9a2c375 100755
--- a/client/proto/generate.sh
+++ b/client/proto/generate.sh
@@ -11,7 +11,7 @@ fi
 old_pwd=$(pwd)
 script_path=$(dirname $(realpath "$0"))
 cd "$script_path"
-go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
+go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.6
 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
 protoc -I ./ ./daemon.proto --go_out=../ --go-grpc_out=../ --experimental_allow_proto3_optional
 cd "$old_pwd"
\ No newline at end of file
diff --git a/client/server/debug.go b/client/server/debug.go
index 7de3e860..412602b0 100644
--- a/client/server/debug.go
+++ b/client/server/debug.go
@@ -42,6 +42,7 @@ func (s *Server) DebugBundle(_ context.Context, req *proto.DebugBundleRequest) (
 			Anonymize:         req.GetAnonymize(),
 			ClientStatus:      req.GetStatus(),
 			IncludeSystemInfo: req.GetSystemInfo(),
+			LogFileCount:      req.GetLogFileCount(),
 		},
 	)
 
diff --git a/client/server/server.go b/client/server/server.go
index 31a437c9..e3ce1a2b 100644
--- a/client/server/server.go
+++ b/client/server/server.go
@@ -707,7 +707,9 @@ func (s *Server) Status(
 	s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
 
 	if msg.GetFullPeerStatus {
-		s.runProbes()
+		if msg.ShouldRunProbes {
+			s.runProbes()
+		}
 
 		fullStatus := s.statusRecorder.GetFullStatus()
 		pbFullStatus := toProtoFullStatus(fullStatus)
diff --git a/client/status/status.go b/client/status/status.go
index 18056e36..507c7ea8 100644
--- a/client/status/status.go
+++ b/client/status/status.go
@@ -100,7 +100,7 @@ type OutputOverview struct {
 	LazyConnectionEnabled   bool                       `json:"lazyConnectionEnabled" yaml:"lazyConnectionEnabled"`
 }
 
-func ConvertToStatusOutputOverview(resp *proto.StatusResponse, anon bool, statusFilter string, prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}) OutputOverview {
+func ConvertToStatusOutputOverview(resp *proto.StatusResponse, anon bool, statusFilter string, prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}, connectionTypeFilter string) OutputOverview {
 	pbFullStatus := resp.GetFullStatus()
 
 	managementState := pbFullStatus.GetManagementState()
@@ -118,7 +118,7 @@ func ConvertToStatusOutputOverview(resp *proto.StatusResponse, anon bool, status
 	}
 
 	relayOverview := mapRelays(pbFullStatus.GetRelays())
-	peersOverview := mapPeers(resp.GetFullStatus().GetPeers(), statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilter)
+	peersOverview := mapPeers(resp.GetFullStatus().GetPeers(), statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilter, connectionTypeFilter)
 
 	overview := OutputOverview{
 		Peers:                   peersOverview,
@@ -193,6 +193,7 @@ func mapPeers(
 	prefixNamesFilter []string,
 	prefixNamesFilterMap map[string]struct{},
 	ipsFilter map[string]struct{},
+	connectionTypeFilter string,
 ) PeersStateOutput {
 	var peersStateDetail []PeerStateDetailOutput
 	peersConnected := 0
@@ -208,7 +209,7 @@ func mapPeers(
 		transferSent := int64(0)
 
 		isPeerConnected := pbPeerState.ConnStatus == peer.StatusConnected.String()
-		if skipDetailByFilters(pbPeerState, pbPeerState.ConnStatus, statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilter) {
+		if skipDetailByFilters(pbPeerState, pbPeerState.ConnStatus, statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilter, connectionTypeFilter) {
 			continue
 		}
 		if isPeerConnected {
@@ -218,10 +219,7 @@ func mapPeers(
 			remoteICE = pbPeerState.GetRemoteIceCandidateType()
 			localICEEndpoint = pbPeerState.GetLocalIceCandidateEndpoint()
 			remoteICEEndpoint = pbPeerState.GetRemoteIceCandidateEndpoint()
-			connType = "P2P"
-			if pbPeerState.Relayed {
-				connType = "Relayed"
-			}
+			connType = pbPeerState.GetConnectionType()
 			relayServerAddress = pbPeerState.GetRelayAddress()
 			lastHandshake = pbPeerState.GetLastWireguardHandshake().AsTime().Local()
 			transferReceived = pbPeerState.GetBytesRx()
@@ -542,10 +540,11 @@ func parsePeers(peers PeersStateOutput, rosenpassEnabled, rosenpassPermissive bo
 	return peersString
 }
 
-func skipDetailByFilters(peerState *proto.PeerState, peerStatus string, statusFilter string, prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}) bool {
+func skipDetailByFilters(peerState *proto.PeerState, peerStatus string, statusFilter string, prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}, connectionTypeFilter string) bool {
 	statusEval := false
 	ipEval := false
 	nameEval := true
+	connectionTypeEval := false
 
 	if statusFilter != "" {
 		if !strings.EqualFold(peerStatus, statusFilter) {
@@ -570,8 +569,11 @@ func skipDetailByFilters(peerState *proto.PeerState, peerStatus string, statusFi
 	} else {
 		nameEval = false
 	}
+	if connectionTypeFilter != "" && !strings.EqualFold(peerState.GetConnectionType(), connectionTypeFilter) {
+		connectionTypeEval = true
+	}
 
-	return statusEval || ipEval || nameEval
+	return statusEval || ipEval || nameEval || connectionTypeEval
 }
 
 func toIEC(b int64) string {
diff --git a/client/ui/client_ui.go b/client/ui/client_ui.go
index b7f98951..ace5b71e 100644
--- a/client/ui/client_ui.go
+++ b/client/ui/client_ui.go
@@ -879,7 +879,7 @@ func (s *serviceClient) onUpdateAvailable() {
 func (s *serviceClient) onSessionExpire() {
 	s.sendNotification = true
 	if s.sendNotification {
-		s.eventHandler.runSelfCommand(s.ctx, "login-url", "true")
+		go s.eventHandler.runSelfCommand(s.ctx, "login-url", "true")
 		s.sendNotification = false
 	}
 }
diff --git a/client/ui/debug.go b/client/ui/debug.go
index ab7dba37..55829de1 100644
--- a/client/ui/debug.go
+++ b/client/ui/debug.go
@@ -433,7 +433,7 @@ func (s *serviceClient) collectDebugData(
 
 	var postUpStatusOutput string
 	if postUpStatus != nil {
-		overview := nbstatus.ConvertToStatusOutputOverview(postUpStatus, params.anonymize, "", nil, nil, nil)
+		overview := nbstatus.ConvertToStatusOutputOverview(postUpStatus, params.anonymize, "", nil, nil, nil, "")
 		postUpStatusOutput = nbstatus.ParseToFullDetailSummary(overview)
 	}
 	headerPostUp := fmt.Sprintf("----- NetBird post-up - Timestamp: %s", time.Now().Format(time.RFC3339))
@@ -450,7 +450,7 @@ func (s *serviceClient) collectDebugData(
 
 	var preDownStatusOutput string
 	if preDownStatus != nil {
-		overview := nbstatus.ConvertToStatusOutputOverview(preDownStatus, params.anonymize, "", nil, nil, nil)
+		overview := nbstatus.ConvertToStatusOutputOverview(preDownStatus, params.anonymize, "", nil, nil, nil, "")
 		preDownStatusOutput = nbstatus.ParseToFullDetailSummary(overview)
 	}
 	headerPreDown := fmt.Sprintf("----- NetBird pre-down - Timestamp: %s - Duration: %s",
@@ -581,7 +581,7 @@ func (s *serviceClient) createDebugBundle(anonymize bool, systemInfo bool, uploa
 
 	var statusOutput string
 	if statusResp != nil {
-		overview := nbstatus.ConvertToStatusOutputOverview(statusResp, anonymize, "", nil, nil, nil)
+		overview := nbstatus.ConvertToStatusOutputOverview(statusResp, anonymize, "", nil, nil, nil, "")
 		statusOutput = nbstatus.ParseToFullDetailSummary(overview)
 	}
 
diff --git a/go.mod b/go.mod
index a1205827..cf2a2375 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.36.5
+	google.golang.org/protobuf v1.36.6
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 )
 
@@ -63,7 +63,7 @@ require (
 	github.com/miekg/dns v1.1.59
 	github.com/mitchellh/hashstructure/v2 v2.0.2
 	github.com/nadoo/ipset v0.5.0
-	github.com/netbirdio/management-integrations/integrations v0.0.0-20250612164546-6bd7e2338d65
+	github.com/netbirdio/management-integrations/integrations v0.0.0-20250718071730-f4d133556ff5
 	github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250514131221-a464fd5f30cb
 	github.com/okta/okta-sdk-golang/v2 v2.18.0
 	github.com/oschwald/maxminddb-golang v1.12.0
diff --git a/go.sum b/go.sum
index 6ce503dd..699a832d 100644
--- a/go.sum
+++ b/go.sum
@@ -503,8 +503,8 @@ github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944 h1:TDtJKmM6S
 github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ=
 github.com/netbirdio/ice/v3 v3.0.0-20240315174635-e72a50fcb64e h1:PURA50S8u4mF6RrkYYCAvvPCixhqqEiEy3Ej6avh04c=
 github.com/netbirdio/ice/v3 v3.0.0-20240315174635-e72a50fcb64e/go.mod h1:YMLU7qbKfVjmEv7EoZPIVEI+kNYxWCdPK3VS0BU+U4Q=
-github.com/netbirdio/management-integrations/integrations v0.0.0-20250612164546-6bd7e2338d65 h1:5OfYiLjpr4dbQYJI5ouZaylkVdi2KlErLFOwBeBo5Hw=
-github.com/netbirdio/management-integrations/integrations v0.0.0-20250612164546-6bd7e2338d65/go.mod h1:Gi9raplYzCCyh07Olw/DVfCJTFgpr1WCXJ/Q+8TSA9Q=
+github.com/netbirdio/management-integrations/integrations v0.0.0-20250718071730-f4d133556ff5 h1:Zfn8d83OVyELCdxgprcyXR3D8uqoxHtXE9PUxVXDx/w=
+github.com/netbirdio/management-integrations/integrations v0.0.0-20250718071730-f4d133556ff5/go.mod h1:Gi9raplYzCCyh07Olw/DVfCJTFgpr1WCXJ/Q+8TSA9Q=
 github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9axERMVN63dqyFqnvuD+EMJHzM7mNGON8=
 github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
 github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250514131221-a464fd5f30cb h1:Cr6age+ePALqlSvtp7wc6lYY97XN7rkD1K4XEDmY+TU=
@@ -1164,8 +1164,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.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
-google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 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=
diff --git a/infrastructure_files/base.setup.env b/infrastructure_files/base.setup.env
index fdba1f21..e5993919 100644
--- a/infrastructure_files/base.setup.env
+++ b/infrastructure_files/base.setup.env
@@ -15,6 +15,7 @@ NETBIRD_MGMT_API_CERT_KEY_FILE="/etc/letsencrypt/live/$NETBIRD_LETSENCRYPT_DOMAI
 NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN=$NETBIRD_DOMAIN
 NETBIRD_MGMT_DNS_DOMAIN=${NETBIRD_MGMT_DNS_DOMAIN:-netbird.selfhosted}
 NETBIRD_MGMT_IDP_SIGNKEY_REFRESH=${NETBIRD_MGMT_IDP_SIGNKEY_REFRESH:-false}
+NETBIRD_MGMT_DISABLE_DEFAULT_POLICY=${NETBIRD_MGMT_DISABLE_DEFAULT_POLICY:-false}
 
 # Signal
 NETBIRD_SIGNAL_PROTOCOL="http"
@@ -60,7 +61,7 @@ NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
 NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS=${NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS:-"53000"}
 NETBIRD_AUTH_PKCE_USE_ID_TOKEN=${NETBIRD_AUTH_PKCE_USE_ID_TOKEN:-false}
 NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN=${NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN:-false}
-NETBIRD_AUTH_PKCE_LOGIN_FLAG=${NETBIRD_AUTH_PKCE_LOGIN_FLAG:-1}
+NETBIRD_AUTH_PKCE_LOGIN_FLAG=${NETBIRD_AUTH_PKCE_LOGIN_FLAG:-0}
 NETBIRD_AUTH_PKCE_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
 
 # Dashboard
@@ -139,3 +140,4 @@ export NETBIRD_RELAY_PORT
 export NETBIRD_RELAY_ENDPOINT
 export NETBIRD_RELAY_AUTH_SECRET
 export NETBIRD_RELAY_TAG
+export NETBIRD_MGMT_DISABLE_DEFAULT_POLICY
diff --git a/infrastructure_files/docker-compose.yml.tmpl b/infrastructure_files/docker-compose.yml.tmpl
index b529f960..b24e853b 100644
--- a/infrastructure_files/docker-compose.yml.tmpl
+++ b/infrastructure_files/docker-compose.yml.tmpl
@@ -1,8 +1,16 @@
+x-default: &default
+  restart: 'unless-stopped'
+  logging:
+    driver: 'json-file'
+    options:
+      max-size: '500m'
+      max-file: '2'
+
 services:
   # UI dashboard
   dashboard:
+    <<: *default
     image: netbirdio/dashboard:$NETBIRD_DASHBOARD_TAG
-    restart: unless-stopped
     ports:
       - 80:80
       - 443:443
@@ -27,16 +35,11 @@ services:
       - LETSENCRYPT_EMAIL=$NETBIRD_LETSENCRYPT_EMAIL
     volumes:
       - $LETSENCRYPT_VOLUMENAME:/etc/letsencrypt/
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "500m"
-        max-file: "2"
 
   # Signal
   signal:
+    <<: *default
     image: netbirdio/signal:$NETBIRD_SIGNAL_TAG
-    restart: unless-stopped
     volumes:
       - $SIGNAL_VOLUMENAME:/var/lib/netbird
     ports:
@@ -44,16 +47,11 @@ services:
   #      # port and command for Let's Encrypt validation
   #      - 443:443
   #    command: ["--letsencrypt-domain", "$NETBIRD_LETSENCRYPT_DOMAIN", "--log-file", "console"]
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "500m"
-        max-file: "2"
 
   # Relay
   relay:
+    <<: *default
     image: netbirdio/relay:$NETBIRD_RELAY_TAG
-    restart: unless-stopped
     environment:
     - NB_LOG_LEVEL=info
     - NB_LISTEN_ADDRESS=:$NETBIRD_RELAY_PORT
@@ -62,16 +60,11 @@ services:
     - NB_AUTH_SECRET=$NETBIRD_RELAY_AUTH_SECRET
     ports:
       - $NETBIRD_RELAY_PORT:$NETBIRD_RELAY_PORT
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "500m"
-        max-file: "2"
 
   # Management
   management:
+    <<: *default
     image: netbirdio/management:$NETBIRD_MANAGEMENT_TAG
-    restart: unless-stopped
     depends_on:
       - dashboard
     volumes:
@@ -90,19 +83,14 @@ services:
       "--single-account-mode-domain=$NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN",
       "--dns-domain=$NETBIRD_MGMT_DNS_DOMAIN"
       ]
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "500m"
-        max-file: "2"
     environment:
       - NETBIRD_STORE_ENGINE_POSTGRES_DSN=$NETBIRD_STORE_ENGINE_POSTGRES_DSN
       - NETBIRD_STORE_ENGINE_MYSQL_DSN=$NETBIRD_STORE_ENGINE_MYSQL_DSN
       
   # Coturn
   coturn:
+    <<: *default
     image: coturn/coturn:$COTURN_TAG
-    restart: unless-stopped
     #domainname: $TURN_DOMAIN # only needed when TLS is enabled
     volumes:
       - ./turnserver.conf:/etc/turnserver.conf:ro
@@ -111,11 +99,6 @@ services:
     network_mode: host
     command:
       - -c /etc/turnserver.conf
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "500m"
-        max-file: "2"
 
 volumes:
   $MGMT_VOLUMENAME:
diff --git a/infrastructure_files/docker-compose.yml.tmpl.traefik b/infrastructure_files/docker-compose.yml.tmpl.traefik
index 8da3cabb..08749a4f 100644
--- a/infrastructure_files/docker-compose.yml.tmpl.traefik
+++ b/infrastructure_files/docker-compose.yml.tmpl.traefik
@@ -1,8 +1,16 @@
+x-default: &default
+  restart: 'unless-stopped'
+  logging:
+    driver: 'json-file'
+    options:
+      max-size: '500m'
+      max-file: '2'
+
 services:
   # UI dashboard
   dashboard:
+    <<: *default
     image: netbirdio/dashboard:$NETBIRD_DASHBOARD_TAG
-    restart: unless-stopped
     environment:
       # Endpoints
       - NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
@@ -28,16 +36,11 @@ services:
     - traefik.enable=true
     - traefik.http.routers.netbird-dashboard.rule=Host(`$NETBIRD_DOMAIN`)
     - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "500m"
-        max-file: "2"
 
   # Signal
   signal:
+    <<: *default
     image: netbirdio/signal:$NETBIRD_SIGNAL_TAG
-    restart: unless-stopped
     volumes:
       - $SIGNAL_VOLUMENAME:/var/lib/netbird
     labels:
@@ -45,27 +48,17 @@ services:
     - traefik.http.routers.netbird-signal.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/signalexchange.SignalExchange/`)
     - traefik.http.services.netbird-signal.loadbalancer.server.port=10000
     - traefik.http.services.netbird-signal.loadbalancer.server.scheme=h2c
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "500m"
-        max-file: "2"
 
   # Relay
   relay:
+    <<: *default
     image: netbirdio/relay:$NETBIRD_RELAY_TAG
-    restart: unless-stopped
     environment:
     - NB_LOG_LEVEL=info
     - NB_LISTEN_ADDRESS=:33080
     - NB_EXPOSED_ADDRESS=$NETBIRD_RELAY_ENDPOINT
     # todo: change to a secure secret
     - NB_AUTH_SECRET=$NETBIRD_RELAY_AUTH_SECRET
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "500m"
-        max-file: "2"
     labels:
     - traefik.enable=true
     - traefik.http.routers.netbird-relay.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/relay`)
@@ -73,8 +66,8 @@ services:
 
   # Management
   management:
+    <<: *default
     image: netbirdio/management:$NETBIRD_MANAGEMENT_TAG
-    restart: unless-stopped
     depends_on:
       - dashboard
     volumes:
@@ -99,30 +92,20 @@ services:
     - traefik.http.routers.netbird-management.service=netbird-management
     - traefik.http.services.netbird-management.loadbalancer.server.port=33073
     - traefik.http.services.netbird-management.loadbalancer.server.scheme=h2c
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "500m"
-        max-file: "2"
     environment:
       - NETBIRD_STORE_ENGINE_POSTGRES_DSN=$NETBIRD_STORE_ENGINE_POSTGRES_DSN
       - NETBIRD_STORE_ENGINE_MYSQL_DSN=$NETBIRD_STORE_ENGINE_MYSQL_DSN
       
   # Coturn
   coturn:
+    <<: *default
     image: coturn/coturn:$COTURN_TAG
-    restart: unless-stopped
     domainname: $TURN_DOMAIN
     volumes:
       - ./turnserver.conf:/etc/turnserver.conf:ro
     network_mode: host
     command:
       - -c /etc/turnserver.conf
-    logging:
-      driver: "json-file"
-      options:
-        max-size: "500m"
-        max-file: "2"
 
 volumes:
   $MGMT_VOLUMENAME:
diff --git a/infrastructure_files/getting-started-with-zitadel.sh b/infrastructure_files/getting-started-with-zitadel.sh
index 1e67bd17..2d7c65cb 100644
--- a/infrastructure_files/getting-started-with-zitadel.sh
+++ b/infrastructure_files/getting-started-with-zitadel.sh
@@ -780,7 +780,6 @@ EOF
 
 renderDockerCompose() {
   cat <<EOF
-version: "3.4"
 services:
   # Caddy reverse proxy
   caddy:
@@ -791,7 +790,6 @@ services:
       - '443:443'
       - '443:443/udp'
       - '80:80'
-      - '8080:8080'
     volumes:
       - netbird_caddy_data:/data
       - ./Caddyfile:/etc/caddy/Caddyfile
diff --git a/infrastructure_files/management.json.tmpl b/infrastructure_files/management.json.tmpl
index 4d09816e..32f08391 100644
--- a/infrastructure_files/management.json.tmpl
+++ b/infrastructure_files/management.json.tmpl
@@ -38,6 +38,7 @@
              "0.0.0.0/0"
         ]
     },
+    "DisableDefaultPolicy": $NETBIRD_MGMT_DISABLE_DEFAULT_POLICY,
     "Datadir": "",
     "DataStoreEncryptionKey": "$NETBIRD_DATASTORE_ENC_KEY",
     "StoreConfig": {
diff --git a/infrastructure_files/setup.env.example b/infrastructure_files/setup.env.example
index b6a20995..382e1e18 100644
--- a/infrastructure_files/setup.env.example
+++ b/infrastructure_files/setup.env.example
@@ -92,7 +92,8 @@ NETBIRD_LETSENCRYPT_EMAIL=""
 NETBIRD_DISABLE_ANONYMOUS_METRICS=false
 # DNS DOMAIN configures the domain name used for peer resolution. By default it is netbird.selfhosted
 NETBIRD_MGMT_DNS_DOMAIN=netbird.selfhosted
-
+# Disable default all-to-all policy for new accounts
+NETBIRD_MGMT_DISABLE_DEFAULT_POLICY=false
 # -------------------------------------------
 # Relay settings
 # -------------------------------------------
diff --git a/management/client/rest/accounts.go b/management/client/rest/accounts.go
index 2530e4f7..fbe3010e 100644
--- a/management/client/rest/accounts.go
+++ b/management/client/rest/accounts.go
@@ -16,7 +16,7 @@ type AccountsAPI struct {
 // List list all accounts, only returns one account always
 // See more: https://docs.netbird.io/api/resources/accounts#list-all-accounts
 func (a *AccountsAPI) List(ctx context.Context) ([]api.Account, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/accounts", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/accounts", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -34,7 +34,7 @@ func (a *AccountsAPI) Update(ctx context.Context, accountID string, request api.
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/accounts/"+accountID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/accounts/"+accountID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -48,7 +48,7 @@ func (a *AccountsAPI) Update(ctx context.Context, accountID string, request api.
 // Delete delete account
 // See more: https://docs.netbird.io/api/resources/accounts#delete-an-account
 func (a *AccountsAPI) Delete(ctx context.Context, accountID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/accounts/"+accountID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/accounts/"+accountID, nil, nil)
 	if err != nil {
 		return err
 	}
diff --git a/management/client/rest/client.go b/management/client/rest/client.go
index 8bf11caa..b5945985 100644
--- a/management/client/rest/client.go
+++ b/management/client/rest/client.go
@@ -117,7 +117,7 @@ func (c *Client) initialize() {
 }
 
 // NewRequest creates and executes new management API request
-func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) {
+func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader, query map[string]string) (*http.Response, error) {
 	req, err := http.NewRequestWithContext(ctx, method, c.managementURL+path, body)
 	if err != nil {
 		return nil, err
@@ -129,6 +129,14 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re
 		req.Header.Add("Content-Type", "application/json")
 	}
 
+	if len(query) != 0 {
+		q := req.URL.Query()
+		for k, v := range query {
+			q.Add(k, v)
+		}
+		req.URL.RawQuery = q.Encode()
+	}
+
 	resp, err := c.httpClient.Do(req)
 	if err != nil {
 		return nil, err
diff --git a/management/client/rest/dns.go b/management/client/rest/dns.go
index 1e35c022..3fb74d5f 100644
--- a/management/client/rest/dns.go
+++ b/management/client/rest/dns.go
@@ -16,7 +16,7 @@ type DNSAPI struct {
 // ListNameserverGroups list all nameserver groups
 // See more: https://docs.netbird.io/api/resources/dns#list-all-nameserver-groups
 func (a *DNSAPI) ListNameserverGroups(ctx context.Context) ([]api.NameserverGroup, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/dns/nameservers", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/dns/nameservers", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -30,7 +30,7 @@ func (a *DNSAPI) ListNameserverGroups(ctx context.Context) ([]api.NameserverGrou
 // GetNameserverGroup get nameserver group info
 // See more: https://docs.netbird.io/api/resources/dns#retrieve-a-nameserver-group
 func (a *DNSAPI) GetNameserverGroup(ctx context.Context, nameserverGroupID string) (*api.NameserverGroup, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/dns/nameservers/"+nameserverGroupID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/dns/nameservers/"+nameserverGroupID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -48,7 +48,7 @@ func (a *DNSAPI) CreateNameserverGroup(ctx context.Context, request api.PostApiD
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/dns/nameservers", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/dns/nameservers", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +66,7 @@ func (a *DNSAPI) UpdateNameserverGroup(ctx context.Context, nameserverGroupID st
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/dns/nameservers/"+nameserverGroupID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/dns/nameservers/"+nameserverGroupID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -80,7 +80,7 @@ func (a *DNSAPI) UpdateNameserverGroup(ctx context.Context, nameserverGroupID st
 // DeleteNameserverGroup delete nameserver group
 // See more: https://docs.netbird.io/api/resources/dns#delete-a-nameserver-group
 func (a *DNSAPI) DeleteNameserverGroup(ctx context.Context, nameserverGroupID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/dns/nameservers/"+nameserverGroupID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/dns/nameservers/"+nameserverGroupID, nil, nil)
 	if err != nil {
 		return err
 	}
@@ -94,7 +94,7 @@ func (a *DNSAPI) DeleteNameserverGroup(ctx context.Context, nameserverGroupID st
 // GetSettings get DNS settings
 // See more: https://docs.netbird.io/api/resources/dns#retrieve-dns-settings
 func (a *DNSAPI) GetSettings(ctx context.Context) (*api.DNSSettings, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/dns/settings", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/dns/settings", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -112,7 +112,7 @@ func (a *DNSAPI) UpdateSettings(ctx context.Context, request api.PutApiDnsSettin
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/dns/settings", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/dns/settings", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/management/client/rest/events.go b/management/client/rest/events.go
index cae813e8..775d3ba2 100644
--- a/management/client/rest/events.go
+++ b/management/client/rest/events.go
@@ -14,7 +14,7 @@ type EventsAPI struct {
 // List list all events
 // See more: https://docs.netbird.io/api/resources/events#list-all-events
 func (a *EventsAPI) List(ctx context.Context) ([]api.Event, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/events", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/events", nil, nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/management/client/rest/geo.go b/management/client/rest/geo.go
index d06d65d8..dfecee09 100644
--- a/management/client/rest/geo.go
+++ b/management/client/rest/geo.go
@@ -14,7 +14,7 @@ type GeoLocationAPI struct {
 // ListCountries list all country codes
 // See more: https://docs.netbird.io/api/resources/geo-locations#list-all-country-codes
 func (a *GeoLocationAPI) ListCountries(ctx context.Context) ([]api.Country, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/locations/countries", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/locations/countries", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -28,7 +28,7 @@ func (a *GeoLocationAPI) ListCountries(ctx context.Context) ([]api.Country, erro
 // ListCountryCities Get a list of all English city names for a given country code
 // See more: https://docs.netbird.io/api/resources/geo-locations#list-all-city-names-by-country
 func (a *GeoLocationAPI) ListCountryCities(ctx context.Context, countryCode string) ([]api.City, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/locations/countries/"+countryCode+"/cities", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/locations/countries/"+countryCode+"/cities", nil, nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/management/client/rest/groups.go b/management/client/rest/groups.go
index 7612b718..7d4bac62 100644
--- a/management/client/rest/groups.go
+++ b/management/client/rest/groups.go
@@ -16,7 +16,7 @@ type GroupsAPI struct {
 // List list all groups
 // See more: https://docs.netbird.io/api/resources/groups#list-all-groups
 func (a *GroupsAPI) List(ctx context.Context) ([]api.Group, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/groups", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/groups", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -30,7 +30,7 @@ func (a *GroupsAPI) List(ctx context.Context) ([]api.Group, error) {
 // Get get group info
 // See more: https://docs.netbird.io/api/resources/groups#retrieve-a-group
 func (a *GroupsAPI) Get(ctx context.Context, groupID string) (*api.Group, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/groups/"+groupID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/groups/"+groupID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -48,7 +48,7 @@ func (a *GroupsAPI) Create(ctx context.Context, request api.PostApiGroupsJSONReq
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/groups", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/groups", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +66,7 @@ func (a *GroupsAPI) Update(ctx context.Context, groupID string, request api.PutA
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/groups/"+groupID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/groups/"+groupID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -80,7 +80,7 @@ func (a *GroupsAPI) Update(ctx context.Context, groupID string, request api.PutA
 // Delete delete group
 // See more: https://docs.netbird.io/api/resources/groups#delete-a-group
 func (a *GroupsAPI) Delete(ctx context.Context, groupID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/groups/"+groupID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/groups/"+groupID, nil, nil)
 	if err != nil {
 		return err
 	}
diff --git a/management/client/rest/networks.go b/management/client/rest/networks.go
index b744e3fe..9441780f 100644
--- a/management/client/rest/networks.go
+++ b/management/client/rest/networks.go
@@ -16,7 +16,7 @@ type NetworksAPI struct {
 // List list all networks
 // See more: https://docs.netbird.io/api/resources/networks#list-all-networks
 func (a *NetworksAPI) List(ctx context.Context) ([]api.Network, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -30,7 +30,7 @@ func (a *NetworksAPI) List(ctx context.Context) ([]api.Network, error) {
 // Get get network info
 // See more: https://docs.netbird.io/api/resources/networks#retrieve-a-network
 func (a *NetworksAPI) Get(ctx context.Context, networkID string) (*api.Network, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks/"+networkID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks/"+networkID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -48,7 +48,7 @@ func (a *NetworksAPI) Create(ctx context.Context, request api.PostApiNetworksJSO
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/networks", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/networks", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +66,7 @@ func (a *NetworksAPI) Update(ctx context.Context, networkID string, request api.
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/networks/"+networkID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/networks/"+networkID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -80,7 +80,7 @@ func (a *NetworksAPI) Update(ctx context.Context, networkID string, request api.
 // Delete delete network
 // See more: https://docs.netbird.io/api/resources/networks#delete-a-network
 func (a *NetworksAPI) Delete(ctx context.Context, networkID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/networks/"+networkID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/networks/"+networkID, nil, nil)
 	if err != nil {
 		return err
 	}
@@ -108,7 +108,7 @@ func (a *NetworksAPI) Resources(networkID string) *NetworkResourcesAPI {
 // List list all resources in networks
 // See more: https://docs.netbird.io/api/resources/networks#list-all-network-resources
 func (a *NetworkResourcesAPI) List(ctx context.Context) ([]api.NetworkResource, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks/"+a.networkID+"/resources", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks/"+a.networkID+"/resources", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -122,7 +122,7 @@ func (a *NetworkResourcesAPI) List(ctx context.Context) ([]api.NetworkResource,
 // Get get network resource info
 // See more: https://docs.netbird.io/api/resources/networks#retrieve-a-network-resource
 func (a *NetworkResourcesAPI) Get(ctx context.Context, networkResourceID string) (*api.NetworkResource, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks/"+a.networkID+"/resources/"+networkResourceID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks/"+a.networkID+"/resources/"+networkResourceID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -140,7 +140,7 @@ func (a *NetworkResourcesAPI) Create(ctx context.Context, request api.PostApiNet
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/networks/"+a.networkID+"/resources", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/networks/"+a.networkID+"/resources", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -158,7 +158,7 @@ func (a *NetworkResourcesAPI) Update(ctx context.Context, networkResourceID stri
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/networks/"+a.networkID+"/resources/"+networkResourceID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/networks/"+a.networkID+"/resources/"+networkResourceID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -172,7 +172,7 @@ func (a *NetworkResourcesAPI) Update(ctx context.Context, networkResourceID stri
 // Delete delete network resource
 // See more: https://docs.netbird.io/api/resources/networks#delete-a-network-resource
 func (a *NetworkResourcesAPI) Delete(ctx context.Context, networkResourceID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/networks/"+a.networkID+"/resources/"+networkResourceID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/networks/"+a.networkID+"/resources/"+networkResourceID, nil, nil)
 	if err != nil {
 		return err
 	}
@@ -200,7 +200,7 @@ func (a *NetworksAPI) Routers(networkID string) *NetworkRoutersAPI {
 // List list all routers in networks
 // See more: https://docs.netbird.io/api/routers/networks#list-all-network-routers
 func (a *NetworkRoutersAPI) List(ctx context.Context) ([]api.NetworkRouter, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks/"+a.networkID+"/routers", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks/"+a.networkID+"/routers", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -214,7 +214,7 @@ func (a *NetworkRoutersAPI) List(ctx context.Context) ([]api.NetworkRouter, erro
 // Get get network router info
 // See more: https://docs.netbird.io/api/routers/networks#retrieve-a-network-router
 func (a *NetworkRoutersAPI) Get(ctx context.Context, networkRouterID string) (*api.NetworkRouter, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks/"+a.networkID+"/routers/"+networkRouterID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/networks/"+a.networkID+"/routers/"+networkRouterID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -232,7 +232,7 @@ func (a *NetworkRoutersAPI) Create(ctx context.Context, request api.PostApiNetwo
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/networks/"+a.networkID+"/routers", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/networks/"+a.networkID+"/routers", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -250,7 +250,7 @@ func (a *NetworkRoutersAPI) Update(ctx context.Context, networkRouterID string,
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/networks/"+a.networkID+"/routers/"+networkRouterID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/networks/"+a.networkID+"/routers/"+networkRouterID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -264,7 +264,7 @@ func (a *NetworkRoutersAPI) Update(ctx context.Context, networkRouterID string,
 // Delete delete network router
 // See more: https://docs.netbird.io/api/routers/networks#delete-a-network-router
 func (a *NetworkRoutersAPI) Delete(ctx context.Context, networkRouterID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/networks/"+a.networkID+"/routers/"+networkRouterID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/networks/"+a.networkID+"/routers/"+networkRouterID, nil, nil)
 	if err != nil {
 		return err
 	}
diff --git a/management/client/rest/peers.go b/management/client/rest/peers.go
index 37679fdb..f4364bb6 100644
--- a/management/client/rest/peers.go
+++ b/management/client/rest/peers.go
@@ -13,10 +13,30 @@ type PeersAPI struct {
 	c *Client
 }
 
+// PeersListOption options for Peers List API
+type PeersListOption func() (string, string)
+
+func PeerNameFilter(name string) PeersListOption {
+	return func() (string, string) {
+		return "name", name
+	}
+}
+
+func PeerIPFilter(ip string) PeersListOption {
+	return func() (string, string) {
+		return "ip", ip
+	}
+}
+
 // List list all peers
 // See more: https://docs.netbird.io/api/resources/peers#list-all-peers
-func (a *PeersAPI) List(ctx context.Context) ([]api.Peer, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/peers", nil)
+func (a *PeersAPI) List(ctx context.Context, opts ...PeersListOption) ([]api.Peer, error) {
+	query := make(map[string]string)
+	for _, o := range opts {
+		k, v := o()
+		query[k] = v
+	}
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/peers", nil, query)
 	if err != nil {
 		return nil, err
 	}
@@ -30,7 +50,7 @@ func (a *PeersAPI) List(ctx context.Context) ([]api.Peer, error) {
 // Get retrieve a peer
 // See more: https://docs.netbird.io/api/resources/peers#retrieve-a-peer
 func (a *PeersAPI) Get(ctx context.Context, peerID string) (*api.Peer, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/peers/"+peerID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/peers/"+peerID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -48,7 +68,7 @@ func (a *PeersAPI) Update(ctx context.Context, peerID string, request api.PutApi
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/peers/"+peerID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/peers/"+peerID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -62,7 +82,7 @@ func (a *PeersAPI) Update(ctx context.Context, peerID string, request api.PutApi
 // Delete delete a peer
 // See more: https://docs.netbird.io/api/resources/peers#delete-a-peer
 func (a *PeersAPI) Delete(ctx context.Context, peerID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/peers/"+peerID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/peers/"+peerID, nil, nil)
 	if err != nil {
 		return err
 	}
@@ -76,7 +96,7 @@ func (a *PeersAPI) Delete(ctx context.Context, peerID string) error {
 // ListAccessiblePeers list all peers that the specified peer can connect to within the network
 // See more: https://docs.netbird.io/api/resources/peers#list-accessible-peers
 func (a *PeersAPI) ListAccessiblePeers(ctx context.Context, peerID string) ([]api.Peer, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/peers/"+peerID+"/accessible-peers", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/peers/"+peerID+"/accessible-peers", nil, nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/management/client/rest/policies.go b/management/client/rest/policies.go
index 2f2df4a7..a6e0e38d 100644
--- a/management/client/rest/policies.go
+++ b/management/client/rest/policies.go
@@ -18,7 +18,7 @@ type PoliciesAPI struct {
 func (a *PoliciesAPI) List(ctx context.Context) ([]api.Policy, error) {
 	path := "/api/policies"
 
-	resp, err := a.c.NewRequest(ctx, "GET", path, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", path, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -32,7 +32,7 @@ func (a *PoliciesAPI) List(ctx context.Context) ([]api.Policy, error) {
 // Get get policy info
 // See more: https://docs.netbird.io/api/resources/policies#retrieve-a-policy
 func (a *PoliciesAPI) Get(ctx context.Context, policyID string) (*api.Policy, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/policies/"+policyID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/policies/"+policyID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -50,7 +50,7 @@ func (a *PoliciesAPI) Create(ctx context.Context, request api.PostApiPoliciesJSO
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/policies", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/policies", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -70,7 +70,7 @@ func (a *PoliciesAPI) Update(ctx context.Context, policyID string, request api.P
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", path, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", path, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -84,7 +84,7 @@ func (a *PoliciesAPI) Update(ctx context.Context, policyID string, request api.P
 // Delete delete policy
 // See more: https://docs.netbird.io/api/resources/policies#delete-a-policy
 func (a *PoliciesAPI) Delete(ctx context.Context, policyID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/policies/"+policyID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/policies/"+policyID, nil, nil)
 	if err != nil {
 		return err
 	}
diff --git a/management/client/rest/posturechecks.go b/management/client/rest/posturechecks.go
index 622eeeb6..2ab8f454 100644
--- a/management/client/rest/posturechecks.go
+++ b/management/client/rest/posturechecks.go
@@ -16,7 +16,7 @@ type PostureChecksAPI struct {
 // List list all posture checks
 // See more: https://docs.netbird.io/api/resources/posture-checks#list-all-posture-checks
 func (a *PostureChecksAPI) List(ctx context.Context) ([]api.PostureCheck, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/posture-checks", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/posture-checks", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -30,7 +30,7 @@ func (a *PostureChecksAPI) List(ctx context.Context) ([]api.PostureCheck, error)
 // Get get posture check info
 // See more: https://docs.netbird.io/api/resources/posture-checks#retrieve-a-posture-check
 func (a *PostureChecksAPI) Get(ctx context.Context, postureCheckID string) (*api.PostureCheck, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/posture-checks/"+postureCheckID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/posture-checks/"+postureCheckID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -48,7 +48,7 @@ func (a *PostureChecksAPI) Create(ctx context.Context, request api.PostApiPostur
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/posture-checks", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/posture-checks", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +66,7 @@ func (a *PostureChecksAPI) Update(ctx context.Context, postureCheckID string, re
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/posture-checks/"+postureCheckID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/posture-checks/"+postureCheckID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -80,7 +80,7 @@ func (a *PostureChecksAPI) Update(ctx context.Context, postureCheckID string, re
 // Delete delete posture check
 // See more: https://docs.netbird.io/api/resources/posture-checks#delete-a-posture-check
 func (a *PostureChecksAPI) Delete(ctx context.Context, postureCheckID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/posture-checks/"+postureCheckID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/posture-checks/"+postureCheckID, nil, nil)
 	if err != nil {
 		return err
 	}
diff --git a/management/client/rest/routes.go b/management/client/rest/routes.go
index 671c3bfc..183c363c 100644
--- a/management/client/rest/routes.go
+++ b/management/client/rest/routes.go
@@ -16,7 +16,7 @@ type RoutesAPI struct {
 // List list all routes
 // See more: https://docs.netbird.io/api/resources/routes#list-all-routes
 func (a *RoutesAPI) List(ctx context.Context) ([]api.Route, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/routes", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/routes", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -30,7 +30,7 @@ func (a *RoutesAPI) List(ctx context.Context) ([]api.Route, error) {
 // Get get route info
 // See more: https://docs.netbird.io/api/resources/routes#retrieve-a-route
 func (a *RoutesAPI) Get(ctx context.Context, routeID string) (*api.Route, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/routes/"+routeID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/routes/"+routeID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -48,7 +48,7 @@ func (a *RoutesAPI) Create(ctx context.Context, request api.PostApiRoutesJSONReq
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/routes", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/routes", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +66,7 @@ func (a *RoutesAPI) Update(ctx context.Context, routeID string, request api.PutA
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/routes/"+routeID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/routes/"+routeID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -80,7 +80,7 @@ func (a *RoutesAPI) Update(ctx context.Context, routeID string, request api.PutA
 // Delete delete route
 // See more: https://docs.netbird.io/api/resources/routes#delete-a-route
 func (a *RoutesAPI) Delete(ctx context.Context, routeID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/routes/"+routeID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/routes/"+routeID, nil, nil)
 	if err != nil {
 		return err
 	}
diff --git a/management/client/rest/setupkeys.go b/management/client/rest/setupkeys.go
index 5625b6ac..6f26cd0b 100644
--- a/management/client/rest/setupkeys.go
+++ b/management/client/rest/setupkeys.go
@@ -16,7 +16,7 @@ type SetupKeysAPI struct {
 // List list all setup keys
 // See more: https://docs.netbird.io/api/resources/setup-keys#list-all-setup-keys
 func (a *SetupKeysAPI) List(ctx context.Context) ([]api.SetupKey, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/setup-keys", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/setup-keys", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -30,7 +30,7 @@ func (a *SetupKeysAPI) List(ctx context.Context) ([]api.SetupKey, error) {
 // Get get setup key info
 // See more: https://docs.netbird.io/api/resources/setup-keys#retrieve-a-setup-key
 func (a *SetupKeysAPI) Get(ctx context.Context, setupKeyID string) (*api.SetupKey, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/setup-keys/"+setupKeyID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/setup-keys/"+setupKeyID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -50,7 +50,7 @@ func (a *SetupKeysAPI) Create(ctx context.Context, request api.PostApiSetupKeysJ
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", path, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", path, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -68,7 +68,7 @@ func (a *SetupKeysAPI) Update(ctx context.Context, setupKeyID string, request ap
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/setup-keys/"+setupKeyID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/setup-keys/"+setupKeyID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -82,7 +82,7 @@ func (a *SetupKeysAPI) Update(ctx context.Context, setupKeyID string, request ap
 // Delete delete setup key
 // See more: https://docs.netbird.io/api/resources/setup-keys#delete-a-setup-key
 func (a *SetupKeysAPI) Delete(ctx context.Context, setupKeyID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/setup-keys/"+setupKeyID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/setup-keys/"+setupKeyID, nil, nil)
 	if err != nil {
 		return err
 	}
diff --git a/management/client/rest/tokens.go b/management/client/rest/tokens.go
index 278a0d15..7a63d0c9 100644
--- a/management/client/rest/tokens.go
+++ b/management/client/rest/tokens.go
@@ -16,7 +16,7 @@ type TokensAPI struct {
 // List list user tokens
 // See more: https://docs.netbird.io/api/resources/tokens#list-all-tokens
 func (a *TokensAPI) List(ctx context.Context, userID string) ([]api.PersonalAccessToken, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/users/"+userID+"/tokens", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/users/"+userID+"/tokens", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -30,7 +30,7 @@ func (a *TokensAPI) List(ctx context.Context, userID string) ([]api.PersonalAcce
 // Get get user token info
 // See more: https://docs.netbird.io/api/resources/tokens#retrieve-a-token
 func (a *TokensAPI) Get(ctx context.Context, userID, tokenID string) (*api.PersonalAccessToken, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/users/"+userID+"/tokens/"+tokenID, nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/users/"+userID+"/tokens/"+tokenID, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -48,7 +48,7 @@ func (a *TokensAPI) Create(ctx context.Context, userID string, request api.PostA
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/users/"+userID+"/tokens", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/users/"+userID+"/tokens", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -62,7 +62,7 @@ func (a *TokensAPI) Create(ctx context.Context, userID string, request api.PostA
 // Delete delete user token
 // See more: https://docs.netbird.io/api/resources/tokens#delete-a-token
 func (a *TokensAPI) Delete(ctx context.Context, userID, tokenID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/users/"+userID+"/tokens/"+tokenID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/users/"+userID+"/tokens/"+tokenID, nil, nil)
 	if err != nil {
 		return err
 	}
diff --git a/management/client/rest/users.go b/management/client/rest/users.go
index 107b0581..f0ef54be 100644
--- a/management/client/rest/users.go
+++ b/management/client/rest/users.go
@@ -16,7 +16,7 @@ type UsersAPI struct {
 // List list all users, only returns one user always
 // See more: https://docs.netbird.io/api/resources/users#list-all-users
 func (a *UsersAPI) List(ctx context.Context) ([]api.User, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/users", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/users", nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -34,7 +34,7 @@ func (a *UsersAPI) Create(ctx context.Context, request api.PostApiUsersJSONReque
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/users", bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/users", bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -52,7 +52,7 @@ func (a *UsersAPI) Update(ctx context.Context, userID string, request api.PutApi
 	if err != nil {
 		return nil, err
 	}
-	resp, err := a.c.NewRequest(ctx, "PUT", "/api/users/"+userID, bytes.NewReader(requestBytes))
+	resp, err := a.c.NewRequest(ctx, "PUT", "/api/users/"+userID, bytes.NewReader(requestBytes), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +66,7 @@ func (a *UsersAPI) Update(ctx context.Context, userID string, request api.PutApi
 // Delete delete user
 // See more: https://docs.netbird.io/api/resources/users#delete-a-user
 func (a *UsersAPI) Delete(ctx context.Context, userID string) error {
-	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/users/"+userID, nil)
+	resp, err := a.c.NewRequest(ctx, "DELETE", "/api/users/"+userID, nil, nil)
 	if err != nil {
 		return err
 	}
@@ -80,7 +80,7 @@ func (a *UsersAPI) Delete(ctx context.Context, userID string) error {
 // ResendInvitation resend user invitation
 // See more: https://docs.netbird.io/api/resources/users#resend-user-invitation
 func (a *UsersAPI) ResendInvitation(ctx context.Context, userID string) error {
-	resp, err := a.c.NewRequest(ctx, "POST", "/api/users/"+userID+"/invite", nil)
+	resp, err := a.c.NewRequest(ctx, "POST", "/api/users/"+userID+"/invite", nil, nil)
 	if err != nil {
 		return err
 	}
@@ -94,7 +94,7 @@ func (a *UsersAPI) ResendInvitation(ctx context.Context, userID string) error {
 // Current gets the current user info
 // See more: https://docs.netbird.io/api/resources/users#retrieve-current-user
 func (a *UsersAPI) Current(ctx context.Context) (*api.User, error) {
-	resp, err := a.c.NewRequest(ctx, "GET", "/api/users/current", nil)
+	resp, err := a.c.NewRequest(ctx, "GET", "/api/users/current", nil, nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/management/cmd/management.go b/management/cmd/management.go
index 27376e06..24c260e9 100644
--- a/management/cmd/management.go
+++ b/management/cmd/management.go
@@ -215,7 +215,7 @@ var (
 			peersManager := peers.NewManager(store, permissionsManager)
 			proxyController := integrations.NewController(store)
 			accountManager, err := server.BuildManager(ctx, store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
-				dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerValidator, appMetrics, proxyController, settingsManager, permissionsManager)
+				dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerValidator, appMetrics, proxyController, settingsManager, permissionsManager, config.DisableDefaultPolicy)
 			if err != nil {
 				return fmt.Errorf("failed to build default manager: %v", err)
 			}
@@ -292,7 +292,7 @@ var (
 			ephemeralManager.LoadInitialPeers(ctx)
 
 			gRPCAPIHandler := grpc.NewServer(gRPCOpts...)
-			srv, err := server.NewServer(ctx, config, accountManager, settingsManager, peersUpdateManager, secretsManager, appMetrics, ephemeralManager, authManager)
+			srv, err := server.NewServer(ctx, config, accountManager, settingsManager, peersUpdateManager, secretsManager, appMetrics, ephemeralManager, authManager, integratedPeerValidator)
 			if err != nil {
 				return fmt.Errorf("failed creating gRPC API handler: %v", err)
 			}
diff --git a/management/server/account.go b/management/server/account.go
index b376f6f5..cd0c933f 100644
--- a/management/server/account.go
+++ b/management/server/account.go
@@ -102,6 +102,20 @@ type DefaultAccountManager struct {
 
 	accountUpdateLocks               sync.Map
 	updateAccountPeersBufferInterval atomic.Int64
+
+	disableDefaultPolicy bool
+}
+
+func isUniqueConstraintError(err error) bool {
+	switch {
+	case strings.Contains(err.Error(), "(SQLSTATE 23505)"),
+		strings.Contains(err.Error(), "Error 1062 (23000)"),
+		strings.Contains(err.Error(), "UNIQUE constraint failed"):
+		return true
+
+	default:
+		return false
+	}
 }
 
 // getJWTGroupsChanges calculates the changes needed to sync a user's JWT groups.
@@ -170,6 +184,7 @@ func BuildManager(
 	proxyController port_forwarding.Controller,
 	settingsManager settings.Manager,
 	permissionsManager permissions.Manager,
+	disableDefaultPolicy bool,
 ) (*DefaultAccountManager, error) {
 	start := time.Now()
 	defer func() {
@@ -195,6 +210,7 @@ func BuildManager(
 		proxyController:          proxyController,
 		settingsManager:          settingsManager,
 		permissionsManager:       permissionsManager,
+		disableDefaultPolicy:     disableDefaultPolicy,
 	}
 
 	am.startWarmup(ctx)
@@ -543,7 +559,7 @@ func (am *DefaultAccountManager) newAccount(ctx context.Context, userID, domain
 			log.WithContext(ctx).Warnf("an account with ID already exists, retrying...")
 			continue
 		case statusErr.Type() == status.NotFound:
-			newAccount := newAccountWithId(ctx, accountId, userID, domain)
+			newAccount := newAccountWithId(ctx, accountId, userID, domain, am.disableDefaultPolicy)
 			am.StoreEvent(ctx, userID, newAccount.Id, accountId, activity.AccountCreated, nil)
 			return newAccount, nil
 		default:
@@ -1188,6 +1204,71 @@ func (am *DefaultAccountManager) GetAccountMeta(ctx context.Context, accountID s
 	return am.Store.GetAccountMeta(ctx, store.LockingStrengthShare, accountID)
 }
 
+// GetAccountOnboarding retrieves the onboarding information for a specific account.
+func (am *DefaultAccountManager) GetAccountOnboarding(ctx context.Context, accountID string, userID string) (*types.AccountOnboarding, error) {
+	allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Accounts, operations.Read)
+	if err != nil {
+		return nil, status.NewPermissionValidationError(err)
+	}
+
+	if !allowed {
+		return nil, status.NewPermissionDeniedError()
+	}
+
+	onboarding, err := am.Store.GetAccountOnboarding(ctx, accountID)
+	if err != nil && err.Error() != status.NewAccountOnboardingNotFoundError(accountID).Error() {
+		log.Errorf("failed to get account onboarding for accountssssssss %s: %v", accountID, err)
+		return nil, err
+	}
+
+	if onboarding == nil {
+		onboarding = &types.AccountOnboarding{
+			AccountID: accountID,
+		}
+	}
+
+	return onboarding, nil
+}
+
+func (am *DefaultAccountManager) UpdateAccountOnboarding(ctx context.Context, accountID, userID string, newOnboarding *types.AccountOnboarding) (*types.AccountOnboarding, error) {
+	allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Update)
+	if err != nil {
+		return nil, fmt.Errorf("failed to validate user permissions: %w", err)
+	}
+
+	if !allowed {
+		return nil, status.NewPermissionDeniedError()
+	}
+
+	oldOnboarding, err := am.Store.GetAccountOnboarding(ctx, accountID)
+	if err != nil && err.Error() != status.NewAccountOnboardingNotFoundError(accountID).Error() {
+		return nil, fmt.Errorf("failed to get account onboarding: %w", err)
+	}
+
+	if oldOnboarding == nil {
+		oldOnboarding = &types.AccountOnboarding{
+			AccountID: accountID,
+		}
+	}
+
+	if newOnboarding == nil {
+		return oldOnboarding, nil
+	}
+
+	if oldOnboarding.IsEqual(*newOnboarding) {
+		log.WithContext(ctx).Debugf("no changes in onboarding for account %s", accountID)
+		return oldOnboarding, nil
+	}
+
+	newOnboarding.AccountID = accountID
+	err = am.Store.SaveAccountOnboarding(ctx, newOnboarding)
+	if err != nil {
+		return nil, fmt.Errorf("failed to update account onboarding: %w", err)
+	}
+
+	return newOnboarding, nil
+}
+
 func (am *DefaultAccountManager) GetAccountIDFromUserAuth(ctx context.Context, userAuth nbcontext.UserAuth) (string, string, error) {
 	if userAuth.UserId == "" {
 		return "", "", errors.New(emptyUserID)
@@ -1657,25 +1738,6 @@ func (am *DefaultAccountManager) handleUserPeer(ctx context.Context, transaction
 	return false, nil
 }
 
-func (am *DefaultAccountManager) getFreeDNSLabel(ctx context.Context, s store.Store, accountID string, peerHostName string) (string, error) {
-	existingLabels, err := s.GetPeerLabelsInAccount(ctx, store.LockingStrengthShare, accountID)
-	if err != nil {
-		return "", fmt.Errorf("failed to get peer dns labels: %w", err)
-	}
-
-	labelMap := ConvertSliceToMap(existingLabels)
-	newLabel, err := types.GetPeerHostLabel(peerHostName, labelMap)
-	if err != nil {
-		return "", fmt.Errorf("failed to get new host label: %w", err)
-	}
-
-	if newLabel == "" {
-		return "", fmt.Errorf("failed to get new host label: %w", err)
-	}
-
-	return newLabel, nil
-}
-
 func (am *DefaultAccountManager) GetAccountSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error) {
 	allowed, err := am.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Settings, operations.Read)
 	if err != nil {
@@ -1688,7 +1750,7 @@ func (am *DefaultAccountManager) GetAccountSettings(ctx context.Context, account
 }
 
 // newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id
-func newAccountWithId(ctx context.Context, accountID, userID, domain string) *types.Account {
+func newAccountWithId(ctx context.Context, accountID, userID, domain string, disableDefaultPolicy bool) *types.Account {
 	log.WithContext(ctx).Debugf("creating new account")
 
 	network := types.NewNetwork()
@@ -1729,9 +1791,13 @@ func newAccountWithId(ctx context.Context, accountID, userID, domain string) *ty
 			PeerInactivityExpiration:        types.DefaultPeerInactivityExpiration,
 			RoutingPeerDNSResolutionEnabled: true,
 		},
+		Onboarding: types.AccountOnboarding{
+			OnboardingFlowPending: true,
+			SignupFormPending:     true,
+		},
 	}
 
-	if err := acc.AddAllGroup(); err != nil {
+	if err := acc.AddAllGroup(disableDefaultPolicy); err != nil {
 		log.WithContext(ctx).Errorf("error adding all group to account %s: %v", acc.Id, err)
 	}
 	return acc
@@ -1833,7 +1899,7 @@ func (am *DefaultAccountManager) GetOrCreateAccountByPrivateDomain(ctx context.C
 			},
 		}
 
-		if err := newAccount.AddAllGroup(); err != nil {
+		if err := newAccount.AddAllGroup(am.disableDefaultPolicy); err != nil {
 			return nil, false, status.Errorf(status.Internal, "failed to add all group to new account by private domain")
 		}
 
diff --git a/management/server/account/manager.go b/management/server/account/manager.go
index de5031c0..f8aa2756 100644
--- a/management/server/account/manager.go
+++ b/management/server/account/manager.go
@@ -39,6 +39,7 @@ type Manager interface {
 	GetSetupKey(ctx context.Context, accountID, userID, keyID string) (*types.SetupKey, error)
 	GetAccountByID(ctx context.Context, accountID string, userID string) (*types.Account, error)
 	GetAccountMeta(ctx context.Context, accountID string, userID string) (*types.AccountMeta, error)
+	GetAccountOnboarding(ctx context.Context, accountID string, userID string) (*types.AccountOnboarding, error)
 	AccountExists(ctx context.Context, accountID string) (bool, error)
 	GetAccountIDByUserID(ctx context.Context, userID, domain string) (string, error)
 	GetAccountIDFromUserAuth(ctx context.Context, userAuth nbcontext.UserAuth) (string, string, error)
@@ -89,6 +90,7 @@ type Manager interface {
 	SaveDNSSettings(ctx context.Context, accountID string, userID string, dnsSettingsToSave *types.DNSSettings) error
 	GetPeer(ctx context.Context, accountID, peerID, userID string) (*nbpeer.Peer, error)
 	UpdateAccountSettings(ctx context.Context, accountID, userID string, newSettings *types.Settings) (*types.Settings, error)
+	UpdateAccountOnboarding(ctx context.Context, accountID, userID string, newOnboarding *types.AccountOnboarding) (*types.AccountOnboarding, error)
 	LoginPeer(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)                // used by peer gRPC API
 	SyncPeer(ctx context.Context, sync types.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) // used by peer gRPC API
 	GetAllConnectedPeers() (map[string]struct{}, error)
@@ -110,6 +112,7 @@ type Manager interface {
 	GetAccountSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error)
 	DeleteSetupKey(ctx context.Context, accountID, userID, keyID string) error
 	UpdateAccountPeers(ctx context.Context, accountID string)
+	BufferUpdateAccountPeers(ctx context.Context, accountID string)
 	BuildUserInfosForAccount(ctx context.Context, accountID, initiatorUserID string, accountUsers []*types.User) (map[string]*types.UserInfo, error)
 	SyncUserJWTGroups(ctx context.Context, userAuth nbcontext.UserAuth) error
 	GetStore() store.Store
diff --git a/management/server/ephemeral.go b/management/server/ephemeral.go
index 3cb9b753..9f4348eb 100644
--- a/management/server/ephemeral.go
+++ b/management/server/ephemeral.go
@@ -15,6 +15,8 @@ import (
 
 const (
 	ephemeralLifeTime = 10 * time.Minute
+	// cleanupWindow is the time window to wait after nearest peer deadline to start the cleanup procedure.
+	cleanupWindow = 1 * time.Minute
 )
 
 var (
@@ -41,6 +43,9 @@ type EphemeralManager struct {
 	tailPeer  *ephemeralPeer
 	peersLock sync.Mutex
 	timer     *time.Timer
+
+	lifeTime      time.Duration
+	cleanupWindow time.Duration
 }
 
 // NewEphemeralManager instantiate new EphemeralManager
@@ -48,6 +53,9 @@ func NewEphemeralManager(store store.Store, accountManager nbAccount.Manager) *E
 	return &EphemeralManager{
 		store:          store,
 		accountManager: accountManager,
+
+		lifeTime:      ephemeralLifeTime,
+		cleanupWindow: cleanupWindow,
 	}
 }
 
@@ -60,7 +68,7 @@ func (e *EphemeralManager) LoadInitialPeers(ctx context.Context) {
 
 	e.loadEphemeralPeers(ctx)
 	if e.headPeer != nil {
-		e.timer = time.AfterFunc(ephemeralLifeTime, func() {
+		e.timer = time.AfterFunc(e.lifeTime, func() {
 			e.cleanup(ctx)
 		})
 	}
@@ -113,9 +121,13 @@ func (e *EphemeralManager) OnPeerDisconnected(ctx context.Context, peer *nbpeer.
 		return
 	}
 
-	e.addPeer(peer.AccountID, peer.ID, newDeadLine())
+	e.addPeer(peer.AccountID, peer.ID, e.newDeadLine())
 	if e.timer == nil {
-		e.timer = time.AfterFunc(e.headPeer.deadline.Sub(timeNow()), func() {
+		delay := e.headPeer.deadline.Sub(timeNow()) + e.cleanupWindow
+		if delay < 0 {
+			delay = 0
+		}
+		e.timer = time.AfterFunc(delay, func() {
 			e.cleanup(ctx)
 		})
 	}
@@ -128,7 +140,7 @@ func (e *EphemeralManager) loadEphemeralPeers(ctx context.Context) {
 		return
 	}
 
-	t := newDeadLine()
+	t := e.newDeadLine()
 	for _, p := range peers {
 		e.addPeer(p.AccountID, p.ID, t)
 	}
@@ -155,7 +167,11 @@ func (e *EphemeralManager) cleanup(ctx context.Context) {
 	}
 
 	if e.headPeer != nil {
-		e.timer = time.AfterFunc(e.headPeer.deadline.Sub(timeNow()), func() {
+		delay := e.headPeer.deadline.Sub(timeNow()) + e.cleanupWindow
+		if delay < 0 {
+			delay = 0
+		}
+		e.timer = time.AfterFunc(delay, func() {
 			e.cleanup(ctx)
 		})
 	} else {
@@ -164,13 +180,20 @@ func (e *EphemeralManager) cleanup(ctx context.Context) {
 
 	e.peersLock.Unlock()
 
+	bufferAccountCall := make(map[string]struct{})
+
 	for id, p := range deletePeers {
 		log.WithContext(ctx).Debugf("delete ephemeral peer: %s", id)
 		err := e.accountManager.DeletePeer(ctx, p.accountID, id, activity.SystemInitiator)
 		if err != nil {
 			log.WithContext(ctx).Errorf("failed to delete ephemeral peer: %s", err)
+		} else {
+			bufferAccountCall[p.accountID] = struct{}{}
 		}
 	}
+	for accountID := range bufferAccountCall {
+		e.accountManager.BufferUpdateAccountPeers(ctx, accountID)
+	}
 }
 
 func (e *EphemeralManager) addPeer(accountID string, peerID string, deadline time.Time) {
@@ -223,6 +246,6 @@ func (e *EphemeralManager) isPeerOnList(id string) bool {
 	return false
 }
 
-func newDeadLine() time.Time {
-	return timeNow().Add(ephemeralLifeTime)
+func (e *EphemeralManager) newDeadLine() time.Time {
+	return timeNow().Add(e.lifeTime)
 }
diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go
index 2b27f9e0..2f1bc367 100644
--- a/management/server/grpcserver.go
+++ b/management/server/grpcserver.go
@@ -19,6 +19,7 @@ import (
 	"google.golang.org/grpc/status"
 
 	integrationsConfig "github.com/netbirdio/management-integrations/integrations/config"
+	"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
 
 	"github.com/netbirdio/netbird/encryption"
 	"github.com/netbirdio/netbird/management/proto"
@@ -40,13 +41,14 @@ type GRPCServer struct {
 	settingsManager settings.Manager
 	wgKey           wgtypes.Key
 	proto.UnimplementedManagementServiceServer
-	peersUpdateManager *PeersUpdateManager
-	config             *types.Config
-	secretsManager     SecretsManager
-	appMetrics         telemetry.AppMetrics
-	ephemeralManager   *EphemeralManager
-	peerLocks          sync.Map
-	authManager        auth.Manager
+	peersUpdateManager      *PeersUpdateManager
+	config                  *types.Config
+	secretsManager          SecretsManager
+	appMetrics              telemetry.AppMetrics
+	ephemeralManager        *EphemeralManager
+	peerLocks               sync.Map
+	authManager             auth.Manager
+	integratedPeerValidator integrated_validator.IntegratedValidator
 }
 
 // NewServer creates a new Management server
@@ -60,6 +62,7 @@ func NewServer(
 	appMetrics telemetry.AppMetrics,
 	ephemeralManager *EphemeralManager,
 	authManager auth.Manager,
+	integratedPeerValidator integrated_validator.IntegratedValidator,
 ) (*GRPCServer, error) {
 	key, err := wgtypes.GeneratePrivateKey()
 	if err != nil {
@@ -79,14 +82,15 @@ func NewServer(
 	return &GRPCServer{
 		wgKey: key,
 		// peerKey -> event channel
-		peersUpdateManager: peersUpdateManager,
-		accountManager:     accountManager,
-		settingsManager:    settingsManager,
-		config:             config,
-		secretsManager:     secretsManager,
-		authManager:        authManager,
-		appMetrics:         appMetrics,
-		ephemeralManager:   ephemeralManager,
+		peersUpdateManager:      peersUpdateManager,
+		accountManager:          accountManager,
+		settingsManager:         settingsManager,
+		config:                  config,
+		secretsManager:          secretsManager,
+		authManager:             authManager,
+		appMetrics:              appMetrics,
+		ephemeralManager:        ephemeralManager,
+		integratedPeerValidator: integratedPeerValidator,
 	}, nil
 }
 
@@ -850,7 +854,7 @@ func (s *GRPCServer) GetPKCEAuthorizationFlow(ctx context.Context, req *proto.En
 		return nil, status.Error(codes.NotFound, "no pkce authorization flow information available")
 	}
 
-	flowInfoResp := &proto.PKCEAuthorizationFlow{
+	initInfoFlow := &proto.PKCEAuthorizationFlow{
 		ProviderConfig: &proto.ProviderConfig{
 			Audience:              s.config.PKCEAuthorizationFlow.ProviderConfig.Audience,
 			ClientID:              s.config.PKCEAuthorizationFlow.ProviderConfig.ClientID,
@@ -865,6 +869,8 @@ func (s *GRPCServer) GetPKCEAuthorizationFlow(ctx context.Context, req *proto.En
 		},
 	}
 
+	flowInfoResp := s.integratedPeerValidator.ValidateFlowResponse(ctx, peerKey.String(), initInfoFlow)
+
 	encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, flowInfoResp)
 	if err != nil {
 		return nil, status.Error(codes.Internal, "failed to encrypt no pkce authorization flow information")
diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml
index 1c5ca9b0..f8c2b985 100644
--- a/management/server/http/api/openapi.yml
+++ b/management/server/http/api/openapi.yml
@@ -60,6 +60,8 @@ components:
           description: Account creator
           type: string
           example: google-oauth2|277474792786460067937
+        onboarding:
+          $ref: '#/components/schemas/AccountOnboarding'
       required:
         - id
         - settings
@@ -67,6 +69,21 @@ components:
         - domain_category
         - created_at
         - created_by
+        - onboarding
+    AccountOnboarding:
+      type: object
+      properties:
+        signup_form_pending:
+          description: Indicates whether the account signup form is pending
+          type: boolean
+          example: true
+        onboarding_flow_pending:
+          description: Indicates whether the account onboarding flow is pending
+          type: boolean
+          example: false
+      required:
+        - signup_form_pending
+        - onboarding_flow_pending
     AccountSettings:
       type: object
       properties:
@@ -153,6 +170,8 @@ components:
       properties:
         settings:
           $ref: '#/components/schemas/AccountSettings'
+        onboarding:
+          $ref: '#/components/schemas/AccountOnboarding'
       required:
         - settings
     User:
diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go
index d27fd2a5..a9f17aab 100644
--- a/management/server/http/api/types.gen.go
+++ b/management/server/http/api/types.gen.go
@@ -250,8 +250,9 @@ type Account struct {
 	DomainCategory string `json:"domain_category"`
 
 	// Id Account ID
-	Id       string          `json:"id"`
-	Settings AccountSettings `json:"settings"`
+	Id         string            `json:"id"`
+	Onboarding AccountOnboarding `json:"onboarding"`
+	Settings   AccountSettings   `json:"settings"`
 }
 
 // AccountExtraSettings defines model for AccountExtraSettings.
@@ -266,9 +267,19 @@ type AccountExtraSettings struct {
 	PeerApprovalEnabled bool `json:"peer_approval_enabled"`
 }
 
+// AccountOnboarding defines model for AccountOnboarding.
+type AccountOnboarding struct {
+	// OnboardingFlowPending Indicates whether the account onboarding flow is pending
+	OnboardingFlowPending bool `json:"onboarding_flow_pending"`
+
+	// SignupFormPending Indicates whether the account signup form is pending
+	SignupFormPending bool `json:"signup_form_pending"`
+}
+
 // AccountRequest defines model for AccountRequest.
 type AccountRequest struct {
-	Settings AccountSettings `json:"settings"`
+	Onboarding *AccountOnboarding `json:"onboarding,omitempty"`
+	Settings   AccountSettings    `json:"settings"`
 }
 
 // AccountSettings defines model for AccountSettings.
diff --git a/management/server/http/handlers/accounts/accounts_handler.go b/management/server/http/handlers/accounts/accounts_handler.go
index dfc782b3..ab59434d 100644
--- a/management/server/http/handlers/accounts/accounts_handler.go
+++ b/management/server/http/handlers/accounts/accounts_handler.go
@@ -59,7 +59,13 @@ func (h *handler) getAllAccounts(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	resp := toAccountResponse(accountID, settings, meta)
+	onboarding, err := h.accountManager.GetAccountOnboarding(r.Context(), accountID, userID)
+	if err != nil {
+		util.WriteError(r.Context(), err, w)
+		return
+	}
+
+	resp := toAccountResponse(accountID, settings, meta, onboarding)
 	util.WriteJSONObject(r.Context(), w, []*api.Account{resp})
 }
 
@@ -126,6 +132,20 @@ func (h *handler) updateAccount(w http.ResponseWriter, r *http.Request) {
 		settings.LazyConnectionEnabled = *req.Settings.LazyConnectionEnabled
 	}
 
+	var onboarding *types.AccountOnboarding
+	if req.Onboarding != nil {
+		onboarding = &types.AccountOnboarding{
+			OnboardingFlowPending: req.Onboarding.OnboardingFlowPending,
+			SignupFormPending:     req.Onboarding.SignupFormPending,
+		}
+	}
+
+	updatedOnboarding, err := h.accountManager.UpdateAccountOnboarding(r.Context(), accountID, userID, onboarding)
+	if err != nil {
+		util.WriteError(r.Context(), err, w)
+		return
+	}
+
 	updatedSettings, err := h.accountManager.UpdateAccountSettings(r.Context(), accountID, userID, settings)
 	if err != nil {
 		util.WriteError(r.Context(), err, w)
@@ -138,7 +158,7 @@ func (h *handler) updateAccount(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	resp := toAccountResponse(accountID, updatedSettings, meta)
+	resp := toAccountResponse(accountID, updatedSettings, meta, updatedOnboarding)
 
 	util.WriteJSONObject(r.Context(), w, &resp)
 }
@@ -167,7 +187,7 @@ func (h *handler) deleteAccount(w http.ResponseWriter, r *http.Request) {
 	util.WriteJSONObject(r.Context(), w, util.EmptyObject{})
 }
 
-func toAccountResponse(accountID string, settings *types.Settings, meta *types.AccountMeta) *api.Account {
+func toAccountResponse(accountID string, settings *types.Settings, meta *types.AccountMeta, onboarding *types.AccountOnboarding) *api.Account {
 	jwtAllowGroups := settings.JWTAllowGroups
 	if jwtAllowGroups == nil {
 		jwtAllowGroups = []string{}
@@ -188,6 +208,11 @@ func toAccountResponse(accountID string, settings *types.Settings, meta *types.A
 		DnsDomain:                       &settings.DNSDomain,
 	}
 
+	apiOnboarding := api.AccountOnboarding{
+		OnboardingFlowPending: onboarding.OnboardingFlowPending,
+		SignupFormPending:     onboarding.SignupFormPending,
+	}
+
 	if settings.Extra != nil {
 		apiSettings.Extra = &api.AccountExtraSettings{
 			PeerApprovalEnabled:                settings.Extra.PeerApprovalEnabled,
@@ -203,5 +228,6 @@ func toAccountResponse(accountID string, settings *types.Settings, meta *types.A
 		CreatedBy:      meta.CreatedBy,
 		Domain:         meta.Domain,
 		DomainCategory: meta.DomainCategory,
+		Onboarding:     apiOnboarding,
 	}
 }
diff --git a/management/server/http/handlers/policies/policies_handler.go b/management/server/http/handlers/policies/policies_handler.go
index 4d8cce3d..267d5744 100644
--- a/management/server/http/handlers/policies/policies_handler.go
+++ b/management/server/http/handlers/policies/policies_handler.go
@@ -424,9 +424,10 @@ func toPolicyResponse(groups []*types.Group, policy *types.Policy) *api.Policy {
 			}
 			if group, ok := groupsMap[gid]; ok {
 				minimum := api.GroupMinimum{
-					Id:         group.ID,
-					Name:       group.Name,
-					PeersCount: len(group.Peers),
+					Id:             group.ID,
+					Name:           group.Name,
+					PeersCount:     len(group.Peers),
+					ResourcesCount: len(group.Resources),
 				}
 				destinations = append(destinations, minimum)
 				cache[gid] = minimum
diff --git a/management/server/integrated_validator.go b/management/server/integrated_validator.go
index edb89466..e3e47441 100644
--- a/management/server/integrated_validator.go
+++ b/management/server/integrated_validator.go
@@ -6,6 +6,7 @@ import (
 
 	log "github.com/sirupsen/logrus"
 
+	"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
 	nbpeer "github.com/netbirdio/netbird/management/server/peer"
 	"github.com/netbirdio/netbird/management/server/store"
 	"github.com/netbirdio/netbird/management/server/types"
@@ -83,15 +84,12 @@ func (am *DefaultAccountManager) GetValidatedPeers(ctx context.Context, accountI
 	var peers []*nbpeer.Peer
 	var settings *types.Settings
 
-	err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
-		groups, err = transaction.GetAccountGroups(ctx, store.LockingStrengthShare, accountID)
-		if err != nil {
-			return err
-		}
+	groups, err = am.Store.GetAccountGroups(ctx, store.LockingStrengthShare, accountID)
+	if err != nil {
+		return nil, err
+	}
 
-		peers, err = transaction.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "")
-		return err
-	})
+	peers, err = am.Store.GetAccountPeers(ctx, store.LockingStrengthShare, accountID, "", "")
 	if err != nil {
 		return nil, err
 	}
@@ -104,22 +102,23 @@ func (am *DefaultAccountManager) GetValidatedPeers(ctx context.Context, accountI
 	return am.integratedPeerValidator.GetValidatedPeers(accountID, groups, peers, settings.Extra)
 }
 
-type MocIntegratedValidator struct {
+type MockIntegratedValidator struct {
+	integrated_validator.IntegratedValidator
 	ValidatePeerFunc func(_ context.Context, update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *types.ExtraSettings) (*nbpeer.Peer, bool, error)
 }
 
-func (a MocIntegratedValidator) ValidateExtraSettings(_ context.Context, newExtraSettings *types.ExtraSettings, oldExtraSettings *types.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error {
+func (a MockIntegratedValidator) ValidateExtraSettings(_ context.Context, newExtraSettings *types.ExtraSettings, oldExtraSettings *types.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error {
 	return nil
 }
 
-func (a MocIntegratedValidator) ValidatePeer(_ context.Context, update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *types.ExtraSettings) (*nbpeer.Peer, bool, error) {
+func (a MockIntegratedValidator) ValidatePeer(_ context.Context, update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *types.ExtraSettings) (*nbpeer.Peer, bool, error) {
 	if a.ValidatePeerFunc != nil {
 		return a.ValidatePeerFunc(context.Background(), update, peer, userID, accountID, dnsDomain, peersGroup, extraSettings)
 	}
 	return update, false, nil
 }
 
-func (a MocIntegratedValidator) GetValidatedPeers(accountID string, groups []*types.Group, peers []*nbpeer.Peer, extraSettings *types.ExtraSettings) (map[string]struct{}, error) {
+func (a MockIntegratedValidator) GetValidatedPeers(accountID string, groups []*types.Group, peers []*nbpeer.Peer, extraSettings *types.ExtraSettings) (map[string]struct{}, error) {
 	validatedPeers := make(map[string]struct{})
 	for _, peer := range peers {
 		validatedPeers[peer.ID] = struct{}{}
@@ -127,22 +126,22 @@ func (a MocIntegratedValidator) GetValidatedPeers(accountID string, groups []*ty
 	return validatedPeers, nil
 }
 
-func (MocIntegratedValidator) PreparePeer(_ context.Context, accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *types.ExtraSettings) *nbpeer.Peer {
+func (MockIntegratedValidator) PreparePeer(_ context.Context, accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *types.ExtraSettings) *nbpeer.Peer {
 	return peer
 }
 
-func (MocIntegratedValidator) IsNotValidPeer(_ context.Context, accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *types.ExtraSettings) (bool, bool, error) {
+func (MockIntegratedValidator) IsNotValidPeer(_ context.Context, accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *types.ExtraSettings) (bool, bool, error) {
 	return false, false, nil
 }
 
-func (MocIntegratedValidator) PeerDeleted(_ context.Context, _, _ string) error {
+func (MockIntegratedValidator) PeerDeleted(_ context.Context, _, _ string) error {
 	return nil
 }
 
-func (MocIntegratedValidator) SetPeerInvalidationListener(func(accountID string)) {
+func (MockIntegratedValidator) SetPeerInvalidationListener(func(accountID string)) {
 	// just a dummy
 }
 
-func (MocIntegratedValidator) Stop(_ context.Context) {
+func (MockIntegratedValidator) Stop(_ context.Context) {
 	// just a dummy
 }
diff --git a/management/server/integrations/integrated_validator/interface.go b/management/server/integrations/integrated_validator/interface.go
index 083baa65..245c0168 100644
--- a/management/server/integrations/integrated_validator/interface.go
+++ b/management/server/integrations/integrated_validator/interface.go
@@ -3,6 +3,7 @@ package integrated_validator
 import (
 	"context"
 
+	"github.com/netbirdio/netbird/management/proto"
 	nbpeer "github.com/netbirdio/netbird/management/server/peer"
 	"github.com/netbirdio/netbird/management/server/types"
 )
@@ -17,4 +18,5 @@ type IntegratedValidator interface {
 	PeerDeleted(ctx context.Context, accountID, peerID string) error
 	SetPeerInvalidationListener(fn func(accountID string))
 	Stop(ctx context.Context)
+	ValidateFlowResponse(ctx context.Context, peerKey string, flowResponse *proto.PKCEAuthorizationFlow) *proto.PKCEAuthorizationFlow
 }
diff --git a/management/server/migration/migration.go b/management/server/migration/migration.go
index c8a852e0..c2f1a5ab 100644
--- a/management/server/migration/migration.go
+++ b/management/server/migration/migration.go
@@ -283,7 +283,7 @@ func MigrateSetupKeyToHashedSetupKey[T any](ctx context.Context, db *gorm.DB) er
 			}
 		}
 
-		if err := tx.Exec(fmt.Sprintf("ALTER TABLE %s DROP COLUMN %s", "peers", "setup_key")).Error; err != nil {
+		if err := tx.Exec(fmt.Sprintf("ALTER TABLE %s DROP COLUMN IF EXISTS %s", "peers", "setup_key")).Error; err != nil {
 			log.WithContext(ctx).Errorf("Failed to drop column %s: %v", "setup_key", err)
 		}
 
@@ -373,3 +373,52 @@ func DropIndex[T any](ctx context.Context, db *gorm.DB, indexName string) error
 	log.WithContext(ctx).Infof("dropped index %s from table %T", indexName, model)
 	return nil
 }
+
+func CreateIndexIfNotExists[T any](ctx context.Context, db *gorm.DB, indexName string, columns ...string) error {
+	var model T
+
+	if !db.Migrator().HasTable(&model) {
+		log.WithContext(ctx).Debugf("table for %T does not exist, no migration needed", model)
+		return nil
+	}
+
+	stmt := &gorm.Statement{DB: db}
+	if err := stmt.Parse(&model); err != nil {
+		return fmt.Errorf("failed to parse model schema: %w", err)
+	}
+	tableName := stmt.Schema.Table
+	dialect := db.Dialector.Name()
+
+	if db.Migrator().HasIndex(&model, indexName) {
+		log.WithContext(ctx).Infof("index %s already exists on table %s", indexName, tableName)
+		return nil
+	}
+
+	var columnClause string
+	if dialect == "mysql" {
+		var withLength []string
+		for _, col := range columns {
+			if col == "ip" || col == "dns_label" {
+				withLength = append(withLength, fmt.Sprintf("%s(64)", col))
+			} else {
+				withLength = append(withLength, col)
+			}
+		}
+		columnClause = strings.Join(withLength, ", ")
+	} else {
+		columnClause = strings.Join(columns, ", ")
+	}
+
+	createStmt := fmt.Sprintf("CREATE UNIQUE INDEX %s ON %s (%s)", indexName, tableName, columnClause)
+	if dialect == "postgres" || dialect == "sqlite" {
+		createStmt = strings.Replace(createStmt, "CREATE UNIQUE INDEX", "CREATE UNIQUE INDEX IF NOT EXISTS", 1)
+	}
+
+	log.WithContext(ctx).Infof("executing index creation: %s", createStmt)
+	if err := db.Exec(createStmt).Error; err != nil {
+		return fmt.Errorf("failed to create index %s: %w", indexName, err)
+	}
+
+	log.WithContext(ctx).Infof("successfully created index %s on table %s", indexName, tableName)
+	return nil
+}
diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go
index 3caa6744..b1ec6628 100644
--- a/management/server/mock_server/account_mock.go
+++ b/management/server/mock_server/account_mock.go
@@ -30,99 +30,110 @@ type MockAccountManager struct {
 	GetAccountFunc               func(ctx context.Context, accountID string) (*types.Account, error)
 	CreateSetupKeyFunc           func(ctx context.Context, accountId string, keyName string, keyType types.SetupKeyType,
 		expiresIn time.Duration, autoGroups []string, usageLimit int, userID string, ephemeral bool, allowExtraDNSLabels bool) (*types.SetupKey, error)
-	GetSetupKeyFunc                     func(ctx context.Context, accountID, userID, keyID string) (*types.SetupKey, error)
-	AccountExistsFunc                   func(ctx context.Context, accountID string) (bool, error)
-	GetAccountIDByUserIdFunc            func(ctx context.Context, userId, domain string) (string, error)
-	GetUserFromUserAuthFunc             func(ctx context.Context, userAuth nbcontext.UserAuth) (*types.User, error)
-	ListUsersFunc                       func(ctx context.Context, accountID string) ([]*types.User, error)
-	GetPeersFunc                        func(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error)
-	MarkPeerConnectedFunc               func(ctx context.Context, peerKey string, connected bool, realIP net.IP) error
-	SyncAndMarkPeerFunc                 func(ctx context.Context, accountID string, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
-	DeletePeerFunc                      func(ctx context.Context, accountID, peerKey, userID string) error
-	GetNetworkMapFunc                   func(ctx context.Context, peerKey string) (*types.NetworkMap, error)
-	GetPeerNetworkFunc                  func(ctx context.Context, peerKey string) (*types.Network, error)
-	AddPeerFunc                         func(ctx context.Context, setupKey string, userId string, peer *nbpeer.Peer) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
-	GetGroupFunc                        func(ctx context.Context, accountID, groupID, userID string) (*types.Group, error)
-	GetAllGroupsFunc                    func(ctx context.Context, accountID, userID string) ([]*types.Group, error)
-	GetGroupByNameFunc                  func(ctx context.Context, accountID, groupName string) (*types.Group, error)
-	SaveGroupFunc                       func(ctx context.Context, accountID, userID string, group *types.Group, create bool) error
-	SaveGroupsFunc                      func(ctx context.Context, accountID, userID string, groups []*types.Group, create bool) error
-	DeleteGroupFunc                     func(ctx context.Context, accountID, userId, groupID string) error
-	DeleteGroupsFunc                    func(ctx context.Context, accountId, userId string, groupIDs []string) error
-	GroupAddPeerFunc                    func(ctx context.Context, accountID, groupID, peerID string) error
-	GroupDeletePeerFunc                 func(ctx context.Context, accountID, groupID, peerID string) error
-	GetPeerGroupsFunc                   func(ctx context.Context, accountID, peerID string) ([]*types.Group, error)
-	DeleteRuleFunc                      func(ctx context.Context, accountID, ruleID, userID string) error
-	GetPolicyFunc                       func(ctx context.Context, accountID, policyID, userID string) (*types.Policy, error)
-	SavePolicyFunc                      func(ctx context.Context, accountID, userID string, policy *types.Policy, create bool) (*types.Policy, error)
-	DeletePolicyFunc                    func(ctx context.Context, accountID, policyID, userID string) error
-	ListPoliciesFunc                    func(ctx context.Context, accountID, userID string) ([]*types.Policy, error)
-	GetUsersFromAccountFunc             func(ctx context.Context, accountID, userID string) (map[string]*types.UserInfo, error)
-	UpdatePeerMetaFunc                  func(ctx context.Context, peerID string, meta nbpeer.PeerSystemMeta) error
-	UpdatePeerFunc                      func(ctx context.Context, accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error)
-	CreateRouteFunc                     func(ctx context.Context, accountID string, prefix netip.Prefix, networkType route.NetworkType, domains domain.List, peer string, peerGroups []string, description string, netID route.NetID, masquerade bool, metric int, groups, accessControlGroupIDs []string, enabled bool, userID string, keepRoute bool) (*route.Route, error)
-	GetRouteFunc                        func(ctx context.Context, accountID string, routeID route.ID, userID string) (*route.Route, error)
-	SaveRouteFunc                       func(ctx context.Context, accountID string, userID string, route *route.Route) error
-	DeleteRouteFunc                     func(ctx context.Context, accountID string, routeID route.ID, userID string) error
-	ListRoutesFunc                      func(ctx context.Context, accountID, userID string) ([]*route.Route, error)
-	SaveSetupKeyFunc                    func(ctx context.Context, accountID string, key *types.SetupKey, userID string) (*types.SetupKey, error)
-	ListSetupKeysFunc                   func(ctx context.Context, accountID, userID string) ([]*types.SetupKey, error)
-	SaveUserFunc                        func(ctx context.Context, accountID, userID string, user *types.User) (*types.UserInfo, error)
-	SaveOrAddUserFunc                   func(ctx context.Context, accountID, userID string, user *types.User, addIfNotExists bool) (*types.UserInfo, error)
-	SaveOrAddUsersFunc                  func(ctx context.Context, accountID, initiatorUserID string, update []*types.User, addIfNotExists bool) ([]*types.UserInfo, error)
-	DeleteUserFunc                      func(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) error
-	DeleteRegularUsersFunc              func(ctx context.Context, accountID, initiatorUserID string, targetUserIDs []string, userInfos map[string]*types.UserInfo) error
-	CreatePATFunc                       func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenName string, expiresIn int) (*types.PersonalAccessTokenGenerated, error)
-	DeletePATFunc                       func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenID string) error
-	GetPATFunc                          func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenID string) (*types.PersonalAccessToken, error)
-	GetAllPATsFunc                      func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string) ([]*types.PersonalAccessToken, error)
-	GetNameServerGroupFunc              func(ctx context.Context, accountID, userID, nsGroupID string) (*nbdns.NameServerGroup, error)
-	CreateNameServerGroupFunc           func(ctx context.Context, accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string, searchDomainsEnabled bool) (*nbdns.NameServerGroup, error)
-	SaveNameServerGroupFunc             func(ctx context.Context, accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error
-	DeleteNameServerGroupFunc           func(ctx context.Context, accountID, nsGroupID, userID string) error
-	ListNameServerGroupsFunc            func(ctx context.Context, accountID string, userID string) ([]*nbdns.NameServerGroup, error)
-	CreateUserFunc                      func(ctx context.Context, accountID, userID string, key *types.UserInfo) (*types.UserInfo, error)
-	GetAccountIDFromUserAuthFunc        func(ctx context.Context, userAuth nbcontext.UserAuth) (string, string, error)
-	DeleteAccountFunc                   func(ctx context.Context, accountID, userID string) error
-	GetDNSDomainFunc                    func(settings *types.Settings) string
-	StoreEventFunc                      func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any)
-	GetEventsFunc                       func(ctx context.Context, accountID, userID string) ([]*activity.Event, error)
-	GetDNSSettingsFunc                  func(ctx context.Context, accountID, userID string) (*types.DNSSettings, error)
-	SaveDNSSettingsFunc                 func(ctx context.Context, accountID, userID string, dnsSettingsToSave *types.DNSSettings) error
-	GetPeerFunc                         func(ctx context.Context, accountID, peerID, userID string) (*nbpeer.Peer, error)
-	UpdateAccountSettingsFunc           func(ctx context.Context, accountID, userID string, newSettings *types.Settings) (*types.Settings, error)
-	LoginPeerFunc                       func(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
-	SyncPeerFunc                        func(ctx context.Context, sync types.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
-	InviteUserFunc                      func(ctx context.Context, accountID string, initiatorUserID string, targetUserEmail string) error
-	GetAllConnectedPeersFunc            func() (map[string]struct{}, error)
-	HasConnectedChannelFunc             func(peerID string) bool
-	GetExternalCacheManagerFunc         func() account.ExternalCacheManager
-	GetPostureChecksFunc                func(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error)
-	SavePostureChecksFunc               func(ctx context.Context, accountID, userID string, postureChecks *posture.Checks, create bool) (*posture.Checks, error)
-	DeletePostureChecksFunc             func(ctx context.Context, accountID, postureChecksID, userID string) error
-	ListPostureChecksFunc               func(ctx context.Context, accountID, userID string) ([]*posture.Checks, error)
-	GetIdpManagerFunc                   func() idp.Manager
-	UpdateIntegratedValidatorGroupsFunc func(ctx context.Context, accountID string, userID string, groups []string) error
-	GroupValidationFunc                 func(ctx context.Context, accountId string, groups []string) (bool, error)
-	SyncPeerMetaFunc                    func(ctx context.Context, peerPubKey string, meta nbpeer.PeerSystemMeta) error
-	FindExistingPostureCheckFunc        func(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error)
-	GetAccountIDForPeerKeyFunc          func(ctx context.Context, peerKey string) (string, error)
-	GetAccountByIDFunc                  func(ctx context.Context, accountID string, userID string) (*types.Account, error)
-	GetUserByIDFunc                     func(ctx context.Context, id string) (*types.User, error)
-	GetAccountSettingsFunc              func(ctx context.Context, accountID string, userID string) (*types.Settings, error)
-	DeleteSetupKeyFunc                  func(ctx context.Context, accountID, userID, keyID string) error
-	BuildUserInfosForAccountFunc        func(ctx context.Context, accountID, initiatorUserID string, accountUsers []*types.User) (map[string]*types.UserInfo, error)
-	GetStoreFunc                        func() store.Store
-	UpdateToPrimaryAccountFunc          func(ctx context.Context, accountId string) (*types.Account, error)
-	GetOwnerInfoFunc                    func(ctx context.Context, accountID string) (*types.UserInfo, error)
-	GetCurrentUserInfoFunc              func(ctx context.Context, userAuth nbcontext.UserAuth) (*users.UserInfoWithPermissions, error)
-	GetAccountMetaFunc                  func(ctx context.Context, accountID, userID string) (*types.AccountMeta, error)
-
+	GetSetupKeyFunc                       func(ctx context.Context, accountID, userID, keyID string) (*types.SetupKey, error)
+	AccountExistsFunc                     func(ctx context.Context, accountID string) (bool, error)
+	GetAccountIDByUserIdFunc              func(ctx context.Context, userId, domain string) (string, error)
+	GetUserFromUserAuthFunc               func(ctx context.Context, userAuth nbcontext.UserAuth) (*types.User, error)
+	ListUsersFunc                         func(ctx context.Context, accountID string) ([]*types.User, error)
+	GetPeersFunc                          func(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error)
+	MarkPeerConnectedFunc                 func(ctx context.Context, peerKey string, connected bool, realIP net.IP) error
+	SyncAndMarkPeerFunc                   func(ctx context.Context, accountID string, peerPubKey string, meta nbpeer.PeerSystemMeta, realIP net.IP) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
+	DeletePeerFunc                        func(ctx context.Context, accountID, peerKey, userID string) error
+	GetNetworkMapFunc                     func(ctx context.Context, peerKey string) (*types.NetworkMap, error)
+	GetPeerNetworkFunc                    func(ctx context.Context, peerKey string) (*types.Network, error)
+	AddPeerFunc                           func(ctx context.Context, setupKey string, userId string, peer *nbpeer.Peer) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
+	GetGroupFunc                          func(ctx context.Context, accountID, groupID, userID string) (*types.Group, error)
+	GetAllGroupsFunc                      func(ctx context.Context, accountID, userID string) ([]*types.Group, error)
+	GetGroupByNameFunc                    func(ctx context.Context, accountID, groupName string) (*types.Group, error)
+	SaveGroupFunc                         func(ctx context.Context, accountID, userID string, group *types.Group, create bool) error
+	SaveGroupsFunc                        func(ctx context.Context, accountID, userID string, groups []*types.Group, create bool) error
+	DeleteGroupFunc                       func(ctx context.Context, accountID, userId, groupID string) error
+	DeleteGroupsFunc                      func(ctx context.Context, accountId, userId string, groupIDs []string) error
+	GroupAddPeerFunc                      func(ctx context.Context, accountID, groupID, peerID string) error
+	GroupDeletePeerFunc                   func(ctx context.Context, accountID, groupID, peerID string) error
+	GetPeerGroupsFunc                     func(ctx context.Context, accountID, peerID string) ([]*types.Group, error)
+	DeleteRuleFunc                        func(ctx context.Context, accountID, ruleID, userID string) error
+	GetPolicyFunc                         func(ctx context.Context, accountID, policyID, userID string) (*types.Policy, error)
+	SavePolicyFunc                        func(ctx context.Context, accountID, userID string, policy *types.Policy, create bool) (*types.Policy, error)
+	DeletePolicyFunc                      func(ctx context.Context, accountID, policyID, userID string) error
+	ListPoliciesFunc                      func(ctx context.Context, accountID, userID string) ([]*types.Policy, error)
+	GetUsersFromAccountFunc               func(ctx context.Context, accountID, userID string) (map[string]*types.UserInfo, error)
+	UpdatePeerMetaFunc                    func(ctx context.Context, peerID string, meta nbpeer.PeerSystemMeta) error
+	UpdatePeerFunc                        func(ctx context.Context, accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error)
+	CreateRouteFunc                       func(ctx context.Context, accountID string, prefix netip.Prefix, networkType route.NetworkType, domains domain.List, peer string, peerGroups []string, description string, netID route.NetID, masquerade bool, metric int, groups, accessControlGroupIDs []string, enabled bool, userID string, keepRoute bool) (*route.Route, error)
+	GetRouteFunc                          func(ctx context.Context, accountID string, routeID route.ID, userID string) (*route.Route, error)
+	SaveRouteFunc                         func(ctx context.Context, accountID string, userID string, route *route.Route) error
+	DeleteRouteFunc                       func(ctx context.Context, accountID string, routeID route.ID, userID string) error
+	ListRoutesFunc                        func(ctx context.Context, accountID, userID string) ([]*route.Route, error)
+	SaveSetupKeyFunc                      func(ctx context.Context, accountID string, key *types.SetupKey, userID string) (*types.SetupKey, error)
+	ListSetupKeysFunc                     func(ctx context.Context, accountID, userID string) ([]*types.SetupKey, error)
+	SaveUserFunc                          func(ctx context.Context, accountID, userID string, user *types.User) (*types.UserInfo, error)
+	SaveOrAddUserFunc                     func(ctx context.Context, accountID, userID string, user *types.User, addIfNotExists bool) (*types.UserInfo, error)
+	SaveOrAddUsersFunc                    func(ctx context.Context, accountID, initiatorUserID string, update []*types.User, addIfNotExists bool) ([]*types.UserInfo, error)
+	DeleteUserFunc                        func(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) error
+	DeleteRegularUsersFunc                func(ctx context.Context, accountID, initiatorUserID string, targetUserIDs []string, userInfos map[string]*types.UserInfo) error
+	CreatePATFunc                         func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenName string, expiresIn int) (*types.PersonalAccessTokenGenerated, error)
+	DeletePATFunc                         func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenID string) error
+	GetPATFunc                            func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string, tokenID string) (*types.PersonalAccessToken, error)
+	GetAllPATsFunc                        func(ctx context.Context, accountID string, initiatorUserID string, targetUserId string) ([]*types.PersonalAccessToken, error)
+	GetNameServerGroupFunc                func(ctx context.Context, accountID, userID, nsGroupID string) (*nbdns.NameServerGroup, error)
+	CreateNameServerGroupFunc             func(ctx context.Context, accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string, searchDomainsEnabled bool) (*nbdns.NameServerGroup, error)
+	SaveNameServerGroupFunc               func(ctx context.Context, accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error
+	DeleteNameServerGroupFunc             func(ctx context.Context, accountID, nsGroupID, userID string) error
+	ListNameServerGroupsFunc              func(ctx context.Context, accountID string, userID string) ([]*nbdns.NameServerGroup, error)
+	CreateUserFunc                        func(ctx context.Context, accountID, userID string, key *types.UserInfo) (*types.UserInfo, error)
+	GetAccountIDFromUserAuthFunc          func(ctx context.Context, userAuth nbcontext.UserAuth) (string, string, error)
+	DeleteAccountFunc                     func(ctx context.Context, accountID, userID string) error
+	GetDNSDomainFunc                      func(settings *types.Settings) string
+	StoreEventFunc                        func(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any)
+	GetEventsFunc                         func(ctx context.Context, accountID, userID string) ([]*activity.Event, error)
+	GetDNSSettingsFunc                    func(ctx context.Context, accountID, userID string) (*types.DNSSettings, error)
+	SaveDNSSettingsFunc                   func(ctx context.Context, accountID, userID string, dnsSettingsToSave *types.DNSSettings) error
+	GetPeerFunc                           func(ctx context.Context, accountID, peerID, userID string) (*nbpeer.Peer, error)
+	UpdateAccountSettingsFunc             func(ctx context.Context, accountID, userID string, newSettings *types.Settings) (*types.Settings, error)
+	LoginPeerFunc                         func(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
+	SyncPeerFunc                          func(ctx context.Context, sync types.PeerSync, accountID string) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error)
+	InviteUserFunc                        func(ctx context.Context, accountID string, initiatorUserID string, targetUserEmail string) error
+	GetAllConnectedPeersFunc              func() (map[string]struct{}, error)
+	HasConnectedChannelFunc               func(peerID string) bool
+	GetExternalCacheManagerFunc           func() account.ExternalCacheManager
+	GetPostureChecksFunc                  func(ctx context.Context, accountID, postureChecksID, userID string) (*posture.Checks, error)
+	SavePostureChecksFunc                 func(ctx context.Context, accountID, userID string, postureChecks *posture.Checks, create bool) (*posture.Checks, error)
+	DeletePostureChecksFunc               func(ctx context.Context, accountID, postureChecksID, userID string) error
+	ListPostureChecksFunc                 func(ctx context.Context, accountID, userID string) ([]*posture.Checks, error)
+	GetIdpManagerFunc                     func() idp.Manager
+	UpdateIntegratedValidatorGroupsFunc   func(ctx context.Context, accountID string, userID string, groups []string) error
+	GroupValidationFunc                   func(ctx context.Context, accountId string, groups []string) (bool, error)
+	SyncPeerMetaFunc                      func(ctx context.Context, peerPubKey string, meta nbpeer.PeerSystemMeta) error
+	FindExistingPostureCheckFunc          func(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error)
+	GetAccountIDForPeerKeyFunc            func(ctx context.Context, peerKey string) (string, error)
+	GetAccountByIDFunc                    func(ctx context.Context, accountID string, userID string) (*types.Account, error)
+	GetUserByIDFunc                       func(ctx context.Context, id string) (*types.User, error)
+	GetAccountSettingsFunc                func(ctx context.Context, accountID string, userID string) (*types.Settings, error)
+	DeleteSetupKeyFunc                    func(ctx context.Context, accountID, userID, keyID string) error
+	BuildUserInfosForAccountFunc          func(ctx context.Context, accountID, initiatorUserID string, accountUsers []*types.User) (map[string]*types.UserInfo, error)
+	GetStoreFunc                          func() store.Store
+	UpdateToPrimaryAccountFunc            func(ctx context.Context, accountId string) (*types.Account, error)
+	GetOwnerInfoFunc                      func(ctx context.Context, accountID string) (*types.UserInfo, error)
+	GetCurrentUserInfoFunc                func(ctx context.Context, userAuth nbcontext.UserAuth) (*users.UserInfoWithPermissions, error)
+	GetAccountMetaFunc                    func(ctx context.Context, accountID, userID string) (*types.AccountMeta, error)
+	GetAccountOnboardingFunc              func(ctx context.Context, accountID, userID string) (*types.AccountOnboarding, error)
+	UpdateAccountOnboardingFunc           func(ctx context.Context, accountID, userID string, onboarding *types.AccountOnboarding) (*types.AccountOnboarding, error)
 	GetOrCreateAccountByPrivateDomainFunc func(ctx context.Context, initiatorId, domain string) (*types.Account, bool, error)
+	UpdateAccountPeersFunc                func(ctx context.Context, accountID string)
+	BufferUpdateAccountPeersFunc          func(ctx context.Context, accountID string)
 }
 
 func (am *MockAccountManager) UpdateAccountPeers(ctx context.Context, accountID string) {
-	// do nothing
+	if am.UpdateAccountPeersFunc != nil {
+		am.UpdateAccountPeersFunc(ctx, accountID)
+	}
+}
+
+func (am *MockAccountManager) BufferUpdateAccountPeers(ctx context.Context, accountID string) {
+	if am.BufferUpdateAccountPeersFunc != nil {
+		am.BufferUpdateAccountPeersFunc(ctx, accountID)
+	}
 }
 
 func (am *MockAccountManager) DeleteSetupKey(ctx context.Context, accountID, userID, keyID string) error {
@@ -814,6 +825,22 @@ func (am *MockAccountManager) GetAccountMeta(ctx context.Context, accountID stri
 	return nil, status.Errorf(codes.Unimplemented, "method GetAccountMeta is not implemented")
 }
 
+// GetAccountOnboarding mocks GetAccountOnboarding of the AccountManager interface
+func (am *MockAccountManager) GetAccountOnboarding(ctx context.Context, accountID string, userID string) (*types.AccountOnboarding, error) {
+	if am.GetAccountOnboardingFunc != nil {
+		return am.GetAccountOnboardingFunc(ctx, accountID, userID)
+	}
+	return nil, status.Errorf(codes.Unimplemented, "method GetAccountOnboarding is not implemented")
+}
+
+// UpdateAccountOnboarding mocks UpdateAccountOnboarding of the AccountManager interface
+func (am *MockAccountManager) UpdateAccountOnboarding(ctx context.Context, accountID string, userID string, onboarding *types.AccountOnboarding) (*types.AccountOnboarding, error) {
+	if am.UpdateAccountOnboardingFunc != nil {
+		return am.UpdateAccountOnboardingFunc(ctx, accountID, userID, onboarding)
+	}
+	return nil, status.Errorf(codes.Unimplemented, "method UpdateAccountOnboarding is not implemented")
+}
+
 // GetUserByID mocks GetUserByID of the AccountManager interface
 func (am *MockAccountManager) GetUserByID(ctx context.Context, id string) (*types.User, error) {
 	if am.GetUserByIDFunc != nil {
diff --git a/management/server/peer.go b/management/server/peer.go
index 1a128972..c6ade83c 100644
--- a/management/server/peer.go
+++ b/management/server/peer.go
@@ -9,19 +9,21 @@ import (
 	"slices"
 	"strings"
 	"sync"
+	"sync/atomic"
 	"time"
 
 	"github.com/rs/xid"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/exp/maps"
 
+	nbdns "github.com/netbirdio/netbird/dns"
 	"github.com/netbirdio/netbird/management/domain"
 	"github.com/netbirdio/netbird/management/server/geolocation"
+	"github.com/netbirdio/netbird/management/server/idp"
 	routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
 	"github.com/netbirdio/netbird/management/server/permissions/modules"
 	"github.com/netbirdio/netbird/management/server/permissions/operations"
 
-	"github.com/netbirdio/netbird/management/server/idp"
 	"github.com/netbirdio/netbird/management/server/posture"
 	"github.com/netbirdio/netbird/management/server/store"
 	"github.com/netbirdio/netbird/management/server/types"
@@ -234,16 +236,24 @@ func (am *DefaultAccountManager) UpdatePeer(ctx context.Context, accountID, user
 		}
 
 		if peer.Name != update.Name {
-			existingLabels, err := getPeerDNSLabels(ctx, transaction, accountID)
-			if err != nil {
-				return err
-			}
+			var newLabel string
 
-			newLabel, err := types.GetPeerHostLabel(update.Name, existingLabels)
+			newLabel, err = nbdns.GetParsedDomainLabel(update.Name)
 			if err != nil {
-				return err
+				newLabel = ""
+			} else {
+				_, err := transaction.GetPeerIdByLabel(ctx, store.LockingStrengthNone, accountID, update.Name)
+				if err == nil {
+					newLabel = ""
+				}
 			}
 
+			if newLabel == "" {
+				newLabel, err = getPeerIPDNSLabel(peer.IP, update.Name)
+				if err != nil {
+					return fmt.Errorf("failed to get free DNS label: %w", err)
+				}
+			}
 			peer.Name = update.Name
 			peer.DNSLabel = newLabel
 			peerLabelChanged = true
@@ -367,19 +377,6 @@ func (am *DefaultAccountManager) DeletePeer(ctx context.Context, accountID, peer
 			return err
 		}
 
-		groups, err := transaction.GetPeerGroups(ctx, store.LockingStrengthUpdate, accountID, peerID)
-		if err != nil {
-			return fmt.Errorf("failed to get peer groups: %w", err)
-		}
-
-		for _, group := range groups {
-			group.RemovePeer(peerID)
-			err = transaction.SaveGroup(ctx, store.LockingStrengthUpdate, group)
-			if err != nil {
-				return fmt.Errorf("failed to save group: %w", err)
-			}
-		}
-
 		eventsToStore, err = deletePeers(ctx, am, transaction, accountID, userID, []*nbpeer.Peer{peer})
 		return err
 	})
@@ -391,7 +388,7 @@ func (am *DefaultAccountManager) DeletePeer(ctx context.Context, accountID, peer
 		storeEvent()
 	}
 
-	if updateAccountPeers {
+	if updateAccountPeers && userID != activity.SystemInitiator {
 		am.BufferUpdateAccountPeers(ctx, accountID)
 	}
 
@@ -463,208 +460,239 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, setupKey, userID s
 	upperKey := strings.ToUpper(setupKey)
 	hashedKey := sha256.Sum256([]byte(upperKey))
 	encodedHashedKey := b64.StdEncoding.EncodeToString(hashedKey[:])
-	var accountID string
-	var err error
-	addedByUser := false
-	if len(userID) > 0 {
-		addedByUser = true
-		accountID, err = am.Store.GetAccountIDByUserID(ctx, store.LockingStrengthShare, userID)
-	} else {
-		accountID, err = am.Store.GetAccountIDBySetupKey(ctx, encodedHashedKey)
-	}
-	if err != nil {
-		return nil, nil, nil, status.Errorf(status.NotFound, "failed adding new peer: account not found")
-	}
-
-	unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
-	defer func() {
-		if unlock != nil {
-			unlock()
-		}
-	}()
+	addedByUser := len(userID) > 0
 
 	// This is a handling for the case when the same machine (with the same WireGuard pub key) tries to register twice.
 	// Such case is possible when AddPeer function takes long time to finish after AcquireWriteLockByUID (e.g., database is slow)
 	// and the peer disconnects with a timeout and tries to register again.
 	// We just check if this machine has been registered before and reject the second registration.
 	// The connecting peer should be able to recover with a retry.
-	_, err = am.Store.GetPeerByPeerPubKey(ctx, store.LockingStrengthShare, peer.Key)
+	_, err := am.Store.GetPeerByPeerPubKey(ctx, store.LockingStrengthNone, peer.Key)
 	if err == nil {
 		return nil, nil, nil, status.Errorf(status.PreconditionFailed, "peer has been already registered")
 	}
 
 	opEvent := &activity.Event{
 		Timestamp: time.Now().UTC(),
-		AccountID: accountID,
 	}
 
 	var newPeer *nbpeer.Peer
 	var updateAccountPeers bool
 
-	err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
-		var setupKeyID string
-		var setupKeyName string
-		var ephemeral bool
-		var groupsToAdd []string
-		var allowExtraDNSLabels bool
-		if addedByUser {
-			user, err := transaction.GetUserByUserID(ctx, store.LockingStrengthUpdate, userID)
-			if err != nil {
-				return fmt.Errorf("failed to get user groups: %w", err)
-			}
-			groupsToAdd = user.AutoGroups
-			opEvent.InitiatorID = userID
-			opEvent.Activity = activity.PeerAddedByUser
-		} else {
-			// Validate the setup key
-			sk, err := transaction.GetSetupKeyBySecret(ctx, store.LockingStrengthUpdate, encodedHashedKey)
-			if err != nil {
-				return fmt.Errorf("failed to get setup key: %w", err)
-			}
-
-			if !sk.IsValid() {
-				return status.Errorf(status.PreconditionFailed, "couldn't add peer: setup key is invalid")
-			}
+	var setupKeyID string
+	var setupKeyName string
+	var ephemeral bool
+	var groupsToAdd []string
+	var allowExtraDNSLabels bool
+	var accountID string
+	var isEphemeral bool
+	if addedByUser {
+		user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
+		if err != nil {
+			return nil, nil, nil, status.Errorf(status.NotFound, "failed adding new peer: user not found")
+		}
+		groupsToAdd = user.AutoGroups
+		opEvent.InitiatorID = userID
+		opEvent.Activity = activity.PeerAddedByUser
+		accountID = user.AccountID
+	} else {
+		// Validate the setup key
+		sk, err := am.Store.GetSetupKeyBySecret(ctx, store.LockingStrengthNone, encodedHashedKey)
+		if err != nil {
+			return nil, nil, nil, status.Errorf(status.NotFound, "couldn't add peer: setup key is invalid")
+		}
 
-			opEvent.InitiatorID = sk.Id
-			opEvent.Activity = activity.PeerAddedWithSetupKey
-			groupsToAdd = sk.AutoGroups
-			ephemeral = sk.Ephemeral
-			setupKeyID = sk.Id
-			setupKeyName = sk.Name
-			allowExtraDNSLabels = sk.AllowExtraDNSLabels
+		// we will check key twice for early return
+		if !sk.IsValid() {
+			return nil, nil, nil, status.Errorf(status.NotFound, "couldn't add peer: setup key is invalid")
+		}
 
-			if !sk.AllowExtraDNSLabels && len(peer.ExtraDNSLabels) > 0 {
-				return status.Errorf(status.PreconditionFailed, "couldn't add peer: setup key doesn't allow extra DNS labels")
-			}
+		opEvent.InitiatorID = sk.Id
+		opEvent.Activity = activity.PeerAddedWithSetupKey
+		groupsToAdd = sk.AutoGroups
+		ephemeral = sk.Ephemeral
+		setupKeyID = sk.Id
+		setupKeyName = sk.Name
+		allowExtraDNSLabels = sk.AllowExtraDNSLabels
+		accountID = sk.AccountID
+		isEphemeral = sk.Ephemeral
+		if !sk.AllowExtraDNSLabels && len(peer.ExtraDNSLabels) > 0 {
+			return nil, nil, nil, status.Errorf(status.PreconditionFailed, "couldn't add peer: setup key doesn't allow extra DNS labels")
 		}
+	}
+	opEvent.AccountID = accountID
 
-		if (strings.ToLower(peer.Meta.Hostname) == "iphone" || strings.ToLower(peer.Meta.Hostname) == "ipad") && userID != "" {
-			if am.idpManager != nil {
-				userdata, err := am.idpManager.GetUserDataByID(ctx, userID, idp.AppMetadata{WTAccountID: accountID})
-				if err == nil && userdata != nil {
-					peer.Meta.Hostname = fmt.Sprintf("%s-%s", peer.Meta.Hostname, strings.Split(userdata.Email, "@")[0])
-				}
+	if (strings.ToLower(peer.Meta.Hostname) == "iphone" || strings.ToLower(peer.Meta.Hostname) == "ipad") && userID != "" {
+		if am.idpManager != nil {
+			userdata, err := am.idpManager.GetUserDataByID(ctx, userID, idp.AppMetadata{WTAccountID: accountID})
+			if err == nil && userdata != nil {
+				peer.Meta.Hostname = fmt.Sprintf("%s-%s", peer.Meta.Hostname, strings.Split(userdata.Email, "@")[0])
 			}
 		}
+	}
 
-		freeLabel, err := am.getFreeDNSLabel(ctx, transaction, accountID, peer.Meta.Hostname)
-		if err != nil {
-			return fmt.Errorf("failed to get free DNS label: %w", err)
-		}
+	if err := domain.ValidateDomainsList(peer.ExtraDNSLabels); err != nil {
+		return nil, nil, nil, status.Errorf(status.InvalidArgument, "invalid extra DNS labels: %v", err)
+	}
 
-		freeIP, err := getFreeIP(ctx, transaction, accountID)
-		if err != nil {
-			return fmt.Errorf("failed to get free IP: %w", err)
-		}
-
-		if err := domain.ValidateDomainsList(peer.ExtraDNSLabels); err != nil {
-			return status.Errorf(status.InvalidArgument, "invalid extra DNS labels: %v", err)
-		}
-
-		registrationTime := time.Now().UTC()
-		newPeer = &nbpeer.Peer{
-			ID:                          xid.New().String(),
-			AccountID:                   accountID,
-			Key:                         peer.Key,
-			IP:                          freeIP,
-			Meta:                        peer.Meta,
-			Name:                        peer.Meta.Hostname,
-			DNSLabel:                    freeLabel,
-			UserID:                      userID,
-			Status:                      &nbpeer.PeerStatus{Connected: false, LastSeen: registrationTime},
-			SSHEnabled:                  false,
-			SSHKey:                      peer.SSHKey,
-			LastLogin:                   &registrationTime,
-			CreatedAt:                   registrationTime,
-			LoginExpirationEnabled:      addedByUser,
-			Ephemeral:                   ephemeral,
-			Location:                    peer.Location,
-			InactivityExpirationEnabled: addedByUser,
-			ExtraDNSLabels:              peer.ExtraDNSLabels,
-			AllowExtraDNSLabels:         allowExtraDNSLabels,
-		}
-		settings, err := transaction.GetAccountSettings(ctx, store.LockingStrengthShare, accountID)
+	registrationTime := time.Now().UTC()
+	newPeer = &nbpeer.Peer{
+		ID:                          xid.New().String(),
+		AccountID:                   accountID,
+		Key:                         peer.Key,
+		Meta:                        peer.Meta,
+		Name:                        peer.Meta.Hostname,
+		UserID:                      userID,
+		Status:                      &nbpeer.PeerStatus{Connected: false, LastSeen: registrationTime},
+		SSHEnabled:                  false,
+		SSHKey:                      peer.SSHKey,
+		LastLogin:                   &registrationTime,
+		CreatedAt:                   registrationTime,
+		LoginExpirationEnabled:      addedByUser,
+		Ephemeral:                   ephemeral,
+		Location:                    peer.Location,
+		InactivityExpirationEnabled: addedByUser,
+		ExtraDNSLabels:              peer.ExtraDNSLabels,
+		AllowExtraDNSLabels:         allowExtraDNSLabels,
+	}
+	settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("failed to get account settings: %w", err)
+	}
+
+	if am.geo != nil && newPeer.Location.ConnectionIP != nil {
+		location, err := am.geo.Lookup(newPeer.Location.ConnectionIP)
 		if err != nil {
-			return fmt.Errorf("failed to get account settings: %w", err)
+			log.WithContext(ctx).Warnf("failed to get location for new peer realip: [%s]: %v", newPeer.Location.ConnectionIP.String(), err)
+		} else {
+			newPeer.Location.CountryCode = location.Country.ISOCode
+			newPeer.Location.CityName = location.City.Names.En
+			newPeer.Location.GeoNameID = location.City.GeonameID
 		}
+	}
+
+	newPeer = am.integratedPeerValidator.PreparePeer(ctx, accountID, newPeer, groupsToAdd, settings.Extra)
 
-		opEvent.TargetID = newPeer.ID
-		opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain(settings))
-		if !addedByUser {
-			opEvent.Meta["setup_key_name"] = setupKeyName
+	network, err := am.Store.GetAccountNetwork(ctx, store.LockingStrengthNone, accountID)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("failed getting network: %w", err)
+	}
+
+	maxAttempts := 10
+	for attempt := 1; attempt <= maxAttempts; attempt++ {
+		var freeIP net.IP
+		freeIP, err = types.AllocateRandomPeerIP(network.Net)
+		if err != nil {
+			return nil, nil, nil, fmt.Errorf("failed to get free IP: %w", err)
 		}
 
-		if am.geo != nil && newPeer.Location.ConnectionIP != nil {
-			location, err := am.geo.Lookup(newPeer.Location.ConnectionIP)
+		var freeLabel string
+		if isEphemeral || attempt > 1 {
+			freeLabel, err = getPeerIPDNSLabel(freeIP, peer.Meta.Hostname)
 			if err != nil {
-				log.WithContext(ctx).Warnf("failed to get location for new peer realip: [%s]: %v", newPeer.Location.ConnectionIP.String(), err)
-			} else {
-				newPeer.Location.CountryCode = location.Country.ISOCode
-				newPeer.Location.CityName = location.City.Names.En
-				newPeer.Location.GeoNameID = location.City.GeonameID
+				return nil, nil, nil, fmt.Errorf("failed to get free DNS label: %w", err)
+			}
+		} else {
+			freeLabel, err = nbdns.GetParsedDomainLabel(peer.Meta.Hostname)
+			if err != nil {
+				return nil, nil, nil, fmt.Errorf("failed to get free DNS label: %w", err)
 			}
 		}
+		newPeer.DNSLabel = freeLabel
+		newPeer.IP = freeIP
 
-		newPeer = am.integratedPeerValidator.PreparePeer(ctx, accountID, newPeer, groupsToAdd, settings.Extra)
+		unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
+		defer func() {
+			if unlock != nil {
+				unlock()
+			}
+		}()
 
-		err = transaction.AddPeerToAllGroup(ctx, store.LockingStrengthUpdate, accountID, newPeer.ID)
-		if err != nil {
-			return fmt.Errorf("failed adding peer to All group: %w", err)
-		}
+		err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
+			err = transaction.AddPeerToAccount(ctx, store.LockingStrengthUpdate, newPeer)
+			if err != nil {
+				return err
+			}
 
-		if len(groupsToAdd) > 0 {
-			for _, g := range groupsToAdd {
-				err = transaction.AddPeerToGroup(ctx, store.LockingStrengthUpdate, accountID, newPeer.ID, g)
-				if err != nil {
-					return err
+			err = transaction.AddPeerToAllGroup(ctx, store.LockingStrengthUpdate, accountID, newPeer.ID)
+			if err != nil {
+				return fmt.Errorf("failed adding peer to All group: %w", err)
+			}
+
+			if len(groupsToAdd) > 0 {
+				for _, g := range groupsToAdd {
+					err = transaction.AddPeerToGroup(ctx, store.LockingStrengthUpdate, accountID, newPeer.ID, g)
+					if err != nil {
+						return err
+					}
 				}
 			}
-		}
 
-		err = transaction.AddPeerToAccount(ctx, store.LockingStrengthUpdate, newPeer)
-		if err != nil {
-			return fmt.Errorf("failed to add peer to account: %w", err)
-		}
+			if addedByUser {
+				err := transaction.SaveUserLastLogin(ctx, accountID, userID, newPeer.GetLastLogin())
+				if err != nil {
+					log.WithContext(ctx).Debugf("failed to update user last login: %v", err)
+				}
+			} else {
+				sk, err := transaction.GetSetupKeyBySecret(ctx, store.LockingStrengthUpdate, encodedHashedKey)
+				if err != nil {
+					return fmt.Errorf("failed to get setup key: %w", err)
+				}
 
-		err = transaction.IncrementNetworkSerial(ctx, store.LockingStrengthUpdate, accountID)
-		if err != nil {
-			return fmt.Errorf("failed to increment network serial: %w", err)
-		}
+				// we validate at the end to not block the setup key for too long
+				if !sk.IsValid() {
+					return status.Errorf(status.PreconditionFailed, "couldn't add peer: setup key is invalid")
+				}
 
-		if addedByUser {
-			err := transaction.SaveUserLastLogin(ctx, accountID, userID, newPeer.GetLastLogin())
-			if err != nil {
-				log.WithContext(ctx).Debugf("failed to update user last login: %v", err)
+				err = transaction.IncrementSetupKeyUsage(ctx, setupKeyID)
+				if err != nil {
+					return fmt.Errorf("failed to increment setup key usage: %w", err)
+				}
 			}
-		} else {
-			err = transaction.IncrementSetupKeyUsage(ctx, setupKeyID)
+
+			err = transaction.IncrementNetworkSerial(ctx, store.LockingStrengthUpdate, accountID)
 			if err != nil {
-				return fmt.Errorf("failed to increment setup key usage: %w", err)
+				return fmt.Errorf("failed to increment network serial: %w", err)
 			}
+
+			log.WithContext(ctx).Debugf("Peer %s added to account %s", newPeer.ID, accountID)
+			return nil
+		})
+		if err == nil {
+			unlock()
+			unlock = nil
+			break
 		}
 
-		updateAccountPeers, err = isPeerInActiveGroup(ctx, transaction, accountID, newPeer.ID)
-		if err != nil {
-			return err
+		if isUniqueConstraintError(err) {
+			unlock()
+			unlock = nil
+			log.WithContext(ctx).WithFields(log.Fields{"dns_label": freeLabel, "ip": freeIP}).Tracef("Failed to add peer in attempt %d, retrying: %v", attempt, err)
+			continue
 		}
 
-		log.WithContext(ctx).Debugf("Peer %s added to account %s", newPeer.ID, accountID)
-		return nil
-	})
+		return nil, nil, nil, fmt.Errorf("failed to add peer to database: %w", err)
+	}
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("failed to add peer to database after %d attempts: %w", maxAttempts, err)
+	}
 
+	updateAccountPeers, err = isPeerInActiveGroup(ctx, am.Store, accountID, newPeer.ID)
 	if err != nil {
-		return nil, nil, nil, fmt.Errorf("failed to add peer to database: %w", err)
+		updateAccountPeers = true
 	}
 
 	if newPeer == nil {
 		return nil, nil, nil, fmt.Errorf("new peer is nil")
 	}
 
-	am.StoreEvent(ctx, opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta)
+	opEvent.TargetID = newPeer.ID
+	opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain(settings))
+	if !addedByUser {
+		opEvent.Meta["setup_key_name"] = setupKeyName
+	}
 
-	unlock()
-	unlock = nil
+	am.StoreEvent(ctx, opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta)
 
 	if updateAccountPeers {
 		am.BufferUpdateAccountPeers(ctx, accountID)
@@ -673,23 +701,15 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, setupKey, userID s
 	return am.getValidatedPeerWithMap(ctx, false, accountID, newPeer)
 }
 
-func getFreeIP(ctx context.Context, transaction store.Store, accountID string) (net.IP, error) {
-	takenIps, err := transaction.GetTakenIPs(ctx, store.LockingStrengthShare, accountID)
-	if err != nil {
-		return nil, fmt.Errorf("failed to get taken IPs: %w", err)
-	}
+func getPeerIPDNSLabel(ip net.IP, peerHostName string) (string, error) {
+	ip = ip.To4()
 
-	network, err := transaction.GetAccountNetwork(ctx, store.LockingStrengthUpdate, accountID)
+	dnsName, err := nbdns.GetParsedDomainLabel(peerHostName)
 	if err != nil {
-		return nil, fmt.Errorf("failed getting network: %w", err)
+		return "", fmt.Errorf("failed to parse peer host name %s: %w", peerHostName, err)
 	}
 
-	nextIp, err := types.AllocatePeerIP(network.Net, takenIps)
-	if err != nil {
-		return nil, fmt.Errorf("failed to allocate new peer ip: %w", err)
-	}
-
-	return nextIp, nil
+	return fmt.Sprintf("%s-%d-%d", dnsName, ip[2], ip[3]), nil
 }
 
 // SyncPeer checks whether peer is eligible for receiving NetworkMap (authenticated) and returns its NetworkMap if eligible
@@ -838,7 +858,7 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login types.Peer
 		if login.UserID != "" {
 			if peer.UserID != login.UserID {
 				log.Warnf("user mismatch when logging in peer %s: peer user %s, login user %s ", peer.ID, peer.UserID, login.UserID)
-				return status.Errorf(status.Unauthenticated, "invalid user")
+				return status.NewPeerLoginMismatchError()
 			}
 
 			changed, err := am.handleUserPeer(ctx, transaction, peer, settings)
@@ -1087,7 +1107,7 @@ func checkAuth(ctx context.Context, loginUserID string, peer *nbpeer.Peer) error
 	}
 	if peer.UserID != loginUserID {
 		log.WithContext(ctx).Warnf("user mismatch when logging in peer %s: peer user %s, login user %s ", peer.ID, peer.UserID, loginUserID)
-		return status.Errorf(status.Unauthenticated, "can't login with this credentials")
+		return status.NewPeerLoginMismatchError()
 	}
 	return nil
 }
@@ -1169,7 +1189,20 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account
 		return
 	}
 
-	start := time.Now()
+	globalStart := time.Now()
+
+	hasPeersConnected := false
+	for _, peer := range account.Peers {
+		if am.peersUpdateManager.HasChannel(peer.ID) {
+			hasPeersConnected = true
+			break
+		}
+
+	}
+
+	if !hasPeersConnected {
+		return
+	}
 
 	approvedPeersMap, err := am.integratedPeerValidator.GetValidatedPeers(account.Id, maps.Values(account.Groups), maps.Values(account.Peers), account.Settings.Extra)
 	if err != nil {
@@ -1192,6 +1225,12 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account
 		return
 	}
 
+	extraSetting, err := am.settingsManager.GetExtraSettings(ctx, accountID)
+	if err != nil {
+		log.WithContext(ctx).Errorf("failed to get flow enabled status: %v", err)
+		return
+	}
+
 	for _, peer := range account.Peers {
 		if !am.peersUpdateManager.HasChannel(peer.ID) {
 			log.WithContext(ctx).Tracef("peer %s doesn't have a channel, skipping network map update", peer.ID)
@@ -1204,26 +1243,32 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account
 			defer wg.Done()
 			defer func() { <-semaphore }()
 
+			start := time.Now()
+
 			postureChecks, err := am.getPeerPostureChecks(account, p.ID)
 			if err != nil {
 				log.WithContext(ctx).Debugf("failed to get posture checks for peer %s: %v", peer.ID, err)
 				return
 			}
 
+			am.metrics.UpdateChannelMetrics().CountCalcPostureChecksDuration(time.Since(start))
+			start = time.Now()
+
 			remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, p.ID, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics())
 
+			am.metrics.UpdateChannelMetrics().CountCalcPeerNetworkMapDuration(time.Since(start))
+			start = time.Now()
+
 			proxyNetworkMap, ok := proxyNetworkMaps[p.ID]
 			if ok {
 				remotePeerNetworkMap.Merge(proxyNetworkMap)
 			}
+			am.metrics.UpdateChannelMetrics().CountMergeNetworkMapDuration(time.Since(start))
 
-			extraSetting, err := am.settingsManager.GetExtraSettings(ctx, accountID)
-			if err != nil {
-				log.WithContext(ctx).Errorf("failed to get flow enabled status: %v", err)
-				return
-			}
-
+			start = time.Now()
 			update := toSyncResponse(ctx, nil, p, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings, extraSetting)
+			am.metrics.UpdateChannelMetrics().CountToSyncResponseDuration(time.Since(start))
+
 			am.peersUpdateManager.SendUpdate(ctx, p.ID, &UpdateMessage{Update: update, NetworkMap: remotePeerNetworkMap})
 		}(peer)
 	}
@@ -1232,22 +1277,43 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account
 
 	wg.Wait()
 	if am.metrics != nil {
-		am.metrics.AccountManagerMetrics().CountUpdateAccountPeersDuration(time.Since(start))
+		am.metrics.AccountManagerMetrics().CountUpdateAccountPeersDuration(time.Since(globalStart))
 	}
 }
 
+type bufferUpdate struct {
+	mu     sync.Mutex
+	next   *time.Timer
+	update atomic.Bool
+}
+
 func (am *DefaultAccountManager) BufferUpdateAccountPeers(ctx context.Context, accountID string) {
-	mu, _ := am.accountUpdateLocks.LoadOrStore(accountID, &sync.Mutex{})
-	lock := mu.(*sync.Mutex)
+	bufUpd, _ := am.accountUpdateLocks.LoadOrStore(accountID, &bufferUpdate{})
+	b := bufUpd.(*bufferUpdate)
 
-	if !lock.TryLock() {
+	if !b.mu.TryLock() {
+		b.update.Store(true)
 		return
 	}
 
+	if b.next != nil {
+		b.next.Stop()
+	}
+
 	go func() {
-		time.Sleep(time.Duration(am.updateAccountPeersBufferInterval.Load()))
-		lock.Unlock()
+		defer b.mu.Unlock()
 		am.UpdateAccountPeers(ctx, accountID)
+		if !b.update.Load() {
+			return
+		}
+		b.update.Store(false)
+		if b.next == nil {
+			b.next = time.AfterFunc(time.Duration(am.updateAccountPeersBufferInterval.Load()), func() {
+				am.UpdateAccountPeers(ctx, accountID)
+			})
+			return
+		}
+		b.next.Reset(time.Duration(am.updateAccountPeersBufferInterval.Load()))
 	}()
 }
 
@@ -1465,19 +1531,6 @@ func getPeerGroupIDs(ctx context.Context, transaction store.Store, accountID str
 	return groupIDs, err
 }
 
-func getPeerDNSLabels(ctx context.Context, transaction store.Store, accountID string) (types.LookupMap, error) {
-	dnsLabels, err := transaction.GetPeerLabelsInAccount(ctx, store.LockingStrengthShare, accountID)
-	if err != nil {
-		return nil, err
-	}
-
-	existingLabels := make(types.LookupMap)
-	for _, label := range dnsLabels {
-		existingLabels[label] = struct{}{}
-	}
-	return existingLabels, nil
-}
-
 // IsPeerInActiveGroup checks if the given peer is part of a group that is used
 // in an active DNS, route, or ACL configuration.
 func isPeerInActiveGroup(ctx context.Context, transaction store.Store, accountID, peerID string) (bool, error) {
@@ -1499,13 +1552,26 @@ func deletePeers(ctx context.Context, am *DefaultAccountManager, transaction sto
 	}
 	dnsDomain := am.GetDNSDomain(settings)
 
+	network, err := transaction.GetAccountNetwork(ctx, store.LockingStrengthShare, accountID)
+	if err != nil {
+		return nil, err
+	}
+
 	for _, peer := range peers {
-		if err := am.integratedPeerValidator.PeerDeleted(ctx, accountID, peer.ID); err != nil {
-			return nil, err
+		groups, err := transaction.GetPeerGroups(ctx, store.LockingStrengthUpdate, accountID, peer.ID)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get peer groups: %w", err)
 		}
 
-		network, err := transaction.GetAccountNetwork(ctx, store.LockingStrengthShare, accountID)
-		if err != nil {
+		for _, group := range groups {
+			group.RemovePeer(peer.ID)
+			err = transaction.SaveGroup(ctx, store.LockingStrengthUpdate, group)
+			if err != nil {
+				return nil, fmt.Errorf("failed to save group: %w", err)
+			}
+		}
+
+		if err := am.integratedPeerValidator.PeerDeleted(ctx, accountID, peer.ID); err != nil {
 			return nil, err
 		}
 
diff --git a/management/server/peer/peer.go b/management/server/peer/peer.go
index 8ce1dfb4..f7140e25 100644
--- a/management/server/peer/peer.go
+++ b/management/server/peer/peer.go
@@ -20,14 +20,14 @@ type Peer struct {
 	// WireGuard public key
 	Key string `gorm:"index"`
 	// IP address of the Peer
-	IP net.IP `gorm:"serializer:json"`
+	IP net.IP `gorm:"serializer:json"` // uniqueness index per accountID (check migrations)
 	// Meta is a Peer system meta data
 	Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"`
 	// Name is peer's name (machine name)
 	Name string
 	// DNSLabel is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's
 	// domain to the peer label. e.g. peer-dns-label.netbird.cloud
-	DNSLabel string
+	DNSLabel string // uniqueness index per accountID (check migrations)
 	// Status peer's management connection status
 	Status *PeerStatus `gorm:"embedded;embeddedPrefix:peer_status_"`
 	// The user ID that registered the peer
diff --git a/management/server/status/error.go b/management/server/status/error.go
index 5a6f6d1a..e3cc27b2 100644
--- a/management/server/status/error.go
+++ b/management/server/status/error.go
@@ -90,6 +90,11 @@ func NewAccountNotFoundError(accountKey string) error {
 	return Errorf(NotFound, "account not found: %s", accountKey)
 }
 
+// NewAccountOnboardingNotFoundError creates a new Error with NotFound type for a missing account onboarding
+func NewAccountOnboardingNotFoundError(accountKey string) error {
+	return Errorf(NotFound, "account onboarding not found: %s", accountKey)
+}
+
 // NewPeerNotPartOfAccountError creates a new Error with PermissionDenied type for a peer not being part of an account
 func NewPeerNotPartOfAccountError() error {
 	return Errorf(PermissionDenied, "peer is not part of this account")
@@ -105,11 +110,16 @@ func NewUserBlockedError() error {
 	return Errorf(PermissionDenied, "user is blocked")
 }
 
-// NewPeerNotRegisteredError creates a new Error with NotFound type for a missing peer
+// NewPeerNotRegisteredError creates a new Error with Unauthenticated type unregistered peer
 func NewPeerNotRegisteredError() error {
 	return Errorf(Unauthenticated, "peer is not registered")
 }
 
+// NewPeerLoginMismatchError creates a new Error with Unauthenticated type for a peer that is already registered for another user
+func NewPeerLoginMismatchError() error {
+	return Errorf(Unauthenticated, "peer is already registered by a different User or a Setup Key")
+}
+
 // NewPeerLoginExpiredError creates a new Error with PermissionDenied type for an expired peer
 func NewPeerLoginExpiredError() error {
 	return Errorf(PermissionDenied, "peer login has expired, please log in once more")
diff --git a/management/server/store/file_store.go b/management/server/store/file_store.go
index 3b95164f..d5d9337c 100644
--- a/management/server/store/file_store.go
+++ b/management/server/store/file_store.go
@@ -156,7 +156,7 @@ func restore(ctx context.Context, file string) (*FileStore, error) {
 
 		allGroup, err := account.GetGroupAll()
 		if err != nil {
-			log.WithContext(ctx).Errorf("unable to find the All group, this should happen only when migrate from a version that didn't support groups. Error: %v", err)
+			log.WithContext(ctx).Errorf("unable to find the All group, this should happen only when migratePreAuto from a version that didn't support groups. Error: %v", err)
 			// if the All group didn't exist we probably don't have routes to update
 			continue
 		}
diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go
index a6c4d56b..e380a7da 100644
--- a/management/server/store/sql_store.go
+++ b/management/server/store/sql_store.go
@@ -92,17 +92,20 @@ func NewSqlStore(ctx context.Context, db *gorm.DB, storeEngine types.Engine, met
 		return &SqlStore{db: db, storeEngine: storeEngine, metrics: metrics, installationPK: 1}, nil
 	}
 
-	if err := migrate(ctx, db); err != nil {
-		return nil, fmt.Errorf("migrate: %w", err)
+	if err := migratePreAuto(ctx, db); err != nil {
+		return nil, fmt.Errorf("migratePreAuto: %w", err)
 	}
 	err = db.AutoMigrate(
 		&types.SetupKey{}, &nbpeer.Peer{}, &types.User{}, &types.PersonalAccessToken{}, &types.Group{},
 		&types.Account{}, &types.Policy{}, &types.PolicyRule{}, &route.Route{}, &nbdns.NameServerGroup{},
 		&installation{}, &types.ExtraSettings{}, &posture.Checks{}, &nbpeer.NetworkAddress{},
-		&networkTypes.Network{}, &routerTypes.NetworkRouter{}, &resourceTypes.NetworkResource{},
+		&networkTypes.Network{}, &routerTypes.NetworkRouter{}, &resourceTypes.NetworkResource{}, &types.AccountOnboarding{},
 	)
 	if err != nil {
-		return nil, fmt.Errorf("auto migrate: %w", err)
+		return nil, fmt.Errorf("auto migratePreAuto: %w", err)
+	}
+	if err := migratePostAuto(ctx, db); err != nil {
+		return nil, fmt.Errorf("migratePostAuto: %w", err)
 	}
 
 	return &SqlStore{db: db, storeEngine: storeEngine, metrics: metrics, installationPK: 1}, nil
@@ -725,6 +728,32 @@ func (s *SqlStore) GetAccountMeta(ctx context.Context, lockStrength LockingStren
 	return &accountMeta, nil
 }
 
+// GetAccountOnboarding retrieves the onboarding information for a specific account.
+func (s *SqlStore) GetAccountOnboarding(ctx context.Context, accountID string) (*types.AccountOnboarding, error) {
+	var accountOnboarding types.AccountOnboarding
+	result := s.db.Model(&accountOnboarding).First(&accountOnboarding, accountIDCondition, accountID)
+	if result.Error != nil {
+		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
+			return nil, status.NewAccountOnboardingNotFoundError(accountID)
+		}
+		log.WithContext(ctx).Errorf("error when getting account onboarding %s from the store: %s", accountID, result.Error)
+		return nil, status.NewGetAccountFromStoreError(result.Error)
+	}
+
+	return &accountOnboarding, nil
+}
+
+// SaveAccountOnboarding updates the onboarding information for a specific account.
+func (s *SqlStore) SaveAccountOnboarding(ctx context.Context, onboarding *types.AccountOnboarding) error {
+	result := s.db.Clauses(clause.OnConflict{UpdateAll: true}).Create(onboarding)
+	if result.Error != nil {
+		log.WithContext(ctx).Errorf("error when saving account onboarding %s in the store: %s", onboarding.AccountID, result.Error)
+		return status.Errorf(status.Internal, "error when saving account onboarding %s in the store: %s", onboarding.AccountID, result.Error)
+	}
+
+	return nil
+}
+
 func (s *SqlStore) GetAccount(ctx context.Context, accountID string) (*types.Account, error) {
 	start := time.Now()
 	defer func() {
@@ -967,7 +996,7 @@ func (s *SqlStore) GetTakenIPs(ctx context.Context, lockStrength LockingStrength
 	return ips, nil
 }
 
-func (s *SqlStore) GetPeerLabelsInAccount(ctx context.Context, lockStrength LockingStrength, accountID string) ([]string, error) {
+func (s *SqlStore) GetPeerLabelsInAccount(ctx context.Context, lockStrength LockingStrength, accountID string, dnsLabel string) ([]string, error) {
 	tx := s.db
 	if lockStrength != LockingStrengthNone {
 		tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)})
@@ -975,7 +1004,7 @@ func (s *SqlStore) GetPeerLabelsInAccount(ctx context.Context, lockStrength Lock
 
 	var labels []string
 	result := tx.Model(&nbpeer.Peer{}).
-		Where("account_id = ?", accountID).
+		Where("account_id = ? AND dns_label LIKE ?", accountID, dnsLabel+"%").
 		Pluck("dns_label", &labels)
 
 	if result.Error != nil {
@@ -1184,7 +1213,7 @@ func NewSqliteStoreFromFileStore(ctx context.Context, fileStore *FileStore, data
 	for _, account := range fileStore.GetAllAccounts(ctx) {
 		_, err = account.GetGroupAll()
 		if err != nil {
-			if err := account.AddAllGroup(); err != nil {
+			if err := account.AddAllGroup(false); err != nil {
 				return nil, err
 			}
 		}
@@ -1254,7 +1283,7 @@ func (s *SqlStore) GetSetupKeyBySecret(ctx context.Context, lockStrength Locking
 
 	if result.Error != nil {
 		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
-			return nil, status.NewSetupKeyNotFoundError(key)
+			return nil, status.Errorf(status.PreconditionFailed, "setup key not found")
 		}
 		log.WithContext(ctx).Errorf("failed to get setup key by secret from store: %v", result.Error)
 		return nil, status.Errorf(status.Internal, "failed to get setup key by secret from store")
@@ -1410,7 +1439,11 @@ func (s *SqlStore) GetPeerGroups(ctx context.Context, lockStrength LockingStreng
 // GetAccountPeers retrieves peers for an account.
 func (s *SqlStore) GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) {
 	var peers []*nbpeer.Peer
-	query := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).Where(accountIDCondition, accountID)
+	tx := s.db
+	if lockStrength != LockingStrengthNone {
+		tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)})
+	}
+	query := tx.Where(accountIDCondition, accountID)
 
 	if nameFilter != "" {
 		query = query.Where("name LIKE ?", "%"+nameFilter+"%")
@@ -2546,6 +2579,27 @@ func (s *SqlStore) GetPeerByIP(ctx context.Context, lockStrength LockingStrength
 	return &peer, nil
 }
 
+func (s *SqlStore) GetPeerIdByLabel(ctx context.Context, lockStrength LockingStrength, accountID string, hostname string) (string, error) {
+	tx := s.db.WithContext(ctx)
+	if lockStrength != LockingStrengthNone {
+		tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)})
+	}
+
+	var peerID string
+	result := tx.Model(&nbpeer.Peer{}).
+		Select("id").
+		// Where(" = ?", hostname).
+		Where("account_id = ? AND dns_label = ?", accountID, hostname).
+		Limit(1).
+		Scan(&peerID)
+
+	if peerID == "" {
+		return "", gorm.ErrRecordNotFound
+	}
+
+	return peerID, result.Error
+}
+
 func (s *SqlStore) CountAccountsByPrivateDomain(ctx context.Context, domain string) (int64, error) {
 	var count int64
 	result := s.db.Model(&types.Account{}).
diff --git a/management/server/store/store.go b/management/server/store/store.go
index d41379b1..b3254c4c 100644
--- a/management/server/store/store.go
+++ b/management/server/store/store.go
@@ -52,6 +52,7 @@ type Store interface {
 	GetAllAccounts(ctx context.Context) []*types.Account
 	GetAccount(ctx context.Context, accountID string) (*types.Account, error)
 	GetAccountMeta(ctx context.Context, lockStrength LockingStrength, accountID string) (*types.AccountMeta, error)
+	GetAccountOnboarding(ctx context.Context, accountID string) (*types.AccountOnboarding, error)
 	AccountExists(ctx context.Context, lockStrength LockingStrength, id string) (bool, error)
 	GetAccountDomainAndCategory(ctx context.Context, lockStrength LockingStrength, accountID string) (string, string, error)
 	GetAccountByUser(ctx context.Context, userID string) (*types.Account, error)
@@ -74,6 +75,7 @@ type Store interface {
 	SaveDNSSettings(ctx context.Context, lockStrength LockingStrength, accountID string, settings *types.DNSSettings) error
 	SaveAccountSettings(ctx context.Context, lockStrength LockingStrength, accountID string, settings *types.Settings) error
 	CountAccountsByPrivateDomain(ctx context.Context, domain string) (int64, error)
+	SaveAccountOnboarding(ctx context.Context, onboarding *types.AccountOnboarding) error
 
 	GetUserByPATID(ctx context.Context, lockStrength LockingStrength, patID string) (*types.User, error)
 	GetUserByUserID(ctx context.Context, lockStrength LockingStrength, userID string) (*types.User, error)
@@ -117,7 +119,7 @@ type Store interface {
 	SavePostureChecks(ctx context.Context, lockStrength LockingStrength, postureCheck *posture.Checks) error
 	DeletePostureChecks(ctx context.Context, lockStrength LockingStrength, accountID, postureChecksID string) error
 
-	GetPeerLabelsInAccount(ctx context.Context, lockStrength LockingStrength, accountId string) ([]string, error)
+	GetPeerLabelsInAccount(ctx context.Context, lockStrength LockingStrength, accountId string, hostname string) ([]string, error)
 	AddPeerToAllGroup(ctx context.Context, lockStrength LockingStrength, accountID string, peerID string) error
 	AddPeerToGroup(ctx context.Context, lockStrength LockingStrength, accountId string, peerId string, groupID string) error
 	GetPeerGroups(ctx context.Context, lockStrength LockingStrength, accountId string, peerId string) ([]*types.Group, error)
@@ -193,6 +195,7 @@ type Store interface {
 	SaveNetworkResource(ctx context.Context, lockStrength LockingStrength, resource *resourceTypes.NetworkResource) error
 	DeleteNetworkResource(ctx context.Context, lockStrength LockingStrength, accountID, resourceID string) error
 	GetPeerByIP(ctx context.Context, lockStrength LockingStrength, accountID string, ip net.IP) (*nbpeer.Peer, error)
+	GetPeerIdByLabel(ctx context.Context, lockStrength LockingStrength, accountID string, hostname string) (string, error)
 }
 
 const (
@@ -234,9 +237,9 @@ func getStoreEngine(ctx context.Context, dataDir string, kind types.Engine) type
 			if util.FileExists(jsonStoreFile) && !util.FileExists(sqliteStoreFile) {
 				log.WithContext(ctx).Warnf("unsupported store engine specified, but found %s. Automatically migrating to SQLite.", jsonStoreFile)
 
-				// Attempt to migrate from JSON store to SQLite
+				// Attempt to migratePreAuto from JSON store to SQLite
 				if err := MigrateFileStoreToSqlite(ctx, dataDir); err != nil {
-					log.WithContext(ctx).Errorf("failed to migrate filestore to SQLite: %v", err)
+					log.WithContext(ctx).Errorf("failed to migratePreAuto filestore to SQLite: %v", err)
 					kind = types.FileStoreEngine
 				}
 			}
@@ -280,9 +283,9 @@ func checkFileStoreEngine(kind types.Engine, dataDir string) error {
 	return nil
 }
 
-// migrate migrates the SQLite database to the latest schema
-func migrate(ctx context.Context, db *gorm.DB) error {
-	migrations := getMigrations(ctx)
+// migratePreAuto migrates the SQLite database to the latest schema
+func migratePreAuto(ctx context.Context, db *gorm.DB) error {
+	migrations := getMigrationsPreAuto(ctx)
 
 	for _, m := range migrations {
 		if err := m(db); err != nil {
@@ -293,7 +296,7 @@ func migrate(ctx context.Context, db *gorm.DB) error {
 	return nil
 }
 
-func getMigrations(ctx context.Context) []migrationFunc {
+func getMigrationsPreAuto(ctx context.Context) []migrationFunc {
 	return []migrationFunc{
 		func(db *gorm.DB) error {
 			return migration.MigrateFieldFromGobToJSON[types.Account, net.IPNet](ctx, db, "network_net")
@@ -329,6 +332,28 @@ func getMigrations(ctx context.Context) []migrationFunc {
 			return migration.DropIndex[routerTypes.NetworkRouter](ctx, db, "idx_network_routers_id")
 		},
 	}
+} // migratePostAuto migrates the SQLite database to the latest schema
+func migratePostAuto(ctx context.Context, db *gorm.DB) error {
+	migrations := getMigrationsPostAuto(ctx)
+
+	for _, m := range migrations {
+		if err := m(db); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func getMigrationsPostAuto(ctx context.Context) []migrationFunc {
+	return []migrationFunc{
+		func(db *gorm.DB) error {
+			return migration.CreateIndexIfNotExists[nbpeer.Peer](ctx, db, "idx_account_ip", "account_id", "ip")
+		},
+		func(db *gorm.DB) error {
+			return migration.CreateIndexIfNotExists[nbpeer.Peer](ctx, db, "idx_account_dnslabel", "account_id", "dns_label")
+		},
+	}
 }
 
 // NewTestStoreFromSQL is only used in tests. It will create a test database base of the store engine set in env.
@@ -391,7 +416,7 @@ func addAllGroupToAccount(ctx context.Context, store Store) error {
 
 		_, err := account.GetGroupAll()
 		if err != nil {
-			if err := account.AddAllGroup(); err != nil {
+			if err := account.AddAllGroup(false); err != nil {
 				return err
 			}
 			shouldSave = true
@@ -577,7 +602,7 @@ func MigrateFileStoreToSqlite(ctx context.Context, dataDir string) error {
 
 	sqliteStoreAccounts := len(store.GetAllAccounts(ctx))
 	if fsStoreAccounts != sqliteStoreAccounts {
-		return fmt.Errorf("failed to migrate accounts from file to sqlite. Expected accounts: %d, got: %d",
+		return fmt.Errorf("failed to migratePreAuto accounts from file to sqlite. Expected accounts: %d, got: %d",
 			fsStoreAccounts, sqliteStoreAccounts)
 	}
 
diff --git a/management/server/telemetry/updatechannel_metrics.go b/management/server/telemetry/updatechannel_metrics.go
index 584b9ec2..2b280b35 100644
--- a/management/server/telemetry/updatechannel_metrics.go
+++ b/management/server/telemetry/updatechannel_metrics.go
@@ -18,6 +18,10 @@ type UpdateChannelMetrics struct {
 	getAllConnectedPeersDurationMicro metric.Int64Histogram
 	getAllConnectedPeers              metric.Int64Histogram
 	hasChannelDurationMicro           metric.Int64Histogram
+	calcPostureChecksDurationMicro    metric.Int64Histogram
+	calcPeerNetworkMapDurationMs      metric.Int64Histogram
+	mergeNetworkMapDurationMicro      metric.Int64Histogram
+	toSyncResponseDurationMicro       metric.Int64Histogram
 	ctx                               context.Context
 }
 
@@ -89,6 +93,38 @@ func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateCh
 		return nil, err
 	}
 
+	calcPostureChecksDurationMicro, err := meter.Int64Histogram("management.updatechannel.calc.posturechecks.duration.micro",
+		metric.WithUnit("microseconds"),
+		metric.WithDescription("Duration of how long it takes to get the posture checks for a peer"),
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	calcPeerNetworkMapDurationMs, err := meter.Int64Histogram("management.updatechannel.calc.networkmap.duration.ms",
+		metric.WithUnit("milliseconds"),
+		metric.WithDescription("Duration of how long it takes to calculate the network map for a peer"),
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	mergeNetworkMapDurationMicro, err := meter.Int64Histogram("management.updatechannel.merge.networkmap.duration.micro",
+		metric.WithUnit("microseconds"),
+		metric.WithDescription("Duration of how long it takes to merge the network maps for a peer"),
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	toSyncResponseDurationMicro, err := meter.Int64Histogram("management.updatechannel.tosyncresponse.duration.micro",
+		metric.WithUnit("microseconds"),
+		metric.WithDescription("Duration of how long it takes to convert the network map to sync response"),
+	)
+	if err != nil {
+		return nil, err
+	}
+
 	return &UpdateChannelMetrics{
 		createChannelDurationMicro:        createChannelDurationMicro,
 		closeChannelDurationMicro:         closeChannelDurationMicro,
@@ -98,6 +134,10 @@ func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateCh
 		getAllConnectedPeersDurationMicro: getAllConnectedPeersDurationMicro,
 		getAllConnectedPeers:              getAllConnectedPeers,
 		hasChannelDurationMicro:           hasChannelDurationMicro,
+		calcPostureChecksDurationMicro:    calcPostureChecksDurationMicro,
+		calcPeerNetworkMapDurationMs:      calcPeerNetworkMapDurationMs,
+		mergeNetworkMapDurationMicro:      mergeNetworkMapDurationMicro,
+		toSyncResponseDurationMicro:       toSyncResponseDurationMicro,
 		ctx:                               ctx,
 	}, nil
 }
@@ -137,3 +177,19 @@ func (metrics *UpdateChannelMetrics) CountGetAllConnectedPeersDuration(duration
 func (metrics *UpdateChannelMetrics) CountHasChannelDuration(duration time.Duration) {
 	metrics.hasChannelDurationMicro.Record(metrics.ctx, duration.Microseconds())
 }
+
+func (metrics *UpdateChannelMetrics) CountCalcPostureChecksDuration(duration time.Duration) {
+	metrics.calcPostureChecksDurationMicro.Record(metrics.ctx, duration.Microseconds())
+}
+
+func (metrics *UpdateChannelMetrics) CountCalcPeerNetworkMapDuration(duration time.Duration) {
+	metrics.calcPeerNetworkMapDurationMs.Record(metrics.ctx, duration.Milliseconds())
+}
+
+func (metrics *UpdateChannelMetrics) CountMergeNetworkMapDuration(duration time.Duration) {
+	metrics.mergeNetworkMapDurationMicro.Record(metrics.ctx, duration.Microseconds())
+}
+
+func (metrics *UpdateChannelMetrics) CountToSyncResponseDuration(duration time.Duration) {
+	metrics.toSyncResponseDurationMicro.Record(metrics.ctx, duration.Microseconds())
+}
diff --git a/management/server/types/account.go b/management/server/types/account.go
index 090ba76e..f0887be0 100644
--- a/management/server/types/account.go
+++ b/management/server/types/account.go
@@ -82,11 +82,11 @@ type Account struct {
 	DNSSettings            DNSSettings                       `gorm:"embedded;embeddedPrefix:dns_settings_"`
 	PostureChecks          []*posture.Checks                 `gorm:"foreignKey:AccountID;references:id"`
 	// Settings is a dictionary of Account settings
-	Settings *Settings `gorm:"embedded;embeddedPrefix:settings_"`
-
+	Settings         *Settings                        `gorm:"embedded;embeddedPrefix:settings_"`
 	Networks         []*networkTypes.Network          `gorm:"foreignKey:AccountID;references:id"`
 	NetworkRouters   []*routerTypes.NetworkRouter     `gorm:"foreignKey:AccountID;references:id"`
 	NetworkResources []*resourceTypes.NetworkResource `gorm:"foreignKey:AccountID;references:id"`
+	Onboarding       AccountOnboarding                `gorm:"foreignKey:AccountID;references:id;constraint:OnDelete:CASCADE"`
 }
 
 // Subclass used in gorm to only load network and not whole account
@@ -104,6 +104,20 @@ type AccountSettings struct {
 	Settings *Settings `gorm:"embedded;embeddedPrefix:settings_"`
 }
 
+type AccountOnboarding struct {
+	AccountID             string `gorm:"primaryKey"`
+	OnboardingFlowPending bool
+	SignupFormPending     bool
+	CreatedAt             time.Time
+	UpdatedAt             time.Time
+}
+
+// IsEqual compares two AccountOnboarding objects and returns true if they are equal
+func (o AccountOnboarding) IsEqual(onboarding AccountOnboarding) bool {
+	return o.OnboardingFlowPending == onboarding.OnboardingFlowPending &&
+		o.SignupFormPending == onboarding.SignupFormPending
+}
+
 // GetRoutesToSync returns the enabled routes for the peer ID and the routes
 // from the ACL peers that have distribution groups associated with the peer ID.
 // Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
@@ -866,6 +880,7 @@ func (a *Account) Copy() *Account {
 		Networks:               nets,
 		NetworkRouters:         networkRouters,
 		NetworkResources:       networkResources,
+		Onboarding:             a.Onboarding,
 	}
 }
 
@@ -1546,7 +1561,7 @@ func getPoliciesSourcePeers(policies []*Policy, groups map[string]*Group) map[st
 }
 
 // AddAllGroup to account object if it doesn't exist
-func (a *Account) AddAllGroup() error {
+func (a *Account) AddAllGroup(disableDefaultPolicy bool) error {
 	if len(a.Groups) == 0 {
 		allGroup := &Group{
 			ID:     xid.New().String(),
@@ -1558,6 +1573,10 @@ func (a *Account) AddAllGroup() error {
 		}
 		a.Groups = map[string]*Group{allGroup.ID: allGroup}
 
+		if disableDefaultPolicy {
+			return nil
+		}
+
 		id := xid.New().String()
 
 		defaultPolicy := &Policy{
diff --git a/management/server/types/config.go b/management/server/types/config.go
index 13e0f62b..5c64d6c6 100644
--- a/management/server/types/config.go
+++ b/management/server/types/config.go
@@ -53,6 +53,9 @@ type Config struct {
 	StoreConfig StoreConfig
 
 	ReverseProxy ReverseProxy
+	
+	// disable default all-to-all policy
+	DisableDefaultPolicy bool
 }
 
 // GetAuthAudiences returns the audience from the http config and device authorization flow config
diff --git a/management/server/types/network.go b/management/server/types/network.go
index 00082bb4..eb841526 100644
--- a/management/server/types/network.go
+++ b/management/server/types/network.go
@@ -1,6 +1,7 @@
 package types
 
 import (
+	"encoding/binary"
 	"math/rand"
 	"net"
 	"sync"
@@ -161,24 +162,65 @@ func (n *Network) Copy() *Network {
 // This method considers already taken IPs and reuses IPs if there are gaps in takenIps
 // E.g. if ipNet=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.4] then the result would be 100.30.0.2 or 100.30.0.3
 func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) {
-	takenIPMap := make(map[string]struct{})
-	takenIPMap[ipNet.IP.String()] = struct{}{}
+	baseIP := ipToUint32(ipNet.IP.Mask(ipNet.Mask))
+	totalIPs := uint32(1 << SubnetSize)
+
+	taken := make(map[uint32]struct{}, len(takenIps)+1)
+	taken[baseIP] = struct{}{}            // reserve network IP
+	taken[baseIP+totalIPs-1] = struct{}{} // reserve broadcast IP
+
 	for _, ip := range takenIps {
-		takenIPMap[ip.String()] = struct{}{}
+		taken[ipToUint32(ip)] = struct{}{}
 	}
 
-	ips, _ := generateIPs(&ipNet, takenIPMap)
+	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	maxAttempts := (int(totalIPs) - len(taken)) / 100
 
-	if len(ips) == 0 {
-		return nil, status.Errorf(status.PreconditionFailed, "failed allocating new IP for the ipNet %s - network is out of IPs", ipNet.String())
+	for i := 0; i < maxAttempts; i++ {
+		offset := uint32(rng.Intn(int(totalIPs-2))) + 1
+		candidate := baseIP + offset
+		if _, exists := taken[candidate]; !exists {
+			return uint32ToIP(candidate), nil
+		}
 	}
 
-	// pick a random IP
-	s := rand.NewSource(time.Now().Unix())
-	r := rand.New(s)
-	intn := r.Intn(len(ips))
+	for offset := uint32(1); offset < totalIPs-1; offset++ {
+		candidate := baseIP + offset
+		if _, exists := taken[candidate]; !exists {
+			return uint32ToIP(candidate), nil
+		}
+	}
+
+	return nil, status.Errorf(status.PreconditionFailed, "network %s is out of IPs", ipNet.String())
+}
+
+func AllocateRandomPeerIP(ipNet net.IPNet) (net.IP, error) {
+	baseIP := ipToUint32(ipNet.IP.Mask(ipNet.Mask))
+
+	ones, bits := ipNet.Mask.Size()
+	hostBits := bits - ones
+
+	totalIPs := uint32(1 << hostBits)
+
+	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
+	offset := uint32(rng.Intn(int(totalIPs-2))) + 1
+
+	candidate := baseIP + offset
+	return uint32ToIP(candidate), nil
+}
+
+func ipToUint32(ip net.IP) uint32 {
+	ip = ip.To4()
+	if len(ip) < 4 {
+		return 0
+	}
+	return binary.BigEndian.Uint32(ip)
+}
 
-	return ips[intn], nil
+func uint32ToIP(n uint32) net.IP {
+	ip := make(net.IP, 4)
+	binary.BigEndian.PutUint32(ip, n)
+	return ip
 }
 
 // generateIPs generates a list of all possible IPs of the given network excluding IPs specified in the exclusion list
diff --git a/management/server/user.go b/management/server/user.go
index a1f1c46d..7d838297 100644
--- a/management/server/user.go
+++ b/management/server/user.go
@@ -550,7 +550,7 @@ func (am *DefaultAccountManager) SaveOrAddUsers(ctx context.Context, accountID,
 				ctx, transaction, groupsMap, accountID, initiatorUserID, initiatorUser, update, addIfNotExists, settings,
 			)
 			if err != nil {
-				return fmt.Errorf("failed to process user update: %w", err)
+				return fmt.Errorf("failed to process update for user %s: %w", update.Id, err)
 			}
 			usersToSave = append(usersToSave, updatedUser)
 			addUserEvents = append(addUserEvents, userEvents...)
diff --git a/monotime/time.go b/monotime/time.go
new file mode 100644
index 00000000..ba45b665
--- /dev/null
+++ b/monotime/time.go
@@ -0,0 +1,35 @@
+package monotime
+
+import (
+	"time"
+)
+
+var (
+	baseWallTime time.Time
+	baseWallNano int64
+)
+
+type Time int64
+
+func init() {
+	baseWallTime = time.Now()
+	baseWallNano = baseWallTime.UnixNano()
+}
+
+// Now returns the current time as Unix nanoseconds (int64).
+// It uses monotonic time measurement from the base time to ensure
+// the returned value increases monotonically and is not affected
+// by system clock adjustments.
+//
+// Performance optimization: By capturing the base wall time once at startup
+// and using time.Since() for elapsed calculation, this avoids repeated
+// time.Now() calls and leverages Go's internal monotonic clock for
+// efficient duration measurement.
+func Now() Time {
+	elapsed := time.Since(baseWallTime)
+	return Time(baseWallNano + int64(elapsed))
+}
+
+func Since(t Time) time.Duration {
+	return time.Duration(Now() - t)
+}
diff --git a/relay/auth/validator.go b/relay/auth/validator.go
index 854efd5b..56a20fbf 100644
--- a/relay/auth/validator.go
+++ b/relay/auth/validator.go
@@ -7,13 +7,6 @@ import (
 	authv2 "github.com/netbirdio/netbird/relay/auth/hmac/v2"
 )
 
-// Validator is an interface that defines the Validate method.
-type Validator interface {
-	Validate(any) error
-	// Deprecated: Use Validate instead.
-	ValidateHelloMsgType(any) error
-}
-
 type TimedHMACValidator struct {
 	authenticatorV2 *authv2.Validator
 	authenticator   *auth.TimedHMACValidator
diff --git a/relay/client/client.go b/relay/client/client.go
index 9e7e5439..e4db278f 100644
--- a/relay/client/client.go
+++ b/relay/client/client.go
@@ -124,15 +124,14 @@ func (cc *connContainer) close() {
 // While the Connect is in progress, the OpenConn function will block until the connection is established with relay server.
 type Client struct {
 	log            *log.Entry
-	parentCtx      context.Context
 	connectionURL  string
 	authTokenStore *auth.TokenStore
-	hashedID       []byte
+	hashedID       messages.PeerID
 
 	bufPool *sync.Pool
 
 	relayConn        net.Conn
-	conns            map[string]*connContainer
+	conns            map[messages.PeerID]*connContainer
 	serviceIsRunning bool
 	mu               sync.Mutex // protect serviceIsRunning and conns
 	readLoopMutex    sync.Mutex
@@ -142,14 +141,17 @@ type Client struct {
 
 	onDisconnectListener func(string)
 	listenerMutex        sync.Mutex
+
+	stateSubscription *PeersStateSubscription
 }
 
 // NewClient creates a new client for the relay server. The client is not connected to the server until the Connect
-func NewClient(ctx context.Context, serverURL string, authTokenStore *auth.TokenStore, peerID string) *Client {
-	hashedID, hashedStringId := messages.HashID(peerID)
+func NewClient(serverURL string, authTokenStore *auth.TokenStore, peerID string) *Client {
+	hashedID := messages.HashID(peerID)
+	relayLog := log.WithFields(log.Fields{"relay": serverURL})
+
 	c := &Client{
-		log:            log.WithFields(log.Fields{"relay": serverURL}),
-		parentCtx:      ctx,
+		log:            relayLog,
 		connectionURL:  serverURL,
 		authTokenStore: authTokenStore,
 		hashedID:       hashedID,
@@ -159,14 +161,15 @@ func NewClient(ctx context.Context, serverURL string, authTokenStore *auth.Token
 				return &buf
 			},
 		},
-		conns: make(map[string]*connContainer),
+		conns: make(map[messages.PeerID]*connContainer),
 	}
-	c.log.Infof("create new relay connection: local peerID: %s, local peer hashedID: %s", peerID, hashedStringId)
+
+	c.log.Infof("create new relay connection: local peerID: %s, local peer hashedID: %s", peerID, hashedID)
 	return c
 }
 
 // Connect establishes a connection to the relay server. It blocks until the connection is established or an error occurs.
-func (c *Client) Connect() error {
+func (c *Client) Connect(ctx context.Context) error {
 	c.log.Infof("connecting to relay server")
 	c.readLoopMutex.Lock()
 	defer c.readLoopMutex.Unlock()
@@ -178,17 +181,27 @@ func (c *Client) Connect() error {
 		return nil
 	}
 
-	if err := c.connect(); err != nil {
+	instanceURL, err := c.connect(ctx)
+	if err != nil {
 		return err
 	}
+	c.muInstanceURL.Lock()
+	c.instanceURL = instanceURL
+	c.muInstanceURL.Unlock()
 
-	c.log = c.log.WithField("relay", c.instanceURL.String())
+	c.stateSubscription = NewPeersStateSubscription(c.log, c.relayConn, c.closeConnsByPeerID)
+
+	c.log = c.log.WithField("relay", instanceURL.String())
 	c.log.Infof("relay connection established")
 
 	c.serviceIsRunning = true
 
+	internallyStoppedFlag := newInternalStopFlag()
+	hc := healthcheck.NewReceiver(c.log)
+	go c.listenForStopEvents(ctx, hc, c.relayConn, internallyStoppedFlag)
+
 	c.wgReadLoop.Add(1)
-	go c.readLoop(c.relayConn)
+	go c.readLoop(hc, c.relayConn, internallyStoppedFlag)
 
 	return nil
 }
@@ -196,26 +209,50 @@ func (c *Client) Connect() error {
 // OpenConn create a new net.Conn for the destination peer ID. In case if the connection is in progress
 // to the relay server, the function will block until the connection is established or timed out. Otherwise,
 // it will return immediately.
+// It block until the server confirm the peer is online.
 // todo: what should happen if call with the same peerID with multiple times?
-func (c *Client) OpenConn(dstPeerID string) (net.Conn, error) {
-	c.mu.Lock()
-	defer c.mu.Unlock()
+func (c *Client) OpenConn(ctx context.Context, dstPeerID string) (net.Conn, error) {
+	peerID := messages.HashID(dstPeerID)
 
+	c.mu.Lock()
 	if !c.serviceIsRunning {
+		c.mu.Unlock()
 		return nil, fmt.Errorf("relay connection is not established")
 	}
-
-	hashedID, hashedStringID := messages.HashID(dstPeerID)
-	_, ok := c.conns[hashedStringID]
+	_, ok := c.conns[peerID]
 	if ok {
+		c.mu.Unlock()
 		return nil, ErrConnAlreadyExists
 	}
+	c.mu.Unlock()
 
-	c.log.Infof("open connection to peer: %s", hashedStringID)
+	if err := c.stateSubscription.WaitToBeOnlineAndSubscribe(ctx, peerID); err != nil {
+		c.log.Errorf("peer not available: %s, %s", peerID, err)
+		return nil, err
+	}
+
+	c.log.Infof("remote peer is available, prepare the relayed connection: %s", peerID)
 	msgChannel := make(chan Msg, 100)
-	conn := NewConn(c, hashedID, hashedStringID, msgChannel, c.instanceURL)
 
-	c.conns[hashedStringID] = newConnContainer(c.log, conn, msgChannel)
+	c.mu.Lock()
+	if !c.serviceIsRunning {
+		c.mu.Unlock()
+		return nil, fmt.Errorf("relay connection is not established")
+	}
+
+	c.muInstanceURL.Lock()
+	instanceURL := c.instanceURL
+	c.muInstanceURL.Unlock()
+	conn := NewConn(c, peerID, msgChannel, instanceURL)
+
+	_, ok = c.conns[peerID]
+	if ok {
+		c.mu.Unlock()
+		_ = conn.Close()
+		return nil, ErrConnAlreadyExists
+	}
+	c.conns[peerID] = newConnContainer(c.log, conn, msgChannel)
+	c.mu.Unlock()
 	return conn, nil
 }
 
@@ -254,76 +291,70 @@ func (c *Client) Close() error {
 	return c.close(true)
 }
 
-func (c *Client) connect() error {
-	rd := dialer.NewRaceDial(c.log, c.connectionURL, quic.Dialer{}, ws.Dialer{})
+func (c *Client) connect(ctx context.Context) (*RelayAddr, error) {
+	rd := dialer.NewRaceDial(c.log, dialer.DefaultConnectionTimeout, c.connectionURL, quic.Dialer{}, ws.Dialer{})
 	conn, err := rd.Dial()
 	if err != nil {
-		return err
+		return nil, err
 	}
 	c.relayConn = conn
 
-	if err = c.handShake(); err != nil {
+	instanceURL, err := c.handShake(ctx)
+	if err != nil {
 		cErr := conn.Close()
 		if cErr != nil {
 			c.log.Errorf("failed to close connection: %s", cErr)
 		}
-		return err
+		return nil, err
 	}
 
-	return nil
+	return instanceURL, nil
 }
 
-func (c *Client) handShake() error {
+func (c *Client) handShake(ctx context.Context) (*RelayAddr, error) {
 	msg, err := messages.MarshalAuthMsg(c.hashedID, c.authTokenStore.TokenBinary())
 	if err != nil {
 		c.log.Errorf("failed to marshal auth message: %s", err)
-		return err
+		return nil, err
 	}
 
 	_, err = c.relayConn.Write(msg)
 	if err != nil {
 		c.log.Errorf("failed to send auth message: %s", err)
-		return err
+		return nil, err
 	}
 	buf := make([]byte, messages.MaxHandshakeRespSize)
-	n, err := c.readWithTimeout(buf)
+	n, err := c.readWithTimeout(ctx, buf)
 	if err != nil {
 		c.log.Errorf("failed to read auth response: %s", err)
-		return err
+		return nil, err
 	}
 
 	_, err = messages.ValidateVersion(buf[:n])
 	if err != nil {
-		return fmt.Errorf("validate version: %w", err)
+		return nil, fmt.Errorf("validate version: %w", err)
 	}
 
 	msgType, err := messages.DetermineServerMessageType(buf[:n])
 	if err != nil {
 		c.log.Errorf("failed to determine message type: %s", err)
-		return err
+		return nil, err
 	}
 
 	if msgType != messages.MsgTypeAuthResponse {
 		c.log.Errorf("unexpected message type: %s", msgType)
-		return fmt.Errorf("unexpected message type")
+		return nil, fmt.Errorf("unexpected message type")
 	}
 
 	addr, err := messages.UnmarshalAuthResponse(buf[:n])
 	if err != nil {
-		return err
+		return nil, err
 	}
 
-	c.muInstanceURL.Lock()
-	c.instanceURL = &RelayAddr{addr: addr}
-	c.muInstanceURL.Unlock()
-	return nil
+	return &RelayAddr{addr: addr}, nil
 }
 
-func (c *Client) readLoop(relayConn net.Conn) {
-	internallyStoppedFlag := newInternalStopFlag()
-	hc := healthcheck.NewReceiver(c.log)
-	go c.listenForStopEvents(hc, relayConn, internallyStoppedFlag)
-
+func (c *Client) readLoop(hc *healthcheck.Receiver, relayConn net.Conn, internallyStoppedFlag *internalStopFlag) {
 	var (
 		errExit error
 		n       int
@@ -366,10 +397,7 @@ func (c *Client) readLoop(relayConn net.Conn) {
 
 	hc.Stop()
 
-	c.muInstanceURL.Lock()
-	c.instanceURL = nil
-	c.muInstanceURL.Unlock()
-
+	c.stateSubscription.Cleanup()
 	c.wgReadLoop.Done()
 	_ = c.close(false)
 	c.notifyDisconnected()
@@ -382,6 +410,14 @@ func (c *Client) handleMsg(msgType messages.MsgType, buf []byte, bufPtr *[]byte,
 		c.bufPool.Put(bufPtr)
 	case messages.MsgTypeTransport:
 		return c.handleTransportMsg(buf, bufPtr, internallyStoppedFlag)
+	case messages.MsgTypePeersOnline:
+		c.handlePeersOnlineMsg(buf)
+		c.bufPool.Put(bufPtr)
+		return true
+	case messages.MsgTypePeersWentOffline:
+		c.handlePeersWentOfflineMsg(buf)
+		c.bufPool.Put(bufPtr)
+		return true
 	case messages.MsgTypeClose:
 		c.log.Debugf("relay connection close by server")
 		c.bufPool.Put(bufPtr)
@@ -413,18 +449,16 @@ func (c *Client) handleTransportMsg(buf []byte, bufPtr *[]byte, internallyStoppe
 		return true
 	}
 
-	stringID := messages.HashIDToString(peerID)
-
 	c.mu.Lock()
 	if !c.serviceIsRunning {
 		c.mu.Unlock()
 		c.bufPool.Put(bufPtr)
 		return false
 	}
-	container, ok := c.conns[stringID]
+	container, ok := c.conns[*peerID]
 	c.mu.Unlock()
 	if !ok {
-		c.log.Errorf("peer not found: %s", stringID)
+		c.log.Errorf("peer not found: %s", peerID.String())
 		c.bufPool.Put(bufPtr)
 		return true
 	}
@@ -437,9 +471,9 @@ func (c *Client) handleTransportMsg(buf []byte, bufPtr *[]byte, internallyStoppe
 	return true
 }
 
-func (c *Client) writeTo(connReference *Conn, id string, dstID []byte, payload []byte) (int, error) {
+func (c *Client) writeTo(connReference *Conn, dstID messages.PeerID, payload []byte) (int, error) {
 	c.mu.Lock()
-	conn, ok := c.conns[id]
+	conn, ok := c.conns[dstID]
 	c.mu.Unlock()
 	if !ok {
 		return 0, net.ErrClosed
@@ -464,7 +498,7 @@ func (c *Client) writeTo(connReference *Conn, id string, dstID []byte, payload [
 	return len(payload), err
 }
 
-func (c *Client) listenForStopEvents(hc *healthcheck.Receiver, conn net.Conn, internalStopFlag *internalStopFlag) {
+func (c *Client) listenForStopEvents(ctx context.Context, hc *healthcheck.Receiver, conn net.Conn, internalStopFlag *internalStopFlag) {
 	for {
 		select {
 		case _, ok := <-hc.OnTimeout:
@@ -478,7 +512,7 @@ func (c *Client) listenForStopEvents(hc *healthcheck.Receiver, conn net.Conn, in
 				c.log.Warnf("failed to close connection: %s", err)
 			}
 			return
-		case <-c.parentCtx.Done():
+		case <-ctx.Done():
 			err := c.close(true)
 			if err != nil {
 				c.log.Errorf("failed to teardown connection: %s", err)
@@ -492,10 +526,31 @@ func (c *Client) closeAllConns() {
 	for _, container := range c.conns {
 		container.close()
 	}
-	c.conns = make(map[string]*connContainer)
+	c.conns = make(map[messages.PeerID]*connContainer)
+}
+
+func (c *Client) closeConnsByPeerID(peerIDs []messages.PeerID) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	for _, peerID := range peerIDs {
+		container, ok := c.conns[peerID]
+		if !ok {
+			c.log.Warnf("can not close connection, peer not found: %s", peerID)
+			continue
+		}
+
+		container.log.Infof("remote peer has been disconnected, free up connection: %s", peerID)
+		container.close()
+		delete(c.conns, peerID)
+	}
+
+	if err := c.stateSubscription.UnsubscribeStateChange(peerIDs); err != nil {
+		c.log.Errorf("failed to unsubscribe from peer state change: %s, %s", peerIDs, err)
+	}
 }
 
-func (c *Client) closeConn(connReference *Conn, id string) error {
+func (c *Client) closeConn(connReference *Conn, id messages.PeerID) error {
 	c.mu.Lock()
 	defer c.mu.Unlock()
 
@@ -507,6 +562,11 @@ func (c *Client) closeConn(connReference *Conn, id string) error {
 	if container.conn != connReference {
 		return fmt.Errorf("conn reference mismatch")
 	}
+
+	if err := c.stateSubscription.UnsubscribeStateChange([]messages.PeerID{id}); err != nil {
+		container.log.Errorf("failed to unsubscribe from peer state change: %s", err)
+	}
+
 	c.log.Infof("free up connection to peer: %s", id)
 	delete(c.conns, id)
 	container.close()
@@ -525,8 +585,12 @@ func (c *Client) close(gracefullyExit bool) error {
 		c.log.Warn("relay connection was already marked as not running")
 		return nil
 	}
-
 	c.serviceIsRunning = false
+
+	c.muInstanceURL.Lock()
+	c.instanceURL = nil
+	c.muInstanceURL.Unlock()
+
 	c.log.Infof("closing all peer connections")
 	c.closeAllConns()
 	if gracefullyExit {
@@ -559,8 +623,8 @@ func (c *Client) writeCloseMsg() {
 	}
 }
 
-func (c *Client) readWithTimeout(buf []byte) (int, error) {
-	ctx, cancel := context.WithTimeout(c.parentCtx, serverResponseTimeout)
+func (c *Client) readWithTimeout(ctx context.Context, buf []byte) (int, error) {
+	ctx, cancel := context.WithTimeout(ctx, serverResponseTimeout)
 	defer cancel()
 
 	readDone := make(chan struct{})
@@ -581,3 +645,21 @@ func (c *Client) readWithTimeout(buf []byte) (int, error) {
 		return n, err
 	}
 }
+
+func (c *Client) handlePeersOnlineMsg(buf []byte) {
+	peersID, err := messages.UnmarshalPeersOnlineMsg(buf)
+	if err != nil {
+		c.log.Errorf("failed to unmarshal peers online msg: %s", err)
+		return
+	}
+	c.stateSubscription.OnPeersOnline(peersID)
+}
+
+func (c *Client) handlePeersWentOfflineMsg(buf []byte) {
+	peersID, err := messages.UnMarshalPeersWentOffline(buf)
+	if err != nil {
+		c.log.Errorf("failed to unmarshal peers went offline msg: %s", err)
+		return
+	}
+	c.stateSubscription.OnPeersWentOffline(peersID)
+}
diff --git a/relay/client/conn.go b/relay/client/conn.go
index fe1b6fb5..d8cffa69 100644
--- a/relay/client/conn.go
+++ b/relay/client/conn.go
@@ -3,13 +3,14 @@ package client
 import (
 	"net"
 	"time"
+
+	"github.com/netbirdio/netbird/relay/messages"
 )
 
 // Conn represent a connection to a relayed remote peer.
 type Conn struct {
 	client      *Client
-	dstID       []byte
-	dstStringID string
+	dstID       messages.PeerID
 	messageChan chan Msg
 	instanceURL *RelayAddr
 }
@@ -17,14 +18,12 @@ type Conn struct {
 // NewConn creates a new connection to a relayed remote peer.
 // client: the client instance, it used to send messages to the destination peer
 // dstID: the destination peer ID
-// dstStringID: the destination peer ID in string format
 // messageChan: the channel where the messages will be received
 // instanceURL: the relay instance URL, it used to get the proper server instance address for the remote peer
-func NewConn(client *Client, dstID []byte, dstStringID string, messageChan chan Msg, instanceURL *RelayAddr) *Conn {
+func NewConn(client *Client, dstID messages.PeerID, messageChan chan Msg, instanceURL *RelayAddr) *Conn {
 	c := &Conn{
 		client:      client,
 		dstID:       dstID,
-		dstStringID: dstStringID,
 		messageChan: messageChan,
 		instanceURL: instanceURL,
 	}
@@ -33,7 +32,7 @@ func NewConn(client *Client, dstID []byte, dstStringID string, messageChan chan
 }
 
 func (c *Conn) Write(p []byte) (n int, err error) {
-	return c.client.writeTo(c, c.dstStringID, c.dstID, p)
+	return c.client.writeTo(c, c.dstID, p)
 }
 
 func (c *Conn) Read(b []byte) (n int, err error) {
@@ -48,7 +47,7 @@ func (c *Conn) Read(b []byte) (n int, err error) {
 }
 
 func (c *Conn) Close() error {
-	return c.client.closeConn(c, c.dstStringID)
+	return c.client.closeConn(c, c.dstID)
 }
 
 func (c *Conn) LocalAddr() net.Addr {
diff --git a/relay/client/dialer/race_dialer.go b/relay/client/dialer/race_dialer.go
index 11dba579..0550fc63 100644
--- a/relay/client/dialer/race_dialer.go
+++ b/relay/client/dialer/race_dialer.go
@@ -9,8 +9,8 @@ import (
 	log "github.com/sirupsen/logrus"
 )
 
-var (
-	connectionTimeout = 30 * time.Second
+const (
+	DefaultConnectionTimeout = 30 * time.Second
 )
 
 type DialeFn interface {
@@ -25,16 +25,18 @@ type dialResult struct {
 }
 
 type RaceDial struct {
-	log       *log.Entry
-	serverURL string
-	dialerFns []DialeFn
+	log               *log.Entry
+	serverURL         string
+	dialerFns         []DialeFn
+	connectionTimeout time.Duration
 }
 
-func NewRaceDial(log *log.Entry, serverURL string, dialerFns ...DialeFn) *RaceDial {
+func NewRaceDial(log *log.Entry, connectionTimeout time.Duration, serverURL string, dialerFns ...DialeFn) *RaceDial {
 	return &RaceDial{
-		log:       log,
-		serverURL: serverURL,
-		dialerFns: dialerFns,
+		log:               log,
+		serverURL:         serverURL,
+		dialerFns:         dialerFns,
+		connectionTimeout: connectionTimeout,
 	}
 }
 
@@ -58,7 +60,7 @@ func (r *RaceDial) Dial() (net.Conn, error) {
 }
 
 func (r *RaceDial) dial(dfn DialeFn, abortCtx context.Context, connChan chan dialResult) {
-	ctx, cancel := context.WithTimeout(abortCtx, connectionTimeout)
+	ctx, cancel := context.WithTimeout(abortCtx, r.connectionTimeout)
 	defer cancel()
 
 	r.log.Infof("dialing Relay server via %s", dfn.Protocol())
diff --git a/relay/client/guard.go b/relay/client/guard.go
index 554330ea..f4d3a8cc 100644
--- a/relay/client/guard.go
+++ b/relay/client/guard.go
@@ -8,7 +8,8 @@ import (
 	log "github.com/sirupsen/logrus"
 )
 
-var (
+const (
+	// TODO: make it configurable, the manager should validate all configurable parameters
 	reconnectingTimeout = 60 * time.Second
 )
 
@@ -80,7 +81,7 @@ func (g *Guard) tryToQuickReconnect(parentCtx context.Context, rc *Client) bool
 
 	log.Infof("try to reconnect to Relay server: %s", rc.connectionURL)
 
-	if err := rc.Connect(); err != nil {
+	if err := rc.Connect(parentCtx); err != nil {
 		log.Errorf("failed to reconnect to relay server: %s", err)
 		return false
 	}
diff --git a/relay/client/manager.go b/relay/client/manager.go
index 26b11305..f32bb9f2 100644
--- a/relay/client/manager.go
+++ b/relay/client/manager.go
@@ -39,17 +39,6 @@ func NewRelayTrack() *RelayTrack {
 
 type OnServerCloseListener func()
 
-// ManagerService is the interface for the relay manager.
-type ManagerService interface {
-	Serve() error
-	OpenConn(serverAddress, peerKey string) (net.Conn, error)
-	AddCloseListener(serverAddress string, onClosedListener OnServerCloseListener) error
-	RelayInstanceAddress() (string, error)
-	ServerURLs() []string
-	HasRelayAddress() bool
-	UpdateToken(token *relayAuth.Token) error
-}
-
 // Manager is a manager for the relay client instances. It establishes one persistent connection to the given relay URL
 // and automatically reconnect to them in case disconnection.
 // The manager also manage temporary relay connection. If a client wants to communicate with a client on a
@@ -65,7 +54,7 @@ type Manager struct {
 
 	relayClient *Client
 	// the guard logic can overwrite the relayClient variable, this mutex protect the usage of the variable
-	relayClientMu  sync.Mutex
+	relayClientMu  sync.RWMutex
 	reconnectGuard *Guard
 
 	relayClients      map[string]*RelayTrack
@@ -123,9 +112,9 @@ func (m *Manager) Serve() error {
 // OpenConn opens a connection to the given peer key. If the peer is on the same relay server, the connection will be
 // established via the relay server. If the peer is on a different relay server, the manager will establish a new
 // connection to the relay server. It returns back with a net.Conn what represent the remote peer connection.
-func (m *Manager) OpenConn(serverAddress, peerKey string) (net.Conn, error) {
-	m.relayClientMu.Lock()
-	defer m.relayClientMu.Unlock()
+func (m *Manager) OpenConn(ctx context.Context, serverAddress, peerKey string) (net.Conn, error) {
+	m.relayClientMu.RLock()
+	defer m.relayClientMu.RUnlock()
 
 	if m.relayClient == nil {
 		return nil, ErrRelayClientNotConnected
@@ -141,10 +130,10 @@ func (m *Manager) OpenConn(serverAddress, peerKey string) (net.Conn, error) {
 	)
 	if !foreign {
 		log.Debugf("open peer connection via permanent server: %s", peerKey)
-		netConn, err = m.relayClient.OpenConn(peerKey)
+		netConn, err = m.relayClient.OpenConn(ctx, peerKey)
 	} else {
 		log.Debugf("open peer connection via foreign server: %s", serverAddress)
-		netConn, err = m.openConnVia(serverAddress, peerKey)
+		netConn, err = m.openConnVia(ctx, serverAddress, peerKey)
 	}
 	if err != nil {
 		return nil, err
@@ -155,8 +144,8 @@ func (m *Manager) OpenConn(serverAddress, peerKey string) (net.Conn, error) {
 
 // Ready returns true if the home Relay client is connected to the relay server.
 func (m *Manager) Ready() bool {
-	m.relayClientMu.Lock()
-	defer m.relayClientMu.Unlock()
+	m.relayClientMu.RLock()
+	defer m.relayClientMu.RUnlock()
 
 	if m.relayClient == nil {
 		return false
@@ -174,8 +163,8 @@ func (m *Manager) SetOnReconnectedListener(f func()) {
 // AddCloseListener adds a listener to the given server instance address. The listener will be called if the connection
 // closed.
 func (m *Manager) AddCloseListener(serverAddress string, onClosedListener OnServerCloseListener) error {
-	m.relayClientMu.Lock()
-	defer m.relayClientMu.Unlock()
+	m.relayClientMu.RLock()
+	defer m.relayClientMu.RUnlock()
 
 	if m.relayClient == nil {
 		return ErrRelayClientNotConnected
@@ -199,8 +188,8 @@ func (m *Manager) AddCloseListener(serverAddress string, onClosedListener OnServ
 // RelayInstanceAddress returns the address of the permanent relay server. It could change if the network connection is
 // lost. This address will be sent to the target peer to choose the common relay server for the communication.
 func (m *Manager) RelayInstanceAddress() (string, error) {
-	m.relayClientMu.Lock()
-	defer m.relayClientMu.Unlock()
+	m.relayClientMu.RLock()
+	defer m.relayClientMu.RUnlock()
 
 	if m.relayClient == nil {
 		return "", ErrRelayClientNotConnected
@@ -229,7 +218,7 @@ func (m *Manager) UpdateToken(token *relayAuth.Token) error {
 	return m.tokenStore.UpdateToken(token)
 }
 
-func (m *Manager) openConnVia(serverAddress, peerKey string) (net.Conn, error) {
+func (m *Manager) openConnVia(ctx context.Context, serverAddress, peerKey string) (net.Conn, error) {
 	// check if already has a connection to the desired relay server
 	m.relayClientsMutex.RLock()
 	rt, ok := m.relayClients[serverAddress]
@@ -240,7 +229,7 @@ func (m *Manager) openConnVia(serverAddress, peerKey string) (net.Conn, error) {
 		if rt.err != nil {
 			return nil, rt.err
 		}
-		return rt.relayClient.OpenConn(peerKey)
+		return rt.relayClient.OpenConn(ctx, peerKey)
 	}
 	m.relayClientsMutex.RUnlock()
 
@@ -255,7 +244,7 @@ func (m *Manager) openConnVia(serverAddress, peerKey string) (net.Conn, error) {
 		if rt.err != nil {
 			return nil, rt.err
 		}
-		return rt.relayClient.OpenConn(peerKey)
+		return rt.relayClient.OpenConn(ctx, peerKey)
 	}
 
 	// create a new relay client and store it in the relayClients map
@@ -264,8 +253,8 @@ func (m *Manager) openConnVia(serverAddress, peerKey string) (net.Conn, error) {
 	m.relayClients[serverAddress] = rt
 	m.relayClientsMutex.Unlock()
 
-	relayClient := NewClient(m.ctx, serverAddress, m.tokenStore, m.peerID)
-	err := relayClient.Connect()
+	relayClient := NewClient(serverAddress, m.tokenStore, m.peerID)
+	err := relayClient.Connect(m.ctx)
 	if err != nil {
 		rt.err = err
 		rt.Unlock()
@@ -279,7 +268,7 @@ func (m *Manager) openConnVia(serverAddress, peerKey string) (net.Conn, error) {
 	rt.relayClient = relayClient
 	rt.Unlock()
 
-	conn, err := relayClient.OpenConn(peerKey)
+	conn, err := relayClient.OpenConn(ctx, peerKey)
 	if err != nil {
 		return nil, err
 	}
@@ -300,7 +289,9 @@ func (m *Manager) onServerConnected() {
 func (m *Manager) onServerDisconnected(serverAddress string) {
 	m.relayClientMu.Lock()
 	if serverAddress == m.relayClient.connectionURL {
-		go m.reconnectGuard.StartReconnectTrys(m.ctx, m.relayClient)
+		go func(client *Client) {
+			m.reconnectGuard.StartReconnectTrys(m.ctx, client)
+		}(m.relayClient)
 	}
 	m.relayClientMu.Unlock()
 
diff --git a/relay/client/peer_subscription.go b/relay/client/peer_subscription.go
new file mode 100644
index 00000000..85bd41cb
--- /dev/null
+++ b/relay/client/peer_subscription.go
@@ -0,0 +1,191 @@
+package client
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"sync"
+	"time"
+
+	log "github.com/sirupsen/logrus"
+
+	"github.com/netbirdio/netbird/relay/messages"
+)
+
+const (
+	OpenConnectionTimeout = 30 * time.Second
+)
+
+type relayedConnWriter interface {
+	Write(p []byte) (n int, err error)
+}
+
+// PeersStateSubscription manages subscriptions to peer state changes (online/offline)
+// over a relay connection. It allows tracking peers' availability and handling offline
+// events via a callback. We get online notification from the server only once.
+type PeersStateSubscription struct {
+	log             *log.Entry
+	relayConn       relayedConnWriter
+	offlineCallback func(peerIDs []messages.PeerID)
+
+	listenForOfflinePeers map[messages.PeerID]struct{}
+	waitingPeers          map[messages.PeerID]chan struct{}
+	mu                    sync.Mutex // Mutex to protect access to waitingPeers and listenForOfflinePeers
+}
+
+func NewPeersStateSubscription(log *log.Entry, relayConn relayedConnWriter, offlineCallback func(peerIDs []messages.PeerID)) *PeersStateSubscription {
+	return &PeersStateSubscription{
+		log:                   log,
+		relayConn:             relayConn,
+		offlineCallback:       offlineCallback,
+		listenForOfflinePeers: make(map[messages.PeerID]struct{}),
+		waitingPeers:          make(map[messages.PeerID]chan struct{}),
+	}
+}
+
+// OnPeersOnline should be called when a notification is received that certain peers have come online.
+// It checks if any of the peers are being waited on and signals their availability.
+func (s *PeersStateSubscription) OnPeersOnline(peersID []messages.PeerID) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	for _, peerID := range peersID {
+		waitCh, ok := s.waitingPeers[peerID]
+		if !ok {
+			// If meanwhile the peer was unsubscribed, we don't need to signal it
+			continue
+		}
+
+		waitCh <- struct{}{}
+		delete(s.waitingPeers, peerID)
+		close(waitCh)
+	}
+}
+
+func (s *PeersStateSubscription) OnPeersWentOffline(peersID []messages.PeerID) {
+	s.mu.Lock()
+	relevantPeers := make([]messages.PeerID, 0, len(peersID))
+	for _, peerID := range peersID {
+		if _, ok := s.listenForOfflinePeers[peerID]; ok {
+			relevantPeers = append(relevantPeers, peerID)
+		}
+	}
+	s.mu.Unlock()
+
+	if len(relevantPeers) > 0 {
+		s.offlineCallback(relevantPeers)
+	}
+}
+
+// WaitToBeOnlineAndSubscribe waits for a specific peer to come online and subscribes to its state changes.
+func (s *PeersStateSubscription) WaitToBeOnlineAndSubscribe(ctx context.Context, peerID messages.PeerID) error {
+	// Check if already waiting for this peer
+	s.mu.Lock()
+	if _, exists := s.waitingPeers[peerID]; exists {
+		s.mu.Unlock()
+		return errors.New("already waiting for peer to come online")
+	}
+
+	// Create a channel to wait for the peer to come online
+	waitCh := make(chan struct{}, 1)
+	s.waitingPeers[peerID] = waitCh
+	s.listenForOfflinePeers[peerID] = struct{}{}
+	s.mu.Unlock()
+
+	if err := s.subscribeStateChange(peerID); err != nil {
+		s.log.Errorf("failed to subscribe to peer state: %s", err)
+		s.mu.Lock()
+		if ch, exists := s.waitingPeers[peerID]; exists && ch == waitCh {
+			close(waitCh)
+			delete(s.waitingPeers, peerID)
+			delete(s.listenForOfflinePeers, peerID)
+		}
+		s.mu.Unlock()
+		return err
+	}
+
+	// Wait for peer to come online or context to be cancelled
+	timeoutCtx, cancel := context.WithTimeout(ctx, OpenConnectionTimeout)
+	defer cancel()
+	select {
+	case _, ok := <-waitCh:
+		if !ok {
+			return fmt.Errorf("wait for peer to come online has been cancelled")
+		}
+
+		s.log.Debugf("peer %s is now online", peerID)
+		return nil
+	case <-timeoutCtx.Done():
+		s.log.Debugf("context timed out while waiting for peer %s to come online", peerID)
+		if err := s.unsubscribeStateChange([]messages.PeerID{peerID}); err != nil {
+			s.log.Errorf("failed to unsubscribe from peer state: %s", err)
+		}
+		s.mu.Lock()
+		if ch, exists := s.waitingPeers[peerID]; exists && ch == waitCh {
+			close(waitCh)
+			delete(s.waitingPeers, peerID)
+			delete(s.listenForOfflinePeers, peerID)
+		}
+		s.mu.Unlock()
+		return timeoutCtx.Err()
+	}
+}
+
+func (s *PeersStateSubscription) UnsubscribeStateChange(peerIDs []messages.PeerID) error {
+	msgErr := s.unsubscribeStateChange(peerIDs)
+
+	s.mu.Lock()
+	for _, peerID := range peerIDs {
+		if wch, ok := s.waitingPeers[peerID]; ok {
+			close(wch)
+			delete(s.waitingPeers, peerID)
+		}
+
+		delete(s.listenForOfflinePeers, peerID)
+	}
+	s.mu.Unlock()
+
+	return msgErr
+}
+
+func (s *PeersStateSubscription) Cleanup() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	for _, waitCh := range s.waitingPeers {
+		close(waitCh)
+	}
+
+	s.waitingPeers = make(map[messages.PeerID]chan struct{})
+	s.listenForOfflinePeers = make(map[messages.PeerID]struct{})
+}
+
+func (s *PeersStateSubscription) subscribeStateChange(peerID messages.PeerID) error {
+	msgs, err := messages.MarshalSubPeerStateMsg([]messages.PeerID{peerID})
+	if err != nil {
+		return err
+	}
+
+	for _, msg := range msgs {
+		if _, err := s.relayConn.Write(msg); err != nil {
+			return err
+		}
+
+	}
+	return nil
+}
+
+func (s *PeersStateSubscription) unsubscribeStateChange(peerIDs []messages.PeerID) error {
+	msgs, err := messages.MarshalUnsubPeerStateMsg(peerIDs)
+	if err != nil {
+		return err
+	}
+
+	var connWriteErr error
+	for _, msg := range msgs {
+		if _, err := s.relayConn.Write(msg); err != nil {
+			connWriteErr = err
+		}
+	}
+	return connWriteErr
+}
diff --git a/relay/client/picker.go b/relay/client/picker.go
index eb5062db..9565425a 100644
--- a/relay/client/picker.go
+++ b/relay/client/picker.go
@@ -70,8 +70,8 @@ func (sp *ServerPicker) PickServer(parentCtx context.Context) (*Client, error) {
 
 func (sp *ServerPicker) startConnection(ctx context.Context, resultChan chan connResult, url string) {
 	log.Infof("try to connecting to relay server: %s", url)
-	relayClient := NewClient(ctx, url, sp.TokenStore, sp.PeerID)
-	err := relayClient.Connect()
+	relayClient := NewClient(url, sp.TokenStore, sp.PeerID)
+	err := relayClient.Connect(ctx)
 	resultChan <- connResult{
 		RelayClient: relayClient,
 		Url:         url,
diff --git a/relay/cmd/root.go b/relay/cmd/root.go
index d603ff73..15090024 100644
--- a/relay/cmd/root.go
+++ b/relay/cmd/root.go
@@ -141,7 +141,14 @@ func execute(cmd *cobra.Command, args []string) error {
 	hashedSecret := sha256.Sum256([]byte(cobraConfig.AuthSecret))
 	authenticator := auth.NewTimedHMACValidator(hashedSecret[:], 24*time.Hour)
 
-	srv, err := server.NewServer(metricsServer.Meter, cobraConfig.ExposedAddress, tlsSupport, authenticator)
+	cfg := server.Config{
+		Meter:          metricsServer.Meter,
+		ExposedAddress: cobraConfig.ExposedAddress,
+		AuthValidator:  authenticator,
+		TLSSupport:     tlsSupport,
+	}
+
+	srv, err := server.NewServer(cfg)
 	if err != nil {
 		log.Debugf("failed to create relay server: %v", err)
 		return fmt.Errorf("failed to create relay server: %v", err)
diff --git a/relay/messages/id.go b/relay/messages/id.go
index e2162cd3..96ace347 100644
--- a/relay/messages/id.go
+++ b/relay/messages/id.go
@@ -8,24 +8,24 @@ import (
 
 const (
 	prefixLength = 4
-	IDSize       = prefixLength + sha256.Size
+	peerIDSize   = prefixLength + sha256.Size
 )
 
 var (
 	prefix = []byte("sha-") // 4 bytes
 )
 
-// HashID generates a sha256 hash from the peerID and returns the hash and the human-readable string
-func HashID(peerID string) ([]byte, string) {
-	idHash := sha256.Sum256([]byte(peerID))
-	idHashString := string(prefix) + base64.StdEncoding.EncodeToString(idHash[:])
-	var prefixedHash []byte
-	prefixedHash = append(prefixedHash, prefix...)
-	prefixedHash = append(prefixedHash, idHash[:]...)
-	return prefixedHash, idHashString
+type PeerID [peerIDSize]byte
+
+func (p PeerID) String() string {
+	return fmt.Sprintf("%s%s", p[:prefixLength], base64.StdEncoding.EncodeToString(p[prefixLength:]))
 }
 
-// HashIDToString converts a hash to a human-readable string
-func HashIDToString(idHash []byte) string {
-	return fmt.Sprintf("%s%s", idHash[:prefixLength], base64.StdEncoding.EncodeToString(idHash[prefixLength:]))
+// HashID generates a sha256 hash from the peerID and returns the hash and the human-readable string
+func HashID(peerID string) PeerID {
+	idHash := sha256.Sum256([]byte(peerID))
+	var prefixedHash [peerIDSize]byte
+	copy(prefixedHash[:prefixLength], prefix)
+	copy(prefixedHash[prefixLength:], idHash[:])
+	return prefixedHash
 }
diff --git a/relay/messages/message.go b/relay/messages/message.go
index 7794c57b..54671f5d 100644
--- a/relay/messages/message.go
+++ b/relay/messages/message.go
@@ -9,19 +9,26 @@ import (
 const (
 	MaxHandshakeSize     = 212
 	MaxHandshakeRespSize = 8192
+	MaxMessageSize       = 8820
 
 	CurrentProtocolVersion = 1
 
 	MsgTypeUnknown MsgType = 0
 	// Deprecated: Use MsgTypeAuth instead.
-	MsgTypeHello MsgType = 1
+	MsgTypeHello = 1
 	// Deprecated: Use MsgTypeAuthResponse instead.
-	MsgTypeHelloResponse MsgType = 2
-	MsgTypeTransport     MsgType = 3
-	MsgTypeClose         MsgType = 4
-	MsgTypeHealthCheck   MsgType = 5
-	MsgTypeAuth                  = 6
-	MsgTypeAuthResponse          = 7
+	MsgTypeHelloResponse = 2
+	MsgTypeTransport     = 3
+	MsgTypeClose         = 4
+	MsgTypeHealthCheck   = 5
+	MsgTypeAuth          = 6
+	MsgTypeAuthResponse  = 7
+
+	// Peers state messages
+	MsgTypeSubscribePeerState   = 8
+	MsgTypeUnsubscribePeerState = 9
+	MsgTypePeersOnline          = 10
+	MsgTypePeersWentOffline     = 11
 
 	// base size of the message
 	sizeOfVersionByte = 1
@@ -30,17 +37,17 @@ const (
 
 	// auth message
 	sizeOfMagicByte     = 4
-	headerSizeAuth      = sizeOfMagicByte + IDSize
+	headerSizeAuth      = sizeOfMagicByte + peerIDSize
 	offsetMagicByte     = sizeOfProtoHeader
 	offsetAuthPeerID    = sizeOfProtoHeader + sizeOfMagicByte
 	headerTotalSizeAuth = sizeOfProtoHeader + headerSizeAuth
 
 	// hello message
-	headerSizeHello     = sizeOfMagicByte + IDSize
+	headerSizeHello     = sizeOfMagicByte + peerIDSize
 	headerSizeHelloResp = 0
 
 	// transport
-	headerSizeTransport      = IDSize
+	headerSizeTransport      = peerIDSize
 	offsetTransportID        = sizeOfProtoHeader
 	headerTotalSizeTransport = sizeOfProtoHeader + headerSizeTransport
 )
@@ -72,6 +79,14 @@ func (m MsgType) String() string {
 		return "close"
 	case MsgTypeHealthCheck:
 		return "health check"
+	case MsgTypeSubscribePeerState:
+		return "subscribe peer state"
+	case MsgTypeUnsubscribePeerState:
+		return "unsubscribe peer state"
+	case MsgTypePeersOnline:
+		return "peers online"
+	case MsgTypePeersWentOffline:
+		return "peers went offline"
 	default:
 		return "unknown"
 	}
@@ -102,7 +117,9 @@ func DetermineClientMessageType(msg []byte) (MsgType, error) {
 		MsgTypeAuth,
 		MsgTypeTransport,
 		MsgTypeClose,
-		MsgTypeHealthCheck:
+		MsgTypeHealthCheck,
+		MsgTypeSubscribePeerState,
+		MsgTypeUnsubscribePeerState:
 		return msgType, nil
 	default:
 		return MsgTypeUnknown, fmt.Errorf("invalid msg type %d", msgType)
@@ -122,7 +139,9 @@ func DetermineServerMessageType(msg []byte) (MsgType, error) {
 		MsgTypeAuthResponse,
 		MsgTypeTransport,
 		MsgTypeClose,
-		MsgTypeHealthCheck:
+		MsgTypeHealthCheck,
+		MsgTypePeersOnline,
+		MsgTypePeersWentOffline:
 		return msgType, nil
 	default:
 		return MsgTypeUnknown, fmt.Errorf("invalid msg type %d", msgType)
@@ -135,11 +154,7 @@ func DetermineServerMessageType(msg []byte) (MsgType, error) {
 // message is used to authenticate the client with the server. The authentication is done using an HMAC method.
 // The protocol does not limit to use HMAC, it can be any other method. If the authentication failed the server will
 // close the network connection without any response.
-func MarshalHelloMsg(peerID []byte, additions []byte) ([]byte, error) {
-	if len(peerID) != IDSize {
-		return nil, fmt.Errorf("invalid peerID length: %d", len(peerID))
-	}
-
+func MarshalHelloMsg(peerID PeerID, additions []byte) ([]byte, error) {
 	msg := make([]byte, sizeOfProtoHeader+sizeOfMagicByte, sizeOfProtoHeader+headerSizeHello+len(additions))
 
 	msg[0] = byte(CurrentProtocolVersion)
@@ -147,7 +162,7 @@ func MarshalHelloMsg(peerID []byte, additions []byte) ([]byte, error) {
 
 	copy(msg[sizeOfProtoHeader:sizeOfProtoHeader+sizeOfMagicByte], magicHeader)
 
-	msg = append(msg, peerID...)
+	msg = append(msg, peerID[:]...)
 	msg = append(msg, additions...)
 
 	return msg, nil
@@ -156,7 +171,7 @@ func MarshalHelloMsg(peerID []byte, additions []byte) ([]byte, error) {
 // Deprecated: Use UnmarshalAuthMsg instead.
 // UnmarshalHelloMsg extracts peerID and the additional data from the hello message. The Additional data is used to
 // authenticate the client with the server.
-func UnmarshalHelloMsg(msg []byte) ([]byte, []byte, error) {
+func UnmarshalHelloMsg(msg []byte) (*PeerID, []byte, error) {
 	if len(msg) < sizeOfProtoHeader+headerSizeHello {
 		return nil, nil, ErrInvalidMessageLength
 	}
@@ -164,7 +179,9 @@ func UnmarshalHelloMsg(msg []byte) ([]byte, []byte, error) {
 		return nil, nil, errors.New("invalid magic header")
 	}
 
-	return msg[sizeOfProtoHeader+sizeOfMagicByte : sizeOfProtoHeader+headerSizeHello], msg[headerSizeHello:], nil
+	peerID := PeerID(msg[sizeOfProtoHeader+sizeOfMagicByte : sizeOfProtoHeader+headerSizeHello])
+
+	return &peerID, msg[headerSizeHello:], nil
 }
 
 // Deprecated: Use MarshalAuthResponse instead.
@@ -197,34 +214,33 @@ func UnmarshalHelloResponse(msg []byte) ([]byte, error) {
 // message is used to authenticate the client with the server. The authentication is done using an HMAC method.
 // The protocol does not limit to use HMAC, it can be any other method. If the authentication failed the server will
 // close the network connection without any response.
-func MarshalAuthMsg(peerID []byte, authPayload []byte) ([]byte, error) {
-	if len(peerID) != IDSize {
-		return nil, fmt.Errorf("invalid peerID length: %d", len(peerID))
+func MarshalAuthMsg(peerID PeerID, authPayload []byte) ([]byte, error) {
+	if headerTotalSizeAuth+len(authPayload) > MaxHandshakeSize {
+		return nil, fmt.Errorf("too large auth payload")
 	}
 
-	msg := make([]byte, sizeOfProtoHeader+sizeOfMagicByte, headerTotalSizeAuth+len(authPayload))
-
+	msg := make([]byte, headerTotalSizeAuth+len(authPayload))
 	msg[0] = byte(CurrentProtocolVersion)
 	msg[1] = byte(MsgTypeAuth)
-
 	copy(msg[sizeOfProtoHeader:], magicHeader)
-
-	msg = append(msg, peerID...)
-	msg = append(msg, authPayload...)
-
+	copy(msg[offsetAuthPeerID:], peerID[:])
+	copy(msg[headerTotalSizeAuth:], authPayload)
 	return msg, nil
 }
 
 // UnmarshalAuthMsg extracts peerID and the auth payload from the message
-func UnmarshalAuthMsg(msg []byte) ([]byte, []byte, error) {
+func UnmarshalAuthMsg(msg []byte) (*PeerID, []byte, error) {
 	if len(msg) < headerTotalSizeAuth {
 		return nil, nil, ErrInvalidMessageLength
 	}
+
+	// Validate the magic header
 	if !bytes.Equal(msg[offsetMagicByte:offsetMagicByte+sizeOfMagicByte], magicHeader) {
 		return nil, nil, errors.New("invalid magic header")
 	}
 
-	return msg[offsetAuthPeerID:headerTotalSizeAuth], msg[headerTotalSizeAuth:], nil
+	peerID := PeerID(msg[offsetAuthPeerID:headerTotalSizeAuth])
+	return &peerID, msg[headerTotalSizeAuth:], nil
 }
 
 // MarshalAuthResponse creates a response message to the auth.
@@ -268,45 +284,48 @@ func MarshalCloseMsg() []byte {
 // MarshalTransportMsg creates a transport message.
 // The transport message is used to exchange data between peers. The message contains the data to be exchanged and the
 // destination peer hashed ID.
-func MarshalTransportMsg(peerID, payload []byte) ([]byte, error) {
-	if len(peerID) != IDSize {
-		return nil, fmt.Errorf("invalid peerID length: %d", len(peerID))
-	}
-
-	msg := make([]byte, headerTotalSizeTransport, headerTotalSizeTransport+len(payload))
+func MarshalTransportMsg(peerID PeerID, payload []byte) ([]byte, error) {
+	// todo validate size
+	msg := make([]byte, headerTotalSizeTransport+len(payload))
 	msg[0] = byte(CurrentProtocolVersion)
 	msg[1] = byte(MsgTypeTransport)
-	copy(msg[sizeOfProtoHeader:], peerID)
-	msg = append(msg, payload...)
-
+	copy(msg[sizeOfProtoHeader:], peerID[:])
+	copy(msg[sizeOfProtoHeader+peerIDSize:], payload)
 	return msg, nil
 }
 
 // UnmarshalTransportMsg extracts the peerID and the payload from the transport message.
-func UnmarshalTransportMsg(buf []byte) ([]byte, []byte, error) {
+func UnmarshalTransportMsg(buf []byte) (*PeerID, []byte, error) {
 	if len(buf) < headerTotalSizeTransport {
 		return nil, nil, ErrInvalidMessageLength
 	}
 
-	return buf[offsetTransportID:headerTotalSizeTransport], buf[headerTotalSizeTransport:], nil
+	const offsetEnd = offsetTransportID + peerIDSize
+	var peerID PeerID
+	copy(peerID[:], buf[offsetTransportID:offsetEnd])
+	return &peerID, buf[headerTotalSizeTransport:], nil
 }
 
 // UnmarshalTransportID extracts the peerID from the transport message.
-func UnmarshalTransportID(buf []byte) ([]byte, error) {
+func UnmarshalTransportID(buf []byte) (*PeerID, error) {
 	if len(buf) < headerTotalSizeTransport {
 		return nil, ErrInvalidMessageLength
 	}
-	return buf[offsetTransportID:headerTotalSizeTransport], nil
+
+	const offsetEnd = offsetTransportID + peerIDSize
+	var id PeerID
+	copy(id[:], buf[offsetTransportID:offsetEnd])
+	return &id, nil
 }
 
 // UpdateTransportMsg updates the peerID in the transport message.
 // With this function the server can reuse the given byte slice to update the peerID in the transport message. So do
 // need to allocate a new byte slice.
-func UpdateTransportMsg(msg []byte, peerID []byte) error {
-	if len(msg) < offsetTransportID+len(peerID) {
+func UpdateTransportMsg(msg []byte, peerID PeerID) error {
+	if len(msg) < offsetTransportID+peerIDSize {
 		return ErrInvalidMessageLength
 	}
-	copy(msg[offsetTransportID:], peerID)
+	copy(msg[offsetTransportID:], peerID[:])
 	return nil
 }
 
diff --git a/relay/messages/peer_state.go b/relay/messages/peer_state.go
new file mode 100644
index 00000000..f10bc7bd
--- /dev/null
+++ b/relay/messages/peer_state.go
@@ -0,0 +1,92 @@
+package messages
+
+import (
+	"fmt"
+)
+
+func MarshalSubPeerStateMsg(ids []PeerID) ([][]byte, error) {
+	return marshalPeerIDs(ids, byte(MsgTypeSubscribePeerState))
+}
+
+func UnmarshalSubPeerStateMsg(buf []byte) ([]PeerID, error) {
+	return unmarshalPeerIDs(buf)
+}
+
+func MarshalUnsubPeerStateMsg(ids []PeerID) ([][]byte, error) {
+	return marshalPeerIDs(ids, byte(MsgTypeUnsubscribePeerState))
+}
+
+func UnmarshalUnsubPeerStateMsg(buf []byte) ([]PeerID, error) {
+	return unmarshalPeerIDs(buf)
+}
+
+func MarshalPeersOnline(ids []PeerID) ([][]byte, error) {
+	return marshalPeerIDs(ids, byte(MsgTypePeersOnline))
+}
+
+func UnmarshalPeersOnlineMsg(buf []byte) ([]PeerID, error) {
+	return unmarshalPeerIDs(buf)
+}
+
+func MarshalPeersWentOffline(ids []PeerID) ([][]byte, error) {
+	return marshalPeerIDs(ids, byte(MsgTypePeersWentOffline))
+}
+
+func UnMarshalPeersWentOffline(buf []byte) ([]PeerID, error) {
+	return unmarshalPeerIDs(buf)
+}
+
+// marshalPeerIDs is a generic function to marshal peer IDs with a specific message type
+func marshalPeerIDs(ids []PeerID, msgType byte) ([][]byte, error) {
+	if len(ids) == 0 {
+		return nil, fmt.Errorf("no list of peer ids provided")
+	}
+
+	const maxPeersPerMessage = (MaxMessageSize - sizeOfProtoHeader) / peerIDSize
+	var messages [][]byte
+
+	for i := 0; i < len(ids); i += maxPeersPerMessage {
+		end := i + maxPeersPerMessage
+		if end > len(ids) {
+			end = len(ids)
+		}
+		chunk := ids[i:end]
+
+		totalSize := sizeOfProtoHeader + len(chunk)*peerIDSize
+		buf := make([]byte, totalSize)
+		buf[0] = byte(CurrentProtocolVersion)
+		buf[1] = msgType
+
+		offset := sizeOfProtoHeader
+		for _, id := range chunk {
+			copy(buf[offset:], id[:])
+			offset += peerIDSize
+		}
+
+		messages = append(messages, buf)
+	}
+
+	return messages, nil
+}
+
+// unmarshalPeerIDs is a generic function to unmarshal peer IDs from a buffer
+func unmarshalPeerIDs(buf []byte) ([]PeerID, error) {
+	if len(buf) < sizeOfProtoHeader {
+		return nil, fmt.Errorf("invalid message format")
+	}
+
+	if (len(buf)-sizeOfProtoHeader)%peerIDSize != 0 {
+		return nil, fmt.Errorf("invalid peer list size: %d", len(buf)-sizeOfProtoHeader)
+	}
+
+	numIDs := (len(buf) - sizeOfProtoHeader) / peerIDSize
+
+	ids := make([]PeerID, numIDs)
+	offset := sizeOfProtoHeader
+	for i := 0; i < numIDs; i++ {
+		copy(ids[i][:], buf[offset:offset+peerIDSize])
+		offset += peerIDSize
+	}
+
+	return ids, nil
+}
diff --git a/relay/metrics/realy.go b/relay/metrics/realy.go
index 2e90940e..efb597ff 100644
--- a/relay/metrics/realy.go
+++ b/relay/metrics/realy.go
@@ -20,12 +20,12 @@ type Metrics struct {
 	TransferBytesRecv  metric.Int64Counter
 	AuthenticationTime metric.Float64Histogram
 	PeerStoreTime      metric.Float64Histogram
-
-	peers            metric.Int64UpDownCounter
-	peerActivityChan chan string
-	peerLastActive   map[string]time.Time
-	mutexActivity    sync.Mutex
-	ctx              context.Context
+	peerReconnections  metric.Int64Counter
+	peers              metric.Int64UpDownCounter
+	peerActivityChan   chan string
+	peerLastActive     map[string]time.Time
+	mutexActivity      sync.Mutex
+	ctx                context.Context
 }
 
 func NewMetrics(ctx context.Context, meter metric.Meter) (*Metrics, error) {
@@ -80,6 +80,13 @@ func NewMetrics(ctx context.Context, meter metric.Meter) (*Metrics, error) {
 		return nil, err
 	}
 
+	peerReconnections, err := meter.Int64Counter("relay_peer_reconnections_total",
+		metric.WithDescription("Total number of times peers have reconnected and closed old connections"),
+	)
+	if err != nil {
+		return nil, err
+	}
+
 	m := &Metrics{
 		Meter:              meter,
 		TransferBytesSent:  bytesSent,
@@ -87,6 +94,7 @@ func NewMetrics(ctx context.Context, meter metric.Meter) (*Metrics, error) {
 		AuthenticationTime: authTime,
 		PeerStoreTime:      peerStoreTime,
 		peers:              peers,
+		peerReconnections:  peerReconnections,
 
 		ctx:              ctx,
 		peerActivityChan: make(chan string, 10),
@@ -138,6 +146,10 @@ func (m *Metrics) PeerDisconnected(id string) {
 	delete(m.peerLastActive, id)
 }
 
+func (m *Metrics) RecordPeerReconnection() {
+	m.peerReconnections.Add(m.ctx, 1)
+}
+
 // PeerActivity increases the active connections
 func (m *Metrics) PeerActivity(peerID string) {
 	select {
diff --git a/relay/server/handshake.go b/relay/server/handshake.go
index babd6f95..eb72b3ba 100644
--- a/relay/server/handshake.go
+++ b/relay/server/handshake.go
@@ -6,7 +6,6 @@ import (
 
 	log "github.com/sirupsen/logrus"
 
-	"github.com/netbirdio/netbird/relay/auth"
 	"github.com/netbirdio/netbird/relay/messages"
 	//nolint:staticcheck
 	"github.com/netbirdio/netbird/relay/messages/address"
@@ -14,6 +13,12 @@ import (
 	authmsg "github.com/netbirdio/netbird/relay/messages/auth"
 )
 
+type Validator interface {
+	Validate(any) error
+	// Deprecated: Use Validate instead.
+	ValidateHelloMsgType(any) error
+}
+
 // preparedMsg contains the marshalled success response messages
 type preparedMsg struct {
 	responseHelloMsg []byte
@@ -54,14 +59,14 @@ func marshalResponseHelloMsg(instanceURL string) ([]byte, error) {
 
 type handshake struct {
 	conn        net.Conn
-	validator   auth.Validator
+	validator   Validator
 	preparedMsg *preparedMsg
 
 	handshakeMethodAuth bool
-	peerID              string
+	peerID              *messages.PeerID
 }
 
-func (h *handshake) handshakeReceive() ([]byte, error) {
+func (h *handshake) handshakeReceive() (*messages.PeerID, error) {
 	buf := make([]byte, messages.MaxHandshakeSize)
 	n, err := h.conn.Read(buf)
 	if err != nil {
@@ -80,17 +85,14 @@ func (h *handshake) handshakeReceive() ([]byte, error) {
 		return nil, fmt.Errorf("determine message type from %s: %w", h.conn.RemoteAddr(), err)
 	}
 
-	var (
-		bytePeerID []byte
-		peerID     string
-	)
+	var peerID *messages.PeerID
 	switch msgType {
 	//nolint:staticcheck
 	case messages.MsgTypeHello:
-		bytePeerID, peerID, err = h.handleHelloMsg(buf)
+		peerID, err = h.handleHelloMsg(buf)
 	case messages.MsgTypeAuth:
 		h.handshakeMethodAuth = true
-		bytePeerID, peerID, err = h.handleAuthMsg(buf)
+		peerID, err = h.handleAuthMsg(buf)
 	default:
 		return nil, fmt.Errorf("invalid message type %d from %s", msgType, h.conn.RemoteAddr())
 	}
@@ -98,7 +100,7 @@ func (h *handshake) handshakeReceive() ([]byte, error) {
 		return nil, err
 	}
 	h.peerID = peerID
-	return bytePeerID, nil
+	return peerID, nil
 }
 
 func (h *handshake) handshakeResponse() error {
@@ -116,40 +118,37 @@ func (h *handshake) handshakeResponse() error {
 	return nil
 }
 
-func (h *handshake) handleHelloMsg(buf []byte) ([]byte, string, error) {
+func (h *handshake) handleHelloMsg(buf []byte) (*messages.PeerID, error) {
 	//nolint:staticcheck
-	rawPeerID, authData, err := messages.UnmarshalHelloMsg(buf)
+	peerID, authData, err := messages.UnmarshalHelloMsg(buf)
 	if err != nil {
-		return nil, "", fmt.Errorf("unmarshal hello message: %w", err)
+		return nil, fmt.Errorf("unmarshal hello message: %w", err)
 	}
 
-	peerID := messages.HashIDToString(rawPeerID)
 	log.Warnf("peer %s (%s) is using deprecated initial message type", peerID, h.conn.RemoteAddr())
 
 	authMsg, err := authmsg.UnmarshalMsg(authData)
 	if err != nil {
-		return nil, "", fmt.Errorf("unmarshal auth message: %w", err)
+		return nil, fmt.Errorf("unmarshal auth message: %w", err)
 	}
 
 	//nolint:staticcheck
 	if err := h.validator.ValidateHelloMsgType(authMsg.AdditionalData); err != nil {
-		return nil, "", fmt.Errorf("validate %s (%s): %w", peerID, h.conn.RemoteAddr(), err)
+		return nil, fmt.Errorf("validate %s (%s): %w", peerID, h.conn.RemoteAddr(), err)
 	}
 
-	return rawPeerID, peerID, nil
+	return peerID, nil
 }
 
-func (h *handshake) handleAuthMsg(buf []byte) ([]byte, string, error) {
+func (h *handshake) handleAuthMsg(buf []byte) (*messages.PeerID, error) {
 	rawPeerID, authPayload, err := messages.UnmarshalAuthMsg(buf)
 	if err != nil {
-		return nil, "", fmt.Errorf("unmarshal hello message: %w", err)
+		return nil, fmt.Errorf("unmarshal hello message: %w", err)
 	}
 
-	peerID := messages.HashIDToString(rawPeerID)
-
 	if err := h.validator.Validate(authPayload); err != nil {
-		return nil, "", fmt.Errorf("validate %s (%s): %w", peerID, h.conn.RemoteAddr(), err)
+		return nil, fmt.Errorf("validate %s (%s): %w", rawPeerID.String(), h.conn.RemoteAddr(), err)
 	}
 
-	return rawPeerID, peerID, nil
+	return rawPeerID, nil
 }
diff --git a/relay/server/listener/quic/listener.go b/relay/server/listener/quic/listener.go
index 17a5e8ab..2a4a668f 100644
--- a/relay/server/listener/quic/listener.go
+++ b/relay/server/listener/quic/listener.go
@@ -18,12 +18,9 @@ type Listener struct {
 	TLSConfig *tls.Config
 
 	listener *quic.Listener
-	acceptFn func(conn net.Conn)
 }
 
 func (l *Listener) Listen(acceptFn func(conn net.Conn)) error {
-	l.acceptFn = acceptFn
-
 	quicCfg := &quic.Config{
 		EnableDatagrams:   true,
 		InitialPacketSize: 1452,
@@ -49,7 +46,7 @@ func (l *Listener) Listen(acceptFn func(conn net.Conn)) error {
 
 		log.Infof("QUIC client connected from: %s", session.RemoteAddr())
 		conn := NewConn(session)
-		l.acceptFn(conn)
+		acceptFn(conn)
 	}
 }
 
diff --git a/relay/server/peer.go b/relay/server/peer.go
index aa9790f6..9caa5b06 100644
--- a/relay/server/peer.go
+++ b/relay/server/peer.go
@@ -12,43 +12,53 @@ import (
 	"github.com/netbirdio/netbird/relay/healthcheck"
 	"github.com/netbirdio/netbird/relay/messages"
 	"github.com/netbirdio/netbird/relay/metrics"
+	"github.com/netbirdio/netbird/relay/server/store"
 )
 
 const (
-	bufferSize = 8820
+	bufferSize = messages.MaxMessageSize
 
 	errCloseConn = "failed to close connection to peer: %s"
 )
 
 // Peer represents a peer connection
 type Peer struct {
-	metrics *metrics.Metrics
-	log     *log.Entry
-	idS     string
-	idB     []byte
-	conn    net.Conn
-	connMu  sync.RWMutex
-	store   *Store
+	metrics  *metrics.Metrics
+	log      *log.Entry
+	id       messages.PeerID
+	conn     net.Conn
+	connMu   sync.RWMutex
+	store    *store.Store
+	notifier *store.PeerNotifier
+
+	peersListener *store.Listener
+
+	// between the online peer collection step and the notification sending should not be sent offline notifications from another thread
+	notificationMutex sync.Mutex
 }
 
 // NewPeer creates a new Peer instance and prepare custom logging
-func NewPeer(metrics *metrics.Metrics, id []byte, conn net.Conn, store *Store) *Peer {
-	stringID := messages.HashIDToString(id)
-	return &Peer{
-		metrics: metrics,
-		log:     log.WithField("peer_id", stringID),
-		idS:     stringID,
-		idB:     id,
-		conn:    conn,
-		store:   store,
+func NewPeer(metrics *metrics.Metrics, id messages.PeerID, conn net.Conn, store *store.Store, notifier *store.PeerNotifier) *Peer {
+	p := &Peer{
+		metrics:  metrics,
+		log:      log.WithField("peer_id", id.String()),
+		id:       id,
+		conn:     conn,
+		store:    store,
+		notifier: notifier,
 	}
+
+	return p
 }
 
 // Work reads data from the connection
 // It manages the protocol (healthcheck, transport, close). Read the message and determine the message type and handle
 // the message accordingly.
 func (p *Peer) Work() {
+	p.peersListener = p.notifier.NewListener(p.sendPeersOnline, p.sendPeersWentOffline)
 	defer func() {
+		p.notifier.RemoveListener(p.peersListener)
+
 		if err := p.conn.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
 			p.log.Errorf(errCloseConn, err)
 		}
@@ -94,6 +104,10 @@ func (p *Peer) Work() {
 	}
 }
 
+func (p *Peer) ID() messages.PeerID {
+	return p.id
+}
+
 func (p *Peer) handleMsgType(ctx context.Context, msgType messages.MsgType, hc *healthcheck.Sender, n int, msg []byte) {
 	switch msgType {
 	case messages.MsgTypeHealthCheck:
@@ -107,6 +121,10 @@ func (p *Peer) handleMsgType(ctx context.Context, msgType messages.MsgType, hc *
 		if err := p.conn.Close(); err != nil {
 			log.Errorf(errCloseConn, err)
 		}
+	case messages.MsgTypeSubscribePeerState:
+		p.handleSubscribePeerState(msg)
+	case messages.MsgTypeUnsubscribePeerState:
+		p.handleUnsubscribePeerState(msg)
 	default:
 		p.log.Warnf("received unexpected message type: %s", msgType)
 	}
@@ -145,7 +163,7 @@ func (p *Peer) Close() {
 
 // String returns the peer ID
 func (p *Peer) String() string {
-	return p.idS
+	return p.id.String()
 }
 
 func (p *Peer) writeWithTimeout(ctx context.Context, buf []byte) error {
@@ -197,14 +215,14 @@ func (p *Peer) handleTransportMsg(msg []byte) {
 		return
 	}
 
-	stringPeerID := messages.HashIDToString(peerID)
-	dp, ok := p.store.Peer(stringPeerID)
+	item, ok := p.store.Peer(*peerID)
 	if !ok {
-		p.log.Debugf("peer not found: %s", stringPeerID)
+		p.log.Debugf("peer not found: %s", peerID)
 		return
 	}
+	dp := item.(*Peer)
 
-	err = messages.UpdateTransportMsg(msg, p.idB)
+	err = messages.UpdateTransportMsg(msg, p.id)
 	if err != nil {
 		p.log.Errorf("failed to update transport message: %s", err)
 		return
@@ -217,3 +235,66 @@ func (p *Peer) handleTransportMsg(msg []byte) {
 	}
 	p.metrics.TransferBytesSent.Add(context.Background(), int64(n))
 }
+
+func (p *Peer) handleSubscribePeerState(msg []byte) {
+	peerIDs, err := messages.UnmarshalSubPeerStateMsg(msg)
+	if err != nil {
+		p.log.Errorf("failed to unmarshal open connection message: %s", err)
+		return
+	}
+
+	p.log.Debugf("received subscription message for %d peers", len(peerIDs))
+
+	// collect online peers to response back to the caller
+	p.notificationMutex.Lock()
+	defer p.notificationMutex.Unlock()
+
+	onlinePeers := p.store.GetOnlinePeersAndRegisterInterest(peerIDs, p.peersListener)
+	if len(onlinePeers) == 0 {
+		return
+	}
+
+	p.log.Debugf("response with %d online peers", len(onlinePeers))
+	p.sendPeersOnline(onlinePeers)
+}
+
+func (p *Peer) handleUnsubscribePeerState(msg []byte) {
+	peerIDs, err := messages.UnmarshalUnsubPeerStateMsg(msg)
+	if err != nil {
+		p.log.Errorf("failed to unmarshal open connection message: %s", err)
+		return
+	}
+
+	p.peersListener.RemoveInterestedPeer(peerIDs)
+}
+
+func (p *Peer) sendPeersOnline(peers []messages.PeerID) {
+	msgs, err := messages.MarshalPeersOnline(peers)
+	if err != nil {
+		p.log.Errorf("failed to marshal peer location message: %s", err)
+		return
+	}
+
+	for n, msg := range msgs {
+		if _, err := p.Write(msg); err != nil {
+			p.log.Errorf("failed to write %d. peers offline message: %s", n, err)
+		}
+	}
+}
+
+func (p *Peer) sendPeersWentOffline(peers []messages.PeerID) {
+	p.notificationMutex.Lock()
+	defer p.notificationMutex.Unlock()
+
+	msgs, err := messages.MarshalPeersWentOffline(peers)
+	if err != nil {
+		p.log.Errorf("failed to marshal peer location message: %s", err)
+		return
+	}
+
+	for n, msg := range msgs {
+		if _, err := p.Write(msg); err != nil {
+			p.log.Errorf("failed to write %d. peers offline message: %s", n, err)
+		}
+	}
+}
diff --git a/relay/server/relay.go b/relay/server/relay.go
index a5e77bc6..d8668493 100644
--- a/relay/server/relay.go
+++ b/relay/server/relay.go
@@ -4,26 +4,55 @@ import (
 	"context"
 	"fmt"
 	"net"
-	"net/url"
-	"strings"
 	"sync"
 	"time"
 
 	log "github.com/sirupsen/logrus"
+	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/metric"
 
-	"github.com/netbirdio/netbird/relay/auth"
 	//nolint:staticcheck
 	"github.com/netbirdio/netbird/relay/metrics"
+	"github.com/netbirdio/netbird/relay/server/store"
 )
 
+type Config struct {
+	Meter          metric.Meter
+	ExposedAddress string
+	TLSSupport     bool
+	AuthValidator  Validator
+
+	instanceURL string
+}
+
+func (c *Config) validate() error {
+	if c.Meter == nil {
+		c.Meter = otel.Meter("")
+	}
+	if c.ExposedAddress == "" {
+		return fmt.Errorf("exposed address is required")
+	}
+
+	instanceURL, err := getInstanceURL(c.ExposedAddress, c.TLSSupport)
+	if err != nil {
+		return fmt.Errorf("invalid url: %v", err)
+	}
+	c.instanceURL = instanceURL
+
+	if c.AuthValidator == nil {
+		return fmt.Errorf("auth validator is required")
+	}
+	return nil
+}
+
 // Relay represents the relay server
 type Relay struct {
 	metrics       *metrics.Metrics
 	metricsCancel context.CancelFunc
-	validator     auth.Validator
+	validator     Validator
 
-	store       *Store
+	store       *store.Store
+	notifier    *store.PeerNotifier
 	instanceURL string
 	preparedMsg *preparedMsg
 
@@ -31,24 +60,27 @@ type Relay struct {
 	closeMu sync.RWMutex
 }
 
-// NewRelay creates a new Relay instance
+// NewRelay creates and returns a new Relay instance.
 //
 // Parameters:
-// meter: An instance of metric.Meter from the go.opentelemetry.io/otel/metric package. It is used to create and manage
-// metrics for the relay server.
-// exposedAddress: A string representing the address that the relay server is exposed on. The client will use this
-// address as the relay server's instance URL.
-// tlsSupport: A boolean indicating whether the relay server supports TLS (Transport Layer Security) or not. The
-// instance URL depends on this value.
-// validator: An instance of auth.Validator from the auth package. It is used to validate the authentication of the
-// peers.
+//
+//	config: A Config struct that holds the configuration needed to initialize the relay server.
+//	  - Meter: A metric.Meter used for emitting metrics. If not set, a default no-op meter will be used.
+//	  - ExposedAddress: The external address clients use to reach this relay. Required.
+//	  - TLSSupport: A boolean indicating if the relay uses TLS. Affects the generated instance URL.
+//	  - AuthValidator: A Validator implementation used to authenticate peers. Required.
 //
 // Returns:
-// A pointer to a Relay instance and an error. If the Relay instance is successfully created, the error is nil.
-// Otherwise, the error contains the details of what went wrong.
-func NewRelay(meter metric.Meter, exposedAddress string, tlsSupport bool, validator auth.Validator) (*Relay, error) {
+//
+//	A pointer to a Relay instance and an error. If initialization is successful, the error will be nil;
+//	otherwise, it will contain the reason the relay could not be created (e.g., invalid configuration).
+func NewRelay(config Config) (*Relay, error) {
+	if err := config.validate(); err != nil {
+		return nil, fmt.Errorf("invalid config: %v", err)
+	}
+
 	ctx, metricsCancel := context.WithCancel(context.Background())
-	m, err := metrics.NewMetrics(ctx, meter)
+	m, err := metrics.NewMetrics(ctx, config.Meter)
 	if err != nil {
 		metricsCancel()
 		return nil, fmt.Errorf("creating app metrics: %v", err)
@@ -57,14 +89,10 @@ func NewRelay(meter metric.Meter, exposedAddress string, tlsSupport bool, valida
 	r := &Relay{
 		metrics:       m,
 		metricsCancel: metricsCancel,
-		validator:     validator,
-		store:         NewStore(),
-	}
-
-	r.instanceURL, err = getInstanceURL(exposedAddress, tlsSupport)
-	if err != nil {
-		metricsCancel()
-		return nil, fmt.Errorf("get instance URL: %v", err)
+		validator:     config.AuthValidator,
+		instanceURL:   config.instanceURL,
+		store:         store.NewStore(),
+		notifier:      store.NewPeerNotifier(),
 	}
 
 	r.preparedMsg, err = newPreparedMsg(r.instanceURL)
@@ -76,32 +104,6 @@ func NewRelay(meter metric.Meter, exposedAddress string, tlsSupport bool, valida
 	return r, nil
 }
 
-// getInstanceURL checks if user supplied a URL scheme otherwise adds to the
-// provided address according to TLS definition and parses the address before returning it
-func getInstanceURL(exposedAddress string, tlsSupported bool) (string, error) {
-	addr := exposedAddress
-	split := strings.Split(exposedAddress, "://")
-	switch {
-	case len(split) == 1 && tlsSupported:
-		addr = "rels://" + exposedAddress
-	case len(split) == 1 && !tlsSupported:
-		addr = "rel://" + exposedAddress
-	case len(split) > 2:
-		return "", fmt.Errorf("invalid exposed address: %s", exposedAddress)
-	}
-
-	parsedURL, err := url.ParseRequestURI(addr)
-	if err != nil {
-		return "", fmt.Errorf("invalid exposed address: %v", err)
-	}
-
-	if parsedURL.Scheme != "rel" && parsedURL.Scheme != "rels" {
-		return "", fmt.Errorf("invalid scheme: %s", parsedURL.Scheme)
-	}
-
-	return parsedURL.String(), nil
-}
-
 // Accept start to handle a new peer connection
 func (r *Relay) Accept(conn net.Conn) {
 	acceptTime := time.Now()
@@ -125,15 +127,21 @@ func (r *Relay) Accept(conn net.Conn) {
 		return
 	}
 
-	peer := NewPeer(r.metrics, peerID, conn, r.store)
+	peer := NewPeer(r.metrics, *peerID, conn, r.store, r.notifier)
 	peer.log.Infof("peer connected from: %s", conn.RemoteAddr())
 	storeTime := time.Now()
-	r.store.AddPeer(peer)
+	if isReconnection := r.store.AddPeer(peer); isReconnection {
+		r.metrics.RecordPeerReconnection()
+	}
+	r.notifier.PeerCameOnline(peer.ID())
+
 	r.metrics.RecordPeerStoreTime(time.Since(storeTime))
 	r.metrics.PeerConnected(peer.String())
 	go func() {
 		peer.Work()
-		r.store.DeletePeer(peer)
+		if deleted := r.store.DeletePeer(peer); deleted {
+			r.notifier.PeerWentOffline(peer.ID())
+		}
 		peer.log.Debugf("relay connection closed")
 		r.metrics.PeerDisconnected(peer.String())
 	}()
@@ -154,12 +162,12 @@ func (r *Relay) Shutdown(ctx context.Context) {
 
 	wg := sync.WaitGroup{}
 	peers := r.store.Peers()
-	for _, peer := range peers {
+	for _, v := range peers {
 		wg.Add(1)
 		go func(p *Peer) {
 			p.CloseGracefully(ctx)
 			wg.Done()
-		}(peer)
+		}(v.(*Peer))
 	}
 	wg.Wait()
 	r.metricsCancel()
diff --git a/relay/server/server.go b/relay/server/server.go
index 10aabcac..f0b480ee 100644
--- a/relay/server/server.go
+++ b/relay/server/server.go
@@ -6,15 +6,12 @@ import (
 	"sync"
 
 	"github.com/hashicorp/go-multierror"
-	log "github.com/sirupsen/logrus"
-	"go.opentelemetry.io/otel/metric"
-
 	nberrors "github.com/netbirdio/netbird/client/errors"
-	"github.com/netbirdio/netbird/relay/auth"
 	"github.com/netbirdio/netbird/relay/server/listener"
 	"github.com/netbirdio/netbird/relay/server/listener/quic"
 	"github.com/netbirdio/netbird/relay/server/listener/ws"
 	quictls "github.com/netbirdio/netbird/relay/tls"
+	log "github.com/sirupsen/logrus"
 )
 
 // ListenerConfig is the configuration for the listener.
@@ -33,13 +30,22 @@ type Server struct {
 	listeners []listener.Listener
 }
 
-// NewServer creates a new relay server instance.
-// meter: the OpenTelemetry meter
-// exposedAddress: this address will be used as the instance URL. It should be a domain:port format.
-// tlsSupport: if true, the server will support TLS
-// authValidator: the auth validator to use for the server
-func NewServer(meter metric.Meter, exposedAddress string, tlsSupport bool, authValidator auth.Validator) (*Server, error) {
-	relay, err := NewRelay(meter, exposedAddress, tlsSupport, authValidator)
+// NewServer creates and returns a new relay server instance.
+//
+// Parameters:
+//
+//	config: A Config struct containing the necessary configuration:
+//	  - Meter: An OpenTelemetry metric.Meter used for recording metrics. If nil, a default no-op meter is used.
+//	  - ExposedAddress: The public address (in domain:port format) used as the server's instance URL. Required.
+//	  - TLSSupport: A boolean indicating whether TLS is enabled for the server.
+//	  - AuthValidator: A Validator used to authenticate peers. Required.
+//
+// Returns:
+//
+//	A pointer to a Server instance and an error. If the configuration is valid and initialization succeeds,
+//	the returned error will be nil. Otherwise, the error will describe the problem.
+func NewServer(config Config) (*Server, error) {
+	relay, err := NewRelay(config)
 	if err != nil {
 		return nil, err
 	}
diff --git a/relay/server/store.go b/relay/server/store.go
deleted file mode 100644
index 4288e62c..00000000
--- a/relay/server/store.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package server
-
-import (
-	"sync"
-)
-
-// Store is a thread-safe store of peers
-// It is used to store the peers that are connected to the relay server
-type Store struct {
-	peers     map[string]*Peer // consider to use [32]byte as key. The Peer(id string) would be faster
-	peersLock sync.RWMutex
-}
-
-// NewStore creates a new Store instance
-func NewStore() *Store {
-	return &Store{
-		peers: make(map[string]*Peer),
-	}
-}
-
-// AddPeer adds a peer to the store
-func (s *Store) AddPeer(peer *Peer) {
-	s.peersLock.Lock()
-	defer s.peersLock.Unlock()
-	odlPeer, ok := s.peers[peer.String()]
-	if ok {
-		odlPeer.Close()
-	}
-
-	s.peers[peer.String()] = peer
-}
-
-// DeletePeer deletes a peer from the store
-func (s *Store) DeletePeer(peer *Peer) {
-	s.peersLock.Lock()
-	defer s.peersLock.Unlock()
-
-	dp, ok := s.peers[peer.String()]
-	if !ok {
-		return
-	}
-	if dp != peer {
-		return
-	}
-
-	delete(s.peers, peer.String())
-}
-
-// Peer returns a peer by its ID
-func (s *Store) Peer(id string) (*Peer, bool) {
-	s.peersLock.RLock()
-	defer s.peersLock.RUnlock()
-
-	p, ok := s.peers[id]
-	return p, ok
-}
-
-// Peers returns all the peers in the store
-func (s *Store) Peers() []*Peer {
-	s.peersLock.RLock()
-	defer s.peersLock.RUnlock()
-
-	peers := make([]*Peer, 0, len(s.peers))
-	for _, p := range s.peers {
-		peers = append(peers, p)
-	}
-	return peers
-}
diff --git a/relay/server/store/listener.go b/relay/server/store/listener.go
new file mode 100644
index 00000000..e9c77d95
--- /dev/null
+++ b/relay/server/store/listener.go
@@ -0,0 +1,122 @@
+package store
+
+import (
+	"context"
+	"sync"
+
+	"github.com/netbirdio/netbird/relay/messages"
+)
+
+type event struct {
+	peerID messages.PeerID
+	online bool
+}
+
+type Listener struct {
+	ctx context.Context
+
+	eventChan                 chan *event
+	interestedPeersForOffline map[messages.PeerID]struct{}
+	interestedPeersForOnline  map[messages.PeerID]struct{}
+	mu                        sync.RWMutex
+}
+
+func newListener(ctx context.Context) *Listener {
+	l := &Listener{
+		ctx: ctx,
+
+		// important to use a single channel for offline and online events because with it we can ensure all events
+		// will be processed in the order they were sent
+		eventChan:                 make(chan *event, 244), //244 is the message size limit in the relay protocol
+		interestedPeersForOffline: make(map[messages.PeerID]struct{}),
+		interestedPeersForOnline:  make(map[messages.PeerID]struct{}),
+	}
+
+	return l
+}
+
+func (l *Listener) AddInterestedPeers(peerIDs []messages.PeerID) {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+
+	for _, id := range peerIDs {
+		l.interestedPeersForOnline[id] = struct{}{}
+		l.interestedPeersForOffline[id] = struct{}{}
+	}
+}
+
+func (l *Listener) RemoveInterestedPeer(peerIDs []messages.PeerID) {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+
+	for _, id := range peerIDs {
+		delete(l.interestedPeersForOffline, id)
+		delete(l.interestedPeersForOnline, id)
+	}
+}
+
+func (l *Listener) listenForEvents(onPeersComeOnline, onPeersWentOffline func([]messages.PeerID)) {
+	for {
+		select {
+		case <-l.ctx.Done():
+			return
+		case e := <-l.eventChan:
+			peersOffline := make([]messages.PeerID, 0)
+			peersOnline := make([]messages.PeerID, 0)
+			if e.online {
+				peersOnline = append(peersOnline, e.peerID)
+			} else {
+				peersOffline = append(peersOffline, e.peerID)
+			}
+
+			// Drain the channel to collect all events
+			for len(l.eventChan) > 0 {
+				e = <-l.eventChan
+				if e.online {
+					peersOnline = append(peersOnline, e.peerID)
+				} else {
+					peersOffline = append(peersOffline, e.peerID)
+				}
+			}
+
+			if len(peersOnline) > 0 {
+				onPeersComeOnline(peersOnline)
+			}
+			if len(peersOffline) > 0 {
+				onPeersWentOffline(peersOffline)
+			}
+		}
+	}
+}
+
+func (l *Listener) peerWentOffline(peerID messages.PeerID) {
+	l.mu.RLock()
+	defer l.mu.RUnlock()
+
+	if _, ok := l.interestedPeersForOffline[peerID]; ok {
+		select {
+		case l.eventChan <- &event{
+			peerID: peerID,
+			online: false,
+		}:
+		case <-l.ctx.Done():
+		}
+	}
+}
+
+func (l *Listener) peerComeOnline(peerID messages.PeerID) {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+
+	if _, ok := l.interestedPeersForOnline[peerID]; ok {
+		select {
+		case l.eventChan <- &event{
+			peerID: peerID,
+			online: true,
+		}:
+		case <-l.ctx.Done():
+		}
+
+		delete(l.interestedPeersForOnline, peerID)
+	}
+}
diff --git a/relay/server/store/notifier.go b/relay/server/store/notifier.go
new file mode 100644
index 00000000..33552253
--- /dev/null
+++ b/relay/server/store/notifier.go
@@ -0,0 +1,61 @@
+package store
+
+import (
+	"context"
+	"sync"
+
+	"github.com/netbirdio/netbird/relay/messages"
+)
+
+type PeerNotifier struct {
+	listeners      map[*Listener]context.CancelFunc
+	listenersMutex sync.RWMutex
+}
+
+func NewPeerNotifier() *PeerNotifier {
+	pn := &PeerNotifier{
+		listeners: make(map[*Listener]context.CancelFunc),
+	}
+	return pn
+}
+
+func (pn *PeerNotifier) NewListener(onPeersComeOnline, onPeersWentOffline func([]messages.PeerID)) *Listener {
+	ctx, cancel := context.WithCancel(context.Background())
+	listener := newListener(ctx)
+	go listener.listenForEvents(onPeersComeOnline, onPeersWentOffline)
+
+	pn.listenersMutex.Lock()
+	pn.listeners[listener] = cancel
+	pn.listenersMutex.Unlock()
+	return listener
+}
+
+func (pn *PeerNotifier) RemoveListener(listener *Listener) {
+	pn.listenersMutex.Lock()
+	defer pn.listenersMutex.Unlock()
+
+	cancel, ok := pn.listeners[listener]
+	if !ok {
+		return
+	}
+	cancel()
+	delete(pn.listeners, listener)
+}
+
+func (pn *PeerNotifier) PeerWentOffline(peerID messages.PeerID) {
+	pn.listenersMutex.RLock()
+	defer pn.listenersMutex.RUnlock()
+
+	for listener := range pn.listeners {
+		listener.peerWentOffline(peerID)
+	}
+}
+
+func (pn *PeerNotifier) PeerCameOnline(peerID messages.PeerID) {
+	pn.listenersMutex.RLock()
+	defer pn.listenersMutex.RUnlock()
+
+	for listener := range pn.listeners {
+		listener.peerComeOnline(peerID)
+	}
+}
diff --git a/relay/server/store/store.go b/relay/server/store/store.go
new file mode 100644
index 00000000..fd057860
--- /dev/null
+++ b/relay/server/store/store.go
@@ -0,0 +1,97 @@
+package store
+
+import (
+	"sync"
+
+	"github.com/netbirdio/netbird/relay/messages"
+)
+
+type IPeer interface {
+	Close()
+	ID() messages.PeerID
+}
+
+// Store is a thread-safe store of peers
+// It is used to store the peers that are connected to the relay server
+type Store struct {
+	peers     map[messages.PeerID]IPeer
+	peersLock sync.RWMutex
+}
+
+// NewStore creates a new Store instance
+func NewStore() *Store {
+	return &Store{
+		peers: make(map[messages.PeerID]IPeer),
+	}
+}
+
+// AddPeer adds a peer to the store
+// If the peer already exists, it will be replaced and the old peer will be closed
+// Returns true if the peer was replaced, false if it was added for the first time.
+func (s *Store) AddPeer(peer IPeer) bool {
+	s.peersLock.Lock()
+	defer s.peersLock.Unlock()
+	odlPeer, ok := s.peers[peer.ID()]
+	if ok {
+		odlPeer.Close()
+	}
+
+	s.peers[peer.ID()] = peer
+	return ok
+}
+
+// DeletePeer deletes a peer from the store
+func (s *Store) DeletePeer(peer IPeer) bool {
+	s.peersLock.Lock()
+	defer s.peersLock.Unlock()
+
+	dp, ok := s.peers[peer.ID()]
+	if !ok {
+		return false
+	}
+	if dp != peer {
+		return false
+	}
+
+	delete(s.peers, peer.ID())
+	return true
+}
+
+// Peer returns a peer by its ID
+func (s *Store) Peer(id messages.PeerID) (IPeer, bool) {
+	s.peersLock.RLock()
+	defer s.peersLock.RUnlock()
+
+	p, ok := s.peers[id]
+	return p, ok
+}
+
+// Peers returns all the peers in the store
+func (s *Store) Peers() []IPeer {
+	s.peersLock.RLock()
+	defer s.peersLock.RUnlock()
+
+	peers := make([]IPeer, 0, len(s.peers))
+	for _, p := range s.peers {
+		peers = append(peers, p)
+	}
+	return peers
+}
+
+func (s *Store) GetOnlinePeersAndRegisterInterest(peerIDs []messages.PeerID, listener *Listener) []messages.PeerID {
+	s.peersLock.RLock()
+	defer s.peersLock.RUnlock()
+
+	onlinePeers := make([]messages.PeerID, 0, len(peerIDs))
+
+	listener.AddInterestedPeers(peerIDs)
+
+	// Check for currently online peers
+	for _, id := range peerIDs {
+		if _, ok := s.peers[id]; ok {
+			onlinePeers = append(onlinePeers, id)
+		}
+	}
+
+	return onlinePeers
+}
diff --git a/relay/server/url.go b/relay/server/url.go
new file mode 100644
index 00000000..9cbf4464
--- /dev/null
+++ b/relay/server/url.go
@@ -0,0 +1,33 @@
+package server
+
+import (
+	"fmt"
+	"net/url"
+	"strings"
+)
+
+// getInstanceURL checks if user supplied a URL scheme otherwise adds to the
+// provided address according to TLS definition and parses the address before returning it
+func getInstanceURL(exposedAddress string, tlsSupported bool) (string, error) {
+	addr := exposedAddress
+	split := strings.Split(exposedAddress, "://")
+	switch {
+	case len(split) == 1 && tlsSupported:
+		addr = "rels://" + exposedAddress
+	case len(split) == 1 && !tlsSupported:
+		addr = "rel://" + exposedAddress
+	case len(split) > 2:
+		return "", fmt.Errorf("invalid exposed address: %s", exposedAddress)
+	}
+
+	parsedURL, err := url.ParseRequestURI(addr)
+	if err != nil {
+		return "", fmt.Errorf("invalid exposed address: %v", err)
+	}
+
+	if parsedURL.Scheme != "rel" && parsedURL.Scheme != "rels" {
+		return "", fmt.Errorf("invalid scheme: %s", parsedURL.Scheme)
+	}
+
+	return parsedURL.String(), nil
+}
diff --git a/release_files/install.sh b/release_files/install.sh
index 0f63529e..856d332c 100755
--- a/release_files/install.sh
+++ b/release_files/install.sh
@@ -130,7 +130,7 @@ repo_gpgcheck=1
 EOF
 }
 
-add_aur_repo() {
+install_aur_package() {
     INSTALL_PKGS="git base-devel go"
     REMOVE_PKGS=""
 
@@ -154,8 +154,10 @@ add_aur_repo() {
         cd netbird-ui && makepkg -sri --noconfirm
     fi
 
-    # Clean up the installed packages
-    ${SUDO} pacman -Rs "$REMOVE_PKGS" --noconfirm
+    if [ -n "$REMOVE_PKGS" ]; then
+      # Clean up the installed packages
+      ${SUDO} pacman -Rs "$REMOVE_PKGS" --noconfirm
+    fi
 }
 
 prepare_tun_module() {
@@ -277,7 +279,9 @@ install_netbird() {
     ;;
     pacman)
         ${SUDO} pacman -Syy
-        add_aur_repo
+        install_aur_package
+        # in-line with the docs at https://wiki.archlinux.org/title/Netbird
+        ${SUDO} systemctl enable --now netbird@main.service
     ;;
     pkg)
         # Check if the package is already installed
@@ -494,4 +498,4 @@ case "$UPDATE_FLAG" in
     ;;
     *)
       install_netbird
-esac
\ No newline at end of file
+esac
diff --git a/signal/proto/signalexchange.pb.go b/signal/proto/signalexchange.pb.go
index 30f704c6..3d45dea6 100644
--- a/signal/proto/signalexchange.pb.go
+++ b/signal/proto/signalexchange.pb.go
@@ -29,6 +29,7 @@ const (
 	Body_ANSWER    Body_Type = 1
 	Body_CANDIDATE Body_Type = 2
 	Body_MODE      Body_Type = 4
+	Body_GO_IDLE   Body_Type = 5
 )
 
 // Enum value maps for Body_Type.
@@ -38,12 +39,14 @@ var (
 		1: "ANSWER",
 		2: "CANDIDATE",
 		4: "MODE",
+		5: "GO_IDLE",
 	}
 	Body_Type_value = map[string]int32{
 		"OFFER":     0,
 		"ANSWER":    1,
 		"CANDIDATE": 2,
 		"MODE":      4,
+		"GO_IDLE":   5,
 	}
 )
 
@@ -225,7 +228,7 @@ type Body struct {
 	FeaturesSupported []uint32 `protobuf:"varint,6,rep,packed,name=featuresSupported,proto3" json:"featuresSupported,omitempty"`
 	// RosenpassConfig is a Rosenpass config of the remote peer our peer tries to connect to
 	RosenpassConfig *RosenpassConfig `protobuf:"bytes,7,opt,name=rosenpassConfig,proto3" json:"rosenpassConfig,omitempty"`
-	// relayServerAddress is an IP:port of the relay server
+	// relayServerAddress is url of the relay server
 	RelayServerAddress string `protobuf:"bytes,8,opt,name=relayServerAddress,proto3" json:"relayServerAddress,omitempty"`
 }
 
@@ -440,7 +443,7 @@ var file_signalexchange_proto_rawDesc = []byte{
 	0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x04, 0x62,
 	0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x69, 0x67, 0x6e,
 	0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x52,
-	0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0xa6, 0x03, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x2d,
+	0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0xb3, 0x03, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x2d,
 	0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x73,
 	0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x42, 0x6f,
 	0x64, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a,
@@ -463,33 +466,34 @@ var file_signalexchange_proto_rawDesc = []byte{
 	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x53,
 	0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x08, 0x20, 0x01,
 	0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41,
-	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x36, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09,
+	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x43, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09,
 	0x0a, 0x05, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x53,
 	0x57, 0x45, 0x52, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x41, 0x4e, 0x44, 0x49, 0x44, 0x41,
-	0x54, 0x45, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x4f, 0x44, 0x45, 0x10, 0x04, 0x22, 0x2e,
-	0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
-	0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x6d,
-	0x0a, 0x0f, 0x52, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-	0x67, 0x12, 0x28, 0x0a, 0x0f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x75,
-	0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x72, 0x6f, 0x73, 0x65,
-	0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x13, 0x72,
-	0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64,
-	0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70,
-	0x61, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x32, 0xb9, 0x01,
-	0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65,
-	0x12, 0x4c, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61,
-	0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
-	0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67,
-	0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72,
-	0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x59,
-	0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12,
-	0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65,
-	0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
-	0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e,
-	0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
-	0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x54, 0x45, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x4f, 0x44, 0x45, 0x10, 0x04, 0x12, 0x0b,
+	0x0a, 0x07, 0x47, 0x4f, 0x5f, 0x49, 0x44, 0x4c, 0x45, 0x10, 0x05, 0x22, 0x2e, 0x0a, 0x04, 0x4d,
+	0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01,
+	0x42, 0x09, 0x0a, 0x07, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x6d, 0x0a, 0x0f, 0x52,
+	0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28,
+	0x0a, 0x0f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65,
+	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61,
+	0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73, 0x65,
+	0x6e, 0x70, 0x61, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73,
+	0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x32, 0xb9, 0x01, 0x0a, 0x0e, 0x53,
+	0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x4c, 0x0a,
+	0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78,
+	0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
+	0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c,
+	0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
+	0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x59, 0x0a, 0x0d, 0x43,
+	0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x20, 0x2e, 0x73,
+	0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e,
+	0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20,
+	0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e,
+	0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/signal/proto/signalexchange.proto b/signal/proto/signalexchange.proto
index 4431edd7..b04d6ef2 100644
--- a/signal/proto/signalexchange.proto
+++ b/signal/proto/signalexchange.proto
@@ -47,6 +47,7 @@ message Body {
     ANSWER = 1;
     CANDIDATE = 2;
     MODE = 4;
+    GO_IDLE = 5;
   }
   Type type = 1;
   string payload = 2;
@@ -74,4 +75,4 @@ message RosenpassConfig {
   bytes rosenpassPubKey = 1;
   // rosenpassServerAddr is an IP:port of the rosenpass service
   string rosenpassServerAddr = 2;
-}
\ No newline at end of file
+}
diff --git a/util/log.go b/util/log.go
index 59a06436..53d2b068 100644
--- a/util/log.go
+++ b/util/log.go
@@ -14,7 +14,7 @@ import (
 	"github.com/netbirdio/netbird/formatter"
 )
 
-const defaultLogSize = 5
+const defaultLogSize = 15
 
 // InitLog parses and sets log-level input
 func InitLog(logLevel string, logPath string) error {
diff --git a/util/net/listener_listen.go b/util/net/listener_listen.go
index efffba40..dc99fbd6 100644
--- a/util/net/listener_listen.go
+++ b/util/net/listener_listen.go
@@ -6,6 +6,7 @@ import (
 	"context"
 	"fmt"
 	"net"
+	"net/netip"
 	"sync"
 
 	log "github.com/sirupsen/logrus"
@@ -17,11 +18,16 @@ type ListenerWriteHookFunc func(connID ConnectionID, ip *net.IPAddr, data []byte
 // ListenerCloseHookFunc defines the function signature for close hooks for PacketConn.
 type ListenerCloseHookFunc func(connID ConnectionID, conn net.PacketConn) error
 
+// ListenerAddressRemoveHookFunc defines the function signature for hooks called when addresses are removed.
+type ListenerAddressRemoveHookFunc func(connID ConnectionID, prefix netip.Prefix) error
+
 var (
-	listenerWriteHooksMutex sync.RWMutex
-	listenerWriteHooks      []ListenerWriteHookFunc
-	listenerCloseHooksMutex sync.RWMutex
-	listenerCloseHooks      []ListenerCloseHookFunc
+	listenerWriteHooksMutex         sync.RWMutex
+	listenerWriteHooks              []ListenerWriteHookFunc
+	listenerCloseHooksMutex         sync.RWMutex
+	listenerCloseHooks              []ListenerCloseHookFunc
+	listenerAddressRemoveHooksMutex sync.RWMutex
+	listenerAddressRemoveHooks      []ListenerAddressRemoveHookFunc
 )
 
 // AddListenerWriteHook allows adding a new write hook to be executed before a UDP packet is sent.
@@ -38,7 +44,14 @@ func AddListenerCloseHook(hook ListenerCloseHookFunc) {
 	listenerCloseHooks = append(listenerCloseHooks, hook)
 }
 
-// RemoveListenerHooks removes all dialer hooks.
+// AddListenerAddressRemoveHook allows adding a new hook to be executed when an address is removed.
+func AddListenerAddressRemoveHook(hook ListenerAddressRemoveHookFunc) {
+	listenerAddressRemoveHooksMutex.Lock()
+	defer listenerAddressRemoveHooksMutex.Unlock()
+	listenerAddressRemoveHooks = append(listenerAddressRemoveHooks, hook)
+}
+
+// RemoveListenerHooks removes all listener hooks.
 func RemoveListenerHooks() {
 	listenerWriteHooksMutex.Lock()
 	defer listenerWriteHooksMutex.Unlock()
@@ -47,6 +60,10 @@ func RemoveListenerHooks() {
 	listenerCloseHooksMutex.Lock()
 	defer listenerCloseHooksMutex.Unlock()
 	listenerCloseHooks = nil
+
+	listenerAddressRemoveHooksMutex.Lock()
+	defer listenerAddressRemoveHooksMutex.Unlock()
+	listenerAddressRemoveHooks = nil
 }
 
 // ListenPacket listens on the network address and returns a PacketConn
@@ -61,6 +78,7 @@ func (l *ListenerConfig) ListenPacket(ctx context.Context, network, address stri
 		return nil, fmt.Errorf("listen packet: %w", err)
 	}
 	connID := GenerateConnID()
+
 	return &PacketConn{PacketConn: pc, ID: connID, seenAddrs: &sync.Map{}}, nil
 }
 
@@ -102,6 +120,45 @@ func (c *UDPConn) Close() error {
 	return closeConn(c.ID, c.UDPConn)
 }
 
+// WrapUDPConn wraps an existing *net.UDPConn with nbnet functionality
+func WrapUDPConn(conn *net.UDPConn) *UDPConn {
+	return &UDPConn{
+		UDPConn:   conn,
+		ID:        GenerateConnID(),
+		seenAddrs: &sync.Map{},
+	}
+}
+
+// RemoveAddress removes an address from the seen cache and triggers removal hooks.
+func (c *UDPConn) RemoveAddress(addr string) {
+	if _, exists := c.seenAddrs.LoadAndDelete(addr); !exists {
+		return
+	}
+
+	ipStr, _, err := net.SplitHostPort(addr)
+	if err != nil {
+		log.Errorf("Error splitting IP address and port: %v", err)
+		return
+	}
+
+	ipAddr, err := netip.ParseAddr(ipStr)
+	if err != nil {
+		log.Errorf("Error parsing IP address %s: %v", ipStr, err)
+		return
+	}
+
+	prefix := netip.PrefixFrom(ipAddr, ipAddr.BitLen())
+
+	listenerAddressRemoveHooksMutex.RLock()
+	defer listenerAddressRemoveHooksMutex.RUnlock()
+
+	for _, hook := range listenerAddressRemoveHooks {
+		if err := hook(c.ID, prefix); err != nil {
+			log.Errorf("Error executing listener address remove hook: %v", err)
+		}
+	}
+}
+
 func callWriteHooks(id ConnectionID, seenAddrs *sync.Map, b []byte, addr net.Addr) {
 	// Lookup the address in the seenAddrs map to avoid calling the hooks for every write
 	if _, loaded := seenAddrs.LoadOrStore(addr.String(), true); !loaded {
diff --git a/util/net/listener_listen_ios.go b/util/net/listener_listen_ios.go
new file mode 100644
index 00000000..3cbd2cd7
--- /dev/null
+++ b/util/net/listener_listen_ios.go
@@ -0,0 +1,10 @@
+package net
+
+import (
+	"net"
+)
+
+// WrapUDPConn on iOS just returns the original connection since iOS handles its own networking
+func WrapUDPConn(conn *net.UDPConn) *net.UDPConn {
+	return conn
+}



Test patch:
diff --git a/client/cmd/testutil_test.go b/client/cmd/testutil_test.go
index 258a8daf..228a5d50 100644
--- a/client/cmd/testutil_test.go
+++ b/client/cmd/testutil_test.go
@@ -103,13 +103,13 @@ func startManagement(t *testing.T, config *types.Config, testFile string) (*grpc
 		Return(&types.Settings{}, nil).
 		AnyTimes()
 
-	accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock)
+	accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	secretsManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager)
-	mgmtServer, err := mgmt.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, nil, nil)
+	mgmtServer, err := mgmt.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, nil, nil, &mgmt.MockIntegratedValidator{})
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/client/firewall/uspfilter/uspfilter_bench_test.go b/client/firewall/uspfilter/filter_bench_test.go
similarity index 94%
rename from client/firewall/uspfilter/uspfilter_bench_test.go
rename to client/firewall/uspfilter/filter_bench_test.go
index c03e6064..0cffcc1a 100644
--- a/client/firewall/uspfilter/uspfilter_bench_test.go
+++ b/client/firewall/uspfilter/filter_bench_test.go
@@ -188,13 +188,13 @@ func BenchmarkCoreFiltering(b *testing.B) {
 
 				// For stateful scenarios, establish the connection
 				if sc.stateful {
-					manager.processOutgoingHooks(outbound, 0)
+					manager.filterOutbound(outbound, 0)
 				}
 
 				// Measure inbound packet processing
 				b.ResetTimer()
 				for i := 0; i < b.N; i++ {
-					manager.dropFilter(inbound, 0)
+					manager.filterInbound(inbound, 0)
 				}
 			})
 		}
@@ -220,7 +220,7 @@ func BenchmarkStateScaling(b *testing.B) {
 			for i := 0; i < count; i++ {
 				outbound := generatePacket(b, srcIPs[i], dstIPs[i],
 					uint16(1024+i), 80, layers.IPProtocolTCP)
-				manager.processOutgoingHooks(outbound, 0)
+				manager.filterOutbound(outbound, 0)
 			}
 
 			// Test packet
@@ -228,11 +228,11 @@ func BenchmarkStateScaling(b *testing.B) {
 			testIn := generatePacket(b, dstIPs[0], srcIPs[0], 80, 1024, layers.IPProtocolTCP)
 
 			// First establish our test connection
-			manager.processOutgoingHooks(testOut, 0)
+			manager.filterOutbound(testOut, 0)
 
 			b.ResetTimer()
 			for i := 0; i < b.N; i++ {
-				manager.dropFilter(testIn, 0)
+				manager.filterInbound(testIn, 0)
 			}
 		})
 	}
@@ -263,12 +263,12 @@ func BenchmarkEstablishmentOverhead(b *testing.B) {
 			inbound := generatePacket(b, dstIP, srcIP, 80, 1024, layers.IPProtocolTCP)
 
 			if sc.established {
-				manager.processOutgoingHooks(outbound, 0)
+				manager.filterOutbound(outbound, 0)
 			}
 
 			b.ResetTimer()
 			for i := 0; i < b.N; i++ {
-				manager.dropFilter(inbound, 0)
+				manager.filterInbound(inbound, 0)
 			}
 		})
 	}
@@ -426,25 +426,25 @@ func BenchmarkRoutedNetworkReturn(b *testing.B) {
 			// For stateful cases and established connections
 			if !strings.Contains(sc.name, "allow_non_wg") ||
 				(strings.Contains(sc.state, "established") || sc.state == "post_handshake") {
-				manager.processOutgoingHooks(outbound, 0)
+				manager.filterOutbound(outbound, 0)
 
 				// For TCP post-handshake, simulate full handshake
 				if sc.state == "post_handshake" {
 					// SYN
 					syn := generateTCPPacketWithFlags(b, srcIP, dstIP, 1024, 80, uint16(conntrack.TCPSyn))
-					manager.processOutgoingHooks(syn, 0)
+					manager.filterOutbound(syn, 0)
 					// SYN-ACK
 					synack := generateTCPPacketWithFlags(b, dstIP, srcIP, 80, 1024, uint16(conntrack.TCPSyn|conntrack.TCPAck))
-					manager.dropFilter(synack, 0)
+					manager.filterInbound(synack, 0)
 					// ACK
 					ack := generateTCPPacketWithFlags(b, srcIP, dstIP, 1024, 80, uint16(conntrack.TCPAck))
-					manager.processOutgoingHooks(ack, 0)
+					manager.filterOutbound(ack, 0)
 				}
 			}
 
 			b.ResetTimer()
 			for i := 0; i < b.N; i++ {
-				manager.dropFilter(inbound, 0)
+				manager.filterInbound(inbound, 0)
 			}
 		})
 	}
@@ -568,17 +568,17 @@ func BenchmarkLongLivedConnections(b *testing.B) {
 				// Initial SYN
 				syn := generateTCPPacketWithFlags(b, srcIPs[i], dstIPs[i],
 					uint16(1024+i), 80, uint16(conntrack.TCPSyn))
-				manager.processOutgoingHooks(syn, 0)
+				manager.filterOutbound(syn, 0)
 
 				// SYN-ACK
 				synack := generateTCPPacketWithFlags(b, dstIPs[i], srcIPs[i],
 					80, uint16(1024+i), uint16(conntrack.TCPSyn|conntrack.TCPAck))
-				manager.dropFilter(synack, 0)
+				manager.filterInbound(synack, 0)
 
 				// ACK
 				ack := generateTCPPacketWithFlags(b, srcIPs[i], dstIPs[i],
 					uint16(1024+i), 80, uint16(conntrack.TCPAck))
-				manager.processOutgoingHooks(ack, 0)
+				manager.filterOutbound(ack, 0)
 			}
 
 			// Prepare test packets simulating bidirectional traffic
@@ -599,9 +599,9 @@ func BenchmarkLongLivedConnections(b *testing.B) {
 
 				// Simulate bidirectional traffic
 				// First outbound data
-				manager.processOutgoingHooks(outPackets[connIdx], 0)
+				manager.filterOutbound(outPackets[connIdx], 0)
 				// Then inbound response - this is what we're actually measuring
-				manager.dropFilter(inPackets[connIdx], 0)
+				manager.filterInbound(inPackets[connIdx], 0)
 			}
 		})
 	}
@@ -700,19 +700,19 @@ func BenchmarkShortLivedConnections(b *testing.B) {
 				p := patterns[connIdx]
 
 				// Connection establishment
-				manager.processOutgoingHooks(p.syn, 0)
-				manager.dropFilter(p.synAck, 0)
-				manager.processOutgoingHooks(p.ack, 0)
+				manager.filterOutbound(p.syn, 0)
+				manager.filterInbound(p.synAck, 0)
+				manager.filterOutbound(p.ack, 0)
 
 				// Data transfer
-				manager.processOutgoingHooks(p.request, 0)
-				manager.dropFilter(p.response, 0)
+				manager.filterOutbound(p.request, 0)
+				manager.filterInbound(p.response, 0)
 
 				// Connection teardown
-				manager.processOutgoingHooks(p.finClient, 0)
-				manager.dropFilter(p.ackServer, 0)
-				manager.dropFilter(p.finServer, 0)
-				manager.processOutgoingHooks(p.ackClient, 0)
+				manager.filterOutbound(p.finClient, 0)
+				manager.filterInbound(p.ackServer, 0)
+				manager.filterInbound(p.finServer, 0)
+				manager.filterOutbound(p.ackClient, 0)
 			}
 		})
 	}
@@ -760,15 +760,15 @@ func BenchmarkParallelLongLivedConnections(b *testing.B) {
 			for i := 0; i < sc.connCount; i++ {
 				syn := generateTCPPacketWithFlags(b, srcIPs[i], dstIPs[i],
 					uint16(1024+i), 80, uint16(conntrack.TCPSyn))
-				manager.processOutgoingHooks(syn, 0)
+				manager.filterOutbound(syn, 0)
 
 				synack := generateTCPPacketWithFlags(b, dstIPs[i], srcIPs[i],
 					80, uint16(1024+i), uint16(conntrack.TCPSyn|conntrack.TCPAck))
-				manager.dropFilter(synack, 0)
+				manager.filterInbound(synack, 0)
 
 				ack := generateTCPPacketWithFlags(b, srcIPs[i], dstIPs[i],
 					uint16(1024+i), 80, uint16(conntrack.TCPAck))
-				manager.processOutgoingHooks(ack, 0)
+				manager.filterOutbound(ack, 0)
 			}
 
 			// Pre-generate test packets
@@ -790,8 +790,8 @@ func BenchmarkParallelLongLivedConnections(b *testing.B) {
 					counter++
 
 					// Simulate bidirectional traffic
-					manager.processOutgoingHooks(outPackets[connIdx], 0)
-					manager.dropFilter(inPackets[connIdx], 0)
+					manager.filterOutbound(outPackets[connIdx], 0)
+					manager.filterInbound(inPackets[connIdx], 0)
 				}
 			})
 		})
@@ -879,17 +879,17 @@ func BenchmarkParallelShortLivedConnections(b *testing.B) {
 					p := patterns[connIdx]
 
 					// Full connection lifecycle
-					manager.processOutgoingHooks(p.syn, 0)
-					manager.dropFilter(p.synAck, 0)
-					manager.processOutgoingHooks(p.ack, 0)
+					manager.filterOutbound(p.syn, 0)
+					manager.filterInbound(p.synAck, 0)
+					manager.filterOutbound(p.ack, 0)
 
-					manager.processOutgoingHooks(p.request, 0)
-					manager.dropFilter(p.response, 0)
+					manager.filterOutbound(p.request, 0)
+					manager.filterInbound(p.response, 0)
 
-					manager.processOutgoingHooks(p.finClient, 0)
-					manager.dropFilter(p.ackServer, 0)
-					manager.dropFilter(p.finServer, 0)
-					manager.processOutgoingHooks(p.ackClient, 0)
+					manager.filterOutbound(p.finClient, 0)
+					manager.filterInbound(p.ackServer, 0)
+					manager.filterInbound(p.finServer, 0)
+					manager.filterOutbound(p.ackClient, 0)
 				}
 			})
 		})
diff --git a/client/firewall/uspfilter/uspfilter_filter_test.go b/client/firewall/uspfilter/filter_filter_test.go
similarity index 99%
rename from client/firewall/uspfilter/uspfilter_filter_test.go
rename to client/firewall/uspfilter/filter_filter_test.go
index 318f86a8..b630c9e6 100644
--- a/client/firewall/uspfilter/uspfilter_filter_test.go
+++ b/client/firewall/uspfilter/filter_filter_test.go
@@ -462,7 +462,7 @@ func TestPeerACLFiltering(t *testing.T) {
 
 	t.Run("Implicit DROP (no rules)", func(t *testing.T) {
 		packet := createTestPacket(t, "100.10.0.1", "100.10.0.100", fw.ProtocolTCP, 12345, 443)
-		isDropped := manager.DropIncoming(packet, 0)
+		isDropped := manager.FilterInbound(packet, 0)
 		require.True(t, isDropped, "Packet should be dropped when no rules exist")
 	})
 
@@ -509,7 +509,7 @@ func TestPeerACLFiltering(t *testing.T) {
 			})
 
 			packet := createTestPacket(t, tc.srcIP, tc.dstIP, tc.proto, tc.srcPort, tc.dstPort)
-			isDropped := manager.DropIncoming(packet, 0)
+			isDropped := manager.FilterInbound(packet, 0)
 			require.Equal(t, tc.shouldBeBlocked, isDropped)
 		})
 	}
@@ -1233,7 +1233,7 @@ func TestRouteACLFiltering(t *testing.T) {
 			srcIP := netip.MustParseAddr(tc.srcIP)
 			dstIP := netip.MustParseAddr(tc.dstIP)
 
-			// testing routeACLsPass only and not DropIncoming, as routed packets are dropped after being passed
+			// testing routeACLsPass only and not FilterInbound, as routed packets are dropped after being passed
 			// to the forwarder
 			_, isAllowed := manager.routeACLsPass(srcIP, dstIP, tc.proto, tc.srcPort, tc.dstPort)
 			require.Equal(t, tc.shouldPass, isAllowed)
diff --git a/client/firewall/uspfilter/uspfilter_test.go b/client/firewall/uspfilter/filter_test.go
similarity index 98%
rename from client/firewall/uspfilter/uspfilter_test.go
rename to client/firewall/uspfilter/filter_test.go
index 88de1ddc..5b5cd5a5 100644
--- a/client/firewall/uspfilter/uspfilter_test.go
+++ b/client/firewall/uspfilter/filter_test.go
@@ -321,7 +321,7 @@ func TestNotMatchByIP(t *testing.T) {
 		return
 	}
 
-	if m.dropFilter(buf.Bytes(), 0) {
+	if m.filterInbound(buf.Bytes(), 0) {
 		t.Errorf("expected packet to be accepted")
 		return
 	}
@@ -447,7 +447,7 @@ func TestProcessOutgoingHooks(t *testing.T) {
 	require.NoError(t, err)
 
 	// Test hook gets called
-	result := manager.processOutgoingHooks(buf.Bytes(), 0)
+	result := manager.filterOutbound(buf.Bytes(), 0)
 	require.True(t, result)
 	require.True(t, hookCalled)
 
@@ -457,7 +457,7 @@ func TestProcessOutgoingHooks(t *testing.T) {
 	err = gopacket.SerializeLayers(buf, opts, ipv4)
 	require.NoError(t, err)
 
-	result = manager.processOutgoingHooks(buf.Bytes(), 0)
+	result = manager.filterOutbound(buf.Bytes(), 0)
 	require.False(t, result)
 }
 
@@ -553,7 +553,7 @@ func TestStatefulFirewall_UDPTracking(t *testing.T) {
 	require.NoError(t, err)
 
 	// Process outbound packet and verify connection tracking
-	drop := manager.DropOutgoing(outboundBuf.Bytes(), 0)
+	drop := manager.FilterOutbound(outboundBuf.Bytes(), 0)
 	require.False(t, drop, "Initial outbound packet should not be dropped")
 
 	// Verify connection was tracked
@@ -620,7 +620,7 @@ func TestStatefulFirewall_UDPTracking(t *testing.T) {
 	for _, cp := range checkPoints {
 		time.Sleep(cp.sleep)
 
-		drop = manager.dropFilter(inboundBuf.Bytes(), 0)
+		drop = manager.filterInbound(inboundBuf.Bytes(), 0)
 		require.Equal(t, cp.shouldAllow, !drop, cp.description)
 
 		// If the connection should still be valid, verify it exists
@@ -669,7 +669,7 @@ func TestStatefulFirewall_UDPTracking(t *testing.T) {
 	}
 
 	// Create a new outbound connection for invalid tests
-	drop = manager.processOutgoingHooks(outboundBuf.Bytes(), 0)
+	drop = manager.filterOutbound(outboundBuf.Bytes(), 0)
 	require.False(t, drop, "Second outbound packet should not be dropped")
 
 	for _, tc := range invalidCases {
@@ -691,7 +691,7 @@ func TestStatefulFirewall_UDPTracking(t *testing.T) {
 			require.NoError(t, err)
 
 			// Verify the invalid packet is dropped
-			drop = manager.dropFilter(testBuf.Bytes(), 0)
+			drop = manager.filterInbound(testBuf.Bytes(), 0)
 			require.True(t, drop, tc.description)
 		})
 	}
diff --git a/client/firewall/uspfilter/nat_bench_test.go b/client/firewall/uspfilter/nat_bench_test.go
new file mode 100644
index 00000000..16dba682
--- /dev/null
+++ b/client/firewall/uspfilter/nat_bench_test.go
@@ -0,0 +1,416 @@
+package uspfilter
+
+import (
+	"fmt"
+	"net/netip"
+	"testing"
+
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	log "github.com/sirupsen/logrus"
+
+	"github.com/netbirdio/netbird/client/iface/device"
+)
+
+// BenchmarkDNATTranslation measures the performance of DNAT operations
+func BenchmarkDNATTranslation(b *testing.B) {
+	scenarios := []struct {
+		name        string
+		proto       layers.IPProtocol
+		setupDNAT   bool
+		description string
+	}{
+		{
+			name:        "tcp_with_dnat",
+			proto:       layers.IPProtocolTCP,
+			setupDNAT:   true,
+			description: "TCP packet with DNAT translation enabled",
+		},
+		{
+			name:        "tcp_without_dnat",
+			proto:       layers.IPProtocolTCP,
+			setupDNAT:   false,
+			description: "TCP packet without DNAT (baseline)",
+		},
+		{
+			name:        "udp_with_dnat",
+			proto:       layers.IPProtocolUDP,
+			setupDNAT:   true,
+			description: "UDP packet with DNAT translation enabled",
+		},
+		{
+			name:        "udp_without_dnat",
+			proto:       layers.IPProtocolUDP,
+			setupDNAT:   false,
+			description: "UDP packet without DNAT (baseline)",
+		},
+		{
+			name:        "icmp_with_dnat",
+			proto:       layers.IPProtocolICMPv4,
+			setupDNAT:   true,
+			description: "ICMP packet with DNAT translation enabled",
+		},
+		{
+			name:        "icmp_without_dnat",
+			proto:       layers.IPProtocolICMPv4,
+			setupDNAT:   false,
+			description: "ICMP packet without DNAT (baseline)",
+		},
+	}
+
+	for _, sc := range scenarios {
+		b.Run(sc.name, func(b *testing.B) {
+			manager, err := Create(&IFaceMock{
+				SetFilterFunc: func(device.PacketFilter) error { return nil },
+			}, false, flowLogger)
+			require.NoError(b, err)
+			defer func() {
+				require.NoError(b, manager.Close(nil))
+			}()
+
+			// Set logger to error level to reduce noise during benchmarking
+			manager.SetLogLevel(log.ErrorLevel)
+			defer func() {
+				// Restore to info level after benchmark
+				manager.SetLogLevel(log.InfoLevel)
+			}()
+
+			// Setup DNAT mapping if needed
+			originalIP := netip.MustParseAddr("192.168.1.100")
+			translatedIP := netip.MustParseAddr("10.0.0.100")
+
+			if sc.setupDNAT {
+				err := manager.AddInternalDNATMapping(originalIP, translatedIP)
+				require.NoError(b, err)
+			}
+
+			// Create test packets
+			srcIP := netip.MustParseAddr("172.16.0.1")
+			outboundPacket := generateDNATTestPacket(b, srcIP, originalIP, sc.proto, 12345, 80)
+
+			// Pre-establish connection for reverse DNAT test
+			if sc.setupDNAT {
+				manager.filterOutbound(outboundPacket, 0)
+			}
+
+			b.ResetTimer()
+
+			// Benchmark outbound DNAT translation
+			b.Run("outbound", func(b *testing.B) {
+				for i := 0; i < b.N; i++ {
+					// Create fresh packet each time since translation modifies it
+					packet := generateDNATTestPacket(b, srcIP, originalIP, sc.proto, 12345, 80)
+					manager.filterOutbound(packet, 0)
+				}
+			})
+
+			// Benchmark inbound reverse DNAT translation
+			if sc.setupDNAT {
+				b.Run("inbound_reverse", func(b *testing.B) {
+					for i := 0; i < b.N; i++ {
+						// Create fresh packet each time since translation modifies it
+						packet := generateDNATTestPacket(b, translatedIP, srcIP, sc.proto, 80, 12345)
+						manager.filterInbound(packet, 0)
+					}
+				})
+			}
+		})
+	}
+}
+
+// BenchmarkDNATConcurrency tests DNAT performance under concurrent load
+func BenchmarkDNATConcurrency(b *testing.B) {
+	manager, err := Create(&IFaceMock{
+		SetFilterFunc: func(device.PacketFilter) error { return nil },
+	}, false, flowLogger)
+	require.NoError(b, err)
+	defer func() {
+		require.NoError(b, manager.Close(nil))
+	}()
+
+	// Set logger to error level to reduce noise during benchmarking
+	manager.SetLogLevel(log.ErrorLevel)
+	defer func() {
+		// Restore to info level after benchmark
+		manager.SetLogLevel(log.InfoLevel)
+	}()
+
+	// Setup multiple DNAT mappings
+	numMappings := 100
+	originalIPs := make([]netip.Addr, numMappings)
+	translatedIPs := make([]netip.Addr, numMappings)
+
+	for i := 0; i < numMappings; i++ {
+		originalIPs[i] = netip.MustParseAddr(fmt.Sprintf("192.168.%d.%d", (i/254)+1, (i%254)+1))
+		translatedIPs[i] = netip.MustParseAddr(fmt.Sprintf("10.0.%d.%d", (i/254)+1, (i%254)+1))
+		err := manager.AddInternalDNATMapping(originalIPs[i], translatedIPs[i])
+		require.NoError(b, err)
+	}
+
+	srcIP := netip.MustParseAddr("172.16.0.1")
+
+	// Pre-generate packets
+	outboundPackets := make([][]byte, numMappings)
+	inboundPackets := make([][]byte, numMappings)
+	for i := 0; i < numMappings; i++ {
+		outboundPackets[i] = generateDNATTestPacket(b, srcIP, originalIPs[i], layers.IPProtocolTCP, 12345, 80)
+		inboundPackets[i] = generateDNATTestPacket(b, translatedIPs[i], srcIP, layers.IPProtocolTCP, 80, 12345)
+		// Establish connections
+		manager.filterOutbound(outboundPackets[i], 0)
+	}
+
+	b.ResetTimer()
+
+	b.Run("concurrent_outbound", func(b *testing.B) {
+		b.RunParallel(func(pb *testing.PB) {
+			i := 0
+			for pb.Next() {
+				idx := i % numMappings
+				packet := generateDNATTestPacket(b, srcIP, originalIPs[idx], layers.IPProtocolTCP, 12345, 80)
+				manager.filterOutbound(packet, 0)
+				i++
+			}
+		})
+	})
+
+	b.Run("concurrent_inbound", func(b *testing.B) {
+		b.RunParallel(func(pb *testing.PB) {
+			i := 0
+			for pb.Next() {
+				idx := i % numMappings
+				packet := generateDNATTestPacket(b, translatedIPs[idx], srcIP, layers.IPProtocolTCP, 80, 12345)
+				manager.filterInbound(packet, 0)
+				i++
+			}
+		})
+	})
+}
+
+// BenchmarkDNATScaling tests how DNAT performance scales with number of mappings
+func BenchmarkDNATScaling(b *testing.B) {
+	mappingCounts := []int{1, 10, 100, 1000}
+
+	for _, count := range mappingCounts {
+		b.Run(fmt.Sprintf("mappings_%d", count), func(b *testing.B) {
+			manager, err := Create(&IFaceMock{
+				SetFilterFunc: func(device.PacketFilter) error { return nil },
+			}, false, flowLogger)
+			require.NoError(b, err)
+			defer func() {
+				require.NoError(b, manager.Close(nil))
+			}()
+
+			// Set logger to error level to reduce noise during benchmarking
+			manager.SetLogLevel(log.ErrorLevel)
+			defer func() {
+				// Restore to info level after benchmark
+				manager.SetLogLevel(log.InfoLevel)
+			}()
+
+			// Setup DNAT mappings
+			for i := 0; i < count; i++ {
+				originalIP := netip.MustParseAddr(fmt.Sprintf("192.168.%d.%d", (i/254)+1, (i%254)+1))
+				translatedIP := netip.MustParseAddr(fmt.Sprintf("10.0.%d.%d", (i/254)+1, (i%254)+1))
+				err := manager.AddInternalDNATMapping(originalIP, translatedIP)
+				require.NoError(b, err)
+			}
+
+			// Test with the last mapping added (worst case for lookup)
+			srcIP := netip.MustParseAddr("172.16.0.1")
+			lastOriginal := netip.MustParseAddr(fmt.Sprintf("192.168.%d.%d", ((count-1)/254)+1, ((count-1)%254)+1))
+
+			b.ResetTimer()
+			for i := 0; i < b.N; i++ {
+				packet := generateDNATTestPacket(b, srcIP, lastOriginal, layers.IPProtocolTCP, 12345, 80)
+				manager.filterOutbound(packet, 0)
+			}
+		})
+	}
+}
+
+// generateDNATTestPacket creates a test packet for DNAT benchmarking
+func generateDNATTestPacket(tb testing.TB, srcIP, dstIP netip.Addr, proto layers.IPProtocol, srcPort, dstPort uint16) []byte {
+	tb.Helper()
+
+	ipv4 := &layers.IPv4{
+		TTL:      64,
+		Version:  4,
+		SrcIP:    srcIP.AsSlice(),
+		DstIP:    dstIP.AsSlice(),
+		Protocol: proto,
+	}
+
+	var transportLayer gopacket.SerializableLayer
+	switch proto {
+	case layers.IPProtocolTCP:
+		tcp := &layers.TCP{
+			SrcPort: layers.TCPPort(srcPort),
+			DstPort: layers.TCPPort(dstPort),
+			SYN:     true,
+		}
+		require.NoError(tb, tcp.SetNetworkLayerForChecksum(ipv4))
+		transportLayer = tcp
+	case layers.IPProtocolUDP:
+		udp := &layers.UDP{
+			SrcPort: layers.UDPPort(srcPort),
+			DstPort: layers.UDPPort(dstPort),
+		}
+		require.NoError(tb, udp.SetNetworkLayerForChecksum(ipv4))
+		transportLayer = udp
+	case layers.IPProtocolICMPv4:
+		icmp := &layers.ICMPv4{
+			TypeCode: layers.CreateICMPv4TypeCode(layers.ICMPv4TypeEchoRequest, 0),
+		}
+		transportLayer = icmp
+	}
+
+	buf := gopacket.NewSerializeBuffer()
+	opts := gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true}
+	err := gopacket.SerializeLayers(buf, opts, ipv4, transportLayer, gopacket.Payload("test"))
+	require.NoError(tb, err)
+	return buf.Bytes()
+}
+
+// BenchmarkChecksumUpdate specifically benchmarks checksum calculation performance
+func BenchmarkChecksumUpdate(b *testing.B) {
+	// Create test data for checksum calculations
+	testData := make([]byte, 64) // Typical packet size for checksum testing
+	for i := range testData {
+		testData[i] = byte(i)
+	}
+
+	b.Run("ipv4_checksum", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			_ = ipv4Checksum(testData[:20]) // IPv4 header is typically 20 bytes
+		}
+	})
+
+	b.Run("icmp_checksum", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			_ = icmpChecksum(testData)
+		}
+	})
+
+	b.Run("incremental_update", func(b *testing.B) {
+		oldBytes := []byte{192, 168, 1, 100}
+		newBytes := []byte{10, 0, 0, 100}
+		oldChecksum := uint16(0x1234)
+
+		for i := 0; i < b.N; i++ {
+			_ = incrementalUpdate(oldChecksum, oldBytes, newBytes)
+		}
+	})
+}
+
+// BenchmarkDNATMemoryAllocations checks for memory allocations in DNAT operations
+func BenchmarkDNATMemoryAllocations(b *testing.B) {
+	manager, err := Create(&IFaceMock{
+		SetFilterFunc: func(device.PacketFilter) error { return nil },
+	}, false, flowLogger)
+	require.NoError(b, err)
+	defer func() {
+		require.NoError(b, manager.Close(nil))
+	}()
+
+	// Set logger to error level to reduce noise during benchmarking
+	manager.SetLogLevel(log.ErrorLevel)
+	defer func() {
+		// Restore to info level after benchmark
+		manager.SetLogLevel(log.InfoLevel)
+	}()
+
+	originalIP := netip.MustParseAddr("192.168.1.100")
+	translatedIP := netip.MustParseAddr("10.0.0.100")
+	srcIP := netip.MustParseAddr("172.16.0.1")
+
+	err = manager.AddInternalDNATMapping(originalIP, translatedIP)
+	require.NoError(b, err)
+
+	packet := generateDNATTestPacket(b, srcIP, originalIP, layers.IPProtocolTCP, 12345, 80)
+
+	b.ResetTimer()
+	b.ReportAllocs()
+
+	for i := 0; i < b.N; i++ {
+		// Create fresh packet each time to isolate allocation testing
+		testPacket := make([]byte, len(packet))
+		copy(testPacket, packet)
+
+		// Parse the packet fresh each time to get a clean decoder
+		d := &decoder{decoded: []gopacket.LayerType{}}
+		d.parser = gopacket.NewDecodingLayerParser(
+			layers.LayerTypeIPv4,
+			&d.eth, &d.ip4, &d.ip6, &d.icmp4, &d.icmp6, &d.tcp, &d.udp,
+		)
+		d.parser.IgnoreUnsupported = true
+		err = d.parser.DecodeLayers(testPacket, &d.decoded)
+		assert.NoError(b, err)
+
+		manager.translateOutboundDNAT(testPacket, d)
+	}
+}
+
+// BenchmarkDirectIPExtraction tests the performance improvement of direct IP extraction
+func BenchmarkDirectIPExtraction(b *testing.B) {
+	// Create a test packet
+	srcIP := netip.MustParseAddr("172.16.0.1")
+	dstIP := netip.MustParseAddr("192.168.1.100")
+	packet := generateDNATTestPacket(b, srcIP, dstIP, layers.IPProtocolTCP, 12345, 80)
+
+	b.Run("direct_byte_access", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			// Direct extraction from packet bytes
+			_ = netip.AddrFrom4([4]byte{packet[16], packet[17], packet[18], packet[19]})
+		}
+	})
+
+	b.Run("decoder_extraction", func(b *testing.B) {
+		// Create decoder once for comparison
+		d := &decoder{decoded: []gopacket.LayerType{}}
+		d.parser = gopacket.NewDecodingLayerParser(
+			layers.LayerTypeIPv4,
+			&d.eth, &d.ip4, &d.ip6, &d.icmp4, &d.icmp6, &d.tcp, &d.udp,
+		)
+		d.parser.IgnoreUnsupported = true
+		err := d.parser.DecodeLayers(packet, &d.decoded)
+		assert.NoError(b, err)
+
+		for i := 0; i < b.N; i++ {
+			// Extract using decoder (traditional method)
+			dst, _ := netip.AddrFromSlice(d.ip4.DstIP)
+			_ = dst
+		}
+	})
+}
+
+// BenchmarkChecksumOptimizations compares optimized vs standard checksum implementations
+func BenchmarkChecksumOptimizations(b *testing.B) {
+	// Create test IPv4 header (20 bytes)
+	header := make([]byte, 20)
+	for i := range header {
+		header[i] = byte(i)
+	}
+	// Clear checksum field
+	header[10] = 0
+	header[11] = 0
+
+	b.Run("optimized_ipv4_checksum", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			_ = ipv4Checksum(header)
+		}
+	})
+
+	// Test incremental checksum updates
+	oldIP := []byte{192, 168, 1, 100}
+	newIP := []byte{10, 0, 0, 100}
+	oldChecksum := uint16(0x1234)
+
+	b.Run("optimized_incremental_update", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			_ = incrementalUpdate(oldChecksum, oldIP, newIP)
+		}
+	})
+}
diff --git a/client/firewall/uspfilter/nat_test.go b/client/firewall/uspfilter/nat_test.go
new file mode 100644
index 00000000..710abd44
--- /dev/null
+++ b/client/firewall/uspfilter/nat_test.go
@@ -0,0 +1,145 @@
+package uspfilter
+
+import (
+	"net/netip"
+	"testing"
+
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+	"github.com/stretchr/testify/require"
+
+	"github.com/netbirdio/netbird/client/iface/device"
+)
+
+// TestDNATTranslationCorrectness verifies DNAT translation works correctly
+func TestDNATTranslationCorrectness(t *testing.T) {
+	manager, err := Create(&IFaceMock{
+		SetFilterFunc: func(device.PacketFilter) error { return nil },
+	}, false, flowLogger)
+	require.NoError(t, err)
+	defer func() {
+		require.NoError(t, manager.Close(nil))
+	}()
+
+	originalIP := netip.MustParseAddr("192.168.1.100")
+	translatedIP := netip.MustParseAddr("10.0.0.100")
+	srcIP := netip.MustParseAddr("172.16.0.1")
+
+	// Add DNAT mapping
+	err = manager.AddInternalDNATMapping(originalIP, translatedIP)
+	require.NoError(t, err)
+
+	testCases := []struct {
+		name     string
+		protocol layers.IPProtocol
+		srcPort  uint16
+		dstPort  uint16
+	}{
+		{"TCP", layers.IPProtocolTCP, 12345, 80},
+		{"UDP", layers.IPProtocolUDP, 12345, 53},
+		{"ICMP", layers.IPProtocolICMPv4, 0, 0},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			// Test outbound DNAT translation
+			outboundPacket := generateDNATTestPacket(t, srcIP, originalIP, tc.protocol, tc.srcPort, tc.dstPort)
+			originalOutbound := make([]byte, len(outboundPacket))
+			copy(originalOutbound, outboundPacket)
+
+			// Process outbound packet (should translate destination)
+			translated := manager.translateOutboundDNAT(outboundPacket, parsePacket(t, outboundPacket))
+			require.True(t, translated, "Outbound packet should be translated")
+
+			// Verify destination IP was changed
+			dstIPAfter := netip.AddrFrom4([4]byte{outboundPacket[16], outboundPacket[17], outboundPacket[18], outboundPacket[19]})
+			require.Equal(t, translatedIP, dstIPAfter, "Destination IP should be translated")
+
+			// Test inbound reverse DNAT translation
+			inboundPacket := generateDNATTestPacket(t, translatedIP, srcIP, tc.protocol, tc.dstPort, tc.srcPort)
+			originalInbound := make([]byte, len(inboundPacket))
+			copy(originalInbound, inboundPacket)
+
+			// Process inbound packet (should reverse translate source)
+			reversed := manager.translateInboundReverse(inboundPacket, parsePacket(t, inboundPacket))
+			require.True(t, reversed, "Inbound packet should be reverse translated")
+
+			// Verify source IP was changed back to original
+			srcIPAfter := netip.AddrFrom4([4]byte{inboundPacket[12], inboundPacket[13], inboundPacket[14], inboundPacket[15]})
+			require.Equal(t, originalIP, srcIPAfter, "Source IP should be reverse translated")
+
+			// Test that checksums are recalculated correctly
+			if tc.protocol != layers.IPProtocolICMPv4 {
+				// For TCP/UDP, verify the transport checksum was updated
+				require.NotEqual(t, originalOutbound, outboundPacket, "Outbound packet should be modified")
+				require.NotEqual(t, originalInbound, inboundPacket, "Inbound packet should be modified")
+			}
+		})
+	}
+}
+
+// parsePacket helper to create a decoder for testing
+func parsePacket(t testing.TB, packetData []byte) *decoder {
+	t.Helper()
+	d := &decoder{
+		decoded: []gopacket.LayerType{},
+	}
+	d.parser = gopacket.NewDecodingLayerParser(
+		layers.LayerTypeIPv4,
+		&d.eth, &d.ip4, &d.ip6, &d.icmp4, &d.icmp6, &d.tcp, &d.udp,
+	)
+	d.parser.IgnoreUnsupported = true
+
+	err := d.parser.DecodeLayers(packetData, &d.decoded)
+	require.NoError(t, err)
+	return d
+}
+
+// TestDNATMappingManagement tests adding/removing DNAT mappings
+func TestDNATMappingManagement(t *testing.T) {
+	manager, err := Create(&IFaceMock{
+		SetFilterFunc: func(device.PacketFilter) error { return nil },
+	}, false, flowLogger)
+	require.NoError(t, err)
+	defer func() {
+		require.NoError(t, manager.Close(nil))
+	}()
+
+	originalIP := netip.MustParseAddr("192.168.1.100")
+	translatedIP := netip.MustParseAddr("10.0.0.100")
+
+	// Test adding mapping
+	err = manager.AddInternalDNATMapping(originalIP, translatedIP)
+	require.NoError(t, err)
+
+	// Verify mapping exists
+	result, exists := manager.getDNATTranslation(originalIP)
+	require.True(t, exists)
+	require.Equal(t, translatedIP, result)
+
+	// Test reverse lookup
+	reverseResult, exists := manager.findReverseDNATMapping(translatedIP)
+	require.True(t, exists)
+	require.Equal(t, originalIP, reverseResult)
+
+	// Test removing mapping
+	err = manager.RemoveInternalDNATMapping(originalIP)
+	require.NoError(t, err)
+
+	// Verify mapping no longer exists
+	_, exists = manager.getDNATTranslation(originalIP)
+	require.False(t, exists)
+
+	_, exists = manager.findReverseDNATMapping(translatedIP)
+	require.False(t, exists)
+
+	// Test error cases
+	err = manager.AddInternalDNATMapping(netip.Addr{}, translatedIP)
+	require.Error(t, err, "Should reject invalid original IP")
+
+	err = manager.AddInternalDNATMapping(originalIP, netip.Addr{})
+	require.Error(t, err, "Should reject invalid translated IP")
+
+	err = manager.RemoveInternalDNATMapping(originalIP)
+	require.Error(t, err, "Should error when removing non-existent mapping")
+}
diff --git a/client/iface/bind/activity_test.go b/client/iface/bind/activity_test.go
new file mode 100644
index 00000000..bdd0dca2
--- /dev/null
+++ b/client/iface/bind/activity_test.go
@@ -0,0 +1,25 @@
+package bind
+
+import (
+	"net/netip"
+	"testing"
+	"time"
+
+	"github.com/netbirdio/netbird/monotime"
+)
+
+func TestActivityRecorder_GetLastActivities(t *testing.T) {
+	peer := "peer1"
+	ar := NewActivityRecorder()
+	ar.UpsertAddress("peer1", netip.MustParseAddrPort("192.168.0.5:51820"))
+	activities := ar.GetLastActivities()
+
+	p, ok := activities[peer]
+	if !ok {
+		t.Fatalf("Expected activity for peer %s, but got none", peer)
+	}
+
+	if monotime.Since(p) > 5*time.Second {
+		t.Fatalf("Expected activity for peer %s to be recent, but got %v", peer, p)
+	}
+}
diff --git a/client/iface/device/device_filter_test.go b/client/iface/device/device_filter_test.go
index c90269e8..eef78354 100644
--- a/client/iface/device/device_filter_test.go
+++ b/client/iface/device/device_filter_test.go
@@ -146,7 +146,7 @@ func TestDeviceWrapperRead(t *testing.T) {
 		tun.EXPECT().Write(mockBufs, 0).Return(0, nil)
 
 		filter := mocks.NewMockPacketFilter(ctrl)
-		filter.EXPECT().DropIncoming(gomock.Any(), gomock.Any()).Return(true)
+		filter.EXPECT().FilterInbound(gomock.Any(), gomock.Any()).Return(true)
 
 		wrapped := newDeviceFilter(tun)
 		wrapped.filter = filter
@@ -201,7 +201,7 @@ func TestDeviceWrapperRead(t *testing.T) {
 				return 1, nil
 			})
 		filter := mocks.NewMockPacketFilter(ctrl)
-		filter.EXPECT().DropOutgoing(gomock.Any(), gomock.Any()).Return(true)
+		filter.EXPECT().FilterOutbound(gomock.Any(), gomock.Any()).Return(true)
 
 		wrapped := newDeviceFilter(tun)
 		wrapped.filter = filter
diff --git a/client/iface/wgproxy/proxy_test.go b/client/iface/wgproxy/proxy_test.go
index 64b61762..2165b8ab 100644
--- a/client/iface/wgproxy/proxy_test.go
+++ b/client/iface/wgproxy/proxy_test.go
@@ -98,9 +98,7 @@ func TestProxyCloseByRemoteConn(t *testing.T) {
 				t.Errorf("failed to free ebpf proxy: %s", err)
 			}
 		}()
-		proxyWrapper := &ebpf.ProxyWrapper{
-			WgeBPFProxy: ebpfProxy,
-		}
+		proxyWrapper := ebpf.NewProxyWrapper(ebpfProxy)
 
 		tests = append(tests, struct {
 			name  string
diff --git a/client/internal/connect_test.go b/client/internal/connect_test.go
index 78b4b06e..c317c88d 100644
--- a/client/internal/connect_test.go
+++ b/client/internal/connect_test.go
@@ -13,10 +13,10 @@ func Test_freePort(t *testing.T) {
 		shouldMatch bool
 	}{
 		{
-			name:        "not provided, fallback to default",
+			name:        "when port is 0 use random port",
 			port:        0,
-			want:        51820,
-			shouldMatch: true,
+			want:        0,
+			shouldMatch: false,
 		},
 		{
 			name:        "provided and available",
@@ -31,7 +31,7 @@ func Test_freePort(t *testing.T) {
 			shouldMatch: false,
 		},
 	}
-	c1, err := net.ListenUDP("udp", &net.UDPAddr{Port: 51830})
+	c1, err := net.ListenUDP("udp", &net.UDPAddr{Port: 0})
 	if err != nil {
 		t.Errorf("freePort error = %v", err)
 	}
@@ -39,6 +39,14 @@ func Test_freePort(t *testing.T) {
 		_ = c1.Close()
 	}(c1)
 
+	if tests[1].port == c1.LocalAddr().(*net.UDPAddr).Port {
+		tests[1].port++
+		tests[1].want++
+	}
+
+	tests[2].port = c1.LocalAddr().(*net.UDPAddr).Port
+	tests[2].want = c1.LocalAddr().(*net.UDPAddr).Port
+
 	for _, tt := range tests {
 
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go
index 1cf59fb5..21a9e2f2 100644
--- a/client/internal/dns/server_test.go
+++ b/client/internal/dns/server_test.go
@@ -464,7 +464,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) {
 	defer ctrl.Finish()
 
 	packetfilter := pfmock.NewMockPacketFilter(ctrl)
-	packetfilter.EXPECT().DropOutgoing(gomock.Any(), gomock.Any()).AnyTimes()
+	packetfilter.EXPECT().FilterOutbound(gomock.Any(), gomock.Any()).AnyTimes()
 	packetfilter.EXPECT().AddUDPPacketHook(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
 	packetfilter.EXPECT().RemovePacketHook(gomock.Any())
 
diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go
index 8c084e36..f0213868 100644
--- a/client/internal/engine_test.go
+++ b/client/internal/engine_test.go
@@ -36,7 +36,6 @@ import (
 	"github.com/netbirdio/netbird/client/iface/wgproxy"
 	"github.com/netbirdio/netbird/client/internal/dns"
 	"github.com/netbirdio/netbird/client/internal/peer"
-	"github.com/netbirdio/netbird/client/internal/peer/dispatcher"
 	"github.com/netbirdio/netbird/client/internal/peer/guard"
 	icemaker "github.com/netbirdio/netbird/client/internal/peer/ice"
 	"github.com/netbirdio/netbird/client/internal/routemanager"
@@ -53,6 +52,7 @@ import (
 	"github.com/netbirdio/netbird/management/server/store"
 	"github.com/netbirdio/netbird/management/server/telemetry"
 	"github.com/netbirdio/netbird/management/server/types"
+	"github.com/netbirdio/netbird/monotime"
 	relayClient "github.com/netbirdio/netbird/relay/client"
 	"github.com/netbirdio/netbird/route"
 	signal "github.com/netbirdio/netbird/signal/client"
@@ -97,6 +97,7 @@ type MockWGIface struct {
 	GetInterfaceGUIDStringFunc func() (string, error)
 	GetProxyFunc               func() wgproxy.Proxy
 	GetNetFunc                 func() *netstack.Net
+	LastActivitiesFunc         func() map[string]monotime.Time
 }
 
 func (m *MockWGIface) FullStats() (*configurer.Stats, error) {
@@ -187,6 +188,13 @@ func (m *MockWGIface) GetNet() *netstack.Net {
 	return m.GetNetFunc()
 }
 
+func (m *MockWGIface) LastActivities() map[string]monotime.Time {
+	if m.LastActivitiesFunc != nil {
+		return m.LastActivitiesFunc()
+	}
+	return nil
+}
+
 func TestMain(m *testing.M) {
 	_ = util.InitLog("debug", "console")
 	code := m.Run()
@@ -392,7 +400,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
 		StatusRecorder:   engine.statusRecorder,
 		RelayManager:     relayMgr,
 	})
-	_, _, err = engine.routeManager.Init()
+	err = engine.routeManager.Init()
 	require.NoError(t, err)
 	engine.dnsServer = &dns.MockServer{
 		UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
@@ -404,7 +412,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
 	engine.udpMux = bind.NewUniversalUDPMuxDefault(bind.UniversalUDPMuxParams{UDPConn: conn})
 	engine.ctx = ctx
 	engine.srWatcher = guard.NewSRWatcher(nil, nil, nil, icemaker.Config{})
-	engine.connMgr = NewConnMgr(engine.config, engine.statusRecorder, engine.peerStore, wgIface, dispatcher.NewConnectionDispatcher())
+	engine.connMgr = NewConnMgr(engine.config, engine.statusRecorder, engine.peerStore, wgIface)
 	engine.connMgr.Start(ctx)
 
 	type testCase struct {
@@ -793,7 +801,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
 
 			engine.routeManager = mockRouteManager
 			engine.dnsServer = &dns.MockServer{}
-			engine.connMgr = NewConnMgr(engine.config, engine.statusRecorder, engine.peerStore, engine.wgInterface, dispatcher.NewConnectionDispatcher())
+			engine.connMgr = NewConnMgr(engine.config, engine.statusRecorder, engine.peerStore, engine.wgInterface)
 			engine.connMgr.Start(ctx)
 
 			defer func() {
@@ -991,7 +999,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
 			}
 
 			engine.dnsServer = mockDNSServer
-			engine.connMgr = NewConnMgr(engine.config, engine.statusRecorder, engine.peerStore, engine.wgInterface, dispatcher.NewConnectionDispatcher())
+			engine.connMgr = NewConnMgr(engine.config, engine.statusRecorder, engine.peerStore, engine.wgInterface)
 			engine.connMgr.Start(ctx)
 
 			defer func() {
@@ -1473,16 +1481,20 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
 		GetSettings(gomock.Any(), gomock.Any(), gomock.Any()).
 		Return(&types.Settings{}, nil).
 		AnyTimes()
+	settingsMockManager.EXPECT().
+		GetExtraSettings(gomock.Any(), gomock.Any()).
+		Return(&types.ExtraSettings{}, nil).
+		AnyTimes()
 
 	permissionsManager := permissions.NewManager(store)
 
-	accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
+	accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
 	if err != nil {
 		return nil, "", err
 	}
 
 	secretsManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager)
-	mgmtServer, err := server.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, nil, nil)
+	mgmtServer, err := server.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, nil, nil, &server.MockIntegratedValidator{})
 	if err != nil {
 		return nil, "", err
 	}
diff --git a/client/internal/lazyconn/inactivity/inactivity_test.go b/client/internal/lazyconn/inactivity/inactivity_test.go
deleted file mode 100644
index 94451298..00000000
--- a/client/internal/lazyconn/inactivity/inactivity_test.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package inactivity
-
-import (
-	"context"
-	"testing"
-	"time"
-
-	peerid "github.com/netbirdio/netbird/client/internal/peer/id"
-)
-
-type MocPeer struct {
-}
-
-func (m *MocPeer) ConnID() peerid.ConnID {
-	return peerid.ConnID(m)
-}
-
-func TestInactivityMonitor(t *testing.T) {
-	tCtx, testTimeoutCancel := context.WithTimeout(context.Background(), time.Second*5)
-	defer testTimeoutCancel()
-
-	p := &MocPeer{}
-	im := NewInactivityMonitor(p.ConnID(), time.Second*2)
-
-	timeoutChan := make(chan peerid.ConnID)
-
-	exitChan := make(chan struct{})
-
-	go func() {
-		defer close(exitChan)
-		im.Start(tCtx, timeoutChan)
-	}()
-
-	select {
-	case <-timeoutChan:
-	case <-tCtx.Done():
-		t.Fatal("timeout")
-	}
-
-	select {
-	case <-exitChan:
-	case <-tCtx.Done():
-		t.Fatal("timeout")
-	}
-}
-
-func TestReuseInactivityMonitor(t *testing.T) {
-	p := &MocPeer{}
-	im := NewInactivityMonitor(p.ConnID(), time.Second*2)
-
-	timeoutChan := make(chan peerid.ConnID)
-
-	for i := 2; i > 0; i-- {
-		exitChan := make(chan struct{})
-
-		testTimeoutCtx, testTimeoutCancel := context.WithTimeout(context.Background(), time.Second*5)
-
-		go func() {
-			defer close(exitChan)
-			im.Start(testTimeoutCtx, timeoutChan)
-		}()
-
-		select {
-		case <-timeoutChan:
-		case <-testTimeoutCtx.Done():
-			t.Fatal("timeout")
-		}
-
-		select {
-		case <-exitChan:
-		case <-testTimeoutCtx.Done():
-			t.Fatal("timeout")
-		}
-		testTimeoutCancel()
-	}
-}
-
-func TestStopInactivityMonitor(t *testing.T) {
-	tCtx, testTimeoutCancel := context.WithTimeout(context.Background(), time.Second*5)
-	defer testTimeoutCancel()
-
-	p := &MocPeer{}
-	im := NewInactivityMonitor(p.ConnID(), DefaultInactivityThreshold)
-
-	timeoutChan := make(chan peerid.ConnID)
-
-	exitChan := make(chan struct{})
-
-	go func() {
-		defer close(exitChan)
-		im.Start(tCtx, timeoutChan)
-	}()
-
-	go func() {
-		time.Sleep(3 * time.Second)
-		im.Stop()
-	}()
-
-	select {
-	case <-timeoutChan:
-		t.Fatal("unexpected timeout")
-	case <-exitChan:
-	case <-tCtx.Done():
-		t.Fatal("timeout")
-	}
-}
-
-func TestPauseInactivityMonitor(t *testing.T) {
-	tCtx, testTimeoutCancel := context.WithTimeout(context.Background(), time.Second*10)
-	defer testTimeoutCancel()
-
-	p := &MocPeer{}
-	trashHold := time.Second * 3
-	im := NewInactivityMonitor(p.ConnID(), trashHold)
-
-	ctx, cancel := context.WithCancel(context.Background())
-	defer cancel()
-
-	timeoutChan := make(chan peerid.ConnID)
-
-	exitChan := make(chan struct{})
-
-	go func() {
-		defer close(exitChan)
-		im.Start(ctx, timeoutChan)
-	}()
-
-	time.Sleep(1 * time.Second) // grant time to start the monitor
-	im.PauseTimer()
-
-	// check to do not receive timeout
-	thresholdCtx, thresholdCancel := context.WithTimeout(context.Background(), trashHold+time.Second)
-	defer thresholdCancel()
-	select {
-	case <-exitChan:
-		t.Fatal("unexpected exit")
-	case <-timeoutChan:
-		t.Fatal("unexpected timeout")
-	case <-thresholdCtx.Done():
-		// test ok
-	case <-tCtx.Done():
-		t.Fatal("test timed out")
-	}
-
-	// test reset timer
-	im.ResetTimer()
-
-	select {
-	case <-tCtx.Done():
-		t.Fatal("test timed out")
-	case <-exitChan:
-		t.Fatal("unexpected exit")
-	case <-timeoutChan:
-		// expected timeout
-	}
-}
diff --git a/client/internal/lazyconn/inactivity/manager_test.go b/client/internal/lazyconn/inactivity/manager_test.go
new file mode 100644
index 00000000..10b4ef1e
--- /dev/null
+++ b/client/internal/lazyconn/inactivity/manager_test.go
@@ -0,0 +1,114 @@
+package inactivity
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	log "github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/netbirdio/netbird/client/internal/lazyconn"
+	"github.com/netbirdio/netbird/monotime"
+)
+
+type mockWgInterface struct {
+	lastActivities map[string]monotime.Time
+}
+
+func (m *mockWgInterface) LastActivities() map[string]monotime.Time {
+	return m.lastActivities
+}
+
+func TestPeerTriggersInactivity(t *testing.T) {
+	peerID := "peer1"
+
+	wgMock := &mockWgInterface{
+		lastActivities: map[string]monotime.Time{
+			peerID: monotime.Time(int64(monotime.Now()) - int64(20*time.Minute)),
+		},
+	}
+
+	fakeTick := make(chan time.Time, 1)
+	newTicker = func(d time.Duration) Ticker {
+		return &fakeTickerMock{CChan: fakeTick}
+	}
+
+	peerLog := log.WithField("peer", peerID)
+	peerCfg := &lazyconn.PeerConfig{
+		PublicKey: peerID,
+		Log:       peerLog,
+	}
+
+	manager := NewManager(wgMock, nil)
+	manager.AddPeer(peerCfg)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	// Start the manager in a goroutine
+	go manager.Start(ctx)
+
+	// Send a tick to simulate time passage
+	fakeTick <- time.Now()
+
+	// Check if peer appears on inactivePeersChan
+	select {
+	case inactivePeers := <-manager.inactivePeersChan:
+		assert.Contains(t, inactivePeers, peerID, "expected peer to be marked inactive")
+	case <-time.After(1 * time.Second):
+		t.Fatal("expected inactivity event, but none received")
+	}
+}
+
+func TestPeerTriggersActivity(t *testing.T) {
+	peerID := "peer1"
+
+	wgMock := &mockWgInterface{
+		lastActivities: map[string]monotime.Time{
+			peerID: monotime.Time(int64(monotime.Now()) - int64(5*time.Minute)),
+		},
+	}
+
+	fakeTick := make(chan time.Time, 1)
+	newTicker = func(d time.Duration) Ticker {
+		return &fakeTickerMock{CChan: fakeTick}
+	}
+
+	peerLog := log.WithField("peer", peerID)
+	peerCfg := &lazyconn.PeerConfig{
+		PublicKey: peerID,
+		Log:       peerLog,
+	}
+
+	manager := NewManager(wgMock, nil)
+	manager.AddPeer(peerCfg)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	// Start the manager in a goroutine
+	go manager.Start(ctx)
+
+	// Send a tick to simulate time passage
+	fakeTick <- time.Now()
+
+	// Check if peer appears on inactivePeersChan
+	select {
+	case <-manager.inactivePeersChan:
+		t.Fatal("expected inactive peer to be marked inactive")
+	case <-time.After(1 * time.Second):
+		// No inactivity event should be received
+	}
+}
+
+// fakeTickerMock implements Ticker interface for testing
+type fakeTickerMock struct {
+	CChan chan time.Time
+}
+
+func (f *fakeTickerMock) C() <-chan time.Time {
+	return f.CChan
+}
+
+func (f *fakeTickerMock) Stop() {}
diff --git a/client/internal/routemanager/client/client_test.go b/client/internal/routemanager/client/client_test.go
index e7aff28b..850f6691 100644
--- a/client/internal/routemanager/client/client_test.go
+++ b/client/internal/routemanager/client/client_test.go
@@ -7,12 +7,12 @@ import (
 	"time"
 
 	"github.com/netbirdio/netbird/client/internal/peer"
+	"github.com/netbirdio/netbird/client/internal/routemanager/common"
 	"github.com/netbirdio/netbird/client/internal/routemanager/static"
 	"github.com/netbirdio/netbird/route"
 )
 
 func TestGetBestrouteFromStatuses(t *testing.T) {
-
 	testCases := []struct {
 		name            string
 		statuses        map[route.ID]routerPeerStatus
@@ -811,9 +811,12 @@ func TestGetBestrouteFromStatuses(t *testing.T) {
 				currentRoute = tc.existingRoutes[tc.currentRoute]
 			}
 
+			params := common.HandlerParams{
+				Route: &route.Route{Network: netip.MustParsePrefix("192.168.0.0/24")},
+			}
 			// create new clientNetwork
 			client := &Watcher{
-				handler:       static.NewRoute(&route.Route{Network: netip.MustParsePrefix("192.168.0.0/24")}, nil, nil),
+				handler:       static.NewRoute(params),
 				routes:        tc.existingRoutes,
 				currentChosen: currentRoute,
 			}
diff --git a/client/internal/routemanager/fakeip/fakeip_test.go b/client/internal/routemanager/fakeip/fakeip_test.go
new file mode 100644
index 00000000..ad3e4bd4
--- /dev/null
+++ b/client/internal/routemanager/fakeip/fakeip_test.go
@@ -0,0 +1,240 @@
+package fakeip
+
+import (
+	"net/netip"
+	"sync"
+	"testing"
+)
+
+func TestNewManager(t *testing.T) {
+	manager := NewManager()
+
+	if manager.baseIP.String() != "240.0.0.1" {
+		t.Errorf("Expected base IP 240.0.0.1, got %s", manager.baseIP.String())
+	}
+
+	if manager.maxIP.String() != "240.255.255.254" {
+		t.Errorf("Expected max IP 240.255.255.254, got %s", manager.maxIP.String())
+	}
+
+	if manager.nextIP.Compare(manager.baseIP) != 0 {
+		t.Errorf("Expected nextIP to start at baseIP")
+	}
+}
+
+func TestAllocateFakeIP(t *testing.T) {
+	manager := NewManager()
+	realIP := netip.MustParseAddr("8.8.8.8")
+
+	fakeIP, err := manager.AllocateFakeIP(realIP)
+	if err != nil {
+		t.Fatalf("Failed to allocate fake IP: %v", err)
+	}
+
+	if !fakeIP.Is4() {
+		t.Error("Fake IP should be IPv4")
+	}
+
+	// Check it's in the correct range
+	if fakeIP.As4()[0] != 240 {
+		t.Errorf("Fake IP should be in 240.0.0.0/8 range, got %s", fakeIP.String())
+	}
+
+	// Should return same fake IP for same real IP
+	fakeIP2, err := manager.AllocateFakeIP(realIP)
+	if err != nil {
+		t.Fatalf("Failed to get existing fake IP: %v", err)
+	}
+
+	if fakeIP.Compare(fakeIP2) != 0 {
+		t.Errorf("Expected same fake IP for same real IP, got %s and %s", fakeIP.String(), fakeIP2.String())
+	}
+}
+
+func TestAllocateFakeIPIPv6Rejection(t *testing.T) {
+	manager := NewManager()
+	realIPv6 := netip.MustParseAddr("2001:db8::1")
+
+	_, err := manager.AllocateFakeIP(realIPv6)
+	if err == nil {
+		t.Error("Expected error for IPv6 address")
+	}
+}
+
+func TestGetFakeIP(t *testing.T) {
+	manager := NewManager()
+	realIP := netip.MustParseAddr("1.1.1.1")
+
+	// Should not exist initially
+	_, exists := manager.GetFakeIP(realIP)
+	if exists {
+		t.Error("Fake IP should not exist before allocation")
+	}
+
+	// Allocate and check
+	expectedFakeIP, err := manager.AllocateFakeIP(realIP)
+	if err != nil {
+		t.Fatalf("Failed to allocate: %v", err)
+	}
+
+	fakeIP, exists := manager.GetFakeIP(realIP)
+	if !exists {
+		t.Error("Fake IP should exist after allocation")
+	}
+
+	if fakeIP.Compare(expectedFakeIP) != 0 {
+		t.Errorf("Expected %s, got %s", expectedFakeIP.String(), fakeIP.String())
+	}
+}
+
+func TestMultipleAllocations(t *testing.T) {
+	manager := NewManager()
+
+	allocations := make(map[netip.Addr]netip.Addr)
+
+	// Allocate multiple IPs
+	for i := 1; i <= 100; i++ {
+		realIP := netip.AddrFrom4([4]byte{10, 0, byte(i / 256), byte(i % 256)})
+		fakeIP, err := manager.AllocateFakeIP(realIP)
+		if err != nil {
+			t.Fatalf("Failed to allocate fake IP for %s: %v", realIP.String(), err)
+		}
+
+		// Check for duplicates
+		for _, existingFake := range allocations {
+			if fakeIP.Compare(existingFake) == 0 {
+				t.Errorf("Duplicate fake IP allocated: %s", fakeIP.String())
+			}
+		}
+
+		allocations[realIP] = fakeIP
+	}
+
+	// Verify all allocations can be retrieved
+	for realIP, expectedFake := range allocations {
+		actualFake, exists := manager.GetFakeIP(realIP)
+		if !exists {
+			t.Errorf("Missing allocation for %s", realIP.String())
+		}
+		if actualFake.Compare(expectedFake) != 0 {
+			t.Errorf("Mismatch for %s: expected %s, got %s", realIP.String(), expectedFake.String(), actualFake.String())
+		}
+	}
+}
+
+func TestGetFakeIPBlock(t *testing.T) {
+	manager := NewManager()
+	block := manager.GetFakeIPBlock()
+
+	expected := "240.0.0.0/8"
+	if block.String() != expected {
+		t.Errorf("Expected %s, got %s", expected, block.String())
+	}
+}
+
+func TestConcurrentAccess(t *testing.T) {
+	manager := NewManager()
+
+	const numGoroutines = 50
+	const allocationsPerGoroutine = 10
+
+	var wg sync.WaitGroup
+	results := make(chan netip.Addr, numGoroutines*allocationsPerGoroutine)
+
+	// Concurrent allocations
+	for i := 0; i < numGoroutines; i++ {
+		wg.Add(1)
+		go func(goroutineID int) {
+			defer wg.Done()
+			for j := 0; j < allocationsPerGoroutine; j++ {
+				realIP := netip.AddrFrom4([4]byte{192, 168, byte(goroutineID), byte(j)})
+				fakeIP, err := manager.AllocateFakeIP(realIP)
+				if err != nil {
+					t.Errorf("Failed to allocate in goroutine %d: %v", goroutineID, err)
+					return
+				}
+				results <- fakeIP
+			}
+		}(i)
+	}
+
+	wg.Wait()
+	close(results)
+
+	// Check for duplicates
+	seen := make(map[netip.Addr]bool)
+	count := 0
+	for fakeIP := range results {
+		if seen[fakeIP] {
+			t.Errorf("Duplicate fake IP in concurrent test: %s", fakeIP.String())
+		}
+		seen[fakeIP] = true
+		count++
+	}
+
+	if count != numGoroutines*allocationsPerGoroutine {
+		t.Errorf("Expected %d allocations, got %d", numGoroutines*allocationsPerGoroutine, count)
+	}
+}
+
+func TestIPExhaustion(t *testing.T) {
+	// Create a manager with limited range for testing
+	manager := &Manager{
+		nextIP:     netip.AddrFrom4([4]byte{240, 0, 0, 1}),
+		allocated:  make(map[netip.Addr]netip.Addr),
+		fakeToReal: make(map[netip.Addr]netip.Addr),
+		baseIP:     netip.AddrFrom4([4]byte{240, 0, 0, 1}),
+		maxIP:      netip.AddrFrom4([4]byte{240, 0, 0, 3}), // Only 3 IPs available
+	}
+
+	// Allocate all available IPs
+	realIPs := []netip.Addr{
+		netip.MustParseAddr("1.0.0.1"),
+		netip.MustParseAddr("1.0.0.2"),
+		netip.MustParseAddr("1.0.0.3"),
+	}
+
+	for _, realIP := range realIPs {
+		_, err := manager.AllocateFakeIP(realIP)
+		if err != nil {
+			t.Fatalf("Failed to allocate fake IP: %v", err)
+		}
+	}
+
+	// Try to allocate one more - should fail
+	_, err := manager.AllocateFakeIP(netip.MustParseAddr("1.0.0.4"))
+	if err == nil {
+		t.Error("Expected exhaustion error")
+	}
+}
+
+func TestWrapAround(t *testing.T) {
+	// Create manager starting near the end of range
+	manager := &Manager{
+		nextIP:     netip.AddrFrom4([4]byte{240, 0, 0, 254}),
+		allocated:  make(map[netip.Addr]netip.Addr),
+		fakeToReal: make(map[netip.Addr]netip.Addr),
+		baseIP:     netip.AddrFrom4([4]byte{240, 0, 0, 1}),
+		maxIP:      netip.AddrFrom4([4]byte{240, 0, 0, 254}),
+	}
+
+	// Allocate the last IP
+	fakeIP1, err := manager.AllocateFakeIP(netip.MustParseAddr("1.0.0.1"))
+	if err != nil {
+		t.Fatalf("Failed to allocate first IP: %v", err)
+	}
+
+	if fakeIP1.String() != "240.0.0.254" {
+		t.Errorf("Expected 240.0.0.254, got %s", fakeIP1.String())
+	}
+
+	// Next allocation should wrap around to the beginning
+	fakeIP2, err := manager.AllocateFakeIP(netip.MustParseAddr("1.0.0.2"))
+	if err != nil {
+		t.Fatalf("Failed to allocate second IP: %v", err)
+	}
+
+	if fakeIP2.String() != "240.0.0.1" {
+		t.Errorf("Expected 240.0.0.1 after wrap, got %s", fakeIP2.String())
+	}
+}
diff --git a/client/internal/routemanager/manager_test.go b/client/internal/routemanager/manager_test.go
index 486ee080..2f13c213 100644
--- a/client/internal/routemanager/manager_test.go
+++ b/client/internal/routemanager/manager_test.go
@@ -430,7 +430,7 @@ func TestManagerUpdateRoutes(t *testing.T) {
 				StatusRecorder: statusRecorder,
 			})
 
-			_, _, err = routeManager.Init()
+			err = routeManager.Init()
 
 			require.NoError(t, err, "should init route manager")
 			defer routeManager.Stop(nil)
diff --git a/client/internal/routemanager/systemops/systemops_generic_test.go b/client/internal/routemanager/systemops/systemops_generic_test.go
index 2a57e604..c1c1182b 100644
--- a/client/internal/routemanager/systemops/systemops_generic_test.go
+++ b/client/internal/routemanager/systemops/systemops_generic_test.go
@@ -143,7 +143,7 @@ func TestAddVPNRoute(t *testing.T) {
 			wgInterface := createWGInterface(t, fmt.Sprintf("utun53%d", n), "100.65.75.2/24", 33100+n)
 
 			r := NewSysOps(wgInterface, nil)
-			_, _, err := r.SetupRouting(nil, nil)
+			err := r.SetupRouting(nil, nil)
 			require.NoError(t, err)
 			t.Cleanup(func() {
 				assert.NoError(t, r.CleanupRouting(nil))
@@ -341,7 +341,7 @@ func TestAddRouteToNonVPNIntf(t *testing.T) {
 			wgInterface := createWGInterface(t, fmt.Sprintf("utun54%d", n), "100.65.75.2/24", 33200+n)
 
 			r := NewSysOps(wgInterface, nil)
-			_, _, err := r.SetupRouting(nil, nil)
+			err := r.SetupRouting(nil, nil)
 			require.NoError(t, err)
 			t.Cleanup(func() {
 				assert.NoError(t, r.CleanupRouting(nil))
@@ -484,7 +484,7 @@ func setupTestEnv(t *testing.T) {
 	})
 
 	r := NewSysOps(wgInterface, nil)
-	_, _, err := r.SetupRouting(nil, nil)
+	err := r.SetupRouting(nil, nil)
 	require.NoError(t, err, "setupRouting should not return err")
 	t.Cleanup(func() {
 		assert.NoError(t, r.CleanupRouting(nil))
diff --git a/client/server/server_test.go b/client/server/server_test.go
index f2dff76f..7c46aac5 100644
--- a/client/server/server_test.go
+++ b/client/server/server_test.go
@@ -206,13 +206,13 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
 	settingsMockManager := settings.NewMockManager(ctrl)
 	permissionsManagerMock := permissions.NewMockManager(ctrl)
 
-	accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock)
+	accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
 	if err != nil {
 		return nil, "", err
 	}
 
 	secretsManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager)
-	mgmtServer, err := server.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, nil, nil)
+	mgmtServer, err := server.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, nil, nil, &server.MockIntegratedValidator{})
 	if err != nil {
 		return nil, "", err
 	}
diff --git a/client/status/status_test.go b/client/status/status_test.go
index 33eda4b9..5b5d23ef 100644
--- a/client/status/status_test.go
+++ b/client/status/status_test.go
@@ -234,7 +234,7 @@ var overview = OutputOverview{
 }
 
 func TestConversionFromFullStatusToOutputOverview(t *testing.T) {
-	convertedResult := ConvertToStatusOutputOverview(resp, false, "", nil, nil, nil)
+	convertedResult := ConvertToStatusOutputOverview(resp, false, "", nil, nil, nil, "")
 
 	assert.Equal(t, overview, convertedResult)
 }
diff --git a/infrastructure_files/tests/setup.env b/infrastructure_files/tests/setup.env
index dc1d6e01..5287e6c5 100644
--- a/infrastructure_files/tests/setup.env
+++ b/infrastructure_files/tests/setup.env
@@ -29,3 +29,4 @@ NETBIRD_TURN_EXTERNAL_IP=1.2.3.4
 NETBIRD_RELAY_PORT=33445
 NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN=true
 NETBIRD_AUTH_PKCE_LOGIN_FLAG=0
+NETBIRD_MGMT_DISABLE_DEFAULT_POLICY=$CI_NETBIRD_MGMT_DISABLE_DEFAULT_POLICY
diff --git a/management/client/client_test.go b/management/client/client_test.go
index b22a7993..b59b7c98 100644
--- a/management/client/client_test.go
+++ b/management/client/client_test.go
@@ -87,6 +87,12 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
 		).
 		Return(&types.Settings{}, nil).
 		AnyTimes()
+	settingsMockManager.
+		EXPECT().
+		GetExtraSettings(gomock.Any(), gomock.Any()).
+		Return(&types.ExtraSettings{}, nil).
+		AnyTimes()
+
 	permissionsManagerMock := permissions.NewMockManager(ctrl)
 	permissionsManagerMock.
 		EXPECT().
@@ -100,13 +106,13 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
 		Return(true, nil).
 		AnyTimes()
 
-	accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock)
+	accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	secretsManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager)
-	mgmtServer, err := mgmt.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, nil, nil)
+	mgmtServer, err := mgmt.NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, nil, nil, mgmt.MockIntegratedValidator{})
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/management/client/rest/peers_test.go b/management/client/rest/peers_test.go
index 4c5cd1e6..f31e44e1 100644
--- a/management/client/rest/peers_test.go
+++ b/management/client/rest/peers_test.go
@@ -184,6 +184,10 @@ func TestPeers_Integration(t *testing.T) {
 		require.NoError(t, err)
 		require.NotEmpty(t, peers)
 
+		filteredPeers, err := c.Peers.List(context.Background(), rest.PeerIPFilter("192.168.10.0"))
+		require.NoError(t, err)
+		require.Empty(t, filteredPeers)
+
 		peer, err := c.Peers.Get(context.Background(), peers[0].Id)
 		require.NoError(t, err)
 		assert.Equal(t, peers[0].Id, peer.Id)
diff --git a/management/server/account_test.go b/management/server/account_test.go
index c3b1f31a..b65dffe6 100644
--- a/management/server/account_test.go
+++ b/management/server/account_test.go
@@ -373,7 +373,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
 	}
 
 	for _, testCase := range tt {
-		account := newAccountWithId(context.Background(), "account-1", userID, "netbird.io")
+		account := newAccountWithId(context.Background(), "account-1", userID, "netbird.io", false)
 		account.UpdateSettings(&testCase.accountSettings)
 		account.Network = network
 		account.Peers = testCase.peers
@@ -398,7 +398,7 @@ func TestNewAccount(t *testing.T) {
 	domain := "netbird.io"
 	userId := "account_creator"
 	accountID := "account_id"
-	account := newAccountWithId(context.Background(), accountID, userId, domain)
+	account := newAccountWithId(context.Background(), accountID, userId, domain, false)
 	verifyNewAccountHasDefaultFields(t, account, userId, domain, []string{userId})
 }
 
@@ -640,7 +640,7 @@ func TestDefaultAccountManager_GetAccountIDFromToken(t *testing.T) {
 func TestDefaultAccountManager_SyncUserJWTGroups(t *testing.T) {
 	userId := "user-id"
 	domain := "test.domain"
-	_ = newAccountWithId(context.Background(), "", userId, domain)
+	_ = newAccountWithId(context.Background(), "", userId, domain, false)
 	manager, err := createManager(t)
 	require.NoError(t, err, "unable to create account manager")
 	accountID, err := manager.GetAccountIDByUserID(context.Background(), userId, domain)
@@ -793,7 +793,7 @@ func TestAccountManager_GetAccountByUserID(t *testing.T) {
 }
 
 func createAccount(am *DefaultAccountManager, accountID, userID, domain string) (*types.Account, error) {
-	account := newAccountWithId(context.Background(), accountID, userID, domain)
+	account := newAccountWithId(context.Background(), accountID, userID, domain, false)
 	err := am.Store.SaveAccount(context.Background(), account)
 	if err != nil {
 		return nil, err
@@ -1208,6 +1208,14 @@ func TestAccountManager_NetworkUpdates_DeletePolicy(t *testing.T) {
 	updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID)
 	defer manager.peersUpdateManager.CloseChannel(context.Background(), peer1.ID)
 
+	// Ensure that we do not receive an update message before the policy is deleted
+	time.Sleep(time.Second)
+	select {
+	case <-updMsg:
+		t.Logf("received addPeer update message before policy deletion")
+	default:
+	}
+
 	wg := sync.WaitGroup{}
 	wg.Add(1)
 	go func() {
@@ -2615,11 +2623,11 @@ func TestAccount_SetJWTGroups(t *testing.T) {
 	account := &types.Account{
 		Id: "accountID",
 		Peers: map[string]*nbpeer.Peer{
-			"peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
-			"peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
-			"peer3": {ID: "peer3", Key: "key3", UserID: "user1"},
-			"peer4": {ID: "peer4", Key: "key4", UserID: "user2"},
-			"peer5": {ID: "peer5", Key: "key5", UserID: "user2"},
+			"peer1": {ID: "peer1", Key: "key1", UserID: "user1", IP: net.IP{1, 1, 1, 1}, DNSLabel: "peer1.domain.test"},
+			"peer2": {ID: "peer2", Key: "key2", UserID: "user1", IP: net.IP{2, 2, 2, 2}, DNSLabel: "peer2.domain.test"},
+			"peer3": {ID: "peer3", Key: "key3", UserID: "user1", IP: net.IP{3, 3, 3, 3}, DNSLabel: "peer3.domain.test"},
+			"peer4": {ID: "peer4", Key: "key4", UserID: "user2", IP: net.IP{4, 4, 4, 4}, DNSLabel: "peer4.domain.test"},
+			"peer5": {ID: "peer5", Key: "key5", UserID: "user2", IP: net.IP{5, 5, 5, 5}, DNSLabel: "peer5.domain.test"},
 		},
 		Groups: map[string]*types.Group{
 			"group1": {ID: "group1", Name: "group1", Issued: types.GroupIssuedAPI, Peers: []string{}},
@@ -2879,7 +2887,7 @@ func createManager(t testing.TB) (*DefaultAccountManager, error) {
 
 	permissionsManager := permissions.NewManager(store)
 
-	manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
+	manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
 	if err != nil {
 		return nil, err
 	}
@@ -3139,11 +3147,11 @@ func BenchmarkLoginPeer_NewPeer(b *testing.B) {
 		minMsPerOpCICD  float64
 		maxMsPerOpCICD  float64
 	}{
-		{"Small", 50, 5, 7, 20, 10, 80},
+		{"Small", 50, 5, 7, 20, 5, 80},
 		{"Medium", 500, 100, 5, 40, 30, 140},
 		{"Large", 5000, 200, 80, 120, 140, 390},
-		{"Small single", 50, 10, 7, 20, 10, 80},
-		{"Medium single", 500, 10, 5, 40, 20, 85},
+		{"Small single", 50, 10, 7, 20, 6, 80},
+		{"Medium single", 500, 10, 5, 40, 15, 85},
 		{"Large 5", 5000, 15, 80, 120, 80, 200},
 	}
 
@@ -3335,11 +3343,11 @@ func TestPropagateUserGroupMemberships(t *testing.T) {
 	account, err := manager.GetOrCreateAccountByUser(ctx, initiatorId, domain)
 	require.NoError(t, err)
 
-	peer1 := &nbpeer.Peer{ID: "peer1", AccountID: account.Id, UserID: initiatorId}
+	peer1 := &nbpeer.Peer{ID: "peer1", AccountID: account.Id, UserID: initiatorId, IP: net.IP{1, 1, 1, 1}, DNSLabel: "peer1.domain.test"}
 	err = manager.Store.AddPeerToAccount(ctx, store.LockingStrengthUpdate, peer1)
 	require.NoError(t, err)
 
-	peer2 := &nbpeer.Peer{ID: "peer2", AccountID: account.Id, UserID: initiatorId}
+	peer2 := &nbpeer.Peer{ID: "peer2", AccountID: account.Id, UserID: initiatorId, IP: net.IP{2, 2, 2, 2}, DNSLabel: "peer2.domain.test"}
 	err = manager.Store.AddPeerToAccount(ctx, store.LockingStrengthUpdate, peer2)
 	require.NoError(t, err)
 
@@ -3440,3 +3448,74 @@ func TestPropagateUserGroupMemberships(t *testing.T) {
 		}
 	})
 }
+
+func TestDefaultAccountManager_GetAccountOnboarding(t *testing.T) {
+	manager, err := createManager(t)
+	require.NoError(t, err)
+
+	account, err := manager.GetOrCreateAccountByUser(context.Background(), userID, "")
+	require.NoError(t, err)
+
+	t.Run("should return account onboarding when onboarding exist", func(t *testing.T) {
+		onboarding, err := manager.GetAccountOnboarding(context.Background(), account.Id, userID)
+		require.NoError(t, err)
+		require.NotNil(t, onboarding)
+		assert.Equal(t, account.Id, onboarding.AccountID)
+		assert.Equal(t, true, onboarding.OnboardingFlowPending)
+		assert.Equal(t, true, onboarding.SignupFormPending)
+		if onboarding.UpdatedAt.IsZero() {
+			t.Errorf("Onboarding was not retrieved from the store")
+		}
+	})
+
+	t.Run("should return account onboarding when onboard don't exist", func(t *testing.T) {
+		account.Id = "with-zero-onboarding"
+		account.Onboarding = types.AccountOnboarding{}
+		err = manager.Store.SaveAccount(context.Background(), account)
+		require.NoError(t, err)
+		onboarding, err := manager.GetAccountOnboarding(context.Background(), account.Id, userID)
+		require.NoError(t, err)
+		require.NotNil(t, onboarding)
+		_, err = manager.Store.GetAccountOnboarding(context.Background(), account.Id)
+		require.Error(t, err, "should return error when onboarding is not set")
+	})
+}
+
+func TestDefaultAccountManager_UpdateAccountOnboarding(t *testing.T) {
+	manager, err := createManager(t)
+	require.NoError(t, err)
+
+	account, err := manager.GetOrCreateAccountByUser(context.Background(), userID, "")
+	require.NoError(t, err)
+
+	onboarding := &types.AccountOnboarding{
+		OnboardingFlowPending: true,
+		SignupFormPending:     true,
+	}
+
+	t.Run("update onboarding with no change", func(t *testing.T) {
+		updated, err := manager.UpdateAccountOnboarding(context.Background(), account.Id, userID, onboarding)
+		require.NoError(t, err)
+		assert.Equal(t, onboarding.OnboardingFlowPending, updated.OnboardingFlowPending)
+		assert.Equal(t, onboarding.SignupFormPending, updated.SignupFormPending)
+		if updated.UpdatedAt.IsZero() {
+			t.Errorf("Onboarding was updated in the store")
+		}
+	})
+
+	onboarding.OnboardingFlowPending = false
+	onboarding.SignupFormPending = false
+
+	t.Run("update onboarding", func(t *testing.T) {
+		updated, err := manager.UpdateAccountOnboarding(context.Background(), account.Id, userID, onboarding)
+		require.NoError(t, err)
+		require.NotNil(t, updated)
+		assert.Equal(t, onboarding.OnboardingFlowPending, updated.OnboardingFlowPending)
+		assert.Equal(t, onboarding.SignupFormPending, updated.SignupFormPending)
+	})
+
+	t.Run("update onboarding with no onboarding", func(t *testing.T) {
+		_, err = manager.UpdateAccountOnboarding(context.Background(), account.Id, userID, nil)
+		require.NoError(t, err)
+	})
+}
diff --git a/management/server/dns_test.go b/management/server/dns_test.go
index 36476b14..f2295450 100644
--- a/management/server/dns_test.go
+++ b/management/server/dns_test.go
@@ -216,8 +216,10 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
 	t.Cleanup(ctrl.Finish)
 
 	settingsMockManager := settings.NewMockManager(ctrl)
+	// return empty extra settings for expected calls to UpdateAccountPeers
+	settingsMockManager.EXPECT().GetExtraSettings(gomock.Any(), gomock.Any()).Return(&types.ExtraSettings{}, nil).AnyTimes()
 	permissionsManager := permissions.NewManager(store)
-	return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
+	return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
 }
 
 func createDNSStore(t *testing.T) (store.Store, error) {
@@ -267,7 +269,7 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*types.Account
 
 	domain := "example.com"
 
-	account := newAccountWithId(context.Background(), dnsAccountID, dnsAdminUserID, domain)
+	account := newAccountWithId(context.Background(), dnsAccountID, dnsAdminUserID, domain, false)
 
 	account.Users[dnsRegularUserID] = &types.User{
 		Id:   dnsRegularUserID,
diff --git a/management/server/ephemeral_test.go b/management/server/ephemeral_test.go
index 38477f7a..f71d48c5 100644
--- a/management/server/ephemeral_test.go
+++ b/management/server/ephemeral_test.go
@@ -3,9 +3,12 @@ package server
 import (
 	"context"
 	"fmt"
+	"sync"
 	"testing"
 	"time"
 
+	"github.com/stretchr/testify/assert"
+
 	nbAccount "github.com/netbirdio/netbird/management/server/account"
 	nbpeer "github.com/netbirdio/netbird/management/server/peer"
 	"github.com/netbirdio/netbird/management/server/store"
@@ -27,28 +30,65 @@ func (s *MockStore) GetAllEphemeralPeers(_ context.Context, _ store.LockingStren
 	return peers, nil
 }
 
-type MocAccountManager struct {
+type MockAccountManager struct {
+	mu sync.Mutex
 	nbAccount.Manager
-	store *MockStore
+	store             *MockStore
+	deletePeerCalls   int
+	bufferUpdateCalls map[string]int
+	wg                *sync.WaitGroup
 }
 
-func (a MocAccountManager) DeletePeer(_ context.Context, accountID, peerID, userID string) error {
+func (a *MockAccountManager) DeletePeer(_ context.Context, accountID, peerID, userID string) error {
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	a.deletePeerCalls++
+	if a.wg != nil {
+		a.wg.Done()
+	}
 	delete(a.store.account.Peers, peerID)
-	return nil //nolint:nil
+	return nil
+}
+
+func (a *MockAccountManager) GetDeletePeerCalls() int {
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	return a.deletePeerCalls
+}
+
+func (a *MockAccountManager) BufferUpdateAccountPeers(ctx context.Context, accountID string) {
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	if a.bufferUpdateCalls == nil {
+		a.bufferUpdateCalls = make(map[string]int)
+	}
+	a.bufferUpdateCalls[accountID]++
 }
 
-func (a MocAccountManager) GetStore() store.Store {
+func (a *MockAccountManager) GetBufferUpdateCalls(accountID string) int {
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	if a.bufferUpdateCalls == nil {
+		return 0
+	}
+	return a.bufferUpdateCalls[accountID]
+}
+
+func (a *MockAccountManager) GetStore() store.Store {
 	return a.store
 }
 
 func TestNewManager(t *testing.T) {
+	t.Cleanup(func() {
+		timeNow = time.Now
+	})
 	startTime := time.Now()
 	timeNow = func() time.Time {
 		return startTime
 	}
 
 	store := &MockStore{}
-	am := MocAccountManager{
+	am := MockAccountManager{
 		store: store,
 	}
 
@@ -56,7 +96,7 @@ func TestNewManager(t *testing.T) {
 	numberOfEphemeralPeers := 3
 	seedPeers(store, numberOfPeers, numberOfEphemeralPeers)
 
-	mgr := NewEphemeralManager(store, am)
+	mgr := NewEphemeralManager(store, &am)
 	mgr.loadEphemeralPeers(context.Background())
 	startTime = startTime.Add(ephemeralLifeTime + 1)
 	mgr.cleanup(context.Background())
@@ -67,13 +107,16 @@ func TestNewManager(t *testing.T) {
 }
 
 func TestNewManagerPeerConnected(t *testing.T) {
+	t.Cleanup(func() {
+		timeNow = time.Now
+	})
 	startTime := time.Now()
 	timeNow = func() time.Time {
 		return startTime
 	}
 
 	store := &MockStore{}
-	am := MocAccountManager{
+	am := MockAccountManager{
 		store: store,
 	}
 
@@ -81,7 +124,7 @@ func TestNewManagerPeerConnected(t *testing.T) {
 	numberOfEphemeralPeers := 3
 	seedPeers(store, numberOfPeers, numberOfEphemeralPeers)
 
-	mgr := NewEphemeralManager(store, am)
+	mgr := NewEphemeralManager(store, &am)
 	mgr.loadEphemeralPeers(context.Background())
 	mgr.OnPeerConnected(context.Background(), store.account.Peers["ephemeral_peer_0"])
 
@@ -95,13 +138,16 @@ func TestNewManagerPeerConnected(t *testing.T) {
 }
 
 func TestNewManagerPeerDisconnected(t *testing.T) {
+	t.Cleanup(func() {
+		timeNow = time.Now
+	})
 	startTime := time.Now()
 	timeNow = func() time.Time {
 		return startTime
 	}
 
 	store := &MockStore{}
-	am := MocAccountManager{
+	am := MockAccountManager{
 		store: store,
 	}
 
@@ -109,7 +155,7 @@ func TestNewManagerPeerDisconnected(t *testing.T) {
 	numberOfEphemeralPeers := 3
 	seedPeers(store, numberOfPeers, numberOfEphemeralPeers)
 
-	mgr := NewEphemeralManager(store, am)
+	mgr := NewEphemeralManager(store, &am)
 	mgr.loadEphemeralPeers(context.Background())
 	for _, v := range store.account.Peers {
 		mgr.OnPeerConnected(context.Background(), v)
@@ -126,8 +172,38 @@ func TestNewManagerPeerDisconnected(t *testing.T) {
 	}
 }
 
+func TestCleanupSchedulingBehaviorIsBatched(t *testing.T) {
+	const (
+		ephemeralPeers    = 10
+		testLifeTime      = 1 * time.Second
+		testCleanupWindow = 100 * time.Millisecond
+	)
+	mockStore := &MockStore{}
+	mockAM := &MockAccountManager{
+		store: mockStore,
+	}
+	mockAM.wg = &sync.WaitGroup{}
+	mockAM.wg.Add(ephemeralPeers)
+	mgr := NewEphemeralManager(mockStore, mockAM)
+	mgr.lifeTime = testLifeTime
+	mgr.cleanupWindow = testCleanupWindow
+
+	account := newAccountWithId(context.Background(), "account", "", "", false)
+	mockStore.account = account
+	for i := range ephemeralPeers {
+		p := &nbpeer.Peer{ID: fmt.Sprintf("peer-%d", i), AccountID: account.Id, Ephemeral: true}
+		mockStore.account.Peers[p.ID] = p
+		time.Sleep(testCleanupWindow / ephemeralPeers)
+		mgr.OnPeerDisconnected(context.Background(), p)
+	}
+	mockAM.wg.Wait()
+	assert.Len(t, mockStore.account.Peers, 0, "all ephemeral peers should be cleaned up after the lifetime")
+	assert.Equal(t, 1, mockAM.GetBufferUpdateCalls(account.Id), "buffer update should be called once")
+	assert.Equal(t, ephemeralPeers, mockAM.GetDeletePeerCalls(), "should have deleted all peers")
+}
+
 func seedPeers(store *MockStore, numberOfPeers int, numberOfEphemeralPeers int) {
-	store.account = newAccountWithId(context.Background(), "my account", "", "")
+	store.account = newAccountWithId(context.Background(), "my account", "", "", false)
 
 	for i := 0; i < numberOfPeers; i++ {
 		peerId := fmt.Sprintf("peer_%d", i)
diff --git a/management/server/group_test.go b/management/server/group_test.go
index 4966f2b3..631fe3a7 100644
--- a/management/server/group_test.go
+++ b/management/server/group_test.go
@@ -369,7 +369,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*DefaultAccountManager, *t
 		Id:         "example user",
 		AutoGroups: []string{groupForUsers.ID},
 	}
-	account := newAccountWithId(context.Background(), accountID, groupAdminUserID, domain)
+	account := newAccountWithId(context.Background(), accountID, groupAdminUserID, domain, false)
 	account.Routes[routeResource.ID] = routeResource
 	account.Routes[routePeerGroupResource.ID] = routePeerGroupResource
 	account.NameServerGroups[nameServerGroup.ID] = nameServerGroup
diff --git a/management/server/http/handlers/accounts/accounts_handler_test.go b/management/server/http/handlers/accounts/accounts_handler_test.go
index a1879874..dbf0c22b 100644
--- a/management/server/http/handlers/accounts/accounts_handler_test.go
+++ b/management/server/http/handlers/accounts/accounts_handler_test.go
@@ -54,6 +54,18 @@ func initAccountsTestData(t *testing.T, account *types.Account) *handler {
 			GetAccountMetaFunc: func(ctx context.Context, accountID string, userID string) (*types.AccountMeta, error) {
 				return account.GetMeta(), nil
 			},
+			GetAccountOnboardingFunc: func(ctx context.Context, accountID string, userID string) (*types.AccountOnboarding, error) {
+				return &types.AccountOnboarding{
+					OnboardingFlowPending: true,
+					SignupFormPending:     true,
+				}, nil
+			},
+			UpdateAccountOnboardingFunc: func(ctx context.Context, accountID, userID string, onboarding *types.AccountOnboarding) (*types.AccountOnboarding, error) {
+				return &types.AccountOnboarding{
+					OnboardingFlowPending: true,
+					SignupFormPending:     true,
+				}, nil
+			},
 		},
 		settingsManager: settingsMockManager,
 	}
@@ -117,7 +129,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
 			expectedBody:   true,
 			requestType:    http.MethodPut,
 			requestPath:    "/api/accounts/" + accountID,
-			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": true}}"),
+			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": true},\"onboarding\": {\"onboarding_flow_pending\": true,\"signup_form_pending\": true}}"),
 			expectedStatus: http.StatusOK,
 			expectedSettings: api.AccountSettings{
 				PeerLoginExpiration:             15552000,
@@ -139,7 +151,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
 			expectedBody:   true,
 			requestType:    http.MethodPut,
 			requestPath:    "/api/accounts/" + accountID,
-			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": false,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"roles\",\"jwt_allow_groups\":[\"test\"],\"regular_users_view_blocked\":true}}"),
+			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": false,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"roles\",\"jwt_allow_groups\":[\"test\"],\"regular_users_view_blocked\":true},\"onboarding\": {\"onboarding_flow_pending\": true,\"signup_form_pending\": true}}"),
 			expectedStatus: http.StatusOK,
 			expectedSettings: api.AccountSettings{
 				PeerLoginExpiration:             15552000,
@@ -161,7 +173,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
 			expectedBody:   true,
 			requestType:    http.MethodPut,
 			requestPath:    "/api/accounts/" + accountID,
-			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 554400,\"peer_login_expiration_enabled\": true,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"groups\",\"groups_propagation_enabled\":true,\"regular_users_view_blocked\":true}}"),
+			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 554400,\"peer_login_expiration_enabled\": true,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"groups\",\"groups_propagation_enabled\":true,\"regular_users_view_blocked\":true},\"onboarding\": {\"onboarding_flow_pending\": true,\"signup_form_pending\": true}}"),
 			expectedStatus: http.StatusOK,
 			expectedSettings: api.AccountSettings{
 				PeerLoginExpiration:             554400,
@@ -178,12 +190,34 @@ func TestAccounts_AccountsHandler(t *testing.T) {
 			expectedArray: false,
 			expectedID:    accountID,
 		},
+		{
+			name:           "PutAccount OK without onboarding",
+			expectedBody:   true,
+			requestType:    http.MethodPut,
+			requestPath:    "/api/accounts/" + accountID,
+			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": false,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"roles\",\"jwt_allow_groups\":[\"test\"],\"regular_users_view_blocked\":true}}"),
+			expectedStatus: http.StatusOK,
+			expectedSettings: api.AccountSettings{
+				PeerLoginExpiration:             15552000,
+				PeerLoginExpirationEnabled:      false,
+				GroupsPropagationEnabled:        br(false),
+				JwtGroupsClaimName:              sr("roles"),
+				JwtGroupsEnabled:                br(true),
+				JwtAllowGroups:                  &[]string{"test"},
+				RegularUsersViewBlocked:         true,
+				RoutingPeerDnsResolutionEnabled: br(false),
+				LazyConnectionEnabled:           br(false),
+				DnsDomain:                       sr(""),
+			},
+			expectedArray: false,
+			expectedID:    accountID,
+		},
 		{
 			name:           "Update account failure with high peer_login_expiration more than 180 days",
 			expectedBody:   true,
 			requestType:    http.MethodPut,
 			requestPath:    "/api/accounts/" + accountID,
-			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552001,\"peer_login_expiration_enabled\": true}}"),
+			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552001,\"peer_login_expiration_enabled\": true},\"onboarding\": {\"onboarding_flow_pending\": true,\"signup_form_pending\": true}}"),
 			expectedStatus: http.StatusUnprocessableEntity,
 			expectedArray:  false,
 		},
@@ -192,7 +226,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
 			expectedBody:   true,
 			requestType:    http.MethodPut,
 			requestPath:    "/api/accounts/" + accountID,
-			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 3599,\"peer_login_expiration_enabled\": true}}"),
+			requestBody:    bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 3599,\"peer_login_expiration_enabled\": true},\"onboarding\": {\"onboarding_flow_pending\": true,\"signup_form_pending\": true}}"),
 			expectedStatus: http.StatusUnprocessableEntity,
 			expectedArray:  false,
 		},
diff --git a/management/server/http/testing/testing_tools/tools.go b/management/server/http/testing/testing_tools/tools.go
index 8c5d2e38..e308f100 100644
--- a/management/server/http/testing/testing_tools/tools.go
+++ b/management/server/http/testing/testing_tools/tools.go
@@ -133,12 +133,12 @@ func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *serve
 	}
 
 	geoMock := &geolocation.Mock{}
-	validatorMock := server.MocIntegratedValidator{}
+	validatorMock := server.MockIntegratedValidator{}
 	proxyController := integrations.NewController(store)
 	userManager := users.NewManager(store)
 	permissionsManager := permissions.NewManager(store)
 	settingsManager := settings.NewManager(store, userManager, integrations.NewManager(&activity.InMemoryEventStore{}), permissionsManager)
-	am, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, proxyController, settingsManager, permissionsManager)
+	am, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, proxyController, settingsManager, permissionsManager, false)
 	if err != nil {
 		t.Fatalf("Failed to create manager: %v", err)
 	}
diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go
index b85a43da..0d61b3a1 100644
--- a/management/server/management_proto_test.go
+++ b/management/server/management_proto_test.go
@@ -440,11 +440,15 @@ func startManagementForTest(t *testing.T, testFile string, config *types.Config)
 		GetSettings(gomock.Any(), gomock.Any(), gomock.Any()).
 		AnyTimes().
 		Return(&types.Settings{}, nil)
-
+	settingsMockManager.
+		EXPECT().
+		GetExtraSettings(gomock.Any(), gomock.Any()).
+		Return(&types.ExtraSettings{}, nil).
+		AnyTimes()
 	permissionsManager := permissions.NewManager(store)
 
 	accountManager, err := BuildManager(ctx, store, peersUpdateManager, nil, "", "netbird.selfhosted",
-		eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
+		eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
 
 	if err != nil {
 		cleanup()
@@ -454,7 +458,7 @@ func startManagementForTest(t *testing.T, testFile string, config *types.Config)
 	secretsManager := NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.Relay, settingsMockManager)
 
 	ephemeralMgr := NewEphemeralManager(store, accountManager)
-	mgmtServer, err := NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, ephemeralMgr, nil)
+	mgmtServer, err := NewServer(context.Background(), config, accountManager, settingsMockManager, peersUpdateManager, secretsManager, nil, ephemeralMgr, nil, MockIntegratedValidator{})
 	if err != nil {
 		return nil, nil, "", cleanup, err
 	}
diff --git a/management/server/management_test.go b/management/server/management_test.go
index a4f9a5e3..ab6f0095 100644
--- a/management/server/management_test.go
+++ b/management/server/management_test.go
@@ -206,12 +206,12 @@ func startServer(
 		eventStore,
 		nil,
 		false,
-		server.MocIntegratedValidator{},
+		server.MockIntegratedValidator{},
 		metrics,
 		port_forwarding.NewControllerMock(),
 		settingsMockManager,
 		permissionsManager,
-	)
+		false)
 	if err != nil {
 		t.Fatalf("failed creating an account manager: %v", err)
 	}
@@ -227,6 +227,7 @@ func startServer(
 		nil,
 		nil,
 		nil,
+		server.MockIntegratedValidator{},
 	)
 	if err != nil {
 		t.Fatalf("failed creating management server: %v", err)
diff --git a/management/server/migration/migration_test.go b/management/server/migration/migration_test.go
index 94377930..ce76bd66 100644
--- a/management/server/migration/migration_test.go
+++ b/management/server/migration/migration_test.go
@@ -4,16 +4,21 @@ import (
 	"context"
 	"encoding/gob"
 	"net"
+	"os"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"gorm.io/driver/mysql"
+	"gorm.io/driver/postgres"
 	"gorm.io/driver/sqlite"
 	"gorm.io/gorm"
 
 	"github.com/netbirdio/netbird/management/server/migration"
 	nbpeer "github.com/netbirdio/netbird/management/server/peer"
+	"github.com/netbirdio/netbird/management/server/testutil"
 	"github.com/netbirdio/netbird/management/server/types"
 	"github.com/netbirdio/netbird/route"
 )
@@ -21,7 +26,41 @@ import (
 func setupDatabase(t *testing.T) *gorm.DB {
 	t.Helper()
 
-	db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
+	var db *gorm.DB
+	var err error
+	var dsn string
+	var cleanup func()
+	switch os.Getenv("NETBIRD_STORE_ENGINE") {
+	case "mysql":
+		cleanup, dsn, err = testutil.CreateMysqlTestContainer()
+		if err != nil {
+			t.Fatalf("Failed to create MySQL test container: %v", err)
+		}
+
+		if dsn == "" {
+			t.Fatal("MySQL connection string is empty, ensure the test container is running")
+		}
+
+		db, err = gorm.Open(mysql.Open(dsn+"?charset=utf8&parseTime=True&loc=Local"), &gorm.Config{})
+	case "postgres":
+		cleanup, dsn, err = testutil.CreatePostgresTestContainer()
+		if err != nil {
+			t.Fatalf("Failed to create PostgreSQL test container: %v", err)
+		}
+
+		if dsn == "" {
+			t.Fatalf("PostgreSQL connection string is empty, ensure the test container is running")
+		}
+
+		db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
+	case "sqlite":
+		db, err = gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
+	default:
+		db, err = gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
+	}
+	if cleanup != nil {
+		t.Cleanup(cleanup)
+	}
 
 	require.NoError(t, err, "Failed to open database")
 	return db
@@ -34,6 +73,7 @@ func TestMigrateFieldFromGobToJSON_EmptyDB(t *testing.T) {
 }
 
 func TestMigrateFieldFromGobToJSON_WithGobData(t *testing.T) {
+	t.Setenv("NETBIRD_STORE_ENGINE", "sqlite")
 	db := setupDatabase(t)
 
 	err := db.AutoMigrate(&types.Account{}, &route.Route{})
@@ -97,6 +137,7 @@ func TestMigrateNetIPFieldFromBlobToJSON_EmptyDB(t *testing.T) {
 }
 
 func TestMigrateNetIPFieldFromBlobToJSON_WithBlobData(t *testing.T) {
+	t.Setenv("NETBIRD_STORE_ENGINE", "sqlite")
 	db := setupDatabase(t)
 
 	err := db.AutoMigrate(&types.Account{}, &nbpeer.Peer{})
@@ -117,12 +158,18 @@ func TestMigrateNetIPFieldFromBlobToJSON_WithBlobData(t *testing.T) {
 		Peers []peer `gorm:"foreignKey:AccountID;references:id"`
 	}
 
-	err = db.Save(&account{
+	a := &account{
 		Account: types.Account{Id: "123"},
-		Peers: []peer{
-			{Location: location{ConnectionIP: net.IP{10, 0, 0, 1}}},
-		}},
-	).Error
+	}
+
+	err = db.Save(a).Error
+	require.NoError(t, err, "Failed to insert account")
+
+	a.Peers = []peer{
+		{Location: location{ConnectionIP: net.IP{10, 0, 0, 1}}},
+	}
+
+	err = db.Save(a).Error
 	require.NoError(t, err, "Failed to insert blob data")
 
 	var blobValue string
@@ -143,12 +190,18 @@ func TestMigrateNetIPFieldFromBlobToJSON_WithJSONData(t *testing.T) {
 	err := db.AutoMigrate(&types.Account{}, &nbpeer.Peer{})
 	require.NoError(t, err, "Failed to auto-migrate tables")
 
-	err = db.Save(&types.Account{
+	account := &types.Account{
 		Id: "1234",
-		PeersG: []nbpeer.Peer{
-			{Location: nbpeer.Location{ConnectionIP: net.IP{10, 0, 0, 1}}},
-		}},
-	).Error
+	}
+
+	err = db.Save(account).Error
+	require.NoError(t, err, "Failed to insert account")
+
+	account.PeersG = []nbpeer.Peer{
+		{AccountID: "1234", Location: nbpeer.Location{ConnectionIP: net.IP{10, 0, 0, 1}}},
+	}
+
+	err = db.Save(account).Error
 	require.NoError(t, err, "Failed to insert JSON data")
 
 	err = migration.MigrateNetIPFieldFromBlobToJSON[nbpeer.Peer](context.Background(), db, "location_connection_ip", "")
@@ -162,12 +215,13 @@ func TestMigrateNetIPFieldFromBlobToJSON_WithJSONData(t *testing.T) {
 func TestMigrateSetupKeyToHashedSetupKey_ForPlainKey(t *testing.T) {
 	db := setupDatabase(t)
 
-	err := db.AutoMigrate(&types.SetupKey{})
+	err := db.AutoMigrate(&types.SetupKey{}, &nbpeer.Peer{})
 	require.NoError(t, err, "Failed to auto-migrate tables")
 
 	err = db.Save(&types.SetupKey{
-		Id:  "1",
-		Key: "EEFDAB47-C1A5-4472-8C05-71DE9A1E8382",
+		Id:        "1",
+		Key:       "EEFDAB47-C1A5-4472-8C05-71DE9A1E8382",
+		UpdatedAt: time.Now(),
 	}).Error
 	require.NoError(t, err, "Failed to insert setup key")
 
@@ -192,6 +246,7 @@ func TestMigrateSetupKeyToHashedSetupKey_ForAlreadyMigratedKey_Case1(t *testing.
 		Id:        "1",
 		Key:       "9+FQcmNd2GCxIK+SvHmtp6PPGV4MKEicDS+xuSQmvlE=",
 		KeySecret: "EEFDA****",
+		UpdatedAt: time.Now(),
 	}).Error
 	require.NoError(t, err, "Failed to insert setup key")
 
@@ -213,8 +268,9 @@ func TestMigrateSetupKeyToHashedSetupKey_ForAlreadyMigratedKey_Case2(t *testing.
 	require.NoError(t, err, "Failed to auto-migrate tables")
 
 	err = db.Save(&types.SetupKey{
-		Id:  "1",
-		Key: "9+FQcmNd2GCxIK+SvHmtp6PPGV4MKEicDS+xuSQmvlE=",
+		Id:        "1",
+		Key:       "9+FQcmNd2GCxIK+SvHmtp6PPGV4MKEicDS+xuSQmvlE=",
+		UpdatedAt: time.Now(),
 	}).Error
 	require.NoError(t, err, "Failed to insert setup key")
 
@@ -235,8 +291,9 @@ func TestDropIndex(t *testing.T) {
 	require.NoError(t, err, "Failed to auto-migrate tables")
 
 	err = db.Save(&types.SetupKey{
-		Id:  "1",
-		Key: "9+FQcmNd2GCxIK+SvHmtp6PPGV4MKEicDS+xuSQmvlE=",
+		Id:        "1",
+		Key:       "9+FQcmNd2GCxIK+SvHmtp6PPGV4MKEicDS+xuSQmvlE=",
+		UpdatedAt: time.Now(),
 	}).Error
 	require.NoError(t, err, "Failed to insert setup key")
 
@@ -249,3 +306,37 @@ func TestDropIndex(t *testing.T) {
 	exist = db.Migrator().HasIndex(&types.SetupKey{}, "idx_setup_keys_account_id")
 	assert.False(t, exist, "Should not have the index")
 }
+
+func TestCreateIndex(t *testing.T) {
+	db := setupDatabase(t)
+	err := db.AutoMigrate(&nbpeer.Peer{})
+	assert.NoError(t, err, "Failed to auto-migrate tables")
+
+	indexName := "idx_account_ip"
+
+	err = migration.CreateIndexIfNotExists[nbpeer.Peer](context.Background(), db, indexName, "account_id", "ip")
+	assert.NoError(t, err, "Migration should not fail to create index")
+
+	exist := db.Migrator().HasIndex(&nbpeer.Peer{}, indexName)
+	assert.True(t, exist, "Should have the index")
+}
+
+func TestCreateIndexIfExists(t *testing.T) {
+	db := setupDatabase(t)
+	err := db.AutoMigrate(&nbpeer.Peer{})
+	assert.NoError(t, err, "Failed to auto-migrate tables")
+
+	indexName := "idx_account_ip"
+
+	err = migration.CreateIndexIfNotExists[nbpeer.Peer](context.Background(), db, indexName, "account_id", "ip")
+	assert.NoError(t, err, "Migration should not fail to create index")
+
+	exist := db.Migrator().HasIndex(&nbpeer.Peer{}, indexName)
+	assert.True(t, exist, "Should have the index")
+
+	err = migration.CreateIndexIfNotExists[nbpeer.Peer](context.Background(), db, indexName, "account_id", "ip")
+	assert.NoError(t, err, "Create index should not fail if index exists")
+
+	exist = db.Migrator().HasIndex(&nbpeer.Peer{}, indexName)
+	assert.True(t, exist, "Should have the index")
+}
diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go
index dc4be594..25eb03b8 100644
--- a/management/server/nameserver_test.go
+++ b/management/server/nameserver_test.go
@@ -778,8 +778,14 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
 	ctrl := gomock.NewController(t)
 	t.Cleanup(ctrl.Finish)
 	settingsMockManager := settings.NewMockManager(ctrl)
+	settingsMockManager.
+		EXPECT().
+		GetExtraSettings(gomock.Any(), gomock.Any()).
+		Return(&types.ExtraSettings{}, nil).
+		AnyTimes()
+
 	permissionsManager := permissions.NewManager(store)
-	return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
+	return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
 }
 
 func createNSStore(t *testing.T) (store.Store, error) {
@@ -848,7 +854,7 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*types.Account,
 	userID := testUserID
 	domain := "example.com"
 
-	account := newAccountWithId(context.Background(), accountID, userID, domain)
+	account := newAccountWithId(context.Background(), accountID, userID, domain, false)
 
 	account.NameServerGroups[existingNSGroup.ID] = &existingNSGroup
 
diff --git a/management/server/peer_test.go b/management/server/peer_test.go
index 3d782f04..4f6ae500 100644
--- a/management/server/peer_test.go
+++ b/management/server/peer_test.go
@@ -10,7 +10,10 @@ import (
 	"net/netip"
 	"os"
 	"runtime"
+	"strconv"
 	"strings"
+	"sync"
+	"sync/atomic"
 	"testing"
 	"time"
 
@@ -19,11 +22,14 @@ import (
 	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"golang.org/x/exp/maps"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 
 	"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
+	"github.com/netbirdio/netbird/management/server/mock_server"
 	"github.com/netbirdio/netbird/management/server/permissions"
 	"github.com/netbirdio/netbird/management/server/settings"
+	"github.com/netbirdio/netbird/management/server/status"
 
 	"github.com/netbirdio/netbird/management/server/util"
 
@@ -480,7 +486,7 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
 	accountID := "test_account"
 	adminUser := "account_creator"
 	someUser := "some_user"
-	account := newAccountWithId(context.Background(), accountID, adminUser, "")
+	account := newAccountWithId(context.Background(), accountID, adminUser, "", false)
 	account.Users[someUser] = &types.User{
 		Id:   someUser,
 		Role: types.UserRoleUser,
@@ -667,7 +673,7 @@ func TestDefaultAccountManager_GetPeers(t *testing.T) {
 			accountID := "test_account"
 			adminUser := "account_creator"
 			someUser := "some_user"
-			account := newAccountWithId(context.Background(), accountID, adminUser, "")
+			account := newAccountWithId(context.Background(), accountID, adminUser, "", false)
 			account.Users[someUser] = &types.User{
 				Id:            someUser,
 				Role:          testCase.role,
@@ -737,7 +743,7 @@ func setupTestAccountManager(b testing.TB, peers int, groups int) (*DefaultAccou
 	adminUser := "account_creator"
 	regularUser := "regular_user"
 
-	account := newAccountWithId(context.Background(), accountID, adminUser, "")
+	account := newAccountWithId(context.Background(), accountID, adminUser, "", false)
 	account.Users[regularUser] = &types.User{
 		Id:   regularUser,
 		Role: types.UserRoleUser,
@@ -1267,7 +1273,7 @@ func Test_RegisterPeerByUser(t *testing.T) {
 	settingsMockManager := settings.NewMockManager(ctrl)
 	permissionsManager := permissions.NewManager(s)
 
-	am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
+	am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
 	assert.NoError(t, err)
 
 	existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1340,9 +1346,14 @@ func Test_RegisterPeerBySetupKey(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	t.Cleanup(ctrl.Finish)
 	settingsMockManager := settings.NewMockManager(ctrl)
+	settingsMockManager.
+		EXPECT().
+		GetExtraSettings(gomock.Any(), gomock.Any()).
+		Return(&types.ExtraSettings{}, nil).
+		AnyTimes()
 	permissionsManager := permissions.NewManager(s)
 
-	am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
+	am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
 	assert.NoError(t, err)
 
 	existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1373,6 +1384,7 @@ func Test_RegisterPeerBySetupKey(t *testing.T) {
 		existingSetupKeyID        string
 		expectedGroupIDsInAccount []string
 		expectAddPeerError        bool
+		errorType                 status.Type
 		expectedErrorMsgSubstring string
 	}{
 		{
@@ -1385,13 +1397,15 @@ func Test_RegisterPeerBySetupKey(t *testing.T) {
 			name:                      "Failed registration with setup key not allowing extra DNS labels",
 			existingSetupKeyID:        "A2C8E62B-38F5-4553-B31E-DD66C696CEBB",
 			expectAddPeerError:        true,
+			errorType:                 status.PreconditionFailed,
 			expectedErrorMsgSubstring: "setup key doesn't allow extra DNS labels",
 		},
 		{
 			name:                      "Absent setup key",
 			existingSetupKeyID:        "AAAAAAAA-38F5-4553-B31E-DD66C696CEBB",
 			expectAddPeerError:        true,
-			expectedErrorMsgSubstring: "failed adding new peer: account not found",
+			errorType:                 status.NotFound,
+			expectedErrorMsgSubstring: "couldn't add peer: setup key is invalid",
 		},
 	}
 
@@ -1416,6 +1430,11 @@ func Test_RegisterPeerBySetupKey(t *testing.T) {
 			if tc.expectAddPeerError {
 				require.Error(t, err, "Expected an error when adding peer with setup key: %s", tc.existingSetupKeyID)
 				assert.Contains(t, err.Error(), tc.expectedErrorMsgSubstring, "Error message mismatch")
+				e, ok := status.FromError(err)
+				if !ok {
+					t.Fatal("Failed to map error")
+				}
+				assert.Equal(t, e.Type(), tc.errorType)
 				return
 			}
 
@@ -1477,7 +1496,7 @@ func Test_RegisterPeerRollbackOnFailure(t *testing.T) {
 
 	permissionsManager := permissions.NewManager(s)
 
-	am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
+	am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
 	assert.NoError(t, err)
 
 	existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1544,9 +1563,14 @@ func Test_LoginPeer(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	t.Cleanup(ctrl.Finish)
 	settingsMockManager := settings.NewMockManager(ctrl)
+	settingsMockManager.
+		EXPECT().
+		GetExtraSettings(gomock.Any(), gomock.Any()).
+		Return(&types.ExtraSettings{}, nil).
+		AnyTimes()
 	permissionsManager := permissions.NewManager(s)
 
-	am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
+	am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
 	assert.NoError(t, err)
 
 	existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1824,7 +1848,7 @@ func TestPeerAccountPeersUpdate(t *testing.T) {
 			return update, true, nil
 		}
 
-		manager.integratedPeerValidator = MocIntegratedValidator{ValidatePeerFunc: requireUpdateFunc}
+		manager.integratedPeerValidator = MockIntegratedValidator{ValidatePeerFunc: requireUpdateFunc}
 		done := make(chan struct{})
 		go func() {
 			peerShouldReceiveUpdate(t, updMsg)
@@ -1846,7 +1870,7 @@ func TestPeerAccountPeersUpdate(t *testing.T) {
 			return update, false, nil
 		}
 
-		manager.integratedPeerValidator = MocIntegratedValidator{ValidatePeerFunc: requireNoUpdateFunc}
+		manager.integratedPeerValidator = MockIntegratedValidator{ValidatePeerFunc: requireNoUpdateFunc}
 		done := make(chan struct{})
 		go func() {
 			peerShouldNotReceiveUpdate(t, updMsg)
@@ -2052,15 +2076,19 @@ func Test_DeletePeer(t *testing.T) {
 	// account with an admin and a regular user
 	accountID := "test_account"
 	adminUser := "account_creator"
-	account := newAccountWithId(context.Background(), accountID, adminUser, "")
+	account := newAccountWithId(context.Background(), accountID, adminUser, "", false)
 	account.Peers = map[string]*nbpeer.Peer{
 		"peer1": {
 			ID:        "peer1",
 			AccountID: accountID,
+			IP:        net.IP{1, 1, 1, 1},
+			DNSLabel:  "peer1.test",
 		},
 		"peer2": {
 			ID:        "peer2",
 			AccountID: accountID,
+			IP:        net.IP{2, 2, 2, 2},
+			DNSLabel:  "peer2.test",
 		},
 	}
 	account.Groups = map[string]*types.Group{
@@ -2090,3 +2118,266 @@ func Test_DeletePeer(t *testing.T) {
 	assert.NotContains(t, group.Peers, "peer1")
 
 }
+
+func Test_IsUniqueConstraintError(t *testing.T) {
+	tests := []struct {
+		name   string
+		engine types.Engine
+	}{
+		{
+			name:   "PostgreSQL uniqueness error",
+			engine: types.PostgresStoreEngine,
+		},
+		{
+			name:   "MySQL uniqueness error",
+			engine: types.MysqlStoreEngine,
+		},
+		{
+			name:   "SQLite uniqueness error",
+			engine: types.SqliteStoreEngine,
+		},
+	}
+
+	peer := &nbpeer.Peer{
+		ID:        "test-peer-id",
+		AccountID: "bf1c8084-ba50-4ce7-9439-34653001fc3b",
+		DNSLabel:  "test-peer-dns-label",
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Setenv("NETBIRD_STORE_ENGINE", string(tt.engine))
+			s, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "testdata/extended-store.sql", t.TempDir())
+			if err != nil {
+				t.Fatalf("Error when creating store: %s", err)
+			}
+			t.Cleanup(cleanup)
+
+			err = s.AddPeerToAccount(context.Background(), store.LockingStrengthUpdate, peer)
+			assert.NoError(t, err)
+
+			err = s.AddPeerToAccount(context.Background(), store.LockingStrengthUpdate, peer)
+			result := isUniqueConstraintError(err)
+			assert.True(t, result)
+		})
+	}
+}
+
+func Test_AddPeer(t *testing.T) {
+	t.Setenv("NETBIRD_STORE_ENGINE", string(types.PostgresStoreEngine))
+	manager, err := createManager(t)
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	accountID := "testaccount"
+	userID := "testuser"
+
+	_, err = createAccount(manager, accountID, userID, "domain.com")
+	if err != nil {
+		t.Fatal("error creating account")
+		return
+	}
+
+	setupKey, err := manager.CreateSetupKey(context.Background(), accountID, "test-key", types.SetupKeyReusable, time.Hour, nil, 10000, userID, false, false)
+	if err != nil {
+		t.Fatal("error creating setup key")
+		return
+	}
+
+	const totalPeers = 300 // totalPeers / differentHostnames should be less than 10 (due to concurrent retries)
+	const differentHostnames = 50
+
+	var wg sync.WaitGroup
+	errs := make(chan error, totalPeers+differentHostnames)
+	start := make(chan struct{})
+	for i := 0; i < totalPeers; i++ {
+		wg.Add(1)
+		hostNameID := i % differentHostnames
+
+		go func(i int) {
+			defer wg.Done()
+
+			newPeer := &nbpeer.Peer{
+				Key:  "key" + strconv.Itoa(i),
+				Meta: nbpeer.PeerSystemMeta{Hostname: "peer" + strconv.Itoa(hostNameID), GoOS: "linux"},
+			}
+
+			<-start
+
+			_, _, _, err := manager.AddPeer(context.Background(), setupKey.Key, "", newPeer)
+			if err != nil {
+				errs <- fmt.Errorf("AddPeer failed for peer %d: %w", i, err)
+				return
+			}
+
+		}(i)
+	}
+	startTime := time.Now()
+
+	close(start)
+	wg.Wait()
+	close(errs)
+
+	t.Logf("time since start: %s", time.Since(startTime))
+
+	for err := range errs {
+		t.Fatal(err)
+	}
+
+	account, err := manager.Store.GetAccount(context.Background(), accountID)
+	if err != nil {
+		t.Fatalf("Failed to get account %s: %v", accountID, err)
+	}
+
+	assert.Equal(t, totalPeers, len(account.Peers), "Expected %d peers in account %s, got %d", totalPeers, accountID, len(account.Peers))
+
+	seenIP := make(map[string]bool)
+	for _, p := range account.Peers {
+		ipStr := p.IP.String()
+		if seenIP[ipStr] {
+			t.Fatalf("Duplicate IP found in account %s: %s", accountID, ipStr)
+		}
+		seenIP[ipStr] = true
+	}
+
+	seenLabel := make(map[string]bool)
+	for _, p := range account.Peers {
+		if seenLabel[p.DNSLabel] {
+			t.Fatalf("Duplicate Label found in account %s: %s", accountID, p.DNSLabel)
+		}
+		seenLabel[p.DNSLabel] = true
+	}
+
+	assert.Equal(t, totalPeers, maps.Values(account.SetupKeys)[0].UsedTimes)
+	assert.Equal(t, uint64(totalPeers), account.Network.Serial)
+}
+
+func TestBufferUpdateAccountPeers(t *testing.T) {
+	const (
+		peersCount            = 1000
+		updateAccountInterval = 50 * time.Millisecond
+	)
+
+	var (
+		deletedPeers, updatePeersDeleted, updatePeersRuns atomic.Int32
+		uapLastRun, dpLastRun                             atomic.Int64
+
+		totalNewRuns, totalOldRuns int
+	)
+
+	uap := func(ctx context.Context, accountID string) {
+		updatePeersDeleted.Store(deletedPeers.Load())
+		updatePeersRuns.Add(1)
+		uapLastRun.Store(time.Now().UnixMilli())
+		time.Sleep(100 * time.Millisecond)
+	}
+
+	t.Run("new approach", func(t *testing.T) {
+		updatePeersRuns.Store(0)
+		updatePeersDeleted.Store(0)
+		deletedPeers.Store(0)
+
+		var mustore sync.Map
+		bufupd := func(ctx context.Context, accountID string) {
+			mu, _ := mustore.LoadOrStore(accountID, &bufferUpdate{})
+			b := mu.(*bufferUpdate)
+
+			if !b.mu.TryLock() {
+				b.update.Store(true)
+				return
+			}
+
+			if b.next != nil {
+				b.next.Stop()
+			}
+
+			go func() {
+				defer b.mu.Unlock()
+				uap(ctx, accountID)
+				if !b.update.Load() {
+					return
+				}
+				b.update.Store(false)
+				b.next = time.AfterFunc(updateAccountInterval, func() {
+					uap(ctx, accountID)
+				})
+			}()
+		}
+		dp := func(ctx context.Context, accountID, peerID, userID string) error {
+			deletedPeers.Add(1)
+			dpLastRun.Store(time.Now().UnixMilli())
+			time.Sleep(10 * time.Millisecond)
+			bufupd(ctx, accountID)
+			return nil
+		}
+
+		am := mock_server.MockAccountManager{
+			UpdateAccountPeersFunc:       uap,
+			BufferUpdateAccountPeersFunc: bufupd,
+			DeletePeerFunc:               dp,
+		}
+		empty := ""
+		for range peersCount {
+			//nolint
+			am.DeletePeer(context.Background(), empty, empty, empty)
+		}
+		time.Sleep(100 * time.Millisecond)
+
+		assert.Equal(t, peersCount, int(deletedPeers.Load()), "Expected all peers to be deleted")
+		assert.Equal(t, peersCount, int(updatePeersDeleted.Load()), "Expected all peers to be updated in the buffer")
+		assert.GreaterOrEqual(t, uapLastRun.Load(), dpLastRun.Load(), "Expected update account peers to run after delete peer")
+
+		totalNewRuns = int(updatePeersRuns.Load())
+	})
+
+	t.Run("old approach", func(t *testing.T) {
+		updatePeersRuns.Store(0)
+		updatePeersDeleted.Store(0)
+		deletedPeers.Store(0)
+
+		var mustore sync.Map
+		bufupd := func(ctx context.Context, accountID string) {
+			mu, _ := mustore.LoadOrStore(accountID, &sync.Mutex{})
+			b := mu.(*sync.Mutex)
+
+			if !b.TryLock() {
+				return
+			}
+
+			go func() {
+				time.Sleep(updateAccountInterval)
+				b.Unlock()
+				uap(ctx, accountID)
+			}()
+		}
+		dp := func(ctx context.Context, accountID, peerID, userID string) error {
+			deletedPeers.Add(1)
+			dpLastRun.Store(time.Now().UnixMilli())
+			time.Sleep(10 * time.Millisecond)
+			bufupd(ctx, accountID)
+			return nil
+		}
+
+		am := mock_server.MockAccountManager{
+			UpdateAccountPeersFunc:       uap,
+			BufferUpdateAccountPeersFunc: bufupd,
+			DeletePeerFunc:               dp,
+		}
+		empty := ""
+		for range peersCount {
+			//nolint
+			am.DeletePeer(context.Background(), empty, empty, empty)
+		}
+		time.Sleep(100 * time.Millisecond)
+
+		assert.Equal(t, peersCount, int(deletedPeers.Load()), "Expected all peers to be deleted")
+		assert.Equal(t, peersCount, int(updatePeersDeleted.Load()), "Expected all peers to be updated in the buffer")
+		assert.GreaterOrEqual(t, uapLastRun.Load(), dpLastRun.Load(), "Expected update account peers to run after delete peer")
+
+		totalOldRuns = int(updatePeersRuns.Load())
+	})
+	assert.Less(t, totalNewRuns, totalOldRuns, "Expected new approach to run less than old approach. New runs: %d, Old runs: %d", totalNewRuns, totalOldRuns)
+	t.Logf("New runs: %d, Old runs: %d", totalNewRuns, totalOldRuns)
+}
diff --git a/management/server/posture_checks_test.go b/management/server/posture_checks_test.go
index 8bd2fab6..f9346737 100644
--- a/management/server/posture_checks_test.go
+++ b/management/server/posture_checks_test.go
@@ -106,7 +106,7 @@ func initTestPostureChecksAccount(am *DefaultAccountManager) (*types.Account, er
 		Role: types.UserRoleUser,
 	}
 
-	account := newAccountWithId(context.Background(), accountID, groupAdminUserID, domain)
+	account := newAccountWithId(context.Background(), accountID, groupAdminUserID, domain, false)
 	account.Users[admin.Id] = admin
 	account.Users[user.Id] = user
 
diff --git a/management/server/route_test.go b/management/server/route_test.go
index 833477b5..37c37f62 100644
--- a/management/server/route_test.go
+++ b/management/server/route_test.go
@@ -1284,7 +1284,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
 
 	permissionsManager := permissions.NewManager(store)
 
-	return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
+	return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
 }
 
 func createRouterStore(t *testing.T) (store.Store, error) {
@@ -1305,7 +1305,7 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*types.Accou
 	accountID := "testingAcc"
 	domain := "example.com"
 
-	account := newAccountWithId(context.Background(), accountID, userID, domain)
+	account := newAccountWithId(context.Background(), accountID, userID, domain, false)
 	err := am.Store.SaveAccount(context.Background(), account)
 	if err != nil {
 		return nil, err
diff --git a/management/server/store/sql_store_test.go b/management/server/store/sql_store_test.go
index fab9048e..738c5a28 100644
--- a/management/server/store/sql_store_test.go
+++ b/management/server/store/sql_store_test.go
@@ -10,6 +10,7 @@ import (
 	"net/netip"
 	"os"
 	"runtime"
+	"sort"
 	"sync"
 	"testing"
 	"time"
@@ -353,9 +354,16 @@ func TestSqlite_DeleteAccount(t *testing.T) {
 		t.Errorf("expecting 1 Accounts to be stored after SaveAccount()")
 	}
 
+	o, err := store.GetAccountOnboarding(context.Background(), account.Id)
+	require.NoError(t, err)
+	require.Equal(t, o.AccountID, account.Id)
+
 	err = store.DeleteAccount(context.Background(), account)
 	require.NoError(t, err)
 
+	_, err = store.GetAccountOnboarding(context.Background(), account.Id)
+	require.Error(t, err, "expecting error after removing DeleteAccount when getting onboarding")
+
 	if len(store.GetAllAccounts(context.Background())) != 0 {
 		t.Errorf("expecting 0 Accounts to be stored after DeleteAccount()")
 	}
@@ -413,12 +421,21 @@ func Test_GetAccount(t *testing.T) {
 		account, err := store.GetAccount(context.Background(), id)
 		require.NoError(t, err)
 		require.Equal(t, id, account.Id, "account id should match")
+		require.Equal(t, false, account.Onboarding.OnboardingFlowPending)
+
+		id = "9439-34653001fc3b-bf1c8084-ba50-4ce7"
+
+		account, err = store.GetAccount(context.Background(), id)
+		require.NoError(t, err)
+		require.Equal(t, id, account.Id, "account id should match")
+		require.Equal(t, true, account.Onboarding.OnboardingFlowPending)
 
 		_, err = store.GetAccount(context.Background(), "non-existing-account")
 		assert.Error(t, err)
 		parsedErr, ok := status.FromError(err)
 		require.True(t, ok)
 		require.Equal(t, status.NotFound, parsedErr.Type(), "should return not found error")
+
 	})
 }
 
@@ -630,7 +647,7 @@ func TestMigrate(t *testing.T) {
 	t.Cleanup(cleanUp)
 	assert.NoError(t, err)
 
-	err = migrate(context.Background(), store.(*SqlStore).db)
+	err = migratePreAuto(context.Background(), store.(*SqlStore).db)
 	require.NoError(t, err, "Migration should not fail on empty db")
 
 	_, ipnet, err := net.ParseCIDR("10.0.0.0/24")
@@ -685,10 +702,10 @@ func TestMigrate(t *testing.T) {
 	err = store.(*SqlStore).db.Save(rt).Error
 	require.NoError(t, err, "Failed to insert Gob data")
 
-	err = migrate(context.Background(), store.(*SqlStore).db)
+	err = migratePreAuto(context.Background(), store.(*SqlStore).db)
 	require.NoError(t, err, "Migration should not fail on gob populated db")
 
-	err = migrate(context.Background(), store.(*SqlStore).db)
+	err = migratePreAuto(context.Background(), store.(*SqlStore).db)
 	require.NoError(t, err, "Migration should not fail on migrated db")
 
 	err = store.(*SqlStore).db.Delete(rt).Where("id = ?", "route1").Error
@@ -704,10 +721,10 @@ func TestMigrate(t *testing.T) {
 	err = store.(*SqlStore).db.Save(nRT).Error
 	require.NoError(t, err, "Failed to insert json nil slice data")
 
-	err = migrate(context.Background(), store.(*SqlStore).db)
+	err = migratePreAuto(context.Background(), store.(*SqlStore).db)
 	require.NoError(t, err, "Migration should not fail on json nil slice populated db")
 
-	err = migrate(context.Background(), store.(*SqlStore).db)
+	err = migratePreAuto(context.Background(), store.(*SqlStore).db)
 	require.NoError(t, err, "Migration should not fail on migrated db")
 
 }
@@ -950,6 +967,7 @@ func TestSqlite_GetTakenIPs(t *testing.T) {
 	peer1 := &nbpeer.Peer{
 		ID:        "peer1",
 		AccountID: existingAccountID,
+		DNSLabel:  "peer1",
 		IP:        net.IP{1, 1, 1, 1},
 	}
 	err = store.AddPeerToAccount(context.Background(), LockingStrengthUpdate, peer1)
@@ -961,8 +979,9 @@ func TestSqlite_GetTakenIPs(t *testing.T) {
 	assert.Equal(t, []net.IP{ip1}, takenIPs)
 
 	peer2 := &nbpeer.Peer{
-		ID:        "peer2",
+		ID:        "peer1second",
 		AccountID: existingAccountID,
+		DNSLabel:  "peer1-1",
 		IP:        net.IP{2, 2, 2, 2},
 	}
 	err = store.AddPeerToAccount(context.Background(), LockingStrengthUpdate, peer2)
@@ -972,49 +991,100 @@ func TestSqlite_GetTakenIPs(t *testing.T) {
 	require.NoError(t, err)
 	ip2 := net.IP{2, 2, 2, 2}.To16()
 	assert.Equal(t, []net.IP{ip1, ip2}, takenIPs)
-
 }
 
 func TestSqlite_GetPeerLabelsInAccount(t *testing.T) {
-	t.Setenv("NETBIRD_STORE_ENGINE", string(types.SqliteStoreEngine))
-	store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/extended-store.sql", t.TempDir())
-	if err != nil {
-		return
-	}
-	t.Cleanup(cleanup)
+	runTestForAllEngines(t, "../testdata/extended-store.sql", func(t *testing.T, store Store) {
+		existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
+		peerHostname := "peer1"
 
-	existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
+		_, err := store.GetAccount(context.Background(), existingAccountID)
+		require.NoError(t, err)
 
-	_, err = store.GetAccount(context.Background(), existingAccountID)
-	require.NoError(t, err)
+		labels, err := store.GetPeerLabelsInAccount(context.Background(), LockingStrengthShare, existingAccountID, peerHostname)
+		require.NoError(t, err)
+		assert.Equal(t, []string{}, labels)
 
-	labels, err := store.GetPeerLabelsInAccount(context.Background(), LockingStrengthShare, existingAccountID)
-	require.NoError(t, err)
-	assert.Equal(t, []string{}, labels)
+		peer1 := &nbpeer.Peer{
+			ID:        "peer1",
+			AccountID: existingAccountID,
+			DNSLabel:  "peer1",
+			IP:        net.IP{1, 1, 1, 1},
+		}
+		err = store.AddPeerToAccount(context.Background(), LockingStrengthUpdate, peer1)
+		require.NoError(t, err)
 
-	peer1 := &nbpeer.Peer{
-		ID:        "peer1",
-		AccountID: existingAccountID,
-		DNSLabel:  "peer1.domain.test",
-	}
-	err = store.AddPeerToAccount(context.Background(), LockingStrengthUpdate, peer1)
-	require.NoError(t, err)
+		labels, err = store.GetPeerLabelsInAccount(context.Background(), LockingStrengthShare, existingAccountID, peerHostname)
+		require.NoError(t, err)
+		assert.Equal(t, []string{"peer1"}, labels)
 
-	labels, err = store.GetPeerLabelsInAccount(context.Background(), LockingStrengthShare, existingAccountID)
-	require.NoError(t, err)
-	assert.Equal(t, []string{"peer1.domain.test"}, labels)
+		peer2 := &nbpeer.Peer{
+			ID:        "peer1second",
+			AccountID: existingAccountID,
+			DNSLabel:  "peer1-1",
+			IP:        net.IP{2, 2, 2, 2},
+		}
+		err = store.AddPeerToAccount(context.Background(), LockingStrengthUpdate, peer2)
+		require.NoError(t, err)
 
-	peer2 := &nbpeer.Peer{
-		ID:        "peer2",
-		AccountID: existingAccountID,
-		DNSLabel:  "peer2.domain.test",
-	}
-	err = store.AddPeerToAccount(context.Background(), LockingStrengthUpdate, peer2)
-	require.NoError(t, err)
+		labels, err = store.GetPeerLabelsInAccount(context.Background(), LockingStrengthShare, existingAccountID, peerHostname)
+		require.NoError(t, err)
 
-	labels, err = store.GetPeerLabelsInAccount(context.Background(), LockingStrengthShare, existingAccountID)
-	require.NoError(t, err)
-	assert.Equal(t, []string{"peer1.domain.test", "peer2.domain.test"}, labels)
+		expected := []string{"peer1", "peer1-1"}
+		sort.Strings(expected)
+		sort.Strings(labels)
+		assert.Equal(t, expected, labels)
+	})
+}
+
+func Test_AddPeerWithSameDnsLabel(t *testing.T) {
+	runTestForAllEngines(t, "../testdata/extended-store.sql", func(t *testing.T, store Store) {
+		existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
+
+		_, err := store.GetAccount(context.Background(), existingAccountID)
+		require.NoError(t, err)
+
+		peer1 := &nbpeer.Peer{
+			ID:        "peer1",
+			AccountID: existingAccountID,
+			DNSLabel:  "peer1.domain.test",
+		}
+		err = store.AddPeerToAccount(context.Background(), LockingStrengthUpdate, peer1)
+		require.NoError(t, err)
+
+		peer2 := &nbpeer.Peer{
+			ID:        "peer1second",
+			AccountID: existingAccountID,
+			DNSLabel:  "peer1.domain.test",
+		}
+		err = store.AddPeerToAccount(context.Background(), LockingStrengthUpdate, peer2)
+		require.Error(t, err)
+	})
+}
+
+func Test_AddPeerWithSameIP(t *testing.T) {
+	runTestForAllEngines(t, "../testdata/extended-store.sql", func(t *testing.T, store Store) {
+		existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
+
+		_, err := store.GetAccount(context.Background(), existingAccountID)
+		require.NoError(t, err)
+
+		peer1 := &nbpeer.Peer{
+			ID:        "peer1",
+			AccountID: existingAccountID,
+			IP:        net.IP{1, 1, 1, 1},
+		}
+		err = store.AddPeerToAccount(context.Background(), LockingStrengthUpdate, peer1)
+		require.NoError(t, err)
+
+		peer2 := &nbpeer.Peer{
+			ID:        "peer1second",
+			AccountID: existingAccountID,
+			IP:        net.IP{1, 1, 1, 1},
+		}
+		err = store.AddPeerToAccount(context.Background(), LockingStrengthUpdate, peer2)
+		require.Error(t, err)
+	})
 }
 
 func TestSqlite_GetAccountNetwork(t *testing.T) {
@@ -2042,9 +2112,10 @@ func newAccountWithId(ctx context.Context, accountID, userID, domain string) *ty
 			PeerInactivityExpirationEnabled: false,
 			PeerInactivityExpiration:        types.DefaultPeerInactivityExpiration,
 		},
+		Onboarding: types.AccountOnboarding{SignupFormPending: true, OnboardingFlowPending: true},
 	}
 
-	if err := acc.AddAllGroup(); err != nil {
+	if err := acc.AddAllGroup(false); err != nil {
 		log.WithContext(ctx).Errorf("error adding all group to account %s: %v", acc.Id, err)
 	}
 	return acc
@@ -3386,6 +3457,63 @@ func TestSqlStore_GetAccountMeta(t *testing.T) {
 	require.Equal(t, time.Date(2024, time.October, 2, 14, 1, 38, 210000000, time.UTC), accountMeta.CreatedAt.UTC())
 }
 
+func TestSqlStore_GetAccountOnboarding(t *testing.T) {
+	store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store.sql", t.TempDir())
+	t.Cleanup(cleanup)
+	require.NoError(t, err)
+
+	accountID := "9439-34653001fc3b-bf1c8084-ba50-4ce7"
+	a, err := store.GetAccount(context.Background(), accountID)
+	require.NoError(t, err)
+	t.Logf("Onboarding: %+v", a.Onboarding)
+	err = store.SaveAccount(context.Background(), a)
+	require.NoError(t, err)
+	onboarding, err := store.GetAccountOnboarding(context.Background(), accountID)
+	require.NoError(t, err)
+	require.NotNil(t, onboarding)
+	require.Equal(t, accountID, onboarding.AccountID)
+	require.Equal(t, time.Date(2024, time.October, 2, 14, 1, 38, 210000000, time.UTC), onboarding.CreatedAt.UTC())
+}
+
+func TestSqlStore_SaveAccountOnboarding(t *testing.T) {
+	store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store.sql", t.TempDir())
+	t.Cleanup(cleanup)
+	require.NoError(t, err)
+	t.Run("New onboarding should be saved correctly", func(t *testing.T) {
+		accountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
+		onboarding := &types.AccountOnboarding{
+			AccountID:             accountID,
+			SignupFormPending:     true,
+			OnboardingFlowPending: true,
+		}
+
+		err = store.SaveAccountOnboarding(context.Background(), onboarding)
+		require.NoError(t, err)
+
+		savedOnboarding, err := store.GetAccountOnboarding(context.Background(), accountID)
+		require.NoError(t, err)
+		require.Equal(t, onboarding.SignupFormPending, savedOnboarding.SignupFormPending)
+		require.Equal(t, onboarding.OnboardingFlowPending, savedOnboarding.OnboardingFlowPending)
+	})
+
+	t.Run("Existing onboarding should be updated correctly", func(t *testing.T) {
+		accountID := "9439-34653001fc3b-bf1c8084-ba50-4ce7"
+		onboarding, err := store.GetAccountOnboarding(context.Background(), accountID)
+		require.NoError(t, err)
+
+		onboarding.OnboardingFlowPending = !onboarding.OnboardingFlowPending
+		onboarding.SignupFormPending = !onboarding.SignupFormPending
+
+		err = store.SaveAccountOnboarding(context.Background(), onboarding)
+		require.NoError(t, err)
+
+		savedOnboarding, err := store.GetAccountOnboarding(context.Background(), accountID)
+		require.NoError(t, err)
+		require.Equal(t, onboarding.SignupFormPending, savedOnboarding.SignupFormPending)
+		require.Equal(t, onboarding.OnboardingFlowPending, savedOnboarding.OnboardingFlowPending)
+	})
+}
+
 func TestSqlStore_GetAnyAccountID(t *testing.T) {
 	t.Run("should return account ID when accounts exist", func(t *testing.T) {
 		store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/extended-store.sql", t.TempDir())
diff --git a/management/server/testdata/store.sql b/management/server/testdata/store.sql
index 41b8fa2f..a2178385 100644
--- a/management/server/testdata/store.sql
+++ b/management/server/testdata/store.sql
@@ -1,4 +1,5 @@
 CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`));
+CREATE TABLE `account_onboardings` (`account_id` text, `created_at` datetime,`updated_at` datetime, `onboarding_flow_pending` numeric, `signup_form_pending` numeric, PRIMARY KEY (`account_id`));
 CREATE TABLE `setup_keys` (`id` text,`account_id` text,`key` text,`name` text,`type` text,`created_at` datetime,`expires_at` datetime,`updated_at` datetime,`revoked` numeric,`used_times` integer,`last_used` datetime DEFAULT NULL,`auto_groups` text,`usage_limit` integer,`ephemeral` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_setup_keys_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`));
 CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`));
 CREATE TABLE `users` (`id` text,`account_id` text,`role` text,`is_service_user` numeric,`non_deletable` numeric,`service_user_name` text,`auto_groups` text,`blocked` numeric,`last_login` datetime,`created_at` datetime,`issued` text DEFAULT "api",`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_users_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`));
@@ -38,7 +39,8 @@ CREATE INDEX `idx_networks_id` ON `networks`(`id`);
 CREATE INDEX `idx_networks_account_id` ON `networks`(`account_id`);
 
 INSERT INTO accounts VALUES('bf1c8084-ba50-4ce7-9439-34653001fc3b','edafee4e-63fb-11ec-90d6-0242ac120003','2024-10-02 16:03:06.778746+02:00','test.com','private',1,'af1c8024-ha40-4ce2-9418-34653101fc3c','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',0,86400000000000,0,0,0,'',NULL,NULL,NULL);
-INSERT INTO "groups" VALUES('cs1tnh0hhcjnqoiuebeg','bf1c8084-ba50-4ce7-9439-34653001fc3b','All','api','[]',0,'');
+INSERT INTO accounts VALUES('9439-34653001fc3b-bf1c8084-ba50-4ce7','90d6-0242ac120003-edafee4e-63fb-11ec','2024-10-02 16:01:38.210000+02:00','test2.com','private',1,'af1c8024-ha40-4ce2-9418-34653101fc3c','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',0,86400000000000,0,0,0,'',NULL,NULL,NULL);
+INSERT INTO account_onboardings VALUES('9439-34653001fc3b-bf1c8084-ba50-4ce7','2024-10-02 16:01:38.210000+02:00','2021-08-19 20:46:20.005936822+02:00',1,0);INSERT INTO "groups" VALUES('cs1tnh0hhcjnqoiuebeg','bf1c8084-ba50-4ce7-9439-34653001fc3b','All','api','[]',0,'');
 INSERT INTO setup_keys VALUES('','bf1c8084-ba50-4ce7-9439-34653001fc3b','A2C8E62B-38F5-4553-B31E-DD66C696CEBB','Default key','reusable','2021-08-19 20:46:20.005936822+02:00','2321-09-18 20:46:20.005936822+02:00','2021-08-19 20:46:20.005936822+02:00',0,0,NULL,'["cs1tnh0hhcjnqoiuebeg"]',0,0);
 INSERT INTO users VALUES('a23efe53-63fb-11ec-90d6-0242ac120003','bf1c8084-ba50-4ce7-9439-34653001fc3b','owner',0,0,'','[]',0,NULL,'2024-10-02 16:03:06.779156+02:00','api',0,'');
 INSERT INTO users VALUES('edafee4e-63fb-11ec-90d6-0242ac120003','bf1c8084-ba50-4ce7-9439-34653001fc3b','admin',0,0,'','[]',0,NULL,'2024-10-02 16:03:06.779156+02:00','api',0,'');
@@ -52,4 +54,4 @@ INSERT INTO policy_rules VALUES('cs387mkv2d4bgq41b6n0','cs1tnh0hhcjnqoiuebf0','D
 INSERT INTO network_routers VALUES('ctc20ji7qv9ck2sebc80','ct286bi7qv930dsrrug0','bf1c8084-ba50-4ce7-9439-34653001fc3b','cs1tnh0hhcjnqoiuebeg',NULL,0,0);
 INSERT INTO network_resources VALUES ('ctc4nci7qv9061u6ilfg','ct286bi7qv930dsrrug0','bf1c8084-ba50-4ce7-9439-34653001fc3b','Host','192.168.1.1');
 INSERT INTO networks VALUES('ct286bi7qv930dsrrug0','bf1c8084-ba50-4ce7-9439-34653001fc3b','Test Network','Test Network');
-INSERT INTO peers VALUES('ct286bi7qv930dsrrug0','bf1c8084-ba50-4ce7-9439-34653001fc3b','','','192.168.0.0','','','','','','','','','','','','','','','','','test','test','2023-01-01 00:00:00+00:00',0,0,0,'a23efe53-63fb-11ec-90d6-0242ac120003','',0,0,'2023-01-01 00:00:00+00:00','2023-01-01 00:00:00+00:00',0,'','','',0);
+INSERT INTO peers VALUES('ct286bi7qv930dsrrug0','bf1c8084-ba50-4ce7-9439-34653001fc3b','','','"192.168.0.0"','','','','','','','','','','','','','','','','','test','test','2023-01-01 00:00:00+00:00',0,0,0,'a23efe53-63fb-11ec-90d6-0242ac120003','',0,0,'2023-01-01 00:00:00+00:00','2023-01-01 00:00:00+00:00',0,'','','',0);
diff --git a/management/server/testdata/store_with_expired_peers.sql b/management/server/testdata/store_with_expired_peers.sql
index 5990a062..f2ef56a2 100644
--- a/management/server/testdata/store_with_expired_peers.sql
+++ b/management/server/testdata/store_with_expired_peers.sql
@@ -30,7 +30,7 @@ INSERT INTO setup_keys VALUES('','bf1c8084-ba50-4ce7-9439-34653001fc3b','A2C8E62
 INSERT INTO peers VALUES('cfvprsrlo1hqoo49ohog','bf1c8084-ba50-4ce7-9439-34653001fc3b','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','f2a34f6a4731','linux','Linux','11','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'f2a34f6a4731','f2a34f6a4731','2023-03-02 09:21:02.189035775+01:00',0,0,0,'','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,0,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0);
 INSERT INTO peers VALUES('cg05lnblo1hkg2j514p0','bf1c8084-ba50-4ce7-9439-34653001fc3b','RlSy2vzoG2HyMBTUImXOiVhCBiiBa5qD5xzMxkiFDW4=','','"100.64.39.54"','expiredhost','linux','Linux','22.04','x86_64','Ubuntu','','development','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'expiredhost','expiredhost','2023-03-02 09:19:57.276717255+01:00',0,1,0,'edafee4e-63fb-11ec-90d6-0242ac120003','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMbK5ZXJsGOOWoBT4OmkPtgdPZe2Q7bDuS/zjn2CZxhK',0,1,0,'2023-03-02 09:14:21.791679181+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0);
 INSERT INTO peers VALUES('cg3161rlo1hs9cq94gdg','bf1c8084-ba50-4ce7-9439-34653001fc3b','mVABSKj28gv+JRsf7e0NEGKgSOGTfU/nPB2cpuG56HU=','','"100.64.117.96"','testhost','linux','Linux','22.04','x86_64','Ubuntu','','development','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'testhost','testhost','2023-03-06 18:21:27.252010027+01:00',0,0,0,'edafee4e-63fb-11ec-90d6-0242ac120003','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINWvvUkFFcrj48CWTkNUb/do/n52i1L5dH4DhGu+4ZuM',0,0,0,'2023-03-07 09:02:47.442857106+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0);
-INSERT INTO peers VALUES('csrnkiq7qv9d8aitqd50','bf1c8084-ba50-4ce7-9439-34653001fc3b','mVABSKj28gv+JRsf7e0NEGKgSOGTfU/nPB2cpuG56HU=','','"100.64.117.96"','testhost','linux','Linux','22.04','x86_64','Ubuntu','','development','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'testhost','testhost','2023-03-06 18:21:27.252010027+01:00',0,0,0,'f4f6d672-63fb-11ec-90d6-0242ac120003','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINWvvUkFFcrj48CWTkNUb/do/n52i1L5dH4DhGu+4ZuM',0,0,1,'2023-03-07 09:02:47.442857106+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0);
+INSERT INTO peers VALUES('csrnkiq7qv9d8aitqd50','bf1c8084-ba50-4ce7-9439-34653001fc3b','mVABSKj28gv+JRsf7e0NEGKgSOGTfU/nPB2cpuG56HU=','','"100.64.117.97"','testhost','linux','Linux','22.04','x86_64','Ubuntu','','development','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'testhost','testhost-1','2023-03-06 18:21:27.252010027+01:00',0,0,0,'f4f6d672-63fb-11ec-90d6-0242ac120003','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINWvvUkFFcrj48CWTkNUb/do/n52i1L5dH4DhGu+4ZuM',0,0,1,'2023-03-07 09:02:47.442857106+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0);
 INSERT INTO users VALUES('f4f6d672-63fb-11ec-90d6-0242ac120003','bf1c8084-ba50-4ce7-9439-34653001fc3b','user',0,0,'','[]',0,NULL,'2024-10-02 17:00:32.528196+02:00','api',0,'');
 INSERT INTO users VALUES('edafee4e-63fb-11ec-90d6-0242ac120003','bf1c8084-ba50-4ce7-9439-34653001fc3b','admin',0,0,'','[]',0,NULL,'2024-10-02 17:00:32.528196+02:00','api',0,'');
 INSERT INTO installations VALUES(1,'');
diff --git a/management/server/user_test.go b/management/server/user_test.go
index 66bdc168..53baf8f7 100644
--- a/management/server/user_test.go
+++ b/management/server/user_test.go
@@ -56,7 +56,7 @@ func TestUser_CreatePAT_ForSameUser(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	err = s.SaveAccount(context.Background(), account)
 	if err != nil {
@@ -103,7 +103,7 @@ func TestUser_CreatePAT_ForDifferentUser(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 	account.Users[mockTargetUserId] = &types.User{
 		Id:            mockTargetUserId,
 		IsServiceUser: false,
@@ -131,7 +131,7 @@ func TestUser_CreatePAT_ForServiceUser(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 	account.Users[mockTargetUserId] = &types.User{
 		Id:            mockTargetUserId,
 		IsServiceUser: true,
@@ -163,7 +163,7 @@ func TestUser_CreatePAT_WithWrongExpiration(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	err = store.SaveAccount(context.Background(), account)
 	if err != nil {
@@ -188,7 +188,7 @@ func TestUser_CreatePAT_WithEmptyName(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	err = store.SaveAccount(context.Background(), account)
 	if err != nil {
@@ -213,7 +213,7 @@ func TestUser_DeletePAT(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 	account.Users[mockUserID] = &types.User{
 		Id: mockUserID,
 		PATs: map[string]*types.PersonalAccessToken{
@@ -256,7 +256,7 @@ func TestUser_GetPAT(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 	account.Users[mockUserID] = &types.User{
 		Id:        mockUserID,
 		AccountID: mockAccountID,
@@ -296,7 +296,7 @@ func TestUser_GetAllPATs(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 	account.Users[mockUserID] = &types.User{
 		Id:        mockUserID,
 		AccountID: mockAccountID,
@@ -406,7 +406,7 @@ func TestUser_CreateServiceUser(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	err = store.SaveAccount(context.Background(), account)
 	if err != nil {
@@ -453,7 +453,7 @@ func TestUser_CreateUser_ServiceUser(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	err = store.SaveAccount(context.Background(), account)
 	if err != nil {
@@ -501,7 +501,7 @@ func TestUser_CreateUser_RegularUser(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	err = store.SaveAccount(context.Background(), account)
 	if err != nil {
@@ -532,7 +532,7 @@ func TestUser_InviteNewUser(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	err = store.SaveAccount(context.Background(), account)
 	if err != nil {
@@ -639,7 +639,7 @@ func TestUser_DeleteUser_ServiceUser(t *testing.T) {
 			}
 			t.Cleanup(cleanup)
 
-			account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+			account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 			account.Users[mockServiceUserID] = tt.serviceUser
 
 			err = store.SaveAccount(context.Background(), account)
@@ -678,7 +678,7 @@ func TestUser_DeleteUser_SelfDelete(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	err = store.SaveAccount(context.Background(), account)
 	if err != nil {
@@ -705,7 +705,7 @@ func TestUser_DeleteUser_regularUser(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	targetId := "user2"
 	account.Users[targetId] = &types.User{
@@ -792,7 +792,7 @@ func TestUser_DeleteUser_RegularUsers(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	targetId := "user2"
 	account.Users[targetId] = &types.User{
@@ -852,7 +852,7 @@ func TestUser_DeleteUser_RegularUsers(t *testing.T) {
 	am := DefaultAccountManager{
 		Store:                   store,
 		eventStore:              &activity.InMemoryEventStore{},
-		integratedPeerValidator: MocIntegratedValidator{},
+		integratedPeerValidator: MockIntegratedValidator{},
 		permissionsManager:      permissionsManager,
 	}
 
@@ -952,7 +952,7 @@ func TestDefaultAccountManager_GetUser(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 
 	err = store.SaveAccount(context.Background(), account)
 	if err != nil {
@@ -988,7 +988,7 @@ func TestDefaultAccountManager_ListUsers(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 	account.Users["normal_user1"] = types.NewRegularUser("normal_user1")
 	account.Users["normal_user2"] = types.NewRegularUser("normal_user2")
 
@@ -1030,7 +1030,7 @@ func TestDefaultAccountManager_ExternalCache(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 	externalUser := &types.User{
 		Id:     "externalUser",
 		Role:   types.UserRoleUser,
@@ -1098,7 +1098,7 @@ func TestUser_GetUsersFromAccount_ForAdmin(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 	account.Users[mockServiceUserID] = &types.User{
 		Id:            mockServiceUserID,
 		Role:          "user",
@@ -1132,7 +1132,7 @@ func TestUser_GetUsersFromAccount_ForUser(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
+	account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", false)
 	account.Users[mockServiceUserID] = &types.User{
 		Id:            mockServiceUserID,
 		Role:          "user",
@@ -1499,7 +1499,7 @@ func TestSaveOrAddUser_PreventAccountSwitch(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account1 := newAccountWithId(context.Background(), "account1", "ownerAccount1", "")
+	account1 := newAccountWithId(context.Background(), "account1", "ownerAccount1", "", false)
 	targetId := "user2"
 	account1.Users[targetId] = &types.User{
 		Id:              targetId,
@@ -1508,7 +1508,7 @@ func TestSaveOrAddUser_PreventAccountSwitch(t *testing.T) {
 	}
 	require.NoError(t, s.SaveAccount(context.Background(), account1))
 
-	account2 := newAccountWithId(context.Background(), "account2", "ownerAccount2", "")
+	account2 := newAccountWithId(context.Background(), "account2", "ownerAccount2", "", false)
 	require.NoError(t, s.SaveAccount(context.Background(), account2))
 
 	permissionsManager := permissions.NewManager(s)
@@ -1535,7 +1535,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
 	}
 	t.Cleanup(cleanup)
 
-	account1 := newAccountWithId(context.Background(), "account1", "account1Owner", "")
+	account1 := newAccountWithId(context.Background(), "account1", "account1Owner", "", false)
 	account1.Settings.RegularUsersViewBlocked = false
 	account1.Users["blocked-user"] = &types.User{
 		Id:        "blocked-user",
@@ -1557,7 +1557,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
 	}
 	require.NoError(t, store.SaveAccount(context.Background(), account1))
 
-	account2 := newAccountWithId(context.Background(), "account2", "account2Owner", "")
+	account2 := newAccountWithId(context.Background(), "account2", "account2Owner", "", false)
 	account2.Users["settings-blocked-user"] = &types.User{
 		Id:   "settings-blocked-user",
 		Role: types.UserRoleUser,
diff --git a/monotime/time_test.go b/monotime/time_test.go
new file mode 100644
index 00000000..ac837b22
--- /dev/null
+++ b/monotime/time_test.go
@@ -0,0 +1,20 @@
+package monotime
+
+import (
+	"testing"
+	"time"
+)
+
+func BenchmarkMonotimeNow(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		_ = Now()
+	}
+}
+
+func BenchmarkTimeNow(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		_ = time.Now()
+	}
+}
diff --git a/relay/client/client_test.go b/relay/client/client_test.go
index 7ddfba4c..c85ec9fd 100644
--- a/relay/client/client_test.go
+++ b/relay/client/client_test.go
@@ -18,14 +18,19 @@ import (
 )
 
 var (
-	av               = &allow.Auth{}
 	hmacTokenStore   = &hmac.TokenStore{}
 	serverListenAddr = "127.0.0.1:1234"
 	serverURL        = "rel://127.0.0.1:1234"
+	serverCfg        = server.Config{
+		Meter:          otel.Meter(""),
+		ExposedAddress: serverURL,
+		TLSSupport:     false,
+		AuthValidator:  &allow.Auth{},
+	}
 )
 
 func TestMain(m *testing.M) {
-	_ = util.InitLog("error", "console")
+	_ = util.InitLog("debug", "console")
 	code := m.Run()
 	os.Exit(code)
 }
@@ -33,7 +38,7 @@ func TestMain(m *testing.M) {
 func TestClient(t *testing.T) {
 	ctx := context.Background()
 
-	srv, err := server.NewServer(otel.Meter(""), serverURL, false, av)
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -58,37 +63,37 @@ func TestClient(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 	t.Log("alice connecting to server")
-	clientAlice := NewClient(ctx, serverURL, hmacTokenStore, "alice")
-	err = clientAlice.Connect()
+	clientAlice := NewClient(serverURL, hmacTokenStore, "alice")
+	err = clientAlice.Connect(ctx)
 	if err != nil {
 		t.Fatalf("failed to connect to server: %s", err)
 	}
 	defer clientAlice.Close()
 
 	t.Log("placeholder connecting to server")
-	clientPlaceHolder := NewClient(ctx, serverURL, hmacTokenStore, "clientPlaceHolder")
-	err = clientPlaceHolder.Connect()
+	clientPlaceHolder := NewClient(serverURL, hmacTokenStore, "clientPlaceHolder")
+	err = clientPlaceHolder.Connect(ctx)
 	if err != nil {
 		t.Fatalf("failed to connect to server: %s", err)
 	}
 	defer clientPlaceHolder.Close()
 
 	t.Log("Bob connecting to server")
-	clientBob := NewClient(ctx, serverURL, hmacTokenStore, "bob")
-	err = clientBob.Connect()
+	clientBob := NewClient(serverURL, hmacTokenStore, "bob")
+	err = clientBob.Connect(ctx)
 	if err != nil {
 		t.Fatalf("failed to connect to server: %s", err)
 	}
 	defer clientBob.Close()
 
 	t.Log("Alice open connection to Bob")
-	connAliceToBob, err := clientAlice.OpenConn("bob")
+	connAliceToBob, err := clientAlice.OpenConn(ctx, "bob")
 	if err != nil {
 		t.Fatalf("failed to bind channel: %s", err)
 	}
 
 	t.Log("Bob open connection to Alice")
-	connBobToAlice, err := clientBob.OpenConn("alice")
+	connBobToAlice, err := clientBob.OpenConn(ctx, "alice")
 	if err != nil {
 		t.Fatalf("failed to bind channel: %s", err)
 	}
@@ -115,7 +120,7 @@ func TestClient(t *testing.T) {
 func TestRegistration(t *testing.T) {
 	ctx := context.Background()
 	srvCfg := server.ListenerConfig{Address: serverListenAddr}
-	srv, err := server.NewServer(otel.Meter(""), serverURL, false, av)
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -132,8 +137,8 @@ func TestRegistration(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 
-	clientAlice := NewClient(ctx, serverURL, hmacTokenStore, "alice")
-	err = clientAlice.Connect()
+	clientAlice := NewClient(serverURL, hmacTokenStore, "alice")
+	err = clientAlice.Connect(ctx)
 	if err != nil {
 		_ = srv.Shutdown(ctx)
 		t.Fatalf("failed to connect to server: %s", err)
@@ -172,8 +177,8 @@ func TestRegistrationTimeout(t *testing.T) {
 		_ = fakeTCPListener.Close()
 	}(fakeTCPListener)
 
-	clientAlice := NewClient(ctx, "127.0.0.1:1234", hmacTokenStore, "alice")
-	err = clientAlice.Connect()
+	clientAlice := NewClient("127.0.0.1:1234", hmacTokenStore, "alice")
+	err = clientAlice.Connect(ctx)
 	if err == nil {
 		t.Errorf("failed to connect to server: %s", err)
 	}
@@ -189,7 +194,7 @@ func TestEcho(t *testing.T) {
 	idAlice := "alice"
 	idBob := "bob"
 	srvCfg := server.ListenerConfig{Address: serverListenAddr}
-	srv, err := server.NewServer(otel.Meter(""), serverURL, false, av)
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -213,8 +218,8 @@ func TestEcho(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 
-	clientAlice := NewClient(ctx, serverURL, hmacTokenStore, idAlice)
-	err = clientAlice.Connect()
+	clientAlice := NewClient(serverURL, hmacTokenStore, idAlice)
+	err = clientAlice.Connect(ctx)
 	if err != nil {
 		t.Fatalf("failed to connect to server: %s", err)
 	}
@@ -225,8 +230,8 @@ func TestEcho(t *testing.T) {
 		}
 	}()
 
-	clientBob := NewClient(ctx, serverURL, hmacTokenStore, idBob)
-	err = clientBob.Connect()
+	clientBob := NewClient(serverURL, hmacTokenStore, idBob)
+	err = clientBob.Connect(ctx)
 	if err != nil {
 		t.Fatalf("failed to connect to server: %s", err)
 	}
@@ -237,12 +242,12 @@ func TestEcho(t *testing.T) {
 		}
 	}()
 
-	connAliceToBob, err := clientAlice.OpenConn(idBob)
+	connAliceToBob, err := clientAlice.OpenConn(ctx, idBob)
 	if err != nil {
 		t.Fatalf("failed to bind channel: %s", err)
 	}
 
-	connBobToAlice, err := clientBob.OpenConn(idAlice)
+	connBobToAlice, err := clientBob.OpenConn(ctx, idAlice)
 	if err != nil {
 		t.Fatalf("failed to bind channel: %s", err)
 	}
@@ -278,7 +283,7 @@ func TestBindToUnavailabePeer(t *testing.T) {
 	ctx := context.Background()
 
 	srvCfg := server.ListenerConfig{Address: serverListenAddr}
-	srv, err := server.NewServer(otel.Meter(""), serverURL, false, av)
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -303,14 +308,14 @@ func TestBindToUnavailabePeer(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 
-	clientAlice := NewClient(ctx, serverURL, hmacTokenStore, "alice")
-	err = clientAlice.Connect()
+	clientAlice := NewClient(serverURL, hmacTokenStore, "alice")
+	err = clientAlice.Connect(ctx)
 	if err != nil {
 		t.Errorf("failed to connect to server: %s", err)
 	}
-	_, err = clientAlice.OpenConn("bob")
-	if err != nil {
-		t.Errorf("failed to bind channel: %s", err)
+	_, err = clientAlice.OpenConn(ctx, "bob")
+	if err == nil {
+		t.Errorf("expected error when binding to unavailable peer, got nil")
 	}
 
 	log.Infof("closing client")
@@ -324,7 +329,7 @@ func TestBindReconnect(t *testing.T) {
 	ctx := context.Background()
 
 	srvCfg := server.ListenerConfig{Address: serverListenAddr}
-	srv, err := server.NewServer(otel.Meter(""), serverURL, false, av)
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -349,24 +354,24 @@ func TestBindReconnect(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 
-	clientAlice := NewClient(ctx, serverURL, hmacTokenStore, "alice")
-	err = clientAlice.Connect()
+	clientAlice := NewClient(serverURL, hmacTokenStore, "alice")
+	err = clientAlice.Connect(ctx)
 	if err != nil {
-		t.Errorf("failed to connect to server: %s", err)
+		t.Fatalf("failed to connect to server: %s", err)
 	}
 
-	_, err = clientAlice.OpenConn("bob")
+	clientBob := NewClient(serverURL, hmacTokenStore, "bob")
+	err = clientBob.Connect(ctx)
 	if err != nil {
-		t.Errorf("failed to bind channel: %s", err)
+		t.Errorf("failed to connect to server: %s", err)
 	}
 
-	clientBob := NewClient(ctx, serverURL, hmacTokenStore, "bob")
-	err = clientBob.Connect()
+	_, err = clientAlice.OpenConn(ctx, "bob")
 	if err != nil {
-		t.Errorf("failed to connect to server: %s", err)
+		t.Fatalf("failed to bind channel: %s", err)
 	}
 
-	chBob, err := clientBob.OpenConn("alice")
+	chBob, err := clientBob.OpenConn(ctx, "alice")
 	if err != nil {
 		t.Errorf("failed to bind channel: %s", err)
 	}
@@ -377,18 +382,28 @@ func TestBindReconnect(t *testing.T) {
 		t.Errorf("failed to close client: %s", err)
 	}
 
-	clientAlice = NewClient(ctx, serverURL, hmacTokenStore, "alice")
-	err = clientAlice.Connect()
+	clientAlice = NewClient(serverURL, hmacTokenStore, "alice")
+	err = clientAlice.Connect(ctx)
 	if err != nil {
 		t.Errorf("failed to connect to server: %s", err)
 	}
 
-	chAlice, err := clientAlice.OpenConn("bob")
+	chAlice, err := clientAlice.OpenConn(ctx, "bob")
 	if err != nil {
 		t.Errorf("failed to bind channel: %s", err)
 	}
 
 	testString := "hello alice, I am bob"
+	_, err = chBob.Write([]byte(testString))
+	if err == nil {
+		t.Errorf("expected error when writing to channel, got nil")
+	}
+
+	chBob, err = clientBob.OpenConn(ctx, "alice")
+	if err != nil {
+		t.Errorf("failed to bind channel: %s", err)
+	}
+
 	_, err = chBob.Write([]byte(testString))
 	if err != nil {
 		t.Errorf("failed to write to channel: %s", err)
@@ -415,7 +430,7 @@ func TestCloseConn(t *testing.T) {
 	ctx := context.Background()
 
 	srvCfg := server.ListenerConfig{Address: serverListenAddr}
-	srv, err := server.NewServer(otel.Meter(""), serverURL, false, av)
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -440,13 +455,19 @@ func TestCloseConn(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 
-	clientAlice := NewClient(ctx, serverURL, hmacTokenStore, "alice")
-	err = clientAlice.Connect()
+	bob := NewClient(serverURL, hmacTokenStore, "bob")
+	err = bob.Connect(ctx)
+	if err != nil {
+		t.Errorf("failed to connect to server: %s", err)
+	}
+
+	clientAlice := NewClient(serverURL, hmacTokenStore, "alice")
+	err = clientAlice.Connect(ctx)
 	if err != nil {
 		t.Errorf("failed to connect to server: %s", err)
 	}
 
-	conn, err := clientAlice.OpenConn("bob")
+	conn, err := clientAlice.OpenConn(ctx, "bob")
 	if err != nil {
 		t.Errorf("failed to bind channel: %s", err)
 	}
@@ -472,7 +493,7 @@ func TestCloseRelayConn(t *testing.T) {
 	ctx := context.Background()
 
 	srvCfg := server.ListenerConfig{Address: serverListenAddr}
-	srv, err := server.NewServer(otel.Meter(""), serverURL, false, av)
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -496,13 +517,19 @@ func TestCloseRelayConn(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 
-	clientAlice := NewClient(ctx, serverURL, hmacTokenStore, "alice")
-	err = clientAlice.Connect()
+	bob := NewClient(serverURL, hmacTokenStore, "bob")
+	err = bob.Connect(ctx)
 	if err != nil {
 		t.Fatalf("failed to connect to server: %s", err)
 	}
 
-	conn, err := clientAlice.OpenConn("bob")
+	clientAlice := NewClient(serverURL, hmacTokenStore, "alice")
+	err = clientAlice.Connect(ctx)
+	if err != nil {
+		t.Fatalf("failed to connect to server: %s", err)
+	}
+
+	conn, err := clientAlice.OpenConn(ctx, "bob")
 	if err != nil {
 		t.Errorf("failed to bind channel: %s", err)
 	}
@@ -514,7 +541,7 @@ func TestCloseRelayConn(t *testing.T) {
 		t.Errorf("unexpected reading from closed connection")
 	}
 
-	_, err = clientAlice.OpenConn("bob")
+	_, err = clientAlice.OpenConn(ctx, "bob")
 	if err == nil {
 		t.Errorf("unexpected opening connection to closed server")
 	}
@@ -524,7 +551,7 @@ func TestCloseByServer(t *testing.T) {
 	ctx := context.Background()
 
 	srvCfg := server.ListenerConfig{Address: serverListenAddr}
-	srv1, err := server.NewServer(otel.Meter(""), serverURL, false, av)
+	srv1, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -544,11 +571,15 @@ func TestCloseByServer(t *testing.T) {
 
 	idAlice := "alice"
 	log.Debugf("connect by alice")
-	relayClient := NewClient(ctx, serverURL, hmacTokenStore, idAlice)
-	err = relayClient.Connect()
-	if err != nil {
+	relayClient := NewClient(serverURL, hmacTokenStore, idAlice)
+	if err = relayClient.Connect(ctx); err != nil {
 		log.Fatalf("failed to connect to server: %s", err)
 	}
+	defer func() {
+		if err := relayClient.Close(); err != nil {
+			log.Errorf("failed to close client: %s", err)
+		}
+	}()
 
 	disconnected := make(chan struct{})
 	relayClient.SetOnDisconnectListener(func(_ string) {
@@ -564,10 +595,10 @@ func TestCloseByServer(t *testing.T) {
 	select {
 	case <-disconnected:
 	case <-time.After(3 * time.Second):
-		log.Fatalf("timeout waiting for client to disconnect")
+		log.Errorf("timeout waiting for client to disconnect")
 	}
 
-	_, err = relayClient.OpenConn("bob")
+	_, err = relayClient.OpenConn(ctx, "bob")
 	if err == nil {
 		t.Errorf("unexpected opening connection to closed server")
 	}
@@ -577,7 +608,7 @@ func TestCloseByClient(t *testing.T) {
 	ctx := context.Background()
 
 	srvCfg := server.ListenerConfig{Address: serverListenAddr}
-	srv, err := server.NewServer(otel.Meter(""), serverURL, false, av)
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -596,8 +627,8 @@ func TestCloseByClient(t *testing.T) {
 
 	idAlice := "alice"
 	log.Debugf("connect by alice")
-	relayClient := NewClient(ctx, serverURL, hmacTokenStore, idAlice)
-	err = relayClient.Connect()
+	relayClient := NewClient(serverURL, hmacTokenStore, idAlice)
+	err = relayClient.Connect(ctx)
 	if err != nil {
 		log.Fatalf("failed to connect to server: %s", err)
 	}
@@ -607,7 +638,7 @@ func TestCloseByClient(t *testing.T) {
 		t.Errorf("failed to close client: %s", err)
 	}
 
-	_, err = relayClient.OpenConn("bob")
+	_, err = relayClient.OpenConn(ctx, "bob")
 	if err == nil {
 		t.Errorf("unexpected opening connection to closed server")
 	}
@@ -623,7 +654,7 @@ func TestCloseNotDrainedChannel(t *testing.T) {
 	idAlice := "alice"
 	idBob := "bob"
 	srvCfg := server.ListenerConfig{Address: serverListenAddr}
-	srv, err := server.NewServer(otel.Meter(""), serverURL, false, av)
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -647,8 +678,8 @@ func TestCloseNotDrainedChannel(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 
-	clientAlice := NewClient(ctx, serverURL, hmacTokenStore, idAlice)
-	err = clientAlice.Connect()
+	clientAlice := NewClient(serverURL, hmacTokenStore, idAlice)
+	err = clientAlice.Connect(ctx)
 	if err != nil {
 		t.Fatalf("failed to connect to server: %s", err)
 	}
@@ -659,8 +690,8 @@ func TestCloseNotDrainedChannel(t *testing.T) {
 		}
 	}()
 
-	clientBob := NewClient(ctx, serverURL, hmacTokenStore, idBob)
-	err = clientBob.Connect()
+	clientBob := NewClient(serverURL, hmacTokenStore, idBob)
+	err = clientBob.Connect(ctx)
 	if err != nil {
 		t.Fatalf("failed to connect to server: %s", err)
 	}
@@ -671,12 +702,12 @@ func TestCloseNotDrainedChannel(t *testing.T) {
 		}
 	}()
 
-	connAliceToBob, err := clientAlice.OpenConn(idBob)
+	connAliceToBob, err := clientAlice.OpenConn(ctx, idBob)
 	if err != nil {
 		t.Fatalf("failed to bind channel: %s", err)
 	}
 
-	connBobToAlice, err := clientBob.OpenConn(idAlice)
+	connBobToAlice, err := clientBob.OpenConn(ctx, idAlice)
 	if err != nil {
 		t.Fatalf("failed to bind channel: %s", err)
 	}
diff --git a/relay/client/dialer/race_dialer_test.go b/relay/client/dialer/race_dialer_test.go
index 989abb0a..d216ec5e 100644
--- a/relay/client/dialer/race_dialer_test.go
+++ b/relay/client/dialer/race_dialer_test.go
@@ -77,7 +77,7 @@ func TestRaceDialEmptyDialers(t *testing.T) {
 	logger := logrus.NewEntry(logrus.New())
 	serverURL := "test.server.com"
 
-	rd := NewRaceDial(logger, serverURL)
+	rd := NewRaceDial(logger, DefaultConnectionTimeout, serverURL)
 	conn, err := rd.Dial()
 	if err == nil {
 		t.Errorf("Expected an error with empty dialers, got nil")
@@ -103,7 +103,7 @@ func TestRaceDialSingleSuccessfulDialer(t *testing.T) {
 		protocolStr: proto,
 	}
 
-	rd := NewRaceDial(logger, serverURL, mockDialer)
+	rd := NewRaceDial(logger, DefaultConnectionTimeout, serverURL, mockDialer)
 	conn, err := rd.Dial()
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
@@ -136,7 +136,7 @@ func TestRaceDialMultipleDialersWithOneSuccess(t *testing.T) {
 		protocolStr: "proto2",
 	}
 
-	rd := NewRaceDial(logger, serverURL, mockDialer1, mockDialer2)
+	rd := NewRaceDial(logger, DefaultConnectionTimeout, serverURL, mockDialer1, mockDialer2)
 	conn, err := rd.Dial()
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
@@ -144,13 +144,13 @@ func TestRaceDialMultipleDialersWithOneSuccess(t *testing.T) {
 	if conn.RemoteAddr().Network() != proto2 {
 		t.Errorf("Expected connection with protocol %s, got %s", proto2, conn.RemoteAddr().Network())
 	}
+	_ = conn.Close()
 }
 
 func TestRaceDialTimeout(t *testing.T) {
 	logger := logrus.NewEntry(logrus.New())
 	serverURL := "test.server.com"
 
-	connectionTimeout = 3 * time.Second
 	mockDialer := &MockDialer{
 		dialFunc: func(ctx context.Context, address string) (net.Conn, error) {
 			<-ctx.Done()
@@ -159,7 +159,7 @@ func TestRaceDialTimeout(t *testing.T) {
 		protocolStr: "proto1",
 	}
 
-	rd := NewRaceDial(logger, serverURL, mockDialer)
+	rd := NewRaceDial(logger, 3*time.Second, serverURL, mockDialer)
 	conn, err := rd.Dial()
 	if err == nil {
 		t.Errorf("Expected an error, got nil")
@@ -187,7 +187,7 @@ func TestRaceDialAllDialersFail(t *testing.T) {
 		protocolStr: "protocol2",
 	}
 
-	rd := NewRaceDial(logger, serverURL, mockDialer1, mockDialer2)
+	rd := NewRaceDial(logger, DefaultConnectionTimeout, serverURL, mockDialer1, mockDialer2)
 	conn, err := rd.Dial()
 	if err == nil {
 		t.Errorf("Expected an error, got nil")
@@ -229,7 +229,7 @@ func TestRaceDialFirstSuccessfulDialerWins(t *testing.T) {
 		protocolStr: proto2,
 	}
 
-	rd := NewRaceDial(logger, serverURL, mockDialer1, mockDialer2)
+	rd := NewRaceDial(logger, DefaultConnectionTimeout, serverURL, mockDialer1, mockDialer2)
 	conn, err := rd.Dial()
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
diff --git a/relay/client/manager_test.go b/relay/client/manager_test.go
index bfc342f2..d0075f98 100644
--- a/relay/client/manager_test.go
+++ b/relay/client/manager_test.go
@@ -8,11 +8,14 @@ import (
 	log "github.com/sirupsen/logrus"
 	"go.opentelemetry.io/otel"
 
+	"github.com/netbirdio/netbird/relay/auth/allow"
 	"github.com/netbirdio/netbird/relay/server"
 )
 
 func TestEmptyURL(t *testing.T) {
-	mgr := NewManager(context.Background(), nil, "alice")
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+	mgr := NewManager(ctx, nil, "alice")
 	err := mgr.Serve()
 	if err == nil {
 		t.Errorf("expected error, got nil")
@@ -22,16 +25,22 @@ func TestEmptyURL(t *testing.T) {
 func TestForeignConn(t *testing.T) {
 	ctx := context.Background()
 
-	srvCfg1 := server.ListenerConfig{
+	lstCfg1 := server.ListenerConfig{
 		Address: "localhost:1234",
 	}
-	srv1, err := server.NewServer(otel.Meter(""), srvCfg1.Address, false, av)
+
+	srv1, err := server.NewServer(server.Config{
+		Meter:          otel.Meter(""),
+		ExposedAddress: lstCfg1.Address,
+		TLSSupport:     false,
+		AuthValidator:  &allow.Auth{},
+	})
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
 	errChan := make(chan error, 1)
 	go func() {
-		err := srv1.Listen(srvCfg1)
+		err := srv1.Listen(lstCfg1)
 		if err != nil {
 			errChan <- err
 		}
@@ -51,7 +60,12 @@ func TestForeignConn(t *testing.T) {
 	srvCfg2 := server.ListenerConfig{
 		Address: "localhost:2234",
 	}
-	srv2, err := server.NewServer(otel.Meter(""), srvCfg2.Address, false, av)
+	srv2, err := server.NewServer(server.Config{
+		Meter:          otel.Meter(""),
+		ExposedAddress: srvCfg2.Address,
+		TLSSupport:     false,
+		AuthValidator:  &allow.Auth{},
+	})
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -74,32 +88,26 @@ func TestForeignConn(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 
-	idAlice := "alice"
-	log.Debugf("connect by alice")
 	mCtx, cancel := context.WithCancel(ctx)
 	defer cancel()
-	clientAlice := NewManager(mCtx, toURL(srvCfg1), idAlice)
-	err = clientAlice.Serve()
-	if err != nil {
+	clientAlice := NewManager(mCtx, toURL(lstCfg1), "alice")
+	if err := clientAlice.Serve(); err != nil {
 		t.Fatalf("failed to serve manager: %s", err)
 	}
 
-	idBob := "bob"
-	log.Debugf("connect by bob")
-	clientBob := NewManager(mCtx, toURL(srvCfg2), idBob)
-	err = clientBob.Serve()
-	if err != nil {
+	clientBob := NewManager(mCtx, toURL(srvCfg2), "bob")
+	if err := clientBob.Serve(); err != nil {
 		t.Fatalf("failed to serve manager: %s", err)
 	}
 	bobsSrvAddr, err := clientBob.RelayInstanceAddress()
 	if err != nil {
 		t.Fatalf("failed to get relay address: %s", err)
 	}
-	connAliceToBob, err := clientAlice.OpenConn(bobsSrvAddr, idBob)
+	connAliceToBob, err := clientAlice.OpenConn(ctx, bobsSrvAddr, "bob")
 	if err != nil {
 		t.Fatalf("failed to bind channel: %s", err)
 	}
-	connBobToAlice, err := clientBob.OpenConn(bobsSrvAddr, idAlice)
+	connBobToAlice, err := clientBob.OpenConn(ctx, bobsSrvAddr, "alice")
 	if err != nil {
 		t.Fatalf("failed to bind channel: %s", err)
 	}
@@ -137,7 +145,7 @@ func TestForeginConnClose(t *testing.T) {
 	srvCfg1 := server.ListenerConfig{
 		Address: "localhost:1234",
 	}
-	srv1, err := server.NewServer(otel.Meter(""), srvCfg1.Address, false, av)
+	srv1, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -163,7 +171,7 @@ func TestForeginConnClose(t *testing.T) {
 	srvCfg2 := server.ListenerConfig{
 		Address: "localhost:2234",
 	}
-	srv2, err := server.NewServer(otel.Meter(""), srvCfg2.Address, false, av)
+	srv2, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -186,16 +194,20 @@ func TestForeginConnClose(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 
-	idAlice := "alice"
-	log.Debugf("connect by alice")
 	mCtx, cancel := context.WithCancel(ctx)
 	defer cancel()
-	mgr := NewManager(mCtx, toURL(srvCfg1), idAlice)
+
+	mgrBob := NewManager(mCtx, toURL(srvCfg2), "bob")
+	if err := mgrBob.Serve(); err != nil {
+		t.Fatalf("failed to serve manager: %s", err)
+	}
+
+	mgr := NewManager(mCtx, toURL(srvCfg1), "alice")
 	err = mgr.Serve()
 	if err != nil {
 		t.Fatalf("failed to serve manager: %s", err)
 	}
-	conn, err := mgr.OpenConn(toURL(srvCfg2)[0], "anotherpeer")
+	conn, err := mgr.OpenConn(ctx, toURL(srvCfg2)[0], "bob")
 	if err != nil {
 		t.Fatalf("failed to bind channel: %s", err)
 	}
@@ -206,29 +218,29 @@ func TestForeginConnClose(t *testing.T) {
 	}
 }
 
-func TestForeginAutoClose(t *testing.T) {
+func TestForeignAutoClose(t *testing.T) {
 	ctx := context.Background()
 	relayCleanupInterval = 1 * time.Second
+	keepUnusedServerTime = 2 * time.Second
+
 	srvCfg1 := server.ListenerConfig{
 		Address: "localhost:1234",
 	}
-	srv1, err := server.NewServer(otel.Meter(""), srvCfg1.Address, false, av)
+	srv1, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
 	errChan := make(chan error, 1)
 	go func() {
 		t.Log("binding server 1.")
-		err := srv1.Listen(srvCfg1)
-		if err != nil {
+		if err := srv1.Listen(srvCfg1); err != nil {
 			errChan <- err
 		}
 	}()
 
 	defer func() {
 		t.Logf("closing server 1.")
-		err := srv1.Shutdown(ctx)
-		if err != nil {
+		if err := srv1.Shutdown(ctx); err != nil {
 			t.Errorf("failed to close server: %s", err)
 		}
 		t.Logf("server 1. closed")
@@ -241,7 +253,7 @@ func TestForeginAutoClose(t *testing.T) {
 	srvCfg2 := server.ListenerConfig{
 		Address: "localhost:2234",
 	}
-	srv2, err := server.NewServer(otel.Meter(""), srvCfg2.Address, false, av)
+	srv2, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -276,23 +288,35 @@ func TestForeginAutoClose(t *testing.T) {
 		t.Fatalf("failed to serve manager: %s", err)
 	}
 
+	// Set up a disconnect listener to track when foreign server disconnects
+	foreignServerURL := toURL(srvCfg2)[0]
+	disconnected := make(chan struct{})
+	onDisconnect := func() {
+		select {
+		case disconnected <- struct{}{}:
+		default:
+		}
+	}
+
 	t.Log("open connection to another peer")
-	conn, err := mgr.OpenConn(toURL(srvCfg2)[0], "anotherpeer")
-	if err != nil {
-		t.Fatalf("failed to bind channel: %s", err)
+	if _, err = mgr.OpenConn(ctx, foreignServerURL, "anotherpeer"); err == nil {
+		t.Fatalf("should have failed to open connection to another peer")
 	}
 
-	t.Log("close conn")
-	err = conn.Close()
-	if err != nil {
-		t.Fatalf("failed to close connection: %s", err)
+	// Add the disconnect listener after the connection attempt
+	if err := mgr.AddCloseListener(foreignServerURL, onDisconnect); err != nil {
+		t.Logf("failed to add close listener (expected if connection failed): %s", err)
 	}
 
-	timeout := relayCleanupInterval + keepUnusedServerTime + 1*time.Second
+	// Wait for cleanup to happen
+	timeout := relayCleanupInterval + keepUnusedServerTime + 2*time.Second
 	t.Logf("waiting for relay cleanup: %s", timeout)
-	time.Sleep(timeout)
-	if len(mgr.relayClients) != 0 {
-		t.Errorf("expected 0, got %d", len(mgr.relayClients))
+
+	select {
+	case <-disconnected:
+		t.Log("foreign relay connection cleaned up successfully")
+	case <-time.After(timeout):
+		t.Log("timeout waiting for cleanup - this might be expected if connection never established")
 	}
 
 	t.Logf("closing manager")
@@ -300,19 +324,17 @@ func TestForeginAutoClose(t *testing.T) {
 
 func TestAutoReconnect(t *testing.T) {
 	ctx := context.Background()
-	reconnectingTimeout = 2 * time.Second
 
 	srvCfg := server.ListenerConfig{
 		Address: "localhost:1234",
 	}
-	srv, err := server.NewServer(otel.Meter(""), srvCfg.Address, false, av)
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
 	errChan := make(chan error, 1)
 	go func() {
-		err := srv.Listen(srvCfg)
-		if err != nil {
+		if err := srv.Listen(srvCfg); err != nil {
 			errChan <- err
 		}
 	}()
@@ -330,6 +352,13 @@ func TestAutoReconnect(t *testing.T) {
 
 	mCtx, cancel := context.WithCancel(ctx)
 	defer cancel()
+
+	clientBob := NewManager(mCtx, toURL(srvCfg), "bob")
+	err = clientBob.Serve()
+	if err != nil {
+		t.Fatalf("failed to serve manager: %s", err)
+	}
+
 	clientAlice := NewManager(mCtx, toURL(srvCfg), "alice")
 	err = clientAlice.Serve()
 	if err != nil {
@@ -339,7 +368,7 @@ func TestAutoReconnect(t *testing.T) {
 	if err != nil {
 		t.Errorf("failed to get relay address: %s", err)
 	}
-	conn, err := clientAlice.OpenConn(ra, "bob")
+	conn, err := clientAlice.OpenConn(ctx, ra, "bob")
 	if err != nil {
 		t.Errorf("failed to bind channel: %s", err)
 	}
@@ -357,7 +386,7 @@ func TestAutoReconnect(t *testing.T) {
 	time.Sleep(reconnectingTimeout + 1*time.Second)
 
 	log.Infof("reopent the connection")
-	_, err = clientAlice.OpenConn(ra, "bob")
+	_, err = clientAlice.OpenConn(ctx, ra, "bob")
 	if err != nil {
 		t.Errorf("failed to open channel: %s", err)
 	}
@@ -366,24 +395,27 @@ func TestAutoReconnect(t *testing.T) {
 func TestNotifierDoubleAdd(t *testing.T) {
 	ctx := context.Background()
 
-	srvCfg1 := server.ListenerConfig{
+	listenerCfg1 := server.ListenerConfig{
 		Address: "localhost:1234",
 	}
-	srv1, err := server.NewServer(otel.Meter(""), srvCfg1.Address, false, av)
+	srv, err := server.NewServer(server.Config{
+		Meter:          otel.Meter(""),
+		ExposedAddress: listenerCfg1.Address,
+		TLSSupport:     false,
+		AuthValidator:  &allow.Auth{},
+	})
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
 	errChan := make(chan error, 1)
 	go func() {
-		err := srv1.Listen(srvCfg1)
-		if err != nil {
+		if err := srv.Listen(listenerCfg1); err != nil {
 			errChan <- err
 		}
 	}()
 
 	defer func() {
-		err := srv1.Shutdown(ctx)
-		if err != nil {
+		if err := srv.Shutdown(ctx); err != nil {
 			t.Errorf("failed to close server: %s", err)
 		}
 	}()
@@ -392,17 +424,21 @@ func TestNotifierDoubleAdd(t *testing.T) {
 		t.Fatalf("failed to start server: %s", err)
 	}
 
-	idAlice := "alice"
 	log.Debugf("connect by alice")
 	mCtx, cancel := context.WithCancel(ctx)
 	defer cancel()
-	clientAlice := NewManager(mCtx, toURL(srvCfg1), idAlice)
-	err = clientAlice.Serve()
-	if err != nil {
+
+	clientBob := NewManager(mCtx, toURL(listenerCfg1), "bob")
+	if err = clientBob.Serve(); err != nil {
+		t.Fatalf("failed to serve manager: %s", err)
+	}
+
+	clientAlice := NewManager(mCtx, toURL(listenerCfg1), "alice")
+	if err = clientAlice.Serve(); err != nil {
 		t.Fatalf("failed to serve manager: %s", err)
 	}
 
-	conn1, err := clientAlice.OpenConn(clientAlice.ServerURLs()[0], "idBob")
+	conn1, err := clientAlice.OpenConn(ctx, clientAlice.ServerURLs()[0], "bob")
 	if err != nil {
 		t.Fatalf("failed to bind channel: %s", err)
 	}
diff --git a/relay/client/peer_subscription_test.go b/relay/client/peer_subscription_test.go
new file mode 100644
index 00000000..0437efa0
--- /dev/null
+++ b/relay/client/peer_subscription_test.go
@@ -0,0 +1,99 @@
+package client
+
+import (
+	"bytes"
+	"context"
+	"testing"
+	"time"
+
+	"github.com/netbirdio/netbird/relay/messages"
+
+	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+type mockRelayedConn struct {
+}
+
+func (m *mockRelayedConn) Write(p []byte) (n int, err error) {
+	return len(p), nil
+}
+
+func TestWaitToBeOnlineAndSubscribe_Success(t *testing.T) {
+	peerID := messages.HashID("peer1")
+	mockConn := &mockRelayedConn{}
+	logger := logrus.New()
+	logger.SetOutput(&bytes.Buffer{}) // discard log output
+	sub := NewPeersStateSubscription(logrus.NewEntry(logger), mockConn, nil)
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	// Launch wait in background
+	go func() {
+		time.Sleep(100 * time.Millisecond)
+		sub.OnPeersOnline([]messages.PeerID{peerID})
+	}()
+
+	err := sub.WaitToBeOnlineAndSubscribe(ctx, peerID)
+	assert.NoError(t, err)
+}
+
+func TestWaitToBeOnlineAndSubscribe_Timeout(t *testing.T) {
+	peerID := messages.HashID("peer2")
+	mockConn := &mockRelayedConn{}
+	logger := logrus.New()
+	logger.SetOutput(&bytes.Buffer{})
+	sub := NewPeersStateSubscription(logrus.NewEntry(logger), mockConn, nil)
+
+	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
+	defer cancel()
+
+	err := sub.WaitToBeOnlineAndSubscribe(ctx, peerID)
+	assert.Error(t, err)
+	assert.Equal(t, context.DeadlineExceeded, err)
+}
+
+func TestWaitToBeOnlineAndSubscribe_Duplicate(t *testing.T) {
+	peerID := messages.HashID("peer3")
+	mockConn := &mockRelayedConn{}
+	logger := logrus.New()
+	logger.SetOutput(&bytes.Buffer{})
+	sub := NewPeersStateSubscription(logrus.NewEntry(logger), mockConn, nil)
+
+	ctx := context.Background()
+	go func() {
+		_ = sub.WaitToBeOnlineAndSubscribe(ctx, peerID)
+
+	}()
+	time.Sleep(100 * time.Millisecond)
+	err := sub.WaitToBeOnlineAndSubscribe(ctx, peerID)
+	require.Error(t, err)
+	assert.Contains(t, err.Error(), "already waiting")
+}
+
+func TestUnsubscribeStateChange(t *testing.T) {
+	peerID := messages.HashID("peer4")
+	mockConn := &mockRelayedConn{}
+	logger := logrus.New()
+	logger.SetOutput(&bytes.Buffer{})
+	sub := NewPeersStateSubscription(logrus.NewEntry(logger), mockConn, nil)
+
+	doneChan := make(chan struct{})
+	go func() {
+		_ = sub.WaitToBeOnlineAndSubscribe(context.Background(), peerID)
+		close(doneChan)
+	}()
+	time.Sleep(100 * time.Millisecond)
+
+	err := sub.UnsubscribeStateChange([]messages.PeerID{peerID})
+	assert.NoError(t, err)
+
+	select {
+	case <-doneChan:
+	case <-time.After(200 * time.Millisecond):
+		// Expected timeout, meaning the subscription was successfully unsubscribed
+		t.Errorf("timeout")
+	}
+}
diff --git a/relay/healthcheck/receiver_test.go b/relay/healthcheck/receiver_test.go
index 3b3e32fe..2794159f 100644
--- a/relay/healthcheck/receiver_test.go
+++ b/relay/healthcheck/receiver_test.go
@@ -4,38 +4,76 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"sync"
 	"testing"
 	"time"
 
 	log "github.com/sirupsen/logrus"
 )
 
+// Mutex to protect global variable access in tests
+var testMutex sync.Mutex
+
 func TestNewReceiver(t *testing.T) {
+	testMutex.Lock()
+	originalTimeout := heartbeatTimeout
 	heartbeatTimeout = 5 * time.Second
+	testMutex.Unlock()
+
+	defer func() {
+		testMutex.Lock()
+		heartbeatTimeout = originalTimeout
+		testMutex.Unlock()
+	}()
+
 	r := NewReceiver(log.WithContext(context.Background()))
+	defer r.Stop()
 
 	select {
 	case <-r.OnTimeout:
 		t.Error("unexpected timeout")
 	case <-time.After(1 * time.Second):
-
+		// Test passes if no timeout received
 	}
 }
 
 func TestNewReceiverNotReceive(t *testing.T) {
+	testMutex.Lock()
+	originalTimeout := heartbeatTimeout
 	heartbeatTimeout = 1 * time.Second
+	testMutex.Unlock()
+
+	defer func() {
+		testMutex.Lock()
+		heartbeatTimeout = originalTimeout
+		testMutex.Unlock()
+	}()
+
 	r := NewReceiver(log.WithContext(context.Background()))
+	defer r.Stop()
 
 	select {
 	case <-r.OnTimeout:
+		// Test passes if timeout is received
 	case <-time.After(2 * time.Second):
 		t.Error("timeout not received")
 	}
 }
 
 func TestNewReceiverAck(t *testing.T) {
+	testMutex.Lock()
+	originalTimeout := heartbeatTimeout
 	heartbeatTimeout = 2 * time.Second
+	testMutex.Unlock()
+
+	defer func() {
+		testMutex.Lock()
+		heartbeatTimeout = originalTimeout
+		testMutex.Unlock()
+	}()
+
 	r := NewReceiver(log.WithContext(context.Background()))
+	defer r.Stop()
 
 	r.Heartbeat()
 
@@ -59,13 +97,18 @@ func TestReceiverHealthCheckAttemptThreshold(t *testing.T) {
 
 	for _, tc := range testsCases {
 		t.Run(tc.name, func(t *testing.T) {
+			testMutex.Lock()
 			originalInterval := healthCheckInterval
 			originalTimeout := heartbeatTimeout
 			healthCheckInterval = 1 * time.Second
 			heartbeatTimeout = healthCheckInterval + 500*time.Millisecond
+			testMutex.Unlock()
+
 			defer func() {
+				testMutex.Lock()
 				healthCheckInterval = originalInterval
 				heartbeatTimeout = originalTimeout
+				testMutex.Unlock()
 			}()
 			//nolint:tenv
 			os.Setenv(defaultAttemptThresholdEnv, fmt.Sprintf("%d", tc.threshold))
diff --git a/relay/healthcheck/sender_test.go b/relay/healthcheck/sender_test.go
index f2116702..39d266b4 100644
--- a/relay/healthcheck/sender_test.go
+++ b/relay/healthcheck/sender_test.go
@@ -135,7 +135,11 @@ func TestSenderHealthCheckAttemptThreshold(t *testing.T) {
 			defer cancel()
 
 			sender := NewSender(log.WithField("test_name", tc.name))
-			go sender.StartHealthCheck(ctx)
+			senderExit := make(chan struct{})
+			go func() {
+				sender.StartHealthCheck(ctx)
+				close(senderExit)
+			}()
 
 			go func() {
 				responded := false
@@ -169,6 +173,11 @@ func TestSenderHealthCheckAttemptThreshold(t *testing.T) {
 				t.Fatalf("should have timed out before %s", testTimeout)
 			}
 
+			select {
+			case <-senderExit:
+			case <-time.After(2 * time.Second):
+				t.Fatalf("sender did not exit in time")
+			}
 		})
 	}
 
diff --git a/relay/messages/id_test.go b/relay/messages/id_test.go
deleted file mode 100644
index 271a8f90..00000000
--- a/relay/messages/id_test.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package messages
-
-import (
-	"testing"
-)
-
-func TestHashID(t *testing.T) {
-	hashedID, hashedStringId := HashID("alice")
-	enc := HashIDToString(hashedID)
-	if enc != hashedStringId {
-		t.Errorf("expected %s, got %s", hashedStringId, enc)
-	}
-}
diff --git a/relay/messages/message_test.go b/relay/messages/message_test.go
index 19bede07..59a89cad 100644
--- a/relay/messages/message_test.go
+++ b/relay/messages/message_test.go
@@ -5,7 +5,7 @@ import (
 )
 
 func TestMarshalHelloMsg(t *testing.T) {
-	peerID := []byte("abdFAaBcawquEiCMzAabYosuUaGLtSNhKxz+")
+	peerID := HashID("abdFAaBcawquEiCMzAabYosuUaGLtSNhKxz+")
 	msg, err := MarshalHelloMsg(peerID, nil)
 	if err != nil {
 		t.Fatalf("error: %v", err)
@@ -24,13 +24,13 @@ func TestMarshalHelloMsg(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error: %v", err)
 	}
-	if string(receivedPeerID) != string(peerID) {
+	if receivedPeerID.String() != peerID.String() {
 		t.Errorf("expected %s, got %s", peerID, receivedPeerID)
 	}
 }
 
 func TestMarshalAuthMsg(t *testing.T) {
-	peerID := []byte("abdFAaBcawquEiCMzAabYosuUaGLtSNhKxz+")
+	peerID := HashID("abdFAaBcawquEiCMzAabYosuUaGLtSNhKxz+")
 	msg, err := MarshalAuthMsg(peerID, []byte{})
 	if err != nil {
 		t.Fatalf("error: %v", err)
@@ -49,7 +49,7 @@ func TestMarshalAuthMsg(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error: %v", err)
 	}
-	if string(receivedPeerID) != string(peerID) {
+	if receivedPeerID.String() != peerID.String() {
 		t.Errorf("expected %s, got %s", peerID, receivedPeerID)
 	}
 }
@@ -80,7 +80,7 @@ func TestMarshalAuthResponse(t *testing.T) {
 }
 
 func TestMarshalTransportMsg(t *testing.T) {
-	peerID := []byte("abdFAaBcawquEiCMzAabYosuUaGLtSNhKxz+")
+	peerID := HashID("abdFAaBcawquEiCMzAabYosuUaGLtSNhKxz+")
 	payload := []byte("payload")
 	msg, err := MarshalTransportMsg(peerID, payload)
 	if err != nil {
@@ -101,7 +101,7 @@ func TestMarshalTransportMsg(t *testing.T) {
 		t.Fatalf("failed to unmarshal transport id: %v", err)
 	}
 
-	if string(uPeerID) != string(peerID) {
+	if uPeerID.String() != peerID.String() {
 		t.Errorf("expected %s, got %s", peerID, uPeerID)
 	}
 
@@ -110,8 +110,8 @@ func TestMarshalTransportMsg(t *testing.T) {
 		t.Fatalf("error: %v", err)
 	}
 
-	if string(id) != string(peerID) {
-		t.Errorf("expected %s, got %s", peerID, id)
+	if id.String() != peerID.String() {
+		t.Errorf("expected: '%s', got: '%s'", peerID, id)
 	}
 
 	if string(respPayload) != string(payload) {
diff --git a/relay/messages/peer_state_test.go b/relay/messages/peer_state_test.go
new file mode 100644
index 00000000..9e366da5
--- /dev/null
+++ b/relay/messages/peer_state_test.go
@@ -0,0 +1,144 @@
+package messages
+
+import (
+	"bytes"
+	"testing"
+)
+
+const (
+	testPeerCount = 10
+)
+
+// Helper function to generate test PeerIDs
+func generateTestPeerIDs(n int) []PeerID {
+	ids := make([]PeerID, n)
+	for i := 0; i < n; i++ {
+		for j := 0; j < peerIDSize; j++ {
+			ids[i][j] = byte(i + j)
+		}
+	}
+	return ids
+}
+
+// Helper function to compare slices of PeerID
+func peerIDEqual(a, b []PeerID) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i := range a {
+		if !bytes.Equal(a[i][:], b[i][:]) {
+			return false
+		}
+	}
+	return true
+}
+
+func TestMarshalUnmarshalSubPeerState(t *testing.T) {
+	ids := generateTestPeerIDs(testPeerCount)
+
+	msgs, err := MarshalSubPeerStateMsg(ids)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	var allIDs []PeerID
+	for _, msg := range msgs {
+		decoded, err := UnmarshalSubPeerStateMsg(msg)
+		if err != nil {
+			t.Fatalf("unmarshal failed: %v", err)
+		}
+		allIDs = append(allIDs, decoded...)
+	}
+
+	if !peerIDEqual(ids, allIDs) {
+		t.Errorf("expected %v, got %v", ids, allIDs)
+	}
+}
+
+func TestMarshalSubPeerState_EmptyInput(t *testing.T) {
+	_, err := MarshalSubPeerStateMsg([]PeerID{})
+	if err == nil {
+		t.Errorf("expected error for empty input")
+	}
+}
+
+func TestUnmarshalSubPeerState_Invalid(t *testing.T) {
+	// Too short
+	_, err := UnmarshalSubPeerStateMsg([]byte{1})
+	if err == nil {
+		t.Errorf("expected error for short input")
+	}
+
+	// Misaligned length
+	buf := make([]byte, sizeOfProtoHeader+1)
+	_, err = UnmarshalSubPeerStateMsg(buf)
+	if err == nil {
+		t.Errorf("expected error for misaligned input")
+	}
+}
+
+func TestMarshalUnmarshalPeersOnline(t *testing.T) {
+	ids := generateTestPeerIDs(testPeerCount)
+
+	msgs, err := MarshalPeersOnline(ids)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	var allIDs []PeerID
+	for _, msg := range msgs {
+		decoded, err := UnmarshalPeersOnlineMsg(msg)
+		if err != nil {
+			t.Fatalf("unmarshal failed: %v", err)
+		}
+		allIDs = append(allIDs, decoded...)
+	}
+
+	if !peerIDEqual(ids, allIDs) {
+		t.Errorf("expected %v, got %v", ids, allIDs)
+	}
+}
+
+func TestMarshalPeersOnline_EmptyInput(t *testing.T) {
+	_, err := MarshalPeersOnline([]PeerID{})
+	if err == nil {
+		t.Errorf("expected error for empty input")
+	}
+}
+
+func TestUnmarshalPeersOnline_Invalid(t *testing.T) {
+	_, err := UnmarshalPeersOnlineMsg([]byte{1})
+	if err == nil {
+		t.Errorf("expected error for short input")
+	}
+}
+
+func TestMarshalUnmarshalPeersWentOffline(t *testing.T) {
+	ids := generateTestPeerIDs(testPeerCount)
+
+	msgs, err := MarshalPeersWentOffline(ids)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	var allIDs []PeerID
+	for _, msg := range msgs {
+		// MarshalPeersWentOffline shares no unmarshal function, so reuse PeersOnline
+		decoded, err := UnmarshalPeersOnlineMsg(msg)
+		if err != nil {
+			t.Fatalf("unmarshal failed: %v", err)
+		}
+		allIDs = append(allIDs, decoded...)
+	}
+
+	if !peerIDEqual(ids, allIDs) {
+		t.Errorf("expected %v, got %v", ids, allIDs)
+	}
+}
+
+func TestMarshalPeersWentOffline_EmptyInput(t *testing.T) {
+	_, err := MarshalPeersWentOffline([]PeerID{})
+	if err == nil {
+		t.Errorf("expected error for empty input")
+	}
+}
diff --git a/relay/server/store/store_test.go b/relay/server/store/store_test.go
new file mode 100644
index 00000000..ad549a62
--- /dev/null
+++ b/relay/server/store/store_test.go
@@ -0,0 +1,49 @@
+package store
+
+import (
+	"testing"
+
+	"github.com/netbirdio/netbird/relay/messages"
+)
+
+type MocPeer struct {
+	id messages.PeerID
+}
+
+func (m *MocPeer) Close() {
+
+}
+
+func (m *MocPeer) ID() messages.PeerID {
+	return m.id
+}
+
+func TestStore_DeletePeer(t *testing.T) {
+	s := NewStore()
+
+	pID := messages.HashID("peer_one")
+	p := &MocPeer{id: pID}
+	s.AddPeer(p)
+	s.DeletePeer(p)
+	if _, ok := s.Peer(pID); ok {
+		t.Errorf("peer was not deleted")
+	}
+}
+
+func TestStore_DeleteDeprecatedPeer(t *testing.T) {
+	s := NewStore()
+
+	pID1 := messages.HashID("peer_one")
+	pID2 := messages.HashID("peer_one")
+
+	p1 := &MocPeer{id: pID1}
+	p2 := &MocPeer{id: pID2}
+
+	s.AddPeer(p1)
+	s.AddPeer(p2)
+	s.DeletePeer(p1)
+
+	if _, ok := s.Peer(pID2); !ok {
+		t.Errorf("second peer was deleted")
+	}
+}
diff --git a/relay/server/store_test.go b/relay/server/store_test.go
deleted file mode 100644
index 41c7baa9..00000000
--- a/relay/server/store_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package server
-
-import (
-	"context"
-	"net"
-	"testing"
-	"time"
-
-	"go.opentelemetry.io/otel"
-
-	"github.com/netbirdio/netbird/relay/metrics"
-)
-
-type mockConn struct {
-}
-
-func (m mockConn) Read(b []byte) (n int, err error) {
-	//TODO implement me
-	panic("implement me")
-}
-
-func (m mockConn) Write(b []byte) (n int, err error) {
-	//TODO implement me
-	panic("implement me")
-}
-
-func (m mockConn) Close() error {
-	return nil
-}
-
-func (m mockConn) LocalAddr() net.Addr {
-	//TODO implement me
-	panic("implement me")
-}
-
-func (m mockConn) RemoteAddr() net.Addr {
-	//TODO implement me
-	panic("implement me")
-}
-
-func (m mockConn) SetDeadline(t time.Time) error {
-	//TODO implement me
-	panic("implement me")
-}
-
-func (m mockConn) SetReadDeadline(t time.Time) error {
-	//TODO implement me
-	panic("implement me")
-}
-
-func (m mockConn) SetWriteDeadline(t time.Time) error {
-	//TODO implement me
-	panic("implement me")
-}
-
-func TestStore_DeletePeer(t *testing.T) {
-	s := NewStore()
-
-	m, _ := metrics.NewMetrics(context.Background(), otel.Meter(""))
-
-	p := NewPeer(m, []byte("peer_one"), nil, nil)
-	s.AddPeer(p)
-	s.DeletePeer(p)
-	if _, ok := s.Peer(p.String()); ok {
-		t.Errorf("peer was not deleted")
-	}
-}
-
-func TestStore_DeleteDeprecatedPeer(t *testing.T) {
-	s := NewStore()
-
-	m, _ := metrics.NewMetrics(context.Background(), otel.Meter(""))
-
-	conn := &mockConn{}
-	p1 := NewPeer(m, []byte("peer_id"), conn, nil)
-	p2 := NewPeer(m, []byte("peer_id"), conn, nil)
-
-	s.AddPeer(p1)
-	s.AddPeer(p2)
-	s.DeletePeer(p1)
-
-	if _, ok := s.Peer(p2.String()); !ok {
-		t.Errorf("second peer was deleted")
-	}
-}
diff --git a/relay/test/benchmark_test.go b/relay/test/benchmark_test.go
index ec2aa488..2e67ab80 100644
--- a/relay/test/benchmark_test.go
+++ b/relay/test/benchmark_test.go
@@ -12,7 +12,6 @@ import (
 
 	"github.com/pion/logging"
 	"github.com/pion/turn/v3"
-	"go.opentelemetry.io/otel"
 
 	"github.com/netbirdio/netbird/relay/auth/allow"
 	"github.com/netbirdio/netbird/relay/auth/hmac"
@@ -22,7 +21,6 @@ import (
 )
 
 var (
-	av             = &allow.Auth{}
 	hmacTokenStore = &hmac.TokenStore{}
 	pairs          = []int{1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100}
 	dataSize       = 1024 * 1024 * 10
@@ -70,8 +68,12 @@ func transfer(t *testing.T, testData []byte, peerPairs int) {
 	port := 35000 + peerPairs
 	serverAddress := fmt.Sprintf("127.0.0.1:%d", port)
 	serverConnURL := fmt.Sprintf("rel://%s", serverAddress)
-
-	srv, err := server.NewServer(otel.Meter(""), serverConnURL, false, av)
+	serverCfg := server.Config{
+		ExposedAddress: serverConnURL,
+		TLSSupport:     false,
+		AuthValidator:  &allow.Auth{},
+	}
+	srv, err := server.NewServer(serverCfg)
 	if err != nil {
 		t.Fatalf("failed to create server: %s", err)
 	}
@@ -98,8 +100,8 @@ func transfer(t *testing.T, testData []byte, peerPairs int) {
 
 	clientsSender := make([]*client.Client, peerPairs)
 	for i := 0; i < cap(clientsSender); i++ {
-		c := client.NewClient(ctx, serverConnURL, hmacTokenStore, "sender-"+fmt.Sprint(i))
-		err := c.Connect()
+		c := client.NewClient(serverConnURL, hmacTokenStore, "sender-"+fmt.Sprint(i))
+		err := c.Connect(ctx)
 		if err != nil {
 			t.Fatalf("failed to connect to server: %s", err)
 		}
@@ -108,8 +110,8 @@ func transfer(t *testing.T, testData []byte, peerPairs int) {
 
 	clientsReceiver := make([]*client.Client, peerPairs)
 	for i := 0; i < cap(clientsReceiver); i++ {
-		c := client.NewClient(ctx, serverConnURL, hmacTokenStore, "receiver-"+fmt.Sprint(i))
-		err := c.Connect()
+		c := client.NewClient(serverConnURL, hmacTokenStore, "receiver-"+fmt.Sprint(i))
+		err := c.Connect(ctx)
 		if err != nil {
 			t.Fatalf("failed to connect to server: %s", err)
 		}
@@ -119,13 +121,13 @@ func transfer(t *testing.T, testData []byte, peerPairs int) {
 	connsSender := make([]net.Conn, 0, peerPairs)
 	connsReceiver := make([]net.Conn, 0, peerPairs)
 	for i := 0; i < len(clientsSender); i++ {
-		conn, err := clientsSender[i].OpenConn("receiver-" + fmt.Sprint(i))
+		conn, err := clientsSender[i].OpenConn(ctx, "receiver-"+fmt.Sprint(i))
 		if err != nil {
 			t.Fatalf("failed to bind channel: %s", err)
 		}
 		connsSender = append(connsSender, conn)
 
-		conn, err = clientsReceiver[i].OpenConn("sender-" + fmt.Sprint(i))
+		conn, err = clientsReceiver[i].OpenConn(ctx, "sender-"+fmt.Sprint(i))
 		if err != nil {
 			t.Fatalf("failed to bind channel: %s", err)
 		}
diff --git a/relay/testec2/relay.go b/relay/testec2/relay.go
index 93d08438..9e22a80e 100644
--- a/relay/testec2/relay.go
+++ b/relay/testec2/relay.go
@@ -70,8 +70,8 @@ func prepareConnsSender(serverConnURL string, peerPairs int) []net.Conn {
 	ctx := context.Background()
 	clientsSender := make([]*client.Client, peerPairs)
 	for i := 0; i < cap(clientsSender); i++ {
-		c := client.NewClient(ctx, serverConnURL, hmacTokenStore, "sender-"+fmt.Sprint(i))
-		if err := c.Connect(); err != nil {
+		c := client.NewClient(serverConnURL, hmacTokenStore, "sender-"+fmt.Sprint(i))
+		if err := c.Connect(ctx); err != nil {
 			log.Fatalf("failed to connect to server: %s", err)
 		}
 		clientsSender[i] = c
@@ -79,7 +79,7 @@ func prepareConnsSender(serverConnURL string, peerPairs int) []net.Conn {
 
 	connsSender := make([]net.Conn, 0, peerPairs)
 	for i := 0; i < len(clientsSender); i++ {
-		conn, err := clientsSender[i].OpenConn("receiver-" + fmt.Sprint(i))
+		conn, err := clientsSender[i].OpenConn(ctx, "receiver-"+fmt.Sprint(i))
 		if err != nil {
 			log.Fatalf("failed to bind channel: %s", err)
 		}
@@ -156,8 +156,8 @@ func runReader(conn net.Conn) time.Duration {
 func prepareConnsReceiver(serverConnURL string, peerPairs int) []net.Conn {
 	clientsReceiver := make([]*client.Client, peerPairs)
 	for i := 0; i < cap(clientsReceiver); i++ {
-		c := client.NewClient(context.Background(), serverConnURL, hmacTokenStore, "receiver-"+fmt.Sprint(i))
-		err := c.Connect()
+		c := client.NewClient(serverConnURL, hmacTokenStore, "receiver-"+fmt.Sprint(i))
+		err := c.Connect(context.Background())
 		if err != nil {
 			log.Fatalf("failed to connect to server: %s", err)
 		}
@@ -166,7 +166,7 @@ func prepareConnsReceiver(serverConnURL string, peerPairs int) []net.Conn {
 
 	connsReceiver := make([]net.Conn, 0, peerPairs)
 	for i := 0; i < len(clientsReceiver); i++ {
-		conn, err := clientsReceiver[i].OpenConn("sender-" + fmt.Sprint(i))
+		conn, err := clientsReceiver[i].OpenConn(context.Background(), "sender-"+fmt.Sprint(i))
 		if err != nil {
 			log.Fatalf("failed to bind channel: %s", err)
 		}