fatedier/frp · error

get natHoleRespMsg error: %v

Error message

get natHoleRespMsg error: %v

What it means

Returned by nathole.PreCheck (pkg/nathole/nathole.go) when the underlying transporter.Do round-trip fails while sending a NatHoleVisitor{PreCheck:true} and waiting for the matching NatHoleResp. This is the transport-level failure (send error, timeout, connection closed) — distinct from a server-side rejection, which arrives as a successful response carrying an Error string.

Source

Thrown at pkg/nathole/nathole.go:103

// PreCheck is used to check if the proxy is ready for penetration.
// Call this function before calling Prepare to avoid unnecessary preparation work.
func PreCheck(
	ctx context.Context, transporter transport.MessageTransporter,
	proxyName string, timeout time.Duration,
) error {
	timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	var natHoleRespMsg *msg.NatHoleResp
	transactionID := NewTransactionID()
	m, err := transporter.Do(timeoutCtx, &msg.NatHoleVisitor{
		TransactionID: transactionID,
		ProxyName:     proxyName,
		PreCheck:      true,
	}, transactionID, msg.TypeNameNatHoleResp)
	if err != nil {
		return fmt.Errorf("get natHoleRespMsg error: %v", err)
	}
	mm, ok := m.(*msg.NatHoleResp)
	if !ok {
		return fmt.Errorf("get natHoleRespMsg error: invalid message type")
	}
	natHoleRespMsg = mm

	if natHoleRespMsg.Error != "" {
		return fmt.Errorf("%s", natHoleRespMsg.Error)
	}
	return nil
}

// Prepare is used to do some preparation work before penetration.
func Prepare(stunServers []string, opts PrepareOptions) (*PrepareResult, error) {
	// discover for Nat type
	addrs, localAddr, err := Discover(stunServers, "")
	if err != nil {

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Inspect the wrapped error — a context deadline means raise the timeout or fix latency; a connection error means re-establish the transport first
  2. Retry PreCheck after reconnecting the transport
  3. Verify frps is up and the nathole controller port is reachable from the visitor
  4. Only call Prepare after PreCheck succeeds to avoid wasted discovery work

Example fix

// before
err := nathole.PreCheck(ctx, tp, proxyName, 500*time.Millisecond)

// after
err := nathole.PreCheck(ctx, tp, proxyName, 3*time.Second)
if err != nil {
    // reconnect transport and retry once before giving up
}
Defensive patterns

Strategy: retry

Try / catch

if err := nathole.PreCheck(ctx, tp, proxyName, 3*time.Second); err != nil {
    if errors.Is(err, context.DeadlineExceeded) || strings.Contains(err.Error(), "get natHoleRespMsg error") {
        // reconnect transport and retry once before surfacing to the user
    }
}

Prevention

When it happens

Trigger: nathole.PreCheck(ctx, transporter, proxyName, timeout) where transporter.Do errors: the control/UDP connection to frps is broken, the timeout context fires before the response arrives, or the message transport was already closed.

Common situations: frps unreachable or restarted mid-precheck; timeout passed to PreCheck too short for a slow link; visitor's connection to the nathole controller dropped (UDP); network flapping between visitor and server.

Related errors


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