{"record":{"id":"53b3111638877469","repo":"fatedier/frp","slug":"discover-error-not-enough-addresses","errorCode":null,"errorMessage":"discover error: not enough addresses","messagePattern":"discover error: not enough addresses","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nathole/nathole.go","lineNumber":125,"sourceCode":"\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)\n\t}\n\n\t// Apply NAT traversal options","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/nathole/nathole.go#L107-L143","documentation":"Returned by nathole.Prepare (pkg/nathole/nathole.go) when Discover succeeded but returned fewer than two addresses. NAT classification needs mappings observed from at least two distinct STUN servers to detect IP/port behavior differences; with one address there is no basis for classification, so Prepare refuses to continue.","triggerScenarios":"nathole.Prepare where Discover returned exactly one address — one STUN server answered, the second timed out or returned no OTHER-ADDRESS to chain the second request from.","commonSituations":"Only one STUN server configured; one of several servers down; a NAT that drops the follow-up STUN request to the changed address; flaky UDP paths.","solutions":["Configure at least two (preferably three) working STUN servers for the nathole controller","Retry Prepare — losing one of two UDP exchanges is common and often succeeds on retry","Confirm each STUN server individually responds from the host running frpc","If the environment only ever yields one mapping, fall back to stcp/relay"],"exampleFix":"# before (frps.toml)\nnatholeSTUNServer = [\"stun1.example.com:3478\"]\n\n# after\nnatHoleSTUNServer = [\"stun1.example.com:3478\", \"stun2.example.com:3478\", \"stun3.example.com:3478\"]","handlingStrategy":"retry","validationCode":"addrs, _, err := nathole.Discover(servers, \"\")\nif err == nil && len(addrs) < 2 {\n    // not enough observations for classification: retry or expand server list\n}","typeGuard":null,"tryCatchPattern":"res, err := nathole.Prepare(servers, opts)\nif err != nil && strings.Contains(err.Error(), \"not enough addresses\") {\n    // one STUN observation lost: retry once, then add more STUN servers\n}","preventionTips":["Configure >=3 STUN servers so losing one still yields 2 addresses","Retry Prepare once before debugging","Check per-server responsiveness from the frpc host"],"tags":["go","frp","nathole","stun","nat"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}