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
- Read the wrapped error: a SplitHostPort/Atoi failure means one address is malformed — identify which STUN server produced it and replace it
- Prefer IPv4 STUN endpoints for xtcp discovery
- Align frpc/frps versions if formats may differ
- 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
- Sanity-parse discovered addresses before Prepare
- Replace STUN servers that emit malformed mappings
- Keep frp versions consistent across components
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
- not enough addresses
- classify client nat feature error: %v
- classify visitor nat feature error: %v
- wait response from stun server timeout
- no external address found
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/b4d236abdf45237a.
Report an issue: GitHub.