{"record":{"id":"80d9bdc88c4ad05c","repo":"fatedier/frp","slug":"wait-detect-message-error-v","errorCode":null,"errorMessage":"wait detect message error: %v","messagePattern":"wait detect message error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nathole/nathole.go","lineNumber":255,"sourceCode":"\t\t}\n\t}\n\tif m.DetectBehavior.SendRandomPorts > 0 {\n\t\tctx, cancel := context.WithCancel(ctx)\n\t\tdefer cancel()\n\t\tfor i := range listenConns {\n\t\t\tgo sendSidMessageToRandomPorts(ctx, listenConns[i], m.CandidateAddrs, m.DetectBehavior.SendRandomPorts, sendToRangePortsFunc)\n\t\t}\n\t}\n\n\ttimeout := 5 * time.Second\n\tif m.DetectBehavior.ReadTimeoutMs > 0 {\n\t\ttimeout = time.Duration(m.DetectBehavior.ReadTimeoutMs) * time.Millisecond\n\t}\n\n\tif len(listenConns) == 1 {\n\t\traddr, err := waitDetectMessage(ctx, listenConns[0], m.Sid, key, timeout, m.DetectBehavior.Role)\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"wait detect message error: %v\", err)\n\t\t}\n\t\treturn listenConns[0], raddr, nil\n\t}\n\n\ttype result struct {\n\t\tlConn *net.UDPConn\n\t\traddr *net.UDPAddr\n\t}\n\tresultCh := make(chan result)\n\tfor _, conn := range listenConns {\n\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}:","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/nathole/nathole.go#L237-L273","documentation":"MakeHole failed because waitDetectMessage returned an error on the single listening UDP socket: the peer's detect packet could not be received before the deadline (ReadFromUDP error, deadline exceeded, or an invalid SID/role match). This is the actual hole-punch packet exchange failing after the coordination succeeded.","triggerScenarios":"MakeHole with exactly one listenConn and waitDetectMessage errors: UDP read deadline expires, the socket errors, or every incoming packet is discarded because the decrypted SID does not match m.Sid for the expected role.","commonSituations":"NAT rewrites the source port so detect packets arrive from an unexpected address; symmetric NAT on either side blocks the hole; packet loss or heavy UDP filtering; wrong key (auth token) making SID decryption fail so valid packets are dropped.","solutions":["Verify both sides use the same auth token so the detect messages decrypt and the SID matches","Retry the visit — hole punching is inherently racy and often succeeds on later attempts","If it consistently fails, one endpoint is likely behind a symmetric NAT: fall back to relaying through frps (do not rely on P2P)","Check DetectBehavior.ReadTimeoutMs in the server response; raise it server-side if the network is slow"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"conn, addr, err := nathole.MakeHole(ctx, l, resp, key)\nif err != nil && strings.Contains(err.Error(), \"wait detect message\") {\n    // UDP punch is racy: retry N times with backoff before giving up\n    for i := 0; i < 3 && err != nil; i++ {\n        time.Sleep(time.Duration(i+1) * 500 * time.Millisecond)\n        conn, addr, err = retryMakeHole()\n    }\n}","preventionTips":["Verify auth token parity before P2P attempts so SID decryption succeeds","Increase server-side DetectBehavior.ReadTimeoutMs on slow links","Assume symmetric NATs exist: always wire a relay fallback path"],"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"}