{"record":{"id":"b73df926b3286fe4","repo":"fatedier/frp","slug":"s","errorCode":null,"errorMessage":"%s","messagePattern":"%s","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nathole/nathole.go","lineNumber":112,"sourceCode":"\n\tvar natHoleRespMsg *msg.NatHoleResp\n\ttransactionID := NewTransactionID()\n\tm, err := transporter.Do(timeoutCtx, &msg.NatHoleVisitor{\n\t\tTransactionID: transactionID,\n\t\tProxyName:     proxyName,\n\t\tPreCheck:      true,\n\t}, transactionID, msg.TypeNameNatHoleResp)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get natHoleRespMsg error: %v\", err)\n\t}\n\tmm, ok := m.(*msg.NatHoleResp)\n\tif !ok {\n\t\treturn fmt.Errorf(\"get natHoleRespMsg error: invalid message type\")\n\t}\n\tnatHoleRespMsg = mm\n\n\tif natHoleRespMsg.Error != \"\" {\n\t\treturn fmt.Errorf(\"%s\", natHoleRespMsg.Error)\n\t}\n\treturn nil\n}\n\n// Prepare is used to do some preparation work before penetration.\nfunc Prepare(stunServers []string, opts PrepareOptions) (*PrepareResult, error) {\n\t// discover for Nat type\n\taddrs, localAddr, err := Discover(stunServers, \"\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"discover error: %v\", err)\n\t}\n\tif len(addrs) < 2 {\n\t\treturn nil, fmt.Errorf(\"discover error: not enough addresses\")\n\t}\n\n\tlocalIPs, _ := ListLocalIPsForNatHole(10)\n\tnatFeature, err := ClassifyNATFeature(addrs, localIPs)\n\tif err != nil {","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/nathole/nathole.go#L94-L130","documentation":"Returned by nathole.PreCheck (pkg/nathole/nathole.go) when the NatHoleResp arrives successfully but carries a non-empty Error field — i.e. frps (the nathole controller) explicitly rejected the precheck. The string is the server's own message, commonly 'xtcp server for [name] doesn't exist' or 'xtcp connection of [name] auth failed' from the controller's visitor-message handler.","triggerScenarios":"nathole.PreCheck succeeds at transport level, but the controller rejects the request: target xtcp proxy not registered (client offline or name mismatch), visitor sk mismatch, or duplicate registration.","commonSituations":"Visitor references an xtcp proxy whose client is not connected; serverName typo; sk mismatch between visitor and client; client still registering when the visitor fires.","solutions":["Read the embedded server message — it names the exact server-side cause (missing proxy vs auth failure)","If 'doesn't exist': start the frpc that owns the xtcp proxy, confirm the name matches serverName","If 'auth failed': make the visitor's sk identical to the client's xtcp sk","Retry PreCheck after fixing config; call it before Prepare to fail fast"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := nathole.PreCheck(ctx, tp, proxyName, timeout); err != nil {\n    switch {\n    case strings.Contains(err.Error(), \"doesn't exist\"):\n        // wait for client registration, fix serverName\n    case strings.Contains(err.Error(), \"auth failed\"):\n        // fix sk\n    default:\n        return err // transport-level, see error 292\n    }\n}","preventionTips":["PreCheck errors carry the server's reason verbatim — branch on it","Fix config (serverName/sk) before retrying","PreCheck cheaply gates the expensive Prepare step"],"tags":["go","frp","nathole","xtcp","configuration"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}