{"record":{"id":"42c8eaef3832c98e","repo":"fatedier/frp","slug":"missing-v2-crypto-negotiation","errorCode":null,"errorMessage":"missing v2 crypto negotiation","messagePattern":"missing v2 crypto negotiation","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/control_session.go","lineNumber":214,"sourceCode":"\t\t}\n\t\tudpPacketCodec = serverHello.Selected.Message.UDPPacketCodec\n\t}\n\n\tvar loginRespMsg msg.LoginResp\n\tif err := rw.ReadMsgInto(&loginRespMsg); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &loginExchangeResult{\n\t\tresp:           &loginRespMsg,\n\t\tcrypto:         cryptoContext,\n\t\tudpPacketCodec: udpPacketCodec,\n\t}, nil\n}\n\nfunc (d *controlSessionDialer) newControlReadWriter(conn net.Conn, cryptoContext *wire.CryptoContext) (io.ReadWriter, error) {\n\tif d.common.Transport.WireProtocol == wire.ProtocolV2 {\n\t\tif cryptoContext == nil {\n\t\t\treturn nil, errors.New(\"missing v2 crypto negotiation\")\n\t\t}\n\t\treturn netpkg.NewAEADCryptoReadWriter(\n\t\t\tconn,\n\t\t\td.auth.EncryptionKey(),\n\t\t\tnetpkg.AEADCryptoRoleClient,\n\t\t\tcryptoContext.Algorithm,\n\t\t\tcryptoContext.TranscriptHash,\n\t\t)\n\t}\n\treturn netpkg.NewCryptoReadWriter(conn, d.auth.EncryptionKey())\n}\n","sourceCodeStart":196,"sourceCodeEnd":226,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/client/control_session.go#L196-L226","documentation":"Internal invariant error in newControlReadWriter (client/control_session.go:214): the client configured transport.wireProtocol = v2, so the control connection must be wrapped with AEAD crypto derived from the hello exchange, but the cryptoContext passed in is nil. In the current flow cryptoContext is only set when the v2 branch of exchangeLogin completed the ServerHello exchange, so nil here means v2 was requested yet negotiation never produced a context.","triggerScenarios":"WireProtocol == ProtocolV2 while the code path that reads ServerHello and calls NewClientCryptoContext did not run or returned without setting the context — effectively a protocol-state mismatch between the configured wire protocol and what exchangeLogin actually negotiated.","commonSituations":"Version skew or a code change that makes the v2 hello conditional (e.g. server downgraded mid-handshake) while local config still forces v2; custom builds patching exchangeLogin; not something a config typo alone usually triggers.","solutions":["Ensure frpc and frps versions match exactly when using wireProtocol v2","Remove or correct transport.wireProtocol config so client and server agree on the protocol","If you maintain a fork, guarantee exchangeLogin's v2 branch always yields a cryptoContext before newControlReadWriter is called with ProtocolV2","Report with logs if stock binaries on identical versions hit this — it indicates a negotiation bug"],"exampleFix":"// before (fork/code path)\nrw, err := d.newControlReadWriter(conn, nil) // v2 configured -> missing v2 crypto negotiation\n\n// after\nif d.common.Transport.WireProtocol == wire.ProtocolV2 && cryptoContext == nil {\n    return nil, errors.New(\"v2 negotiated no crypto context; check server wireProtocol support\")\n}\nrw, err := d.newControlReadWriter(conn, cryptoContext)","handlingStrategy":"validation","validationCode":"if d.common.Transport.WireProtocol == wire.ProtocolV2 {\n    // verify negotiation actually happened before building the RW\n    if result.crypto == nil {\n        return nil, errors.New(\"v2 configured but server did not negotiate; align versions or unset wireProtocol\")\n    }\n}","typeGuard":null,"tryCatchPattern":"rw, err := d.newControlReadWriter(conn, result.crypto)\nif err != nil && strings.Contains(err.Error(), \"missing v2 crypto negotiation\") {\n    // invariant break: v2 requested, no context — reconnect with v1 after config fix\n    return reconnectWithV1()\n}","preventionTips":["Keep frpc/frps versions identical when wireProtocol v2 is on","In forks, assert cryptoContext != nil immediately after exchangeLogin for v2","Treat this error as a bug report candidate, not a config typo"],"tags":["go","frpc","wire-protocol","v2","invariant"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}