{"record":{"id":"49bca82903f56dd0","repo":"fatedier/frp","slug":"no-external-address-found","errorCode":null,"errorMessage":"no external address found","messagePattern":"no external address found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nathole/discovery.go","lineNumber":126,"sourceCode":"\t}\n\n\tresp := &stunResponse{}\n\tif response.MappedAddr != nil {\n\t\tresp.externalAddr = response.MappedAddr.String()\n\t}\n\tif response.OtherAddr != nil {\n\t\tresp.otherAddr = response.OtherAddr.String()\n\t}\n\treturn resp, nil\n}\n\nfunc (c *discoverConn) discoverFromStunServer(addr string) ([]string, error) {\n\tresp, err := c.doSTUNRequest(addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif resp.externalAddr == \"\" {\n\t\treturn nil, fmt.Errorf(\"no external address found\")\n\t}\n\n\texternalAddrs := make([]string, 0, 2)\n\texternalAddrs = append(externalAddrs, resp.externalAddr)\n\n\tif resp.otherAddr == \"\" {\n\t\treturn externalAddrs, nil\n\t}\n\n\t// find external address from changed address\n\tresp, err = c.doSTUNRequest(resp.otherAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif resp.externalAddr != \"\" {\n\t\texternalAddrs = append(externalAddrs, resp.externalAddr)\n\t}\n\treturn externalAddrs, nil","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/nathole/discovery.go#L108-L144","documentation":"Returned by discoverFromStunServer in pkg/nathole/discovery.go when a STUN binding response arrives but contains no MAPPED-ADDRESS attribute, so externalAddr stays empty. A STUN reply without a mapped address carries no information about the observer's public endpoint, so discovery from that server is useless and fails.","triggerScenarios":"discoverFromStunServer(addr) where the server responded but response.MappedAddr is nil. Causes: a misbehaving or non-compliant STUN server, a proxy/redirector answering UDP on that port with non-STUN data that happened to parse, or protocol-version mismatch in the STUN exchange.","commonSituations":"Pointing natHoleSTUNServer at a host that is not actually a STUN server; a STUN server that answers errors only; middleboxes (transparent proxies) replying to arbitrary UDP; a server that only speaks an incompatible STUN flavor.","solutions":["Replace the STUN server with a known-compliant one (standard RFC 5389/8489 servers)","Verify the port — most STUN servers listen on 3478 UDP","Confirm nothing on the path intercepts and answers the UDP traffic","Use multiple STUN servers so one bad entry does not kill discovery"],"exampleFix":"# before\nnatHoleSTUNServer = [\"203.0.113.10:3478\"]  # answers, but never maps\n\n# after\nnatHoleSTUNServer = [\"stun.easyvoip.com:3478\", \"stun.l.google.com:19302\"]","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"no external address found\") {\n        // this server is not a healthy STUN endpoint: skip it and use the next configured server\n    }\n}","preventionTips":["Use well-known RFC-compliant STUN servers on port 3478","Validate STUN entries once at startup and drop non-responsive ones","Do not point natHoleSTUNServer at arbitrary UDP services"],"tags":["go","frp","nathole","stun","configuration","network"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}