From 4a101599aca0125fc36047f2cb604cc695a52316 Mon Sep 17 00:00:00 2001 From: mlsmaycon Date: Mon, 6 Apr 2026 10:29:25 +0200 Subject: [PATCH 1/4] Add support for configuring custom DNS hosts on iOS --- client/internal/connect.go | 2 ++ client/internal/dns/host_ios.go | 3 ++- client/internal/dns/server.go | 9 ++++++++- client/internal/engine.go | 2 +- client/ios/NetBirdSDK/client.go | 6 +++++- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/client/internal/connect.go b/client/internal/connect.go index 1e8f87c0872..6ba98184ae7 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -111,6 +111,7 @@ func (c *ConnectClient) RunOniOS( fileDescriptor int32, networkChangeListener listener.NetworkChangeListener, dnsManager dns.IosDnsManager, + dnsAddresses []netip.AddrPort, stateFilePath string, ) error { // Set GC percent to 5% to reduce memory usage as iOS only allows 50MB of memory for the extension. @@ -120,6 +121,7 @@ func (c *ConnectClient) RunOniOS( FileDescriptor: fileDescriptor, NetworkChangeListener: networkChangeListener, DnsManager: dnsManager, + HostDNSAddresses: dnsAddresses, StateFilePath: stateFilePath, } return c.run(mobileDependency, nil, "") diff --git a/client/internal/dns/host_ios.go b/client/internal/dns/host_ios.go index 1c0ac63e9b4..ebb9d754ea7 100644 --- a/client/internal/dns/host_ios.go +++ b/client/internal/dns/host_ios.go @@ -26,7 +26,8 @@ func (a iosHostManager) applyDNSConfig(config HostDNSConfig, _ *statemanager.Man return fmt.Errorf("marshal: %w", err) } jsonString := string(jsonData) - log.Debugf("Applying DNS settings: %s", jsonString) + log.Infof("iOS applyDNSConfig: routeAll=%v serverIP=%s domains=%d", config.RouteAll, config.ServerIP, len(config.Domains)) + log.Debugf("iOS applyDNSConfig: %s", jsonString) a.dnsManager.ApplyDns(jsonString) return nil } diff --git a/client/internal/dns/server.go b/client/internal/dns/server.go index 3c47f4ee639..d424c495422 100644 --- a/client/internal/dns/server.go +++ b/client/internal/dns/server.go @@ -186,11 +186,17 @@ func NewDefaultServerIos( ctx context.Context, wgInterface WGIface, iosDnsManager IosDnsManager, + hostsDnsList []netip.AddrPort, statusRecorder *peer.Status, disableSys bool, ) *DefaultServer { + log.Infof("iOS host dns address list is: %v", hostsDnsList) ds := newDefaultServer(ctx, wgInterface, NewServiceViaMemory(wgInterface), statusRecorder, nil, disableSys) ds.iosDnsManager = iosDnsManager + ds.hostsDNSHolder.set(hostsDnsList) + ds.permanent = true + ds.addHostRootZone() + log.Infof("iOS DNS server initialized: permanent=%v, hostDNS=%v", ds.permanent, hostsDnsList) return ds } @@ -1033,9 +1039,10 @@ func (s *DefaultServer) upstreamCallbacks( func (s *DefaultServer) addHostRootZone() { hostDNSServers := s.hostsDNSHolder.get() if len(hostDNSServers) == 0 { - log.Debug("no host DNS servers available, skipping root zone handler creation") + log.Info("no host DNS servers available, skipping root zone handler creation") return } + log.Infof("creating root zone handler with host DNS servers: %v", maps.Keys(hostDNSServers)) handler, err := newUpstreamResolver( s.ctx, diff --git a/client/internal/engine.go b/client/internal/engine.go index 0f09ee36416..1f1926b7c93 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -1800,7 +1800,7 @@ func (e *Engine) newDnsServer(dnsConfig *nbdns.Config) (dns.Server, error) { return dnsServer, nil case "ios": - dnsServer := dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager, e.statusRecorder, e.config.DisableDNS) + dnsServer := dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager, e.mobileDep.HostDNSAddresses, e.statusRecorder, e.config.DisableDNS) return dnsServer, nil default: diff --git a/client/ios/NetBirdSDK/client.go b/client/ios/NetBirdSDK/client.go index 3e2da7f4e9f..7ba0f92f40d 100644 --- a/client/ios/NetBirdSDK/client.go +++ b/client/ios/NetBirdSDK/client.go @@ -161,7 +161,11 @@ func (c *Client) Run(fd int32, interfaceName string, envList *EnvList) error { cfg.WgIface = interfaceName c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder) - return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, c.stateFile) + hostDNS := []netip.AddrPort{ + netip.MustParseAddrPort("1.1.1.1:53"), + netip.MustParseAddrPort("1.0.0.1:53"), + } + return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, hostDNS, c.stateFile) } // Stop the internal client and free the resources From a0b26e4197cd945e619037a5fe022798485dcbd7 Mon Sep 17 00:00:00 2001 From: mlsmaycon Date: Mon, 6 Apr 2026 18:02:15 +0200 Subject: [PATCH 2/4] Add detailed logging for iOS DNS and route management --- client/internal/dns/service_memory.go | 5 +++++ client/internal/routemanager/notifier/notifier_ios.go | 10 +++++++++- .../internal/routemanager/systemops/systemops_ios.go | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/client/internal/dns/service_memory.go b/client/internal/dns/service_memory.go index 6ef0ab5268f..9be5046abde 100644 --- a/client/internal/dns/service_memory.go +++ b/client/internal/dns/service_memory.go @@ -113,6 +113,11 @@ func (s *ServiceViaMemory) filterDNSTraffic() (string, error) { return true } + if len(msg.Question) > 0 { + log.Infof("iOS DNS hook: received query for %s (type=%d) dst=%s:%d", + msg.Question[0].Name, msg.Question[0].Qtype, s.runtimeIP, s.runtimePort) + } + writer := responseWriter{ packet: packet, device: s.wgInterface.GetDevice().Device, diff --git a/client/internal/routemanager/notifier/notifier_ios.go b/client/internal/routemanager/notifier/notifier_ios.go index bb125cfa4e4..0fc72051ab9 100644 --- a/client/internal/routemanager/notifier/notifier_ios.go +++ b/client/internal/routemanager/notifier/notifier_ios.go @@ -9,6 +9,8 @@ import ( "strings" "sync" + log "github.com/sirupsen/logrus" + "github.com/netbirdio/netbird/client/internal/listener" "github.com/netbirdio/netbird/route" ) @@ -47,9 +49,11 @@ func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) { sort.Strings(newNets) if slices.Equal(n.currentPrefixes, newNets) { + log.Debugf("iOS notifier: prefixes unchanged (%d), skipping", len(newNets)) return } + log.Infof("iOS notifier: prefixes changed: %v -> %v", n.currentPrefixes, newNets) n.currentPrefixes = newNets n.notify() } @@ -58,11 +62,15 @@ func (n *Notifier) notify() { n.listenerMux.Lock() defer n.listenerMux.Unlock() if n.listener == nil { + log.Warn("iOS notifier: listener is nil, cannot notify Swift") return } + routes := strings.Join(n.addIPv6RangeIfNeeded(n.currentPrefixes), ",") + log.Infof("iOS notifier: calling OnNetworkChanged with: %s", routes) go func(l listener.NetworkChangeListener) { - l.OnNetworkChanged(strings.Join(n.addIPv6RangeIfNeeded(n.currentPrefixes), ",")) + l.OnNetworkChanged(routes) + log.Infof("iOS notifier: OnNetworkChanged returned") }(n.listener) } diff --git a/client/internal/routemanager/systemops/systemops_ios.go b/client/internal/routemanager/systemops/systemops_ios.go index 99a363371ac..f48c3f8ebf7 100644 --- a/client/internal/routemanager/systemops/systemops_ios.go +++ b/client/internal/routemanager/systemops/systemops_ios.go @@ -42,6 +42,7 @@ func (r *SysOps) RemoveVPNRoute(prefix netip.Prefix, _ *net.Interface) error { defer r.mu.Unlock() delete(r.prefixes, prefix) + log.Infof("iOS RemoveVPNRoute: removed %s, remaining %d prefixes", prefix, len(r.prefixes)) r.notify() return nil } @@ -51,6 +52,7 @@ func (r *SysOps) notify() { for prefix := range r.prefixes { prefixes = append(prefixes, prefix) } + log.Infof("iOS notify: sending %d prefixes to Swift", len(prefixes)) r.notifier.OnNewPrefixes(prefixes) } From 63857b1b09f2b3a47f0f7bc70397838311faff35 Mon Sep 17 00:00:00 2001 From: mlsmaycon Date: Mon, 6 Apr 2026 22:21:51 +0200 Subject: [PATCH 3/4] remove debug logs --- client/internal/dns/host_ios.go | 3 +-- client/internal/dns/server.go | 6 ++---- client/internal/dns/service_memory.go | 5 ----- client/internal/routemanager/notifier/notifier_ios.go | 11 +---------- .../internal/routemanager/systemops/systemops_ios.go | 2 -- 5 files changed, 4 insertions(+), 23 deletions(-) diff --git a/client/internal/dns/host_ios.go b/client/internal/dns/host_ios.go index ebb9d754ea7..1c0ac63e9b4 100644 --- a/client/internal/dns/host_ios.go +++ b/client/internal/dns/host_ios.go @@ -26,8 +26,7 @@ func (a iosHostManager) applyDNSConfig(config HostDNSConfig, _ *statemanager.Man return fmt.Errorf("marshal: %w", err) } jsonString := string(jsonData) - log.Infof("iOS applyDNSConfig: routeAll=%v serverIP=%s domains=%d", config.RouteAll, config.ServerIP, len(config.Domains)) - log.Debugf("iOS applyDNSConfig: %s", jsonString) + log.Debugf("Applying DNS settings: %s", jsonString) a.dnsManager.ApplyDns(jsonString) return nil } diff --git a/client/internal/dns/server.go b/client/internal/dns/server.go index d424c495422..567cd401cef 100644 --- a/client/internal/dns/server.go +++ b/client/internal/dns/server.go @@ -190,13 +190,12 @@ func NewDefaultServerIos( statusRecorder *peer.Status, disableSys bool, ) *DefaultServer { - log.Infof("iOS host dns address list is: %v", hostsDnsList) + log.Debugf("iOS host dns address list is: %v", hostsDnsList) ds := newDefaultServer(ctx, wgInterface, NewServiceViaMemory(wgInterface), statusRecorder, nil, disableSys) ds.iosDnsManager = iosDnsManager ds.hostsDNSHolder.set(hostsDnsList) ds.permanent = true ds.addHostRootZone() - log.Infof("iOS DNS server initialized: permanent=%v, hostDNS=%v", ds.permanent, hostsDnsList) return ds } @@ -1039,10 +1038,9 @@ func (s *DefaultServer) upstreamCallbacks( func (s *DefaultServer) addHostRootZone() { hostDNSServers := s.hostsDNSHolder.get() if len(hostDNSServers) == 0 { - log.Info("no host DNS servers available, skipping root zone handler creation") + log.Debug("no host DNS servers available, skipping root zone handler creation") return } - log.Infof("creating root zone handler with host DNS servers: %v", maps.Keys(hostDNSServers)) handler, err := newUpstreamResolver( s.ctx, diff --git a/client/internal/dns/service_memory.go b/client/internal/dns/service_memory.go index 9be5046abde..6ef0ab5268f 100644 --- a/client/internal/dns/service_memory.go +++ b/client/internal/dns/service_memory.go @@ -113,11 +113,6 @@ func (s *ServiceViaMemory) filterDNSTraffic() (string, error) { return true } - if len(msg.Question) > 0 { - log.Infof("iOS DNS hook: received query for %s (type=%d) dst=%s:%d", - msg.Question[0].Name, msg.Question[0].Qtype, s.runtimeIP, s.runtimePort) - } - writer := responseWriter{ packet: packet, device: s.wgInterface.GetDevice().Device, diff --git a/client/internal/routemanager/notifier/notifier_ios.go b/client/internal/routemanager/notifier/notifier_ios.go index 0fc72051ab9..343d2799e27 100644 --- a/client/internal/routemanager/notifier/notifier_ios.go +++ b/client/internal/routemanager/notifier/notifier_ios.go @@ -9,8 +9,6 @@ import ( "strings" "sync" - log "github.com/sirupsen/logrus" - "github.com/netbirdio/netbird/client/internal/listener" "github.com/netbirdio/netbird/route" ) @@ -49,28 +47,21 @@ func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) { sort.Strings(newNets) if slices.Equal(n.currentPrefixes, newNets) { - log.Debugf("iOS notifier: prefixes unchanged (%d), skipping", len(newNets)) return } - log.Infof("iOS notifier: prefixes changed: %v -> %v", n.currentPrefixes, newNets) n.currentPrefixes = newNets n.notify() } - func (n *Notifier) notify() { n.listenerMux.Lock() defer n.listenerMux.Unlock() if n.listener == nil { - log.Warn("iOS notifier: listener is nil, cannot notify Swift") return } - routes := strings.Join(n.addIPv6RangeIfNeeded(n.currentPrefixes), ",") - log.Infof("iOS notifier: calling OnNetworkChanged with: %s", routes) go func(l listener.NetworkChangeListener) { - l.OnNetworkChanged(routes) - log.Infof("iOS notifier: OnNetworkChanged returned") + l.OnNetworkChanged(strings.Join(n.addIPv6RangeIfNeeded(n.currentPrefixes), ",")) }(n.listener) } diff --git a/client/internal/routemanager/systemops/systemops_ios.go b/client/internal/routemanager/systemops/systemops_ios.go index f48c3f8ebf7..99a363371ac 100644 --- a/client/internal/routemanager/systemops/systemops_ios.go +++ b/client/internal/routemanager/systemops/systemops_ios.go @@ -42,7 +42,6 @@ func (r *SysOps) RemoveVPNRoute(prefix netip.Prefix, _ *net.Interface) error { defer r.mu.Unlock() delete(r.prefixes, prefix) - log.Infof("iOS RemoveVPNRoute: removed %s, remaining %d prefixes", prefix, len(r.prefixes)) r.notify() return nil } @@ -52,7 +51,6 @@ func (r *SysOps) notify() { for prefix := range r.prefixes { prefixes = append(prefixes, prefix) } - log.Infof("iOS notify: sending %d prefixes to Swift", len(prefixes)) r.notifier.OnNewPrefixes(prefixes) } From f08e445fb114204475dedf7802b46144b04f0954 Mon Sep 17 00:00:00 2001 From: mlsmaycon Date: Tue, 7 Apr 2026 18:38:32 +0200 Subject: [PATCH 4/4] Update iOS client DNS servers to Quad9 for improved privacy --- client/ios/NetBirdSDK/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ios/NetBirdSDK/client.go b/client/ios/NetBirdSDK/client.go index 7ba0f92f40d..04367390459 100644 --- a/client/ios/NetBirdSDK/client.go +++ b/client/ios/NetBirdSDK/client.go @@ -162,8 +162,8 @@ func (c *Client) Run(fd int32, interfaceName string, envList *EnvList) error { c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder) hostDNS := []netip.AddrPort{ - netip.MustParseAddrPort("1.1.1.1:53"), - netip.MustParseAddrPort("1.0.0.1:53"), + netip.MustParseAddrPort("9.9.9.9:53"), + netip.MustParseAddrPort("149.112.112.112:53"), } return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, hostDNS, c.stateFile) }