{"record":{"id":"baeed520930f6d37","repo":"fatedier/frp","slug":"missing-v2-crypto-negotiation-baeed5","errorCode":null,"errorMessage":"missing v2 crypto negotiation","messagePattern":"missing v2 crypto negotiation","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service.go","lineNumber":610,"sourceCode":"\treturn acceptedConn, nil\n}\n\nfunc writeWithDeadline(conn net.Conn, timeout time.Duration, writeFn func() error) error {\n\t_ = conn.SetWriteDeadline(time.Now().Add(timeout))\n\tdefer func() {\n\t\t_ = conn.SetWriteDeadline(time.Time{})\n\t}()\n\treturn writeFn()\n}\n\nfunc (ac *acceptedConnection) messageConnFor(rw io.ReadWriter) *msg.Conn {\n\treturn msg.NewConn(ac.conn, msg.NewReadWriter(rw, ac.wireProtocol))\n}\n\nfunc (ac *acceptedConnection) newControlReadWriter(rw io.ReadWriter, key []byte) (io.ReadWriter, error) {\n\tif ac.wireProtocol == wire.ProtocolV2 {\n\t\tif ac.cryptoContext == nil {\n\t\t\treturn nil, fmt.Errorf(\"missing v2 crypto negotiation\")\n\t\t}\n\t\treturn netpkg.NewAEADCryptoReadWriter(\n\t\t\trw,\n\t\t\tkey,\n\t\t\tnetpkg.AEADCryptoRoleServer,\n\t\t\tac.cryptoContext.Algorithm,\n\t\t\tac.cryptoContext.TranscriptHash,\n\t\t)\n\t}\n\treturn netpkg.NewCryptoReadWriter(rw, key)\n}\n\nfunc (ac *acceptedConnection) readFirstV2Msg(conn net.Conn, wireConn *wire.Conn) (msg.Message, error) {\n\tframe, err := wireConn.ReadFrame()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read v2 frame: %w\", err)\n\t}\n\tif frame.Type == wire.FrameTypeClientHello {","sourceCodeStart":592,"sourceCodeEnd":628,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/server/service.go#L592-L628","documentation":"When a connection negotiates wire protocol v2, newControlReadWriter requires an AEAD crypto context established during negotiation (cryptoContext). If wireProtocol is v2 but cryptoContext is nil, the server rejects creating the control read-writer with this invariant error: v2's encryption layer was never set up for this connection.","triggerScenarios":"A v2 client whose crypto negotiation (hello/auth exchange carrying algorithm and transcript hash) never completed before the control connection is finalized; version skew where one side enables v2 crypto but the other skips negotiation; internal code paths creating a control connection without running the negotiation step.","commonSituations":"Mixed frpc/frps versions during a rolling upgrade of the v2 protocol; middleboxes truncating the handshake; custom clients implementing v2 incompletely.","solutions":["Upgrade frpc and frps to the same version so the v2 negotiation flow matches on both ends","Capture which side aborts the negotiation (server logs around the login) and inspect the wrapped handshake","As a stopgap, configure the client to use the v1 wire protocol until versions align"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"rw, err := ac.newControlReadWriter(rw, key)\nif err != nil && strings.Contains(err.Error(), \"missing v2 crypto negotiation\") {\n    // drop the connection; do not fall back to plaintext. Negotiation must be redone.\n    return err\n}","preventionTips":["Keep frpc and frps versions locked together when using wire protocol v2","Never construct v2 control connections without completing the crypto negotiation step","In custom clients, treat cryptoContext == nil on a v2 connection as a hard precondition failure"],"tags":["frps","wire-protocol","crypto","handshake","version-mismatch"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}