netbirdio/netbird · warning
failed to read UDP packet from WG: %w
Error message
failed to read UDP packet from WG: %w
What it means
Returned inside WGEBPFProxy.readAndForwardPacket when conn.ReadFromUDP on the eBPF proxy's loopback UDP socket fails. The socket is the one end of the eBPF remapping: the kernel program rewrites WireGuard egress packets to proxyPort on 127.0.0.1, and this goroutine forwards them to the matching TURN connection. The proxyToRemote loop logs it as 'failed to proxy packet to remote conn' only when the context is still alive, so seeing the message means the socket died outside a normal Free() shutdown.
Source
Thrown at client/iface/wgproxy/ebpf/proxy.go:186
// proxyToRemote read messages from local WireGuard interface and forward it to remote conn
// From this go routine has only one instance.
func (p *WGEBPFProxy) proxyToRemote() {
buf := make([]byte, p.mtu+bufsize.WGBufferOverhead)
for p.ctx.Err() == nil {
if err := p.readAndForwardPacket(buf); err != nil {
if p.ctx.Err() != nil {
return
}
log.Errorf("failed to proxy packet to remote conn: %s", err)
}
}
}
func (p *WGEBPFProxy) readAndForwardPacket(buf []byte) error {
n, addr, err := p.conn.ReadFromUDP(buf)
if err != nil {
return fmt.Errorf("failed to read UDP packet from WG: %w", err)
}
p.turnConnMutex.Lock()
conn, ok := p.turnConnStore[uint16(addr.Port)]
p.turnConnMutex.Unlock()
if !ok {
if p.ctx.Err() == nil {
log.Debugf("turn conn not found by port because conn already has been closed: %d", addr.Port)
}
return nil
}
if _, err := conn.Write(buf[:n]); err != nil {
return fmt.Errorf("failed to forward local WG packet (%d) to remote turn conn: %w", addr.Port, err)
}
return nil
}
View on GitHub (pinned to 93e97f4bf1)
Solutions
- If it appears once at shutdown (netbird down / reconnect), ignore it - it is the known close-vs-read race
- If persistent, capture the wrapped errno: EBADF points to a double close, ENOMEM to socket buffer pressure
- Restart the agent (netbird down && netbird up) to re-load the eBPF program and re-bind the proxy socket
- Check for a NetBird version where the Free() ordering was fixed if the message spams during normal reconnects
Defensive patterns
Strategy: try-catch
Try / catch
if err := p.readAndForwardPacket(buf); err != nil {
if p.ctx.Err() != nil {
return // normal shutdown, not an error
}
var netErr net.Error
if errors.As(err, &netErr) {
log.Debugf("proxy socket closed: %v", err)
return
}
log.Errorf("proxy read failed: %v", err)
} Prevention
- Always shut the proxy down via Free()/cancel so the ctx.Err() suppression path applies
- Treat one occurrence at disconnect as expected; only escalate when it repeats with the context alive
- Capture the wrapped errno (EBADF/ENOMEM) to distinguish races from real socket damage
When it happens
Trigger: The UDP socket's fd being closed outside Free() (double Free races, fd table corruption), an unrecoverable socket error (EBADF, ENOMEM), or Free() closing the conn before ctxCancel is observed (the loop suppresses the ctx-cancelled case, so this appears only in a race window or genuinely broken fd).
Common situations: Usually shutdown noise when Free() runs concurrently with the read loop; occasionally a real fd/sockets leak after heavy churn of TURN connections. Persistent recurrence means the eBPF proxy path is dead and relayed traffic stops flowing.
Related errors
- failed to bind free port for eBPF proxy
- proxy_protocol is not supported for UDP services
- add IP to ipset: %w
- create ipset: %w
- failed to check rule: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/5cb8b800f9789dd4.
Report an issue: GitHub.