{"record":{"id":"298fbd37ba6fdd7a","repo":"fatedier/frp","slug":"natholerespmsg-get-error-info-s","errorCode":null,"errorMessage":"natHoleRespMsg get error info: %s","messagePattern":"natHoleRespMsg get error info: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nathole/nathole.go","lineNumber":183,"sourceCode":"\tctx context.Context, transporter transport.MessageTransporter,\n\tlaneKey string, m msg.Message, timeout time.Duration,\n) (*msg.NatHoleResp, error) {\n\ttimeoutCtx, cancel := context.WithTimeout(ctx, timeout)\n\tdefer cancel()\n\n\tvar natHoleRespMsg *msg.NatHoleResp\n\tm, err := transporter.Do(timeoutCtx, m, laneKey, msg.TypeNameNatHoleResp)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get natHoleRespMsg error: %v\", err)\n\t}\n\tmm, ok := m.(*msg.NatHoleResp)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"get natHoleRespMsg error: invalid message type\")\n\t}\n\tnatHoleRespMsg = mm\n\n\tif natHoleRespMsg.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"natHoleRespMsg get error info: %s\", natHoleRespMsg.Error)\n\t}\n\tif len(natHoleRespMsg.CandidateAddrs) == 0 {\n\t\treturn nil, fmt.Errorf(\"natHoleRespMsg get empty candidate addresses\")\n\t}\n\treturn natHoleRespMsg, nil\n}\n\n// MakeHole is used to make a NAT hole between client and visitor.\nfunc MakeHole(ctx context.Context, listenConn *net.UDPConn, m *msg.NatHoleResp, key []byte) (*net.UDPConn, *net.UDPAddr, error) {\n\txl := xlog.FromContextSafe(ctx)\n\ttransactionID := NewTransactionID()\n\tsendToRangePortsFunc := func(conn *net.UDPConn, addr string) error {\n\t\treturn sendSidMessage(ctx, conn, m.Sid, transactionID, addr, key, m.DetectBehavior.TTL)\n\t}\n\n\tlistenConns := []*net.UDPConn{listenConn}\n\tvar detectAddrs []string\n\tif m.DetectBehavior.Role == DetectRoleSender {","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/nathole/nathole.go#L165-L201","documentation":"The NatHoleResp message arrived intact but carries a non-empty Error field: the frps server explicitly rejected the hole-punch transaction. The message string is the server's own error text (e.g. transaction not found, no counterpart, analysis failed), so it is the authoritative reason for the refusal.","triggerScenarios":"ExchangeInfo receives NatHoleResp with Error != \"\" — frps could not match client and visitor, the transaction ID expired, or the server-side analysis concluded hole punching is impossible.","commonSituations":"Visitor trying to reach an offline or not-yet-registered client; both peers behind symmetric NATs so the server analysis gives up; transaction timeout because client and visitor did not send their NatHolePrepare within the server's window.","solutions":["Read the embedded server error text — it names the exact server-side refusal reason","Verify the sk is running and the target proxy is registered before the visitor connects","Retry the visit: transient analysis failures (race between client and visitor prepare messages) often succeed on a second attempt","If both endpoints are behind symmetric NAT, configure a fallback (STUN server config or direct relay via server)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"resp, err := nathole.ExchangeInfo(ctx, tp, laneKey, m, timeout)\nif err != nil && strings.Contains(err.Error(), \"natHoleRespMsg get error info\") {\n    // server-side refusal; err text after ': ' is the server's reason\n    return fmt.Errorf(\"punch refused by server: %w\", err)\n}","preventionTips":["Ensure the target client is online and registered before the visitor dials","Retry once after a short delay to absorb client/visitor prepare races","Keep a relay fallback for server-refused transactions"],"tags":["nat","p2p","server-rejection","go"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}