{"record":{"id":"0b6b0691a22e5c64","repo":"fatedier/frp","slug":"classify-client-nat-feature-error-v","errorCode":null,"errorMessage":"classify client nat feature error: %v","messagePattern":"classify client nat feature error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nathole/controller.go","lineNumber":306,"sourceCode":"\tif session != nil {\n\t\tsid = session.sid\n\t}\n\treturn &msg.NatHoleResp{\n\t\tTransactionID: transactionID,\n\t\tSid:           sid,\n\t\tError:         errInfo,\n\t}\n}\n\n// analysis analyzes the NAT type and behavior of the visitor and client, then makes hole-punching decisions.\n// return the response to the visitor and client.\nfunc (c *Controller) analysis(session *Session) (*msg.NatHoleResp, *msg.NatHoleResp, error) {\n\tcm := session.clientMsg\n\tvm := session.visitorMsg\n\n\tcNatFeature, err := ClassifyNATFeature(cm.MappedAddrs, parseIPs(cm.AssistedAddrs))\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"classify client nat feature error: %v\", err)\n\t}\n\n\tvNatFeature, err := ClassifyNATFeature(vm.MappedAddrs, parseIPs(vm.AssistedAddrs))\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"classify visitor nat feature error: %v\", err)\n\t}\n\tsession.cNatFeature = cNatFeature\n\tsession.vNatFeature = vNatFeature\n\tsession.genAnalysisKey()\n\n\tmode, index, cBehavior, vBehavior := c.analyzer.GetRecommandBehaviors(session.analysisKey, cNatFeature, vNatFeature)\n\tsession.recommandMode = mode\n\tsession.recommandIndex = index\n\tsession.cBehavior = cBehavior\n\tsession.vBehavior = vBehavior\n\n\ttimeoutMs := max(cBehavior.SendDelayMs, vBehavior.SendDelayMs) + 5000\n\tif cBehavior.ListenRandomPorts > 0 || vBehavior.ListenRandomPorts > 0 {","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/nathole/controller.go#L288-L324","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix STUN reachability from the client: configure multiple working STUN servers (natHoleAnalysisSTUNServer / natHoleSTUNServer in frps.toml)","Confirm the frpc hosting the xtcp proxy and frps run compatible versions so MappedAddrs format matches","Retry the visitor connection — transient UDP loss during discovery is common","Check the wrapped error text: 'not enough addresses' points to discovery, a SplitHostPort error points to malformed data"],"exampleFix":"# before (frps.toml)\n[natholeController]\n# missing or single dead STUN server\n\n# after\nbindAddr = \"0.0.0.0\"\nnatholeAnalysisSTUNServer = [\"stun.easyvoip.com:3478\", \"stun.l.google.com:19302\"]","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"resp, err := controller.Analysis(session) // wraps analysis()\nif err != nil && strings.Contains(err.Error(), \"classify client nat feature error\") {\n    // client-side STUN discovery insufficient: have client re-run discovery / check STUN servers, retry visit\n}","preventionTips":["Configure multiple healthy STUN servers for the nathole controller","Keep frpc/frps versions aligned","Treat one-off classification failures as retryable before debugging config"],"tags":["go","frp","nathole","stun","nat"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}