fatedier/frp · error

natHoleRespMsg get empty candidate addresses

Error message

natHoleRespMsg get empty candidate addresses

What it means

The NatHoleResp from frps is otherwise valid but contains an empty CandidateAddrs list, so the client has no addresses to send detection packets to and hole punching cannot proceed. The server is expected to compute the counterpart's mapped public address candidates from both peers' Prepare messages; an empty list means that computation produced nothing.

Source

Thrown at pkg/nathole/nathole.go:186

	timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	var natHoleRespMsg *msg.NatHoleResp
	m, err := transporter.Do(timeoutCtx, m, laneKey, msg.TypeNameNatHoleResp)
	if err != nil {
		return nil, fmt.Errorf("get natHoleRespMsg error: %v", err)
	}
	mm, ok := m.(*msg.NatHoleResp)
	if !ok {
		return nil, fmt.Errorf("get natHoleRespMsg error: invalid message type")
	}
	natHoleRespMsg = mm

	if natHoleRespMsg.Error != "" {
		return nil, fmt.Errorf("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
	}
	if len(natHoleRespMsg.CandidateAddrs) == 0 {
		return nil, fmt.Errorf("natHoleRespMsg get empty candidate addresses")
	}
	return natHoleRespMsg, nil
}

// MakeHole is used to make a NAT hole between client and visitor.
func MakeHole(ctx context.Context, listenConn *net.UDPConn, m *msg.NatHoleResp, key []byte) (*net.UDPConn, *net.UDPAddr, error) {
	xl := xlog.FromContextSafe(ctx)
	transactionID := NewTransactionID()
	sendToRangePortsFunc := func(conn *net.UDPConn, addr string) error {
		return sendSidMessage(ctx, conn, m.Sid, transactionID, addr, key, m.DetectBehavior.TTL)
	}

	listenConns := []*net.UDPConn{listenConn}
	var detectAddrs []string
	if m.DetectBehavior.Role == DetectRoleSender {
		// sender
		if m.DetectBehavior.SendDelayMs > 0 {
			time.Sleep(time.Duration(m.DetectBehavior.SendDelayMs) * time.Millisecond)

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Check frps logs for the analysis of the same transaction ID to see why no candidates were generated
  2. Upgrade both frps and frpc to matching versions so CandidateAddrs is always populated on success
  3. Retry the connection — if the server returns an explicit analysis error instead, fix that first (error 302 path)
Defensive patterns

Strategy: try-catch

Try / catch

resp, err := nathole.ExchangeInfo(ctx, tp, laneKey, m, timeout)
if err != nil {
    return err
}
if len(resp.CandidateAddrs) == 0 {
    return errors.New("no candidates; retry or fall back to relay")
}

Prevention

When it happens

Trigger: ExchangeInfo gets NatHoleResp with zero CandidateAddrs — server analysis failed to derive any candidate address, or a server bug/version skew serialized an empty array.

Common situations: Server-side NAT analysis found no usable mapped address (e.g. malformed Prepare from the peer); frps/frpc version mismatch changing how candidate addresses are populated; older frps that does not fill the field.

Related errors


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