{"record":{"id":"76cdcbafb745ec3c","repo":"netbirdio/netbird","slug":"proxy-not-started","errorCode":null,"errorMessage":"proxy not started","messagePattern":"proxy not started","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/iface/wgproxy/udp/proxy.go","lineNumber":164,"sourceCode":"\tp.srcFakerConn = srcFakerConn\n\tp.sendPkg = p.srcFakerConn.SendPkg\n}\n\n// InjectPacket writes b to the remote peer over the underlying transport.\nfunc (p *WGUDPProxy) InjectPacket(b []byte) error {\n\tif p.remoteConn == nil {\n\t\treturn errors.New(\"proxy not started\")\n\t}\n\tif _, err := p.remoteConn.Write(b); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// CloseConn close the localConn\nfunc (p *WGUDPProxy) CloseConn() error {\n\tif p.cancel == nil {\n\t\treturn fmt.Errorf(\"proxy not started\")\n\t}\n\treturn p.close()\n}\n\nfunc (p *WGUDPProxy) close() error {\n\tvar result *multierror.Error\n\n\tp.closeMu.Lock()\n\tdefer p.closeMu.Unlock()\n\n\t// prevent double close\n\tif p.closed {\n\t\treturn nil\n\t}\n\n\tp.closeListener.SetCloseListener(nil)\n\tp.closed = true\n","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/iface/wgproxy/udp/proxy.go#L146-L182","documentation":"Returned by WGUDPProxy.InjectPacket when remoteConn is nil, and by WGUDPProxy.CloseConn when cancel is nil. Both fields are only set by a successful AddTurnConn (it dials the local WG port and stores ctx/cancel/conns). The proxy is explicitly documented as not thread safe, and there is no constructor-time state, so this error is a lifecycle guard: the method was called before AddTurnConn ever ran or before it succeeded.","triggerScenarios":"Calling InjectPacket or CloseConn on a WGUDPProxy obtained from NewWGUDPProxy before AddTurnConn; calling them after AddTurnConn failed its dial (fields stay nil); races where a second AddTurnConn overwrites state while another goroutine proxies.","commonSituations":"Integrations/embedders (client/embed) driving the UDP proxy manually; test code exercising close paths; ICE agent code paths that tear down candidates before the proxy is fully wired. In shipped code it surfaces as a programming-order bug rather than an environment issue.","solutions":["Ensure AddTurnConn succeeded (check its error) before any InjectPacket/CloseConn/Work call on the same proxy instance","Order teardown as: stop using the conn, then CloseConn once; guard with your own started flag if multiple goroutines can trigger close","Remember NewWGUDPProxy only allocates - Work() and EndpointAddr() no-op silently until AddTurnConn populates the fields"],"exampleFix":"// before\nproxy := NewWGUDPProxy(wgPort, mtu)\n_ = proxy.InjectPacket(pkt) // \"proxy not started\"\n\n// after\nproxy := NewWGUDPProxy(wgPort, mtu)\nif err := proxy.AddTurnConn(ctx, nil, remoteConn); err != nil {\n    return fmt.Errorf(\"add turn conn: %w\", err)\n}\nif err := proxy.InjectPacket(pkt); err != nil {\n    return fmt.Errorf(\"inject: %w\", err)\n}","handlingStrategy":"validation","validationCode":"// call order contract: AddTurnConn must succeed first\nif err := proxy.AddTurnConn(ctx, nil, remoteConn); err != nil {\n    return fmt.Errorf(\"start proxy: %w\", err)\n}\n// only now inject/close\nif err := proxy.InjectPacket(pkt); err != nil { ... }","typeGuard":"// wrap the proxy to make the started state explicit for callers\ntype startedUDPProxy struct{ p *udp.WGUDPProxy }\n\nfunc startProxy(ctx context.Context, p *udp.WGUDPProxy, rc net.Conn) (*startedUDPProxy, error) {\n    if err := p.AddTurnConn(ctx, nil, rc); err != nil {\n        return nil, err\n    }\n    return &startedUDPProxy{p}, nil\n}","tryCatchPattern":"if err := proxy.InjectPacket(b); err != nil {\n    if errors.Is(err, errProxyNotStarted) || strings.Contains(err.Error(), \"proxy not started\") {\n        return fmt.Errorf(\"lifecycle bug: proxy used before AddTurnConn: %w\", err)\n    }\n    return err\n}","preventionTips":["Treat NewWGUDPProxy as allocation only; every other method requires a successful AddTurnConn","Do not share one WGUDPProxy across goroutines - the type is documented not thread safe","In teardown paths, guard CloseConn so it only runs for proxies that were actually started"],"tags":["go","netbird","udp-proxy","lifecycle","api-misuse"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}