Compare commits

..

1 Commits

Author SHA1 Message Date
Your Name
7acc0bdd90 v1.2.3 - Handle rate limiting properly on kind 99999 request 2026-02-09 07:49:43 -04:00
2 changed files with 93 additions and 33 deletions

View File

@@ -844,58 +844,117 @@ cJSON* retrieve_event(const char* event_id) {
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/**
* Check if filters contain only kind 99999 (NDK ping)
* Returns 1 if all filters only request kind 99999, 0 otherwise
*/
static int is_only_kind_99999_request(cJSON* filters) {
if (!filters || !cJSON_IsArray(filters)) {
return 0;
}
int filter_count = cJSON_GetArraySize(filters);
if (filter_count == 0) {
return 0;
}
for (int i = 0; i < filter_count; i++) {
cJSON* filter = cJSON_GetArrayItem(filters, i);
if (!filter || !cJSON_IsObject(filter)) {
return 0;
}
cJSON* kinds = cJSON_GetObjectItem(filter, "kinds");
if (!kinds || !cJSON_IsArray(kinds)) {
// Filter has no kinds or kinds is not an array - not a pure 99999 request
return 0;
}
int kinds_count = cJSON_GetArraySize(kinds);
if (kinds_count == 0) {
return 0;
}
for (int j = 0; j < kinds_count; j++) {
cJSON* kind_item = cJSON_GetArrayItem(kinds, j);
if (!cJSON_IsNumber(kind_item)) {
return 0;
}
int kind_val = (int)cJSON_GetNumberValue(kind_item);
if (kind_val != 99999) {
return 0;
}
}
}
return 1; // All filters only contain kind 99999
}
int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) {
if (!cJSON_IsArray(filters)) {
DEBUG_ERROR("REQ filters is not an array");
return 0;
}
// Check if this is a kind 99999 (NDK ping) request - these should never count toward rate limiting
int is_ndk_ping = is_only_kind_99999_request(filters);
// EARLY SUBSCRIPTION LIMIT CHECK - Check limits BEFORE any processing
if (pss) {
time_t current_time = time(NULL);
// Check if client is currently rate limited due to excessive failed attempts
if (pss->rate_limit_until > current_time) {
char rate_limit_msg[256];
int remaining_seconds = (int)(pss->rate_limit_until - current_time);
snprintf(rate_limit_msg, sizeof(rate_limit_msg),
"Rate limited due to excessive failed subscription attempts. Try again in %d seconds.", remaining_seconds);
// NDK ping (kind 99999) should not be blocked by rate limiting at all
if (is_ndk_ping) {
// Allow the request through - it will be handled normally and get a proper response
// The subscription will be created but no events will match (which is correct)
DEBUG_TRACE("Allowing kind 99999 NDK ping through despite rate limit");
// Fall through to normal processing (skip rate limit block)
} else {
char rate_limit_msg[256];
int remaining_seconds = (int)(pss->rate_limit_until - current_time);
snprintf(rate_limit_msg, sizeof(rate_limit_msg),
"Rate limited due to excessive failed subscription attempts. Try again in %d seconds.", remaining_seconds);
// Send CLOSED notice for rate limiting
cJSON* closed_msg = cJSON_CreateArray();
cJSON_AddItemToArray(closed_msg, cJSON_CreateString("CLOSED"));
cJSON_AddItemToArray(closed_msg, cJSON_CreateString(sub_id));
cJSON_AddItemToArray(closed_msg, cJSON_CreateString("error: rate limited"));
cJSON_AddItemToArray(closed_msg, cJSON_CreateString(rate_limit_msg));
// Send CLOSED notice for rate limiting
cJSON* closed_msg = cJSON_CreateArray();
cJSON_AddItemToArray(closed_msg, cJSON_CreateString("CLOSED"));
cJSON_AddItemToArray(closed_msg, cJSON_CreateString(sub_id));
cJSON_AddItemToArray(closed_msg, cJSON_CreateString("error: rate limited"));
cJSON_AddItemToArray(closed_msg, cJSON_CreateString(rate_limit_msg));
char* closed_str = cJSON_Print(closed_msg);
if (closed_str) {
size_t closed_len = strlen(closed_str);
unsigned char* buf = malloc(LWS_PRE + closed_len);
if (buf) {
memcpy(buf + LWS_PRE, closed_str, closed_len);
lws_write(wsi, buf + LWS_PRE, closed_len, LWS_WRITE_TEXT);
free(buf);
char* closed_str = cJSON_Print(closed_msg);
if (closed_str) {
size_t closed_len = strlen(closed_str);
unsigned char* buf = malloc(LWS_PRE + closed_len);
if (buf) {
memcpy(buf + LWS_PRE, closed_str, closed_len);
lws_write(wsi, buf + LWS_PRE, closed_len, LWS_WRITE_TEXT);
free(buf);
}
free(closed_str);
}
free(closed_str);
cJSON_Delete(closed_msg);
// Do NOT increment rate limiting counters here - the client is already rate limited.
// Incrementing counters while already blocked would extend the punishment indefinitely,
// especially for benign requests like NDK's kind 99999 pings.
return 0;
}
cJSON_Delete(closed_msg);
// Update rate limiting counters
pss->failed_subscription_attempts++;
pss->last_failed_attempt = current_time;
return 0;
}
// Check session subscription limits
if (pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) {
DEBUG_ERROR("Maximum subscriptions per client exceeded");
// Update rate limiting counters for failed attempt
pss->failed_subscription_attempts++;
pss->last_failed_attempt = current_time;
pss->consecutive_failures++;
// Update rate limiting counters for failed attempt (but not for NDK ping)
if (!is_ndk_ping) {
pss->failed_subscription_attempts++;
pss->last_failed_attempt = current_time;
pss->consecutive_failures++;
}
// Implement progressive backoff: 1s, 5s, 30s, 300s (5min) based on consecutive failures
int backoff_seconds = 1;
@@ -1010,7 +1069,8 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
cJSON_Delete(closed_msg);
// Update rate limiting counters for failed attempt (global limit reached)
if (pss) {
// Do not count NDK ping (kind 99999) toward rate limiting
if (pss && !is_ndk_ping) {
time_t current_time = time(NULL);
pss->failed_subscription_attempts++;
pss->last_failed_attempt = current_time;

View File

@@ -13,8 +13,8 @@
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
#define CRELAY_VERSION_MAJOR 1
#define CRELAY_VERSION_MINOR 2
#define CRELAY_VERSION_PATCH 2
#define CRELAY_VERSION "v1.2.2"
#define CRELAY_VERSION_PATCH 3
#define CRELAY_VERSION "v1.2.3"
// Relay metadata (authoritative source for NIP-11 information)
#define RELAY_NAME "C-Relay"