fatedier/frp · error

wait detect message error: %v

Error message

wait detect message error: %v

What it means

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.

Source

Thrown at pkg/nathole/nathole.go:255

		}
	}
	if m.DetectBehavior.SendRandomPorts > 0 {
		ctx, cancel := context.WithCancel(ctx)
		defer cancel()
		for i := range listenConns {
			go sendSidMessageToRandomPorts(ctx, listenConns[i], m.CandidateAddrs, m.DetectBehavior.SendRandomPorts, sendToRangePortsFunc)
		}
	}

	timeout := 5 * time.Second
	if m.DetectBehavior.ReadTimeoutMs > 0 {
		timeout = time.Duration(m.DetectBehavior.ReadTimeoutMs) * time.Millisecond
	}

	if len(listenConns) == 1 {
		raddr, err := waitDetectMessage(ctx, listenConns[0], m.Sid, key, timeout, m.DetectBehavior.Role)
		if err != nil {
			return nil, nil, fmt.Errorf("wait detect message error: %v", err)
		}
		return listenConns[0], raddr, nil
	}

	type result struct {
		lConn *net.UDPConn
		raddr *net.UDPAddr
	}
	resultCh := make(chan result)
	for _, conn := range listenConns {
		go func(lConn *net.UDPConn) {
			addr, err := waitDetectMessage(ctx, lConn, m.Sid, key, timeout, m.DetectBehavior.Role)
			if err != nil {
				lConn.Close()
				return
			}
			select {
			case resultCh <- result{lConn: lConn, raddr: addr}:

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Verify both sides use the same auth token so the detect messages decrypt and the SID matches
  2. Retry the visit — hole punching is inherently racy and often succeeds on later attempts
  3. If it consistently fails, one endpoint is likely behind a symmetric NAT: fall back to relaying through frps (do not rely on P2P)
  4. Check DetectBehavior.ReadTimeoutMs in the server response; raise it server-side if the network is slow
Defensive patterns

Strategy: retry

Try / catch

conn, addr, err := nathole.MakeHole(ctx, l, resp, key)
if err != nil && strings.Contains(err.Error(), "wait detect message") {
    // UDP punch is racy: retry N times with backoff before giving up
    for i := 0; i < 3 && err != nil; i++ {
        time.Sleep(time.Duration(i+1) * 500 * time.Millisecond)
        conn, addr, err = retryMakeHole()
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/80d9bdc88c4ad05c. Report an issue: GitHub.