Skip to content

[client] Fix DNS resolution with userspace WireGuard and kernel firewall#5873

Merged
lixmal merged 2 commits intomainfrom
fix-dns-userspace-bind-kernel-fw
Apr 13, 2026
Merged

[client] Fix DNS resolution with userspace WireGuard and kernel firewall#5873
lixmal merged 2 commits intomainfrom
fix-dns-userspace-bind-kernel-fw

Conversation

@lixmal
Copy link
Copy Markdown
Collaborator

@lixmal lixmal commented Apr 13, 2026

Describe your changes

When the client runs with userspace WireGuard but uses a kernel firewall (nftables/iptables), DNS resolution via the in-memory DNS service fails because no packet filter is installed on the WireGuard device. The ServiceViaMemory DNS service needs a device filter to intercept DNS packets, but only uspfilter calls SetFilter. After #5668 stopped wrapping native firewalls with uspfilter, the filter is never set in this configuration.

  • Add HooksFilter, a minimal packet filter that only handles outbound DNS hooks without conntrack, netflow, or MSS clamping (those are handled by the kernel firewall)
  • Install HooksFilter on the WireGuard device when userspace bind is active and native firewall succeeds
  • Extract shared hook types (PacketHook, HookMatches, SetHook) to uspfilter/common so both HooksFilter and the full Manager use the same dispatch logic
  • Skip non-IPv4 packets in HooksFilter to avoid interfering with IPv6 traffic

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (internal fix, no user-facing behavior change)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features

    • Added packet-filtering hooks to intercept outbound DNS traffic by destination IP and port.
    • On Linux, the app now attempts to install a minimal hooks-only device filter after firewall setup; if installation fails a warning is logged and startup continues.
  • Refactor

    • Centralized and simplified hook registration and matching for outbound packet hooks, improving maintainability and clarity.
  • Tests

    • Updated tests to reflect renamed hook fields used by the new hook implementation.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 72f4e0f0-f415-4df9-9821-3ca916fa9cde

📥 Commits

Reviewing files that changed from the base of the PR and between d124310 and e298009.

📒 Files selected for processing (1)
  • client/firewall/uspfilter/hooks_filter.go
✅ Files skipped from review due to trivial changes (1)
  • client/firewall/uspfilter/hooks_filter.go

📝 Walkthrough

Walkthrough

Adds a hooks-only userspace packet filter (HooksFilter) with atomic packet-hook primitives, refactors uspfilter to use the shared hook type, and attempts to install the new filter from Linux firewall initialization (installation failures are logged as warnings, non-fatal).

Changes

Cohort / File(s) Summary
Hook infrastructure
client/firewall/uspfilter/common/hooks.go
New exported PacketHook type, HookMatches to check+invoke hooks, and SetHook to atomically register/unregister hooks.
HooksFilter implementation
client/firewall/uspfilter/hooks_filter.go
New HooksFilter implementing device.PacketFilter: strict IPv4 parsing, fragment handling, dst IP/port extraction, dispatch to UDP/TCP hooks; inbound always returns false.
Filter refactor & tests
client/firewall/uspfilter/filter.go, client/firewall/uspfilter/filter_test.go
Replaced local hook type with atomic.Pointer[common.PacketHook], delegate matching to common.HookMatches and registration to common.SetHook; updated tests for exported fields.
Firewall integration
client/firewall/create_linux.go
After native creation or userspace fallback, attempt to install &uspfilter.HooksFilter{} via iface.SetFilter; failures are logged as warnings and do not alter return behavior.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant IF as Interface/Netstack
    participant HF as HooksFilter
    participant CM as common.HookMatches
    participant HK as PacketHook

    App->>IF: send outbound packet
    IF->>HF: FilterOutbound(packetData)
    HF->>HF: validate IPv4 header, compute IHL, drop fragments
    HF->>HF: extract dst IP and dst port
    HF->>CM: HookMatches(hookPtr, dstIP, dPort, packetData)
    alt Hook present & matches
        CM->>HK: invoke hook function(packetData)
        HK-->>CM: return bool (drop?)
        CM-->>HF: return decision
    else No hook or mismatch
        CM-->>HF: return false
    end
    HF-->>IF: drop or pass decision
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

Suggested Reviewers

  • pappz
  • mlsmaycon

Poem

🐇 I nudge the packets, soft and fleet,
Hooks set gently where destinations meet,
IPv4 lanes I mind and scan,
Atomic pulses guide my plan,
A rabbit watches each small beat.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the bug fix: DNS resolution failure when using userspace WireGuard with kernel firewall.
Description check ✅ Passed The description is well-structured, covers the problem, explains the root cause, details the solution, addresses the checklist, and specifies documentation is not needed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-dns-userspace-bind-kernel-fw

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lixmal lixmal force-pushed the fix-dns-userspace-bind-kernel-fw branch from 922b1dc to 5440b0c Compare April 13, 2026 13:36
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/firewall/create_linux.go`:
- Around line 62-64: The call to iface.SetFilter(&uspfilter.HooksFilter{})
currently only logs a warning and continues, leaving the device without DNS
interception; change this so SetFilter failures abort startup: in the function
that constructs/returns fm, replace the log.Warnf branch with error propagation
(returning the error or wrapping it) so that a non-nil error from
iface.SetFilter prevents returning fm; reference the SetFilter call,
uspfilter.HooksFilter, and the returned fm to locate and update the code path.

In `@client/firewall/uspfilter/hooks_filter.go`:
- Around line 46-57: Check and skip malformed or fragmented IPv4 packets before
reading L4 fields: after computing ihl (ihl := int(packetData[0]&0x0f) * 4)
ensure ihl >= 20 and that packetData has at least ihl+dstPortOffset+2 bytes, and
inspect the IPv4 flags/fragment offset (compute fragOff :=
(uint16(packetData[6]&0x1F) << 8) | uint16(packetData[7]) and MF flag via
packetData[6]&0x20) and return false if fragOff != 0 or MF is set; only then
proceed to read dstIP
(netip.AddrFromSlice(packetData[ipv4DstOffset:ipv4DstOffset+4])), proto
(packetData[ipv4ProtoOffset]) and dstPort
(binary.BigEndian.Uint16(packetData[ihl+dstPortOffset:ihl+dstPortOffset+2])) so
you never treat payload bytes as a UDP/TCP header.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fc2ea47f-166e-4d3f-b999-aca054832d4a

📥 Commits

Reviewing files that changed from the base of the PR and between 1353954 and 922b1dc.

📒 Files selected for processing (4)
  • client/firewall/create_linux.go
  • client/firewall/uspfilter/common/hooks.go
  • client/firewall/uspfilter/filter.go
  • client/firewall/uspfilter/hooks_filter.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
client/firewall/create_linux.go (1)

59-64: ⚠️ Potential issue | 🟠 Major

Do not continue when hooks filter installation fails.

If iface.SetFilter(&uspfilter.HooksFilter{}) fails, startup currently proceeds with fm, but the DNS interception path this PR adds is not active. This should fail fast (or fallback explicitly), not degrade silently.

Proposed fix
 	// Native firewall handles packet filtering, but the userspace WireGuard bind
 	// needs a device filter for DNS interception hooks. Install a minimal
 	// hooks-only filter that passes all traffic through to the kernel firewall.
 	if err := iface.SetFilter(&uspfilter.HooksFilter{}); err != nil {
-		log.Warnf("failed to set hooks filter: %v", err)
+		return nil, fmt.Errorf("set hooks filter: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/firewall/create_linux.go` around lines 59 - 64, The call to
iface.SetFilter(&uspfilter.HooksFilter{}) currently only logs a warning and
allows startup to continue, leaving the DNS interception path inactive; change
this to fail-fast by returning or propagating the error instead of continuing
with fm: replace the log.Warnf branch with code that logs an error (log.Errorf
or log.WithError) and returns the error from the surrounding function so startup
aborts (or triggers the existing failure path) when iface.SetFilter fails;
ensure the change references iface.SetFilter and uspfilter.HooksFilter and
prevents further initialization using fm when the filter installation fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@client/firewall/create_linux.go`:
- Around line 59-64: The call to iface.SetFilter(&uspfilter.HooksFilter{})
currently only logs a warning and allows startup to continue, leaving the DNS
interception path inactive; change this to fail-fast by returning or propagating
the error instead of continuing with fm: replace the log.Warnf branch with code
that logs an error (log.Errorf or log.WithError) and returns the error from the
surrounding function so startup aborts (or triggers the existing failure path)
when iface.SetFilter fails; ensure the change references iface.SetFilter and
uspfilter.HooksFilter and prevents further initialization using fm when the
filter installation fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3ceddca2-7661-438c-8a97-1370c68711de

📥 Commits

Reviewing files that changed from the base of the PR and between 922b1dc and 5440b0c.

📒 Files selected for processing (5)
  • client/firewall/create_linux.go
  • client/firewall/uspfilter/common/hooks.go
  • client/firewall/uspfilter/filter.go
  • client/firewall/uspfilter/filter_test.go
  • client/firewall/uspfilter/hooks_filter.go
✅ Files skipped from review due to trivial changes (1)
  • client/firewall/uspfilter/filter_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/firewall/uspfilter/hooks_filter.go

pappz
pappz previously approved these changes Apr 13, 2026
@lixmal lixmal force-pushed the fix-dns-userspace-bind-kernel-fw branch from 5440b0c to d124310 Compare April 13, 2026 13:47
pappz
pappz previously approved these changes Apr 13, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/firewall/uspfilter/hooks_filter.go`:
- Around line 20-21: The fragment-offset mask ipv4FragOffMask is wrong (0x1f) so
checks like flagsAndOffset&ipv4FragOffMask != 0 miss higher offset bits; update
ipv4FragOffMask to the full 13-bit mask 0x1fff and ensure the existing guard
that inspects flagsAndOffset (the fragment-check around lines with
flagsAndOffset&ipv4FragOffMask != 0) uses that constant so any non-zero IPv4
fragment offset is correctly detected and parsed as non-first fragment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 79e25a76-731f-4bf1-90aa-3f79fee953b3

📥 Commits

Reviewing files that changed from the base of the PR and between 5440b0c and d124310.

📒 Files selected for processing (5)
  • client/firewall/create_linux.go
  • client/firewall/uspfilter/common/hooks.go
  • client/firewall/uspfilter/filter.go
  • client/firewall/uspfilter/filter_test.go
  • client/firewall/uspfilter/hooks_filter.go
✅ Files skipped from review due to trivial changes (3)
  • client/firewall/create_linux.go
  • client/firewall/uspfilter/filter_test.go
  • client/firewall/uspfilter/common/hooks.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/firewall/uspfilter/filter.go

@sonarqubecloud
Copy link
Copy Markdown

@lixmal lixmal merged commit 4eed459 into main Apr 13, 2026
42 of 43 checks passed
@lixmal lixmal deleted the fix-dns-userspace-bind-kernel-fw branch April 13, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants