{"record":{"id":"e7566750ced64d55","repo":"fatedier/frp","slug":"clienthello-is-not-allowed-on-work-connections","errorCode":null,"errorMessage":"ClientHello is not allowed on work connections","messagePattern":"ClientHello is not allowed on work connections","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service.go","lineNumber":857,"sourceCode":"\tif err != nil {\n\t\treturn ctl, err\n\t}\n\tif !active {\n\t\treturn ctl, errControlReplaced\n\t}\n\n\treturn ctl, nil\n}\n\n// RegisterWorkConn register a new work connection to control and proxies need it.\nfunc (svr *Service) RegisterWorkConn(\n\tworkConn *msg.Conn,\n\tnewMsg *msg.NewWorkConn,\n\tworkWireProtocol string,\n\tworkClientHelloPresent bool,\n) error {\n\tif workClientHelloPresent {\n\t\treturn fmt.Errorf(\"ClientHello is not allowed on work connections\")\n\t}\n\txl := netpkg.NewLogFromConn(workConn)\n\tctl, exist := svr.ctlManager.GetByID(newMsg.RunID)\n\tif !exist {\n\t\txl.Warnf(\"no client control found for run id [%s]\", newMsg.RunID)\n\t\treturn fmt.Errorf(\"no client control found for run id [%s]\", newMsg.RunID)\n\t}\n\tif workWireProtocol != ctl.sessionCtx.WireProtocol {\n\t\treturn fmt.Errorf(\"work connection wire protocol mismatch: got %s want %s\", workWireProtocol, ctl.sessionCtx.WireProtocol)\n\t}\n\n\t// server plugin hook\n\tcontent := &plugin.NewWorkConnContent{\n\t\tUser: plugin.UserInfo{\n\t\t\tUser:  ctl.sessionCtx.LoginMsg.User,\n\t\t\tMetas: ctl.sessionCtx.LoginMsg.Metas,\n\t\t\tRunID: ctl.sessionCtx.LoginMsg.RunID,\n\t\t},","sourceCodeStart":839,"sourceCodeEnd":875,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/server/service.go#L839-L875","documentation":"RegisterWorkConn rejects a work connection on which the client performed a TLS-style ClientHello exchange. Work connections reuse the credentials negotiated on the control connection and must not carry their own handshake; only the control/visitor paths may present a ClientHello. Sending one indicates a protocol-state bug or an incompatible/hand-rolled client.","triggerScenarios":"Calling RegisterWorkConn with workClientHelloPresent=true — i.e. the server detected a ClientHello message on the work connection before the NewWorkConn message. Happens with modified frpc builds, custom clients that apply TLS to every connection, or version combinations where the work-connection handshake semantics changed.","commonSituations":"Custom client implementations that enable a client hello on all connections; a misconfigured TLS layer wrapping work connections; frpc/frps built from mismatched forks; middleboxes injecting a TLS handshake into the stream.","solutions":["Use a stock, version-matched frpc so work connections skip the ClientHello","If building a custom client, only send ClientHello on control/visitor connections, never on work connections","Disable custom TLS wrapping of work connections and let frp's transport settings govern it","Upgrade both sides to releases whose work-connection protocol matches"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// custom client: never send ClientHello on work connections\nconst (\n    onControl  = true\n    onWorkConn = false\n)\n// dialWorkConn(path, hello=onWorkConn) // hello must be false","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use stock frpc binaries for work connections","Keep frpc/frps from the same release train","If you fork frp, add an assertion in your dialer that work connections never perform a ClientHello"],"tags":["frp","work-connection","clienthello","protocol-misuse"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}