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

  1. 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
  2. If errors cluster right after redirect changes, treat as the known swap race - upgrading the agent usually reduces it
  3. 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

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


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/f0d57b32ce816586. Report an issue: GitHub.