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
- Check frps logs for the analysis of the same transaction ID to see why no candidates were generated
- Upgrade both frps and frpc to matching versions so CandidateAddrs is always populated on success
- 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
- Run matching frps/frpc versions so server analysis always fills CandidateAddrs
- Log the transaction ID alongside this error to correlate with frps analysis logs
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
- natHoleRespMsg get error info: %s
- wait detect message error: %v
- wait detect message timeout
- wait detect message canceled
- open tunnel timeout
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/5b317a3410540563.
Report an issue: GitHub.