{"record":{"id":"0acdc8fb8c79e6bf","repo":"fatedier/frp","slug":"wait-response-from-stun-server-timeout","errorCode":null,"errorMessage":"wait response from stun server timeout","messagePattern":"wait response from stun server timeout","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nathole/discovery.go","lineNumber":105,"sourceCode":"}\n\nfunc (c *discoverConn) doSTUNRequest(addr string) (*stunResponse, error) {\n\tserverAddr, err := net.ResolveUDPAddr(\"udp4\", addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttransaction, err := stun.NewBindingTransaction(serverAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := c.conn.SetReadDeadline(time.Now().Add(responseTimeout)); err != nil {\n\t\treturn nil, err\n\t}\n\tresponse, err := c.client.Do(transaction)\n\tif err != nil {\n\t\tvar netErr net.Error\n\t\tif errors.As(err, &netErr) && netErr.Timeout() {\n\t\t\treturn nil, fmt.Errorf(\"wait response from stun server timeout\")\n\t\t}\n\t\treturn nil, err\n\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","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/nathole/discovery.go#L87-L123","documentation":"Returned by doSTUNRequest in pkg/nathole/discovery.go when the STUN binding transaction fails with a network error whose Timeout() is true — i.e. the read deadline set on the connection (responseTimeout) expired before the STUN server answered. It is the discovery layer's way of saying a STUN server did not respond in time.","triggerScenarios":"discoverConn.doSTUNRequest(addr) with conn.SetReadDeadline(now + responseTimeout), then client.Do(transaction) times out. Causes: STUN server down or slow, UDP packets dropped by a firewall/NAT, wrong server address, or an extremely congested link.","commonSituations":"Dead or blocked STUN server entries in natHoleSTUNServer/natHoleAnalysisSTUNServer; corporate/campus firewalls dropping outbound UDP; DNS resolving a STUN hostname to an unreachable IP; mobile networks throttling UDP.","solutions":["Test the STUN server independently (e.g. with a STUN CLI) and replace dead entries with known-good servers","Configure several STUN servers so discovery can fall back to the next one","Retry discovery after a short delay — UDP loss is often transient","If all UDP is blocked by the network, NAT hole punching cannot work; use a relayed proxy type instead"],"exampleFix":"# before (frps.toml)\nnatholeSTUNServer = [\"stun.example.invalid:3478\"]\n\n# after\nnatHoleSTUNServer = [\"stun.easyvoip.com:3478\", \"stun.l.google.com:19302\"]","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"addrs, localAddr, err := nathole.Discover(servers, \"\")\nif err != nil {\n    if strings.Contains(err.Error(), \"stun server timeout\") {\n        // rotate to the next STUN server or retry after a pause; persistent failure means UDP is blocked\n    }\n}","preventionTips":["List several STUN servers so one timeout is survivable","Probe STUN reachability before enabling xtcp on restricted networks","Remember xtcp fundamentally requires outbound UDP"],"tags":["go","frp","nathole","stun","network","timeout"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}