Repository: netbirdio/netbird

Issue:
DNS Resolution Fails when HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient Exists on Windows 11
**Describe the problem**

DNS Resolution Fails when HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient Exists on Windows 11

**To Reproduce**

Steps to reproduce the behavior:
1. Make sure HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient exists. This will cause the Netburd-match registry key to be created under an alternative path. 
2. Attempt DNS resolution. (Does not work for me (on version 0.36.7)
3. Deleting the HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient key and restarting the netbird service resolve DNS resolution errors.

**Expected behavior**

I should be able to resolve .wan and other match domains configured in netbird.

**Are you using NetBird Cloud?**

I am using the self-hosted version.

**NetBird version**

`0.36.7`



Hopefully this helps others who encounter this issue and the wonderful folks who work on this project make improvements. 




Hints from issue comments:
@OGDeguy: Okay I modified `client/internal/dns/host_windows.go:27` to ``SOFTWARE\Policies\Microsoft\Windows NT\DNSClientttt`` just to confirm and then recompiled the client. I no longer have any DNS resolution issues on my custom compiled client. Will be distributing this internally for now as a temporary work around.@rihards-simanovics: I can confirm that `0.36.7` fails to resolve the nodes' DNS on Windows 11 ent. The temporary workaround without recompiling with a fix is to just use IP for the node. I use RDP with an FQDN to the node, so I noticed today on the way to work.@mlsmaycon: @lixmal can you check this one?@rihards-simanovics: I can't say for certain if this was the case, but on one of my Linux servers running Ubuntu 22.04.5 LTS, the DNS resolution was completely nuked; not even google.com or the server's hostname would resolve.@lixmal: Hey folks,

can you try this build https://github.com/netbirdio/netbird/actions/runs/13390166152 once it's done building?

@rihards-simanovics regarding Linux, what was your previous version running? `v0.36.7` has only touched Windows@rihards-simanovics: > Hey folks,
> 
> can you try this build https://github.com/netbirdio/netbird/actions/runs/13390166152 once it's done building?
> 
> [@rihards-simanovics](https://github.com/rihards-simanovics) regarding Linux, what was your previous version running? `v0.36.7` has only touched Windows

The update happened on the 16th during the regular maintenance window. here is the exact log entries related to netbird

![Image](https://github.com/user-attachments/assets/d44338aa-34bc-4308-b236-d61d7823c587)
and also these, it looks like I have downgraded the package back to `0.36.3` at some point:
![Image](https://github.com/user-attachments/assets/91126b74-8a54-4660-b73e-b62605086c18)@OGDeguy: > Hey folks,
> 
> can you try this build https://github.com/netbirdio/netbird/actions/runs/13390166152 once it's done building?
> 
> [@rihards-simanovics](https://github.com/rihards-simanovics) regarding Linux, what was your previous version running? `v0.36.7` has only touched Windows

Gave this a go on my system and it is working great. Thank you @lixmal 

I'll keep this open on my end until you folks decide to close the issue. @rihards-simanovics: Hey, @lixmal, I just had an email from my ISP. Apparently, they had an outage, which seems to have only affected the DNS resolution. You can disregard the earlier issue with Linux DNS.@OGDeguy: @lixmal I still seem to be getting some DNS issues on the provided build. Not sure why yet - was working fine today until I needed to reconnect. @saule1508: This issue impacts all our windows client, it happened in v0.36.7, resolution is not working. A workaround is to add a match all domain in the dns server configuration.  But even when I downgrade to v0.36.6 it is still broken now :-(

The trick to rename HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient to HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClienttt works

@saule1508: but after reboot, changing again the key to its original name HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient and with v0.36.7 it works.
when I search in the registry, the Netbird-Match is in two locations
* HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\DnsPolicyConfig\NetBird-Match
* HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Policies\Microsoft\Windows NT\DNSClient\DnsPolicyConfig\NetBird-Match

I did a few stop service/start service and it seems to work, will see with my colleagues oversea when they wake up
@saule1508: it is not working for us, after reboot the key is changed back to the original value and DNS is broken

Solution patch:
diff --git a/client/internal/dns/host_windows.go b/client/internal/dns/host_windows.go
index 0cd07847..58b0a14d 100644
--- a/client/internal/dns/host_windows.go
+++ b/client/internal/dns/host_windows.go
@@ -131,11 +131,30 @@ func (r *registryConfigurator) addDNSSetupForAll(ip string) error {
 func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) error {
 	// if the gpo key is present, we need to put our DNS settings there, otherwise our config might be ignored
 	// see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnrpt/8cc31cb9-20cb-4140-9e85-3e08703b4745
-	policyPath := dnsPolicyConfigMatchPath
 	if r.gpo {
-		policyPath = gpoDnsPolicyConfigMatchPath
+		if err := r.configureDNSPolicy(gpoDnsPolicyConfigMatchPath, domains, ip); err != nil {
+			return fmt.Errorf("configure GPO DNS policy: %w", err)
+		}
+
+		if err := r.configureDNSPolicy(dnsPolicyConfigMatchPath, domains, ip); err != nil {
+			return fmt.Errorf("configure local DNS policy: %w", err)
+		}
+
+		if err := refreshGroupPolicy(); err != nil {
+			log.Warnf("failed to refresh group policy: %v", err)
+		}
+	} else {
+		if err := r.configureDNSPolicy(dnsPolicyConfigMatchPath, domains, ip); err != nil {
+			return fmt.Errorf("configure local DNS policy: %w", err)
+		}
 	}
 
+	log.Infof("added %d match domains. Domain list: %s", len(domains), domains)
+	return nil
+}
+
+// configureDNSPolicy handles the actual configuration of a DNS policy at the specified path
+func (r *registryConfigurator) configureDNSPolicy(policyPath string, domains []string, ip string) error {
 	if err := removeRegistryKeyFromDNSPolicyConfig(policyPath); err != nil {
 		return fmt.Errorf("remove existing dns policy: %w", err)
 	}
@@ -162,13 +181,6 @@ func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) er
 		return fmt.Errorf("set %s: %w", dnsPolicyConfigConfigOptionsKey, err)
 	}
 
-	if r.gpo {
-		if err := refreshGroupPolicy(); err != nil {
-			log.Warnf("failed to refresh group policy: %v", err)
-		}
-	}
-
-	log.Infof("added %d match domains. Domain list: %s", len(domains), domains)
 	return nil
 }
 
diff --git a/management/server/geolocation/database.go b/management/server/geolocation/database.go
index 21ae93b9..97ab398f 100644
--- a/management/server/geolocation/database.go
+++ b/management/server/geolocation/database.go
@@ -123,7 +123,6 @@ func importCsvToSqlite(dataDir string, csvFile string, geonamesdbFile string) er
 	db, err := gorm.Open(sqlite.Open(path.Join(dataDir, geonamesdbFile)), &gorm.Config{
 		Logger:          logger.Default.LogMode(logger.Silent),
 		CreateBatchSize: 1000,
-		PrepareStmt:     true,
 	})
 	if err != nil {
 		return err
diff --git a/management/server/geolocation/store.go b/management/server/geolocation/store.go
index 1f94bf47..5af8276b 100644
--- a/management/server/geolocation/store.go
+++ b/management/server/geolocation/store.go
@@ -132,8 +132,7 @@ func connectDB(ctx context.Context, filePath string) (*gorm.DB, error) {
 	}
 
 	db, err := gorm.Open(sqlite.Open(storeStr), &gorm.Config{
-		Logger:      logger.Default.LogMode(logger.Silent),
-		PrepareStmt: true,
+		Logger: logger.Default.LogMode(logger.Silent),
 	})
 	if err != nil {
 		return nil, err
diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go
index 5c4ddf66..94769442 100644
--- a/management/server/store/sql_store.go
+++ b/management/server/store/sql_store.go
@@ -969,7 +969,7 @@ func NewSqliteStore(ctx context.Context, dataDir string, metrics telemetry.AppMe
 	}
 
 	file := filepath.Join(dataDir, storeStr)
-	db, err := gorm.Open(sqlite.Open(file), getGormConfig(SqliteStoreEngine))
+	db, err := gorm.Open(sqlite.Open(file), getGormConfig())
 	if err != nil {
 		return nil, err
 	}
@@ -979,7 +979,7 @@ func NewSqliteStore(ctx context.Context, dataDir string, metrics telemetry.AppMe
 
 // NewPostgresqlStore creates a new Postgres store.
 func NewPostgresqlStore(ctx context.Context, dsn string, metrics telemetry.AppMetrics) (*SqlStore, error) {
-	db, err := gorm.Open(postgres.Open(dsn), getGormConfig(PostgresStoreEngine))
+	db, err := gorm.Open(postgres.Open(dsn), getGormConfig())
 	if err != nil {
 		return nil, err
 	}
@@ -989,7 +989,7 @@ func NewPostgresqlStore(ctx context.Context, dsn string, metrics telemetry.AppMe
 
 // NewMysqlStore creates a new MySQL store.
 func NewMysqlStore(ctx context.Context, dsn string, metrics telemetry.AppMetrics) (*SqlStore, error) {
-	db, err := gorm.Open(mysql.Open(dsn+"?charset=utf8&parseTime=True&loc=Local"), getGormConfig(MysqlStoreEngine))
+	db, err := gorm.Open(mysql.Open(dsn+"?charset=utf8&parseTime=True&loc=Local"), getGormConfig())
 	if err != nil {
 		return nil, err
 	}
@@ -997,15 +997,10 @@ func NewMysqlStore(ctx context.Context, dsn string, metrics telemetry.AppMetrics
 	return NewSqlStore(ctx, db, MysqlStoreEngine, metrics)
 }
 
-func getGormConfig(engine Engine) *gorm.Config {
-	prepStmt := true
-	if engine == SqliteStoreEngine {
-		prepStmt = false
-	}
+func getGormConfig() *gorm.Config {
 	return &gorm.Config{
 		Logger:          logger.Default.LogMode(logger.Silent),
 		CreateBatchSize: 400,
-		PrepareStmt:     prepStmt,
 	}
 }
 
diff --git a/management/server/store/store.go b/management/server/store/store.go
index 6d3a409e..29ed22fa 100644
--- a/management/server/store/store.go
+++ b/management/server/store/store.go
@@ -328,7 +328,7 @@ func NewTestStoreFromSQL(ctx context.Context, filename string, dataDir string) (
 	}
 
 	file := filepath.Join(dataDir, storeStr)
-	db, err := gorm.Open(sqlite.Open(file), getGormConfig(kind))
+	db, err := gorm.Open(sqlite.Open(file), getGormConfig())
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/signal/server/signal.go b/signal/server/signal.go
index 05cc4327..3cae7e86 100644
--- a/signal/server/signal.go
+++ b/signal/server/signal.go
@@ -160,6 +160,7 @@ func (s *Server) forwardMessageToPeer(ctx context.Context, msg *proto.EncryptedM
 		s.metrics.MessageForwardFailures.Add(ctx, 1, metric.WithAttributes(attribute.String(labelType, labelTypeNotConnected)))
 		log.Debugf("message from peer [%s] can't be forwarded to peer [%s] because destination peer is not connected", msg.Key, msg.RemoteKey)
 		// todo respond to the sender?
+		return
 	}
 
 	s.metrics.GetRegistrationDelay.Record(ctx, float64(time.Since(getRegistrationStart).Nanoseconds())/1e6, metric.WithAttributes(attribute.String(labelType, labelTypeStream), attribute.String(labelRegistrationStatus, labelRegistrationFound)))



Test patch:
diff --git a/management/server/account_test.go b/management/server/account_test.go
index 0a7f9119..eb36dbd8 100644
--- a/management/server/account_test.go
+++ b/management/server/account_test.go
@@ -3018,11 +3018,11 @@ func BenchmarkSyncAndMarkPeer(b *testing.B) {
 		minMsPerOpCICD  float64
 		maxMsPerOpCICD  float64
 	}{
-		{"Small", 50, 5, 1, 5, 3, 19},
-		{"Medium", 500, 100, 7, 22, 10, 90},
-		{"Large", 5000, 200, 65, 110, 60, 240},
+		{"Small", 50, 5, 1, 5, 3, 24},
+		{"Medium", 500, 100, 7, 22, 10, 135},
+		{"Large", 5000, 200, 65, 110, 60, 320},
 		{"Small single", 50, 10, 1, 4, 3, 80},
-		{"Medium single", 500, 10, 7, 13, 10, 37},
+		{"Medium single", 500, 10, 7, 13, 10, 43},
 		{"Large 5", 5000, 15, 65, 80, 60, 220},
 	}
 
@@ -3087,8 +3087,8 @@ func BenchmarkLoginPeer_ExistingPeer(b *testing.B) {
 		maxMsPerOpCICD  float64
 	}{
 		{"Small", 50, 5, 2, 10, 3, 35},
-		{"Medium", 500, 100, 5, 40, 20, 110},
-		{"Large", 5000, 200, 60, 100, 120, 260},
+		{"Medium", 500, 100, 5, 40, 20, 140},
+		{"Large", 5000, 200, 60, 100, 120, 320},
 		{"Small single", 50, 10, 2, 10, 5, 40},
 		{"Medium single", 500, 10, 5, 40, 10, 60},
 		{"Large 5", 5000, 15, 60, 100, 60, 180},
@@ -3163,9 +3163,9 @@ func BenchmarkLoginPeer_NewPeer(b *testing.B) {
 	}{
 		{"Small", 50, 5, 7, 20, 10, 80},
 		{"Medium", 500, 100, 5, 40, 30, 140},
-		{"Large", 5000, 200, 80, 120, 140, 300},
+		{"Large", 5000, 200, 80, 120, 140, 390},
 		{"Small single", 50, 10, 7, 20, 10, 80},
-		{"Medium single", 500, 10, 5, 40, 20, 60},
+		{"Medium single", 500, 10, 5, 40, 20, 85},
 		{"Large 5", 5000, 15, 80, 120, 80, 200},
 	}
 
diff --git a/management/server/migration/migration_test.go b/management/server/migration/migration_test.go
index a645ae32..e907d685 100644
--- a/management/server/migration/migration_test.go
+++ b/management/server/migration/migration_test.go
@@ -21,9 +21,7 @@ import (
 func setupDatabase(t *testing.T) *gorm.DB {
 	t.Helper()
 
-	db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{
-		PrepareStmt: true,
-	})
+	db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
 
 	require.NoError(t, err, "Failed to open database")
 	return db