fatedier/frp · error

classify client nat feature error: %v

Error message

classify client nat feature error: %v

What it means

Wraps a ClassifyNATFeature failure for the client side inside Controller.analysis (pkg/nathole/controller.go). analysis takes the NatHoleClient message's MappedAddrs (observed by the xtcp server side via STUN) and classifies its NAT; if classification fails — most commonly 'not enough addresses' when fewer than two mapped addresses were reported — this error aborts the whole hole-punch analysis.

Source

Thrown at pkg/nathole/controller.go:306

	if session != nil {
		sid = session.sid
	}
	return &msg.NatHoleResp{
		TransactionID: transactionID,
		Sid:           sid,
		Error:         errInfo,
	}
}

// analysis analyzes the NAT type and behavior of the visitor and client, then makes hole-punching decisions.
// return the response to the visitor and client.
func (c *Controller) analysis(session *Session) (*msg.NatHoleResp, *msg.NatHoleResp, error) {
	cm := session.clientMsg
	vm := session.visitorMsg

	cNatFeature, err := ClassifyNATFeature(cm.MappedAddrs, parseIPs(cm.AssistedAddrs))
	if err != nil {
		return nil, nil, fmt.Errorf("classify client nat feature error: %v", err)
	}

	vNatFeature, err := ClassifyNATFeature(vm.MappedAddrs, parseIPs(vm.AssistedAddrs))
	if err != nil {
		return nil, nil, fmt.Errorf("classify visitor nat feature error: %v", err)
	}
	session.cNatFeature = cNatFeature
	session.vNatFeature = vNatFeature
	session.genAnalysisKey()

	mode, index, cBehavior, vBehavior := c.analyzer.GetRecommandBehaviors(session.analysisKey, cNatFeature, vNatFeature)
	session.recommandMode = mode
	session.recommandIndex = index
	session.cBehavior = cBehavior
	session.vBehavior = vBehavior

	timeoutMs := max(cBehavior.SendDelayMs, vBehavior.SendDelayMs) + 5000
	if cBehavior.ListenRandomPorts > 0 || vBehavior.ListenRandomPorts > 0 {

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Fix STUN reachability from the client: configure multiple working STUN servers (natHoleAnalysisSTUNServer / natHoleSTUNServer in frps.toml)
  2. Confirm the frpc hosting the xtcp proxy and frps run compatible versions so MappedAddrs format matches
  3. Retry the visitor connection — transient UDP loss during discovery is common
  4. Check the wrapped error text: 'not enough addresses' points to discovery, a SplitHostPort error points to malformed data

Example fix

# before (frps.toml)
[natholeController]
# missing or single dead STUN server

# after
bindAddr = "0.0.0.0"
natholeAnalysisSTUNServer = ["stun.easyvoip.com:3478", "stun.l.google.com:19302"]
Defensive patterns

Strategy: retry

Try / catch

resp, err := controller.Analysis(session) // wraps analysis()
if err != nil && strings.Contains(err.Error(), "classify client nat feature error") {
    // client-side STUN discovery insufficient: have client re-run discovery / check STUN servers, retry visit
}

Prevention

When it happens

Trigger: Controller.analysis(session) runs after both client and visitor messages arrive; cm.MappedAddrs has <= 1 entry, or one of its entries fails net.SplitHostPort / strconv.Atoi (malformed 'host:port' string), causing ClassifyNATFeature to return an error that gets wrapped here.

Common situations: The client's STUN discovery only reached one server (blocked UDP, dead STUN config); the client reported addresses in a non-host:port format due to a version mismatch; network middleboxes mangling discovery traffic.

Related errors


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