{"record":{"id":"f5bc52266bc4416b","repo":"OpenNHP/opennhp","slug":"rkn-rate-limit-exceeded","errorCode":null,"errorMessage":"rkn rate limit exceeded","messagePattern":"rkn rate limit exceeded","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"endpoints/server/msghandler.go","lineNumber":847,"sourceCode":"\t}\n\tinnerPkt.HeaderType = innerType\n\tlog.Info(\"server-relay[HandleRelayForward] inner [%s] from real client %s via relay %s\",\n\t\tcore.HeaderTypeToString(innerType), realAddr, relayAddrStr)\n\n\t// Same RKN-under-overload gate as the direct-UDP path\n\t// (recvPacketRoutine), but keyed on the REAL client IP rather than the\n\t// relay's: a relay legitimately fans out many clients, so keying on\n\t// the relay address would let one busy relay's honest traffic throttle\n\t// itself while doing nothing to isolate a single abusive client. The\n\t// inner RKN reaches the same cookie-verify ECDH (via\n\t// ForwardInboundPacket -> connectionRoutine -> RecvPacketToMsg), so it\n\t// needs the same pre-ECDH throttle. Dropped-only, no block-listing.\n\tif innerType == core.NHP_RKN && s.device.IsOverload() {\n\t\tif !s.rknLimiter.allow(realAddr, time.Now().UnixNano()) {\n\t\t\ts.device.ReleasePoolPacket(innerPkt)\n\t\t\tlog.Warning(\"server-relay[HandleRelayForward] inner RKN from real client %s (via relay %s) dropped: per-IP rate limit exceeded under overload\",\n\t\t\t\trealAddr, relayAddrStr)\n\t\t\treturn fmt.Errorf(\"rkn rate limit exceeded\")\n\t\t}\n\t}\n\n\t// Build or reuse a connection keyed on \"relay|<relayAddr>|<realClientAddr>\".\n\t// This avoids collisions with the relay's own NHP_RLY connection (which is\n\t// already keyed on relayAddrStr) and isolates per-client anti-replay state.\n\t// RemoteAddr must be the relay's UDP address so that response packets\n\t// (ACK/COK) are sent back to the relay — the relay then forwards them\n\t// to the browser over HTTP.  The real client address is used only for\n\t// auth/logging purposes.\n\t//\n\t// The '|' separator is required, not cosmetic: an IPv6 address renders\n\t// as \"[2001:db8::1]:80\", so a ':'-delimited key could not be reliably\n\t// split from the right. See relayConnKeySep for the full reasoning.\n\trelayAddr := ppd.ConnData.RemoteAddr\n\tconnKey := relayConnKeyPrefix + relayAddrStr + relayConnKeySep + realAddr.String()\n\trecvTime := time.Now().UnixNano()\n","sourceCodeStart":829,"sourceCodeEnd":865,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/server/msghandler.go#L829-L865","documentation":"When the server is in overload state, inner NHP_RKN (knock-continue) packets arriving via relay are additionally throttled per real client IP by s.rknLimiter. Packets exceeding the per-IP rate are dropped with this error; it is drop-only and never block-lists the client.","triggerScenarios":"Device IsOverload() is true (total connections above OverloadConnectionThreshold) and a relayed NHP_RKN arrives from the same real client IP faster than rknLimiter's per-IP allowance.","commonSituations":"Client retry loops hammering RKN during a load spike; relay aggregating many clients behind one IP hitting a single per-IP bucket; benchmark or load test via relay.","solutions":["Reduce client RKN send frequency or add client-side backoff/jitter","Scale the server or raise MaxConcurrentConnection so overload mode clears","Inspect rknLimiter configuration for a per-IP rate appropriate for NATed clients","Retry the knock after the rate window elapses"],"exampleFix":"// before: tight retry loop on client\nfor { sendRKN() }\n// after\nif !lastSent.IsZero() && time.Since(lastSent) < rknInterval { time.Sleep(rknInterval) }\nsendRKN()","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := forwardViaRelay(pkt)\nif err != nil && strings.Contains(err.Error(), \"rate limit\") {\n    time.Sleep(backoff) // exponential backoff with jitter, then retry\n    return forwardViaRelay(pkt)\n}","preventionTips":["Add client-side backoff/jitter for RKN retries","Size the server so overload mode rarely triggers","Account for NAT aggregation when setting per-IP rates"],"tags":["relay","rate-limit","overload","udp"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}