{"record":{"id":"725a60b2e1b90366","repo":"fatedier/frp","slug":"discover-error-v","errorCode":null,"errorMessage":"discover error: %v","messagePattern":"discover error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nathole/nathole.go","lineNumber":122,"sourceCode":"\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 {\n\t\treturn nil, fmt.Errorf(\"classify nat feature error: %v\", err)\n\t}\n\n\tladdr, err := net.ResolveUDPAddr(\"udp4\", localAddr.String())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"resolve local udp addr error: %v\", err)\n\t}\n\tlistenConn, err := net.ListenUDP(\"udp4\", laddr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"listen local udp addr error: %v\", err)","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/nathole/nathole.go#L104-L140","documentation":"Returned by nathole.Prepare (pkg/nathole/nathole.go) when Discover(stunServers, \"\") fails outright before any addresses are gathered. The wrapped error comes from the discovery layer — typically every STUN server timing out ('wait response from stun server timeout') or responding without a mapped address ('no external address found').","triggerScenarios":"nathole.Prepare(stunServers, opts) where all STUN servers fail: unreachable/dead servers, UDP fully blocked by the local firewall, DNS failure resolving server hostnames.","commonSituations":"Running xtcp on hosts with outbound UDP denied; empty or stale natHoleSTUNServer config; captive portals or VPNs swallowing UDP; containers with restricted networking.","solutions":["Check the wrapped error to distinguish timeout (blocked/dead server) from no-mapped-address (bad server)","Verify outbound UDP to the STUN servers works from this host","Configure multiple valid STUN servers on frps","If UDP is blocked, hole punching is impossible — use stcp/tcp relay instead of xtcp"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"res, err := nathole.Prepare(servers, opts)\nif err != nil {\n    if strings.Contains(err.Error(), \"discover error:\") && !strings.Contains(err.Error(), \"not enough addresses\") {\n        // all STUN failed: check UDP egress; if blocked, use stcp/tcp instead of xtcp\n    }\n}","preventionTips":["Pre-verify UDP/STUN reachability on hosts that will use xtcp","Maintain a healthy STUN server list","Have a relay (stcp) fallback ready in restricted networks"],"tags":["go","frp","nathole","stun","network"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}