{"record":{"id":"b331c62187e33ea2","repo":"fatedier/frp","slug":"wait-detect-message-timeout","errorCode":null,"errorMessage":"wait detect message timeout","messagePattern":"wait detect message timeout","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nathole/nathole.go","lineNumber":284,"sourceCode":"\t\tgo func(lConn *net.UDPConn) {\n\t\t\taddr, err := waitDetectMessage(ctx, lConn, m.Sid, key, timeout, m.DetectBehavior.Role)\n\t\t\tif err != nil {\n\t\t\t\tlConn.Close()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase resultCh <- result{lConn: lConn, raddr: addr}:\n\t\t\tdefault:\n\t\t\t\tlConn.Close()\n\t\t\t}\n\t\t}(conn)\n\t}\n\n\tselect {\n\tcase result := <-resultCh:\n\t\treturn result.lConn, result.raddr, nil\n\tcase <-time.After(timeout):\n\t\treturn nil, nil, fmt.Errorf(\"wait detect message timeout\")\n\tcase <-ctx.Done():\n\t\treturn nil, nil, fmt.Errorf(\"wait detect message canceled\")\n\t}\n}\n\nfunc waitDetectMessage(\n\tctx context.Context, conn *net.UDPConn, sid string, key []byte,\n\ttimeout time.Duration, role string,\n) (*net.UDPAddr, error) {\n\txl := xlog.FromContextSafe(ctx)\n\tfor {\n\t\tbuf := pool.GetBuf(1024)\n\t\t_ = conn.SetReadDeadline(time.Now().Add(timeout))\n\t\tn, raddr, err := conn.ReadFromUDP(buf)\n\t\t_ = conn.SetReadDeadline(time.Time{})\n\t\tif err != nil {\n\t\t\tpool.PutBuf(buf)\n\t\t\treturn nil, err","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/nathole/nathole.go#L266-L302","documentation":"MakeHole listened on multiple UDP sockets concurrently and none of them received a valid detect message before the overall timeout (from DetectBehavior.ReadTimeoutMs, default 5s). The time.After(timeout) branch fired, meaning the hole-punch packet exchange produced no successful candidate connection across all sockets.","triggerScenarios":"MakeHole with len(listenConns) > 1 and no goroutine delivers a result to resultCh within the timeout — every waitDetectMessage on every socket missed the peer's detect packet.","commonSituations":"Symmetric NAT on the peer side maps each outbound socket to a different port so none of the guessed candidates is correct; aggressive UDP firewalls; timeouts too short for high-latency links.","solutions":["Retry the P2P visit — multi-socket punching succeeds when the NAT mapping happens to align","Confirm auth tokens match so detect messages decrypt on arrival","If failures are consistent, disable P2P expectations for this pair and let traffic relay through frps","Server operators: tune nathole DetectBehavior (more ports, longer ReadTimeoutMs) to widen the punch window"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"conn, addr, err := nathole.MakeHole(ctx, l, resp, key)\nif errors.Is(err, errDetectTimeout) || strings.Contains(err.Error(), \"wait detect message timeout\") {\n    return relayFallback() // punch window exhausted across all sockets\n}","preventionTips":["Retry P2P visits — multi-socket punching has a per-attempt success probability","Classify consistent timeouts as symmetric-NAT topology and stop retrying, use relay","Server operators: widen DetectBehavior ports/timeouts to raise punch success rate"],"tags":["nat","p2p","udp","timeout","go"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}