{"record":{"id":"a822eda2372c62a9","repo":"fatedier/frp","slug":"udp-packet-codec-q-requires-wire-protocol-v2-a822ed","errorCode":null,"errorMessage":"UDP packet codec %q requires wire protocol v2","messagePattern":"UDP packet codec %q requires wire protocol v2","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service.go","lineNumber":777,"sourceCode":"\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tgo svr.handleConnection(ctx, netpkg.QuicStreamToNetConn(stream, frpConn), false)\n\t\t\t}\n\t\t}(context.Background(), c)\n\t}\n}\n\nfunc (svr *Service) RegisterControl(\n\tctlConn *msg.Conn,\n\tloginMsg *msg.Login,\n\tinternal bool,\n\twireProtocol string,\n\tudpPacketCodec string,\n) (*Control, error) {\n\tswitch wireProtocol {\n\tcase wire.ProtocolV1:\n\t\tif udpPacketCodec != \"\" {\n\t\t\treturn nil, fmt.Errorf(\"UDP packet codec %q requires wire protocol v2\", udpPacketCodec)\n\t\t}\n\tcase wire.ProtocolV2:\n\t\tif udpPacketCodec != \"\" && udpPacketCodec != wire.UDPPacketCodecBinary {\n\t\t\treturn nil, fmt.Errorf(\"unsupported UDP packet codec selection: %s\", udpPacketCodec)\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported wire protocol: %s\", wireProtocol)\n\t}\n\t// If client's RunID is empty, it's a new client, we just create a new controller.\n\t// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.\n\tvar err error\n\tif loginMsg.RunID == \"\" {\n\t\tloginMsg.RunID, err = util.RandID()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif err := validation.ValidateRunID(loginMsg.RunID); err != nil {","sourceCodeStart":759,"sourceCodeEnd":795,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/server/service.go#L759-L795","documentation":"RegisterControl validates transport options at login: with wire protocol v1, a non-empty udpPacketCodec is rejected because UDP packet codecs only exist in the v2 protocol. The client asked for a codec feature its declared protocol cannot carry, so the control connection is refused.","triggerScenarios":"Client config sets a udpPacketCodec option while transport protocol is unset/v1; enabling a codec knob copied from a v2 example onto a v1 client; tooling that writes the codec field unconditionally.","commonSituations":"Adopting the new UDP codec feature on an older client config that still defaults to protocol v1; partial config migration.","solutions":["Set the client transport protocol to v2 (transport.protocol) so the codec is honored","Or remove/comment out the udpPacketCodec setting to stay on v1","Confirm frps also supports v2 before switching protocols"],"exampleFix":"# before\n[transport]\nprotocol = \"v1\"\nudpPacketCodec = \"binary\"\n\n# after\n[transport]\nprotocol = \"v2\"\nudpPacketCodec = \"binary\"","handlingStrategy":"validation","validationCode":"// Validate client transport config before connecting.\nif cfg.UDPPacketCodec != \"\" && cfg.Protocol != \"v2\" {\n    return fmt.Errorf(\"udpPacketCodec requires transport.protocol = v2\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Config-schema lint: udpPacketCodec is only legal together with protocol v2","Default new deployments to matching protocol settings on both ends","Fail fast at config load rather than at server login"],"tags":["frpc","config","udp","wire-protocol","validation"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}