netbirdio/netbird · warning
write to raw conn: %w
Error message
write to raw conn: %w
What it means
Returned by SrcFaker.SendPkg when rawSocket.WriteTo to 127.0.0.1 (v4 faker) or ::1 (v6 faker) fails. The raw socket is bound to lo with SO_MARK and header-include; a sendto on it can still fail with ENETUNREACH/EAFNOSUPPORT when the loopback family is disabled on the host, EBADF when the socket was closed concurrently, or EPERM from security modules. The v6 path is the fragile one: choosing the IPv6 faker (source address not v4-mapped) on a host with `net.ipv6.conf.all.disable_ipv6=1` makes ::1 unreachable.
Source
Thrown at client/iface/wgproxy/udp/rawsocket.go:96
return f.rawSocket.Close()
}
func (f *SrcFaker) SendPkg(data []byte) (int, error) {
defer func() {
if err := f.layerBuffer.Clear(); err != nil {
log.Errorf("failed to clear layer buffer: %s", err)
}
}()
payload := gopacket.Payload(data)
err := gopacket.SerializeLayers(f.layerBuffer, serializeOpts, f.ipH, f.udpH, payload)
if err != nil {
return 0, fmt.Errorf("serialize layers: %w", err)
}
n, err := f.rawSocket.WriteTo(f.layerBuffer.Bytes(), f.localHostAddr)
if err != nil {
return 0, fmt.Errorf("write to raw conn: %w", err)
}
return n, nil
}
func prepareHeaders(dstPort int, srcAddr *net.UDPAddr) (gopacket.SerializableLayer, gopacket.SerializableLayer, error) {
var ipH gopacket.SerializableLayer
var networkLayer gopacket.NetworkLayer
// Check if source IP is IPv4 or IPv6
if srcAddr.IP.To4() != nil {
// IPv4
ipv4 := &layers.IPv4{
DstIP: localHostNetIPAddrV4.IP,
SrcIP: srcAddr.IP,
Version: 4,
TTL: 64,
Protocol: layers.IPProtocolUDP,
}View on GitHub (pinned to 93e97f4bf1)
Solutions
- Verify loopback for the family in use: `ping -c1 127.0.0.1` and `ping -c1 ::1`; if ::1 fails, enable IPv6 (`sysctl -w net.ipv6.conf.all.disable_ipv6=0`) or peer over v4 endpoints
- If errors cluster right after redirect changes, treat as the known swap race - upgrading the agent usually reduces it
- Check dmesg/audit for SELinux denials on the netbird process sending raw packets
Defensive patterns
Strategy: validation
Validate before calling
// before creating a v6 SrcFaker, confirm v6 loopback is usable
func ipv6LoopbackAvailable() bool {
c, err := net.Dial("udp6", "[::1]:9"): // discard port
if err != nil {
return false
}
_ = c.Close()
return true
} Type guard
func isUsableSrcAddr(a *net.UDPAddr) bool {
if a.IP.To4() != nil {
return true
}
return ipv6LoopbackAvailable()
} Try / catch
if _, err := f.rawSocket.WriteTo(b, f.localHostAddr); err != nil {
if errors.Is(err, syscall.ENETUNREACH) || errors.Is(err, syscall.EAFNOSUPPORT) {
log.Debugf("family loopback unavailable, dropping faked packet: %v", err)
return 0, nil // degrade, do not kill the proxy loop
}
return 0, fmt.Errorf("write to raw conn: %w", err)
} Prevention
- Enable IPv6 loopback (`net.ipv6.conf.all.disable_ipv6=0`) when peering over v6 endpoints
- Prefer v4 relay/direct endpoints on hosts where IPv6 is administratively disabled
- Treat single write failures during redirect swaps as transient; only act on persistent failure
When it happens
Trigger: IPv6 faker selected (peer endpoint is v6) while IPv6 is disabled kernel-side; SrcFaker closed by Work()/RedirectAs() while proxyToLocal is still inside SendPkg (the pausedCond lock narrows but does not eliminate this); SELinux/AppArmor denying raw sends.
Common situations: Hosts hardened with ipv6.disable=1 or disable_ipv6 sysctls peering over v6 management/relays; rapid direct<->relay redirects during ICE negotiation. The proxyToLocal loop logs it at debug and continues, so throughput degrades rather than dies.
Related errors
- src faker raw conn: %s
- create IPv6 firewall: %w
- init ip6tables: %w
- create v6 router: %w
- create v6 acl manager: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/f0d57b32ce816586.
Report an issue: GitHub.