netbirdio/netbird · error
failed to forward local WG packet (%d) to remote turn conn:
Error message
failed to forward local WG packet (%d) to remote turn conn: %w
What it means
Returned by WGEBPFProxy.readAndForwardPacket when writing a locally captured WireGuard packet to the TURN (relay) connection registered for that source port fails. The turnConnStore maps an allocated local port to a net.Conn; the write is the last hop of the path local WG -> eBPF redirect -> proxy socket -> relay. Failure means that particular relay connection is broken or closed while still registered.
Source
Thrown at client/iface/wgproxy/ebpf/proxy.go:200
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
}
func (p *WGEBPFProxy) storeTurnConn(turnConn net.Conn) (uint16, error) {
p.turnConnMutex.Lock()
defer p.turnConnMutex.Unlock()
np, err := p.nextFreePort()
if err != nil {
return np, err
}
p.turnConnStore[np] = turnConn
return np, nil
}
func (p *WGEBPFProxy) removeTurnConn(turnConnID uint16) {
p.turnConnMutex.Lock()View on GitHub (pinned to 93e97f4bf1)
Solutions
- Check relay availability and TLS/port reachability from the host to the configured relay endpoints
- Confirm the agent reconnects: the ICE/relay layer should notice the dead conn, call removeTurnConn and re-establish; if the error loops forever, restart the agent
- Verify agent clock/certificate health if relay auth is failing (the underlying %w error carries the cause)
- Upgrade agent if an old build kept stale conns in turnConnStore after relay failover
Defensive patterns
Strategy: try-catch
Try / catch
if _, err := conn.Write(buf); err != nil {
var netErr net.Error
if errors.As(err, &netErr) || errors.Is(err, net.ErrClosed) {
p.removeTurnConn(port)
// signal the ICE/relay layer to re-establish this relay conn
p.onConnDead(port)
return nil // drop packet, path is being rebuilt
}
return fmt.Errorf("forward: %w", err)
} Prevention
- Monitor relay endpoint health and prefer direct connections when ICE succeeds
- Remove TURN conns from the store the moment they are closed, not lazily on write failure
- Alert on sustained relay write failures - they mean relayed peers are silently losing traffic
When it happens
Trigger: Relay (TURN) server dropped the connection or restarted; NAT middlebox tore down the mapping; the peer closed its side; or the conn was closed locally but removeTurnConn has not run yet, so a packet still arrives for the stale port and Write hits a closed conn.
Common situations: Relay restarts or failover; long-lived relayed sessions through aggressive NATs; the moment a relayed peer disconnects while its queued packets are still being drained. Logged via log.Errorf in a loop, so it can repeat until the conn is removed.
Related errors
- proxy_protocol is not supported for UDP services
- set network layer for UDP checksum: %w
- address %s is part of the NetBird network %s, refusing to wr
- failed to bind free port for eBPF proxy
- failed to read UDP packet from WG: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/612d8bde4470f700.
Report an issue: GitHub.