Repository navigation
Kernel WireGuard: after a relay → direct IPv6 handover, a handshake response leaves with source ::1 and the remote peer drops it (wgproxy loopback, 0.82.0) #8473
Replies: 1 comment
|
Adding an independent sample of the same symptom from our 2026-11-04 AT01 run ( The retained captures show a WireGuard type-2 response with an IPv6 loopback (
This corrects our earlier inference from the zero WireGuard counter: the response did reach the member's underlay ingress. The evidence locates the remaining gap after member underlay ingress and before WireGuard receive accounting. We have no packet-specific kernel drop trace for this sample; the counter increment does not prove which packet caused it, and these records do not prove the source-selection mechanism. The retrospective extraction was checked against the original capture byte counts and SHA-256 receipts. The original uncounted outcome is unchanged. Successful initial transfers in a later run do not establish that this issue is fixed. Evidence, pinned to commit |
Uh oh!
There was an error while loading. Please reload this page.
Before posting
Affected area
Peer connectivity, Relay / Signal / NAT traversal, Client / Agent
Deployment type
Self-hosted - advanced/custom deployment
Operating system or environment
Linux, Docker
NetBird version and upgrade status
netbirdio/netbird:0.82.0@sha256:eca5b274c635bfbb04ec6724a0712d1146190dd450eea199eeb83916fdd94965)netbirdio/netbird-server:0.82.0@sha256:7b5b53b2dc8bc25639f791cf72d47cf86911894ff3f8a97cb79bab9be958109c)main(ad8c1cc9).Did this work before?
Not sure
Regression details
Summary
On Linux with kernel WireGuard, a peer connection can hand over from the relay to a direct IPv6 path. After that handover, peer B can send a WireGuard handshake response with IPv6 source address
::1out of its real interface. Peer A's kernel discards that packet as a header error: a loopback source on a non-loopback interface (RFC 4291 §2.5.3). So the response never reaches WireGuard.Our reading of the code:
RedirectAs(ep)keeps injecting packets that still arrive over the relay into the local WireGuard. Each one has the direct endpoint as its source and::1as its destination.::1.Current behavior
This comes from one fully captured run (attempt
cacad79c, 2026-11-01, 09:33 UTC). Packet numbers are positions in each peer's capture. Peer A is the "member" and peer B the "node" in the linked analysis.::1as destination. The WireGuard message bytes match.::1, and leaves peer B's underlay interface (eth2).eth1)./proc/net/snmp6andnstat -asz):Ip6InHdrErrorsgoes from 0 to 1 over the request.Ip6InAddrErrors,Udp6InErrorsandUdp6InCsumErrorsstay at 0.Ip6InDiscardsis unchanged.::1. Peer B's endpoint for peer A became peer A's IPv6 address, port 51820.The visible signs are the
::1-sourced packet on the wire and the remote peer's IPv6 header-error counter.Expected behavior
No packet with a loopback source address leaves a non-loopback interface. WireGuard's replies to a peer on the direct path carry a source address that is valid for the outgoing interface, so the remote peer accepts them.
Steps to reproduce
nstat -az Ip6InHdrErrorson the receiving peer before and after.For intermittent issues:
::1response within its capture window.af718646).cacad79c, the::1response came 200 ms after data was already flowing over the relay session (09:33:52.903737Z against 09:33:52.703797Z).Environment and topology
Self-hosted details, if available
netbird-serverimage (digest above).Logs, status output, or debug evidence
Raw pcaps, nftables traces and debug logs are kept private. The linked analysis has the header-level facts, counters and hashes.
Related issues or discussions
client/iface/wgproxy/loopback), including the::1destination for injected IPv6 packets.Impact
cacad79cthe request still succeeded, because an earlier session was carrying traffic.af718646, the::1-sourced response (12:50:50.714 UTC) was lost the same way, and both kernel handshakes stayed at 0. A later direct IPv6 exchange with a normal source (12:50:56.181) completed the handshake. The request, with a 5 s deadline, timed out: its first packet reached peer B at 5.671 s. This fits WireGuard's handshake retry after 5 s, but we have not proven that link.NB_DISABLE_KERNEL_WG_PROXY=trueselects the UDP proxy. Its raw IPv6 headers also use destination::1(client/iface/wgproxy/udp/rawsocket.go), so it probably does not help.NB_WG_KERNEL_DISABLED=true(userspace WireGuard with the bind proxy) should avoid this injection path. We have not tested it.127.0.0.1).Additional context
NetBird code (v0.82.0 at
3c00a978; the same lines are unchanged onmainatad8c1cc9):loopback/wrapper.go#L27defineslocalHostNetIPv6 = net.ParseIP("::1").NewPacketHeadersuses it as the IPv6 destination, with the redirect endpoint as the source.peer/conn.go#L472-L486: when ICE is ready,ConfigureWGEndpoint(ep)runs, thenwgProxyRelay.RedirectAs(ep).udp/rawsocket.go#L119-L121: the UDP proxy builds the same IPv6 header shape.Linux code (v7.0 at
028ef9c9):wireguard/socket.c#L251: WireGuard stores a received packet's destination as the endpoint's reply source (src6).#L133-L135:send6clears that source only whenipv6_chk_addr()fails.::1normally passes, because it is assigned tolo.net/ipv6/ip6_input.c#L209-L219: the receiver drops loopback-source packets on non-loopback interfaces beforeNF_INET_PRE_ROUTING, and counts them inIp6InHdrErrors(#L295-L296).This is a reading of the source that is consistent with our counters and traces. We did not capture a kernel drop tracepoint.
A possible direction, for you to judge: once the redirect source is a real remote address, inject relayed IPv6 traffic with a destination WireGuard can validly reply from, instead of
::1. Or otherwise keep::1from becoming the reply source.Our public write-up, with the per-step evidence and hashes:
cacad79canalysis;af718646re-grant run.All reactions