-
-
Notifications
You must be signed in to change notification settings - Fork 957
[client] Add non-root ICMP support to userspace firewall forwarder #4792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis PR restructures ICMP and UDP packet handling in the forwarder module. It introduces atomic operations for endpoint MTU management, adds raw ICMP socket capability detection with a ping-based fallback, implements asynchronous Echo request processing, modifies UDP handler return semantics, updates network binding to support generic receivers, adds logging utilities, and upgrades dependencies. Changes
Sequence DiagramssequenceDiagram
autonumber
participant Client
participant Forwarder
participant ICMP as ICMP Handler
participant RawSocket as Raw Socket Path
participant PingCmd as Ping Command Path
participant Stack as Netstack
Client->>Forwarder: Packet arrives (Echo request)
Forwarder->>ICMP: handleICMP(id, pkt)
alt Echo Request
ICMP->>ICMP: handleICMPEcho()
alt hasRawICMPAccess == true
ICMP->>RawSocket: handleICMPViaSocket()
RawSocket->>RawSocket: forwardICMPPacket() create raw socket
RawSocket->>RawSocket: Send ICMP payload
RawSocket->>RawSocket: handleEchoResponse() receive reply
RawSocket->>ICMP: injectICMPReply()
ICMP->>Stack: Inject synthesized reply
else hasRawICMPAccess == false
ICMP->>PingCmd: handleICMPViaPing()
PingCmd->>PingCmd: buildPingCommand() construct OS command
PingCmd->>PingCmd: Execute ping, capture RTT
PingCmd->>PingCmd: synthesizeEchoReply()
PingCmd->>ICMP: injectICMPReply()
ICMP->>Stack: Inject synthesized reply
end
else Non-Echo Type
ICMP->>RawSocket: forwardICMPPacket() if raw access available
alt Success
RawSocket-->>ICMP: PacketConn returned
else Fallback
ICMP->>Stack: Forward or drop
end
end
ICMP-->>Client: Reply or forwarded packet
sequenceDiagram
autonumber
participant UDP as UDP Handler
participant Dial as Dial Flow
participant Endpoint as Endpoint
participant Proxy as Proxy Loop
UDP->>Dial: handleUDP(request)
alt Context cancelled
Dial-->>UDP: return false
else New connection
Dial->>Dial: Set endpoint MTU (atomic.Store)
Dial->>Endpoint: gonet.NewUDPConn() without stack param
alt Success
Endpoint-->>Dial: Connection created
Dial-->>UDP: return true
UDP->>Proxy: Spawn proxy goroutine
else Error
Dial-->>UDP: return false
end
else Existing connection (race)
Dial-->>UDP: return true
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas requiring extra attention:
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (1)📓 Common learnings⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
🔇 Additional comments (3)
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. Comment |
81ce68d to
a7a5407
Compare
a7a5407 to
e7d5cdc
Compare
There was a problem hiding this 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
🧹 Nitpick comments (1)
client/firewall/uspfilter/forwarder/icmp.go (1)
57-65: Consider adding panic recovery to the goroutine.If
handleICMPViaSocketorhandleICMPViaPingpanic, the goroutine will crash silently. Consider adding a deferred panic recovery handler to log any unexpected panics.Apply this diff to add panic recovery:
go func() { defer func() { <-f.pingSemaphore }() + defer func() { + if r := recover(); r != nil { + f.logger.Error1("forwarder: Panic in ICMP echo handler: %v", r) + } + }() if f.hasRawICMPAccess { f.handleICMPViaSocket(flowID, id, icmpHdr, icmpData, rxBytes)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
client/firewall/uspfilter/forwarder/icmp.go(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: lixmal
Repo: netbirdio/netbird PR: 4777
File: client/firewall/iptables/acl_linux.go:439-450
Timestamp: 2025-11-13T15:19:32.799Z
Learning: In the netbird client firewall iptables implementation (client/firewall/iptables/), IPv6 is not currently handled or supported.
🧬 Code graph analysis (1)
client/firewall/uspfilter/forwarder/icmp.go (1)
client/firewall/uspfilter/forwarder/forwarder.go (2)
Forwarder(35-49)New(51-140)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (32)
- GitHub Check: Management / Benchmark (amd64, postgres)
- GitHub Check: Management / Benchmark (amd64, sqlite)
- GitHub Check: Signal / Unit (amd64)
- GitHub Check: Relay / Unit (386)
- GitHub Check: Signal / Unit (386)
- GitHub Check: Management / Benchmark (API) (amd64, postgres)
- GitHub Check: Relay / Unit (amd64, -race)
- GitHub Check: Management / Unit (amd64, sqlite)
- GitHub Check: Management / Integration (amd64, sqlite)
- GitHub Check: Management / Benchmark (API) (amd64, sqlite)
- GitHub Check: Management / Integration (amd64, postgres)
- GitHub Check: Management / Unit (amd64, postgres)
- GitHub Check: Management / Unit (amd64, mysql)
- GitHub Check: Client / Unit (386)
- GitHub Check: Client / Unit (amd64)
- GitHub Check: Client (Docker) / Unit
- GitHub Check: test-docker-compose (mysql)
- GitHub Check: test-docker-compose (postgres)
- GitHub Check: test-docker-compose (sqlite)
- GitHub Check: test-getting-started-script
- GitHub Check: Client / Unit
- GitHub Check: Windows
- GitHub Check: Linux
- GitHub Check: Darwin
- GitHub Check: release_ui
- GitHub Check: release_ui_darwin
- GitHub Check: release
- GitHub Check: iOS / Build
- GitHub Check: Client / Unit
- GitHub Check: JS / Lint
- GitHub Check: Android / Build
- GitHub Check: Client / Unit
🔇 Additional comments (8)
client/firewall/uspfilter/forwarder/icmp.go (8)
5-9: LGTM! Necessary imports for ping fallback.The new imports support the ping binary fallback mechanism and platform-specific command construction.
20-48: LGTM! Clean separation of echo and non-echo ICMP handling.The refactored logic properly delegates echo requests to asynchronous handling and forwards non-echo ICMP types when raw socket access is available.
73-99: LGTM! Proper resource management.The function correctly creates a raw ICMP socket, sends the packet, and handles errors appropriately. The caller is responsible for closing the returned connection as documented.
125-141: LGTM! Proper timeout and buffer sizing.The function correctly sets a read deadline and sizes the response buffer using the atomic MTU value.
233-245: LGTM! Correct ICMP echo reply synthesis.The function properly creates an echo reply by copying the data, changing the type, and recalculating the checksum following the standard ICMP checksum algorithm.
247-272: LGTM! Proper IP header construction and injection.The function correctly constructs an IPv4 header, wraps the ICMP payload, and bypasses the netstack to avoid re-processing by the ICMP handler. The TTL value of 64 is appropriate for local responses.
195-196: TheWarn4logger method exists and is properly implemented.Verification confirms that
Warn4has been added to the Logger type atclient/firewall/uspfilter/log/log.go:171with the correct signature supporting four arguments. The method call at line 195-196 is valid.
212-231: The original review comment's reasoning is fundamentally incorrect.The review claims
-tis TTL on macOS/BSD, but macOS uses-t <timeout>to specify timeout in seconds and FreeBSD uses-t <timeout>for timeout in seconds. The current code is correct for both platforms.However, there IS a real issue: OpenBSD uses
-w <maxwait>, not-t(which is TTL on OpenBSD), and NetBSD uses-w <deadline>for timeout. The current code incorrectly groups all three BSD variants together on line 225, which fails for OpenBSD and NetBSD.The correct fix requires splitting BSD platforms:
- FreeBSD: keep
-t- OpenBSD: change to
-w- NetBSD: change to
-wThe original review's suggested fix is partially correct by accident but based on false premises.
Likely an incorrect or invalid review comment.
There was a problem hiding this 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
♻️ Duplicate comments (2)
client/firewall/uspfilter/forwarder/icmp.go (2)
101-123: Send TypeEnd event on error to complete flow tracking.When
forwardICMPPacketfails at line 107, the function returns early without sending aTypeEndevent. This leaves the flow incomplete with only aTypeStartevent.Apply this diff to send an end event on error:
conn, err := f.forwardICMPPacket(id, payload, icmpHdr, 5*time.Second) if err != nil { f.logger.Error2("forwarder: Failed to send ICMP packet for %v: %v", epID(id), err) + f.sendICMPEvent(nftypes.TypeEnd, flowID, id, uint8(icmpHdr.Type()), uint8(icmpHdr.Code()), uint64(rxBytes), 0) return }
184-210: Send TypeEnd event on error to complete flow tracking.When the ping binary fails at line 194, the function returns without sending a
TypeEndevent, leaving the flow incomplete.Apply this diff to send an end event on error:
pingStart := time.Now() if err := cmd.Run(); err != nil { f.logger.Warn4("forwarder: Ping binary failed for %v type %v code %v: %v", epID(id), icmpHdr.Type(), icmpHdr.Code(), err) + f.sendICMPEvent(nftypes.TypeEnd, flowID, id, uint8(icmpHdr.Type()), uint8(icmpHdr.Code()), uint64(rxBytes), 0) return }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
client/firewall/uspfilter/forwarder/icmp.go(4 hunks)client/firewall/uspfilter/forwarder/udp.go(8 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: lixmal
Repo: netbirdio/netbird PR: 4777
File: client/firewall/iptables/acl_linux.go:439-450
Timestamp: 2025-11-13T15:19:32.799Z
Learning: In the netbird client firewall iptables implementation (client/firewall/iptables/), IPv6 is not currently handled or supported.
🧬 Code graph analysis (2)
client/firewall/uspfilter/forwarder/udp.go (1)
client/firewall/uspfilter/forwarder/forwarder.go (1)
Forwarder(35-49)
client/firewall/uspfilter/forwarder/icmp.go (1)
client/firewall/uspfilter/forwarder/forwarder.go (2)
Forwarder(35-49)New(51-140)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (30)
- GitHub Check: Client / Unit (amd64)
- GitHub Check: Relay / Unit (386)
- GitHub Check: Management / Benchmark (API) (amd64, postgres)
- GitHub Check: Management / Unit (amd64, mysql)
- GitHub Check: Management / Benchmark (API) (amd64, sqlite)
- GitHub Check: Relay / Unit (amd64, -race)
- GitHub Check: Management / Integration (amd64, postgres)
- GitHub Check: Management / Integration (amd64, sqlite)
- GitHub Check: Management / Unit (amd64, sqlite)
- GitHub Check: Signal / Unit (amd64)
- GitHub Check: Management / Benchmark (amd64, sqlite)
- GitHub Check: Management / Unit (amd64, postgres)
- GitHub Check: Management / Benchmark (amd64, postgres)
- GitHub Check: Signal / Unit (386)
- GitHub Check: Client / Unit (386)
- GitHub Check: Client (Docker) / Unit
- GitHub Check: release_ui_darwin
- GitHub Check: release_ui
- GitHub Check: release
- GitHub Check: Client / Unit
- GitHub Check: Android / Build
- GitHub Check: test-docker-compose (sqlite)
- GitHub Check: test-docker-compose (mysql)
- GitHub Check: test-getting-started-script
- GitHub Check: test-docker-compose (postgres)
- GitHub Check: JS / Lint
- GitHub Check: Client / Unit
- GitHub Check: Darwin
- GitHub Check: Linux
- GitHub Check: Windows
🔇 Additional comments (4)
client/firewall/uspfilter/forwarder/udp.go (2)
352-354: Good addition of io.EOF to closed-error detection.Treating
io.EOFas a terminal condition inisClosedErroris appropriate, as EOF signals a closed connection and should not be logged as an unexpected error.
180-180: Signature change verified and correct.The web search confirms that
gonet.NewUDPConnin the upgraded gvisor/netstack has signaturefunc NewUDPConn(wq *waiter.Queue, ep tcpip.Endpoint) *UDPConn. The code at line 180 (gonet.NewUDPConn(&wq, ep)) correctly matches this signature, with the stack parameter properly removed as part of the netstack upgrade.client/firewall/uspfilter/forwarder/icmp.go (2)
247-272: Good implementation of ICMP reply injection.The
injectICMPReplymethod correctly:
- Constructs a proper IPv4 header with appropriate fields
- Calculates and sets the IP checksum
- Bypasses netstack to avoid looping through the ICMP handler
- Returns the injected size for flow tracking
212-231: I need to verify the flags across all platforms mentioned. The web search already revealed important information about macOS that contradicts the review comment. Let me search for the other platforms:Based on my verification, the review comment contains fundamental inaccuracies regarding the macOS ping flags.
The review comment is incorrect and should be disregarded.
Evidence from official documentation:
Linux/Android:
-Wspecifies time to wait for a response in seconds, with-wsetting the timeout for the entire program session. The code correctly uses-Wfor timeout.FreeBSD/OpenBSD/NetBSD:
-tspecifies a timeout in seconds before ping exits. The code correctly uses-tfor timeout on these platforms.Windows:
-wsets timeout in milliseconds. The code correctly multipliestimeoutSecby 1000 to convert to milliseconds.macOS/iOS: The review's claim is incorrect. From the earlier web search,
-t timeouton macOS is "overall timeout in seconds before ping exits regardless of packets received." The code correctly uses-twith timeout in seconds. The review mistakenly claims-tis TTL and suggests using-W, but on macOS,-Wuses milliseconds, not seconds, which would be inconsistent with the code's handling of seconds.The platform-specific ping flags in the code are correct and consistent with official documentation across all platforms.
Likely an incorrect or invalid review comment.
|



Describe your changes
Depends on netbirdio/wireguard-go#12
Issue ticket number and link
Stack
Checklist
Documentation
Select exactly one:
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
Release Notes
New Features
Dependencies
✏️ Tip: You can customize this high-level summary in your review settings.