diff --git a/src/main.c b/src/main.c index b79372f..dc5ea7e 100644 --- a/src/main.c +++ b/src/main.c @@ -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; diff --git a/src/main.h b/src/main.h index 810c5a6..bc47193 100644 --- a/src/main.h +++ b/src/main.h @@ -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"