Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import (
"github.com/netbirdio/netbird/client/firewall"
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/iface"
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
"github.com/netbirdio/netbird/client/iface/device"
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
"github.com/netbirdio/netbird/client/iface/udpmux"
"github.com/netbirdio/netbird/client/internal/acl"
"github.com/netbirdio/netbird/client/internal/debug"
Expand Down
5 changes: 5 additions & 0 deletions client/internal/routemanager/dnsinterceptor/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,11 @@ func (d *DnsInterceptor) writeMsg(w dns.ResponseWriter, r *dns.Msg, logger *log.
logger.Errorf("failed to update domain prefixes: %v", err)
}

// Allow time for route changes to be applied before sending
// the DNS response (relevant on iOS where setTunnelNetworkSettings
// is asynchronous).
waitForRouteSettlement(logger)

d.replaceIPsInDNSResponse(r, newPrefixes, logger)
}
}
Expand Down
20 changes: 20 additions & 0 deletions client/internal/routemanager/dnsinterceptor/handler_ios.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build ios

package dnsinterceptor

import (
"time"

log "github.com/sirupsen/logrus"
)

const routeSettleDelay = 500 * time.Millisecond

// waitForRouteSettlement introduces a short delay on iOS to allow
// setTunnelNetworkSettings to apply route changes before the DNS
// response reaches the application. Without this, the first request
// to a newly resolved domain may bypass the tunnel.
func waitForRouteSettlement(logger *log.Entry) {
logger.Tracef("waiting %v for iOS route settlement", routeSettleDelay)
time.Sleep(routeSettleDelay)
}
12 changes: 12 additions & 0 deletions client/internal/routemanager/dnsinterceptor/handler_nonios.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build !ios

package dnsinterceptor

import log "github.com/sirupsen/logrus"

func waitForRouteSettlement(_ *log.Entry) {
// No-op on non-iOS platforms: route changes are applied synchronously by
// the kernel, so no settlement delay is needed before the DNS response
// reaches the application. The delay is only required on iOS where
// setTunnelNetworkSettings applies routes asynchronously.
}
Comment on lines +7 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for handler_*.go files in dnsinterceptor
echo "=== Handler files in dnsinterceptor ==="
fd -g 'handler_*.go' client/internal/routemanager/dnsinterceptor/

# Check for android references in dnsinterceptor
echo -e "\n=== Android references in dnsinterceptor ==="
rg -i 'android' client/internal/routemanager/dnsinterceptor/

# Check handler.go for build constraints and waitForRouteSettlement calls
echo -e "\n=== Build constraints and relevant code in handler.go ==="
rg -n '//go:build|//\+build|waitForRouteSettlement' client/internal/routemanager/dnsinterceptor/handler.go

# Check build tags in handler_nonios.go
echo -e "\n=== Build tags in handler_nonios.go ==="
head -10 client/internal/routemanager/dnsinterceptor/handler_nonios.go

# Look for Android target support at repo level
echo -e "\n=== Android GOOS references in root/build files ==="
fd -g 'Makefile|*.mk|*.sh|go.mod|go.sum' | head -5 | xargs rg -l 'android' 2>/dev/null || echo "No android refs in common build files"

# Check if there's any reference to Android vpn service or Android-specific route handling elsewhere
echo -e "\n=== Android VpnService or Android route handling in codebase ==="
rg -i 'vpnservice|android.*route|route.*android' client/ | head -20

Repository: netbirdio/netbird

Length of output: 6892


🏁 Script executed:

#!/bin/bash
# Check the iOS implementation
echo "=== handler_ios.go implementation ==="
cat -n client/internal/routemanager/dnsinterceptor/handler_ios.go

# Check context around line 357 in handler.go to see if there's Android gating
echo -e "\n=== Context around waitForRouteSettlement call in handler.go (line 357) ==="
sed -n '350,365p' client/internal/routemanager/dnsinterceptor/handler.go

# Check if there's any condition checking for android before calling waitForRouteSettlement
echo -e "\n=== Full context for waitForRouteSettlement usage ==="
rg -B5 -A5 'waitForRouteSettlement' client/internal/routemanager/dnsinterceptor/handler.go

# Check if there's Android-specific route settlement elsewhere
echo -e "\n=== Android route settlement or similar in Android-specific files ==="
rg -i 'settlement|delay.*route' client/android/ client/internal/routemanager/systemops/systemops_android.go client/internal/routemanager/notifier/notifier_android.go 2>/dev/null

Repository: netbirdio/netbird

Length of output: 1784


Android also executes handler_nonios.go's no-op — add Android-specific route settlement delay or verify it's not needed.

Android is a supported platform in NetBird, and because no handler_android.go exists, the //go:build !ios constraint on handler_nonios.go causes Android to use the no-op implementation. The unconditional call to waitForRouteSettlement at line 357 in handler.go will execute the no-op on Android despite Android's VpnService.establish() also applying tunnel settings asynchronously—the same async behavior that necessitates the 500ms settlement delay on iOS. Without this delay on Android, the DNS-bypass bug this PR fixes on iOS can occur on Android as well.

Either create handler_android.go with the same sleep implementation as handler_ios.go, or confirm Android's route handling does not exhibit the same async timing issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/internal/routemanager/dnsinterceptor/handler_nonios.go` around lines 7
- 12, The no-op waitForRouteSettlement in handler_nonios.go is used on Android
due to the !ios build tag, but Android's VpnService.establish() applies routes
asynchronously like iOS; either add an Android-specific implementation or verify
it's unnecessary. Create handler_android.go that implements
waitForRouteSettlement with the same short sleep/delay logic as handler_ios.go
(so the unconditional call from handler.go still waits), or if you confirm
Android timing is safe, document that and remove the Android-specific sleep path
by updating waitForRouteSettlement behavior accordingly; reference the
waitForRouteSettlement function, handler_ios.go implementation, and the call
site in handler.go for locating the change.

Loading