{"record":{"id":"5cb8b800f9789dd4","repo":"netbirdio/netbird","slug":"failed-to-read-udp-packet-from-wg-w","errorCode":null,"errorMessage":"failed to read UDP packet from WG: %w","messagePattern":"failed to read UDP packet from WG: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"client/iface/wgproxy/ebpf/proxy.go","lineNumber":186,"sourceCode":"\n// proxyToRemote read messages from local WireGuard interface and forward it to remote conn\n// From this go routine has only one instance.\nfunc (p *WGEBPFProxy) proxyToRemote() {\n\tbuf := make([]byte, p.mtu+bufsize.WGBufferOverhead)\n\tfor p.ctx.Err() == nil {\n\t\tif err := p.readAndForwardPacket(buf); err != nil {\n\t\t\tif p.ctx.Err() != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlog.Errorf(\"failed to proxy packet to remote conn: %s\", err)\n\t\t}\n\t}\n}\n\nfunc (p *WGEBPFProxy) readAndForwardPacket(buf []byte) error {\n\tn, addr, err := p.conn.ReadFromUDP(buf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read UDP packet from WG: %w\", err)\n\t}\n\n\tp.turnConnMutex.Lock()\n\tconn, ok := p.turnConnStore[uint16(addr.Port)]\n\tp.turnConnMutex.Unlock()\n\tif !ok {\n\t\tif p.ctx.Err() == nil {\n\t\t\tlog.Debugf(\"turn conn not found by port because conn already has been closed: %d\", addr.Port)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif _, err := conn.Write(buf[:n]); err != nil {\n\t\treturn fmt.Errorf(\"failed to forward local WG packet (%d) to remote turn conn: %w\", addr.Port, err)\n\t}\n\treturn nil\n}\n","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/iface/wgproxy/ebpf/proxy.go#L168-L204","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := p.readAndForwardPacket(buf); err != nil {\n    if p.ctx.Err() != nil {\n        return // normal shutdown, not an error\n    }\n    var netErr net.Error\n    if errors.As(err, &netErr) {\n        log.Debugf(\"proxy socket closed: %v\", err)\n        return\n    }\n    log.Errorf(\"proxy read failed: %v\", err)\n}","preventionTips":["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"],"tags":["go","netbird","ebpf","udp","shutdown-race","linux"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}