fatedier/frp · error

wait detect message timeout

Error message

wait detect message timeout

What it means

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.

Source

Thrown at pkg/nathole/nathole.go:284

		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}:
			default:
				lConn.Close()
			}
		}(conn)
	}

	select {
	case result := <-resultCh:
		return result.lConn, result.raddr, nil
	case <-time.After(timeout):
		return nil, nil, fmt.Errorf("wait detect message timeout")
	case <-ctx.Done():
		return nil, nil, fmt.Errorf("wait detect message canceled")
	}
}

func waitDetectMessage(
	ctx context.Context, conn *net.UDPConn, sid string, key []byte,
	timeout time.Duration, role string,
) (*net.UDPAddr, error) {
	xl := xlog.FromContextSafe(ctx)
	for {
		buf := pool.GetBuf(1024)
		_ = conn.SetReadDeadline(time.Now().Add(timeout))
		n, raddr, err := conn.ReadFromUDP(buf)
		_ = conn.SetReadDeadline(time.Time{})
		if err != nil {
			pool.PutBuf(buf)
			return nil, err

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Retry the P2P visit — multi-socket punching succeeds when the NAT mapping happens to align
  2. Confirm auth tokens match so detect messages decrypt on arrival
  3. If failures are consistent, disable P2P expectations for this pair and let traffic relay through frps
  4. Server operators: tune nathole DetectBehavior (more ports, longer ReadTimeoutMs) to widen the punch window
Defensive patterns

Strategy: retry

Try / catch

conn, addr, err := nathole.MakeHole(ctx, l, resp, key)
if errors.Is(err, errDetectTimeout) || strings.Contains(err.Error(), "wait detect message timeout") {
    return relayFallback() // punch window exhausted across all sockets
}

Prevention

When it happens

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

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

Understand the failure class

Related errors


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