fatedier/frp · error

classify nat feature error: %v

Error message

classify nat feature error: %v

What it means

Returned by nathole.Prepare (pkg/nathole/nathole.go) when ClassifyNATFeature(addrs, localIPs) fails after discovery succeeded with >= 2 addresses. The wrapped error is either 'not enough addresses' (defensive; normally caught by the earlier length check) or a parsing failure — an address in the discovery result that does not survive net.SplitHostPort / strconv.Atoi.

Source

Thrown at pkg/nathole/nathole.go:131

	}
	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 {
		return nil, fmt.Errorf("discover error: %v", err)
	}
	if len(addrs) < 2 {
		return nil, fmt.Errorf("discover error: not enough addresses")
	}

	localIPs, _ := ListLocalIPsForNatHole(10)
	natFeature, err := ClassifyNATFeature(addrs, localIPs)
	if err != nil {
		return nil, fmt.Errorf("classify nat feature error: %v", err)
	}

	laddr, err := net.ResolveUDPAddr("udp4", localAddr.String())
	if err != nil {
		return nil, fmt.Errorf("resolve local udp addr error: %v", err)
	}
	listenConn, err := net.ListenUDP("udp4", laddr)
	if err != nil {
		return nil, fmt.Errorf("listen local udp addr error: %v", err)
	}

	// Apply NAT traversal options
	var assistedAddrs []string
	if !opts.DisableAssistedAddrs {
		assistedAddrs = make([]string, 0, len(localIPs))
		for _, ip := range localIPs {
			assistedAddrs = append(assistedAddrs, net.JoinHostPort(ip, strconv.Itoa(laddr.Port)))
		}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Read the wrapped error: a SplitHostPort/Atoi failure means one address is malformed — identify which STUN server produced it and replace it
  2. Prefer IPv4 STUN endpoints for xtcp discovery
  3. Align frpc/frps versions if formats may differ
  4. Retry discovery; a single malformed response can be transient
Defensive patterns

Strategy: validation

Validate before calling

for _, a := range addrs {
    if _, _, err := net.SplitHostPort(a); err != nil {
        return fmt.Errorf("malformed address %q from STUN discovery", a)
    }
}

Try / catch

if err != nil && strings.Contains(err.Error(), "classify nat feature error") {
    // wrapped error tells whether it is a count problem or a parse problem; fix the offending STUN source
}

Prevention

When it happens

Trigger: nathole.Prepare where a discovered address string is malformed (missing port, non-numeric port, bracket issues with IPv6 literals). Reachable only when discovery produced >= 2 entries but one is not a clean host:port string.

Common situations: A misbehaving STUN server returning odd MAPPED-ADDRESS formatting; IPv6 handling edge cases where the address lacks brackets; version-skewed components producing different address formats.

Related errors


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