From 086d2af56c4491787eda87e1d3a6d79aa2854cd3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 3 Feb 2026 13:19:33 -0400 Subject: [PATCH] v1.2.1 - Handle NDKs pings of kind 99999 --- src/main.h | 4 ++-- src/subscriptions.c | 20 +++++++++++++++++ src/websockets.c | 52 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/main.h b/src/main.h index 3bdb34d..7357f78 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 0 -#define CRELAY_VERSION "v1.2.0" +#define CRELAY_VERSION_PATCH 1 +#define CRELAY_VERSION "v1.2.1" // Relay metadata (authoritative source for NIP-11 information) #define RELAY_NAME "C-Relay" diff --git a/src/subscriptions.c b/src/subscriptions.c index 078c92f..cfbeca2 100644 --- a/src/subscriptions.c +++ b/src/subscriptions.c @@ -1456,6 +1456,10 @@ int validate_search_term(const char* search_term, char* error_message, size_t er /** * Validate all filter values in a filter object + * Returns: + * 1 = valid + * 0 = invalid (malformed, should count toward rate limit) + * -1 = invalid but benign (e.g., kind 99999 from NDK ping, should not count toward rate limit) */ int validate_filter_values(cJSON* filter_json, char* error_message, size_t error_size) { if (!filter_json || !cJSON_IsObject(filter_json)) { @@ -1463,6 +1467,8 @@ int validate_filter_values(cJSON* filter_json, char* error_message, size_t error return 0; } + int has_kind_99999 = 0; // Track if we encounter kind 99999 (NDK ping) + // Validate kinds array cJSON* kinds = cJSON_GetObjectItem(filter_json, "kinds"); if (kinds) { @@ -1485,11 +1491,25 @@ int validate_filter_values(cJSON* filter_json, char* error_message, size_t error } int kind_val = (int)cJSON_GetNumberValue(kind_item); + + // Special case: kind 99999 is used by NDK for ping/connectivity checks + // We reject it but don't count it as a malformed request + if (kind_val == 99999) { + has_kind_99999 = 1; + snprintf(error_message, error_size, "kinds[%d]: invalid event kind %d (used by NDK for ping)", i, kind_val); + continue; // Continue checking other kinds + } + if (kind_val < 0 || kind_val > 65535) { // Reasonable range for event kinds snprintf(error_message, error_size, "kinds[%d]: invalid event kind %d", i, kind_val); return 0; } } + + // If we only found kind 99999 and no other validation errors, return -1 (benign error) + if (has_kind_99999) { + return -1; + } } // Validate authors array diff --git a/src/websockets.c b/src/websockets.c index b156c71..9f06ed6 100644 --- a/src/websockets.c +++ b/src/websockets.c @@ -975,11 +975,15 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // Validate filters before processing char filter_error[512] = {0}; - if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) { + int validation_result = validate_filter_array(filters, filter_error, sizeof(filter_error)); + if (validation_result <= 0) { DEBUG_TRACE("REQ rejected: filter validation failed - %s", filter_error); send_notice_message(wsi, pss, filter_error); DEBUG_WARN("REQ rejected: invalid filters"); - record_malformed_request(pss); + // Only record as malformed if it's a true error (0), not benign error (-1) + if (validation_result == 0) { + record_malformed_request(pss); + } cJSON_Delete(filters); cJSON_Delete(json); // Note: complete_message points to reassembly_buffer, which is managed separately @@ -1060,10 +1064,14 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // Validate filters before processing char filter_error[512] = {0}; - if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) { + int validation_result = validate_filter_array(filters, filter_error, sizeof(filter_error)); + if (validation_result <= 0) { send_notice_message(wsi, pss, filter_error); DEBUG_WARN("COUNT rejected: invalid filters"); - record_malformed_request(pss); + // Only record as malformed if it's a true error (0), not benign error (-1) + if (validation_result == 0) { + record_malformed_request(pss); + } cJSON_Delete(filters); cJSON_Delete(json); // Note: complete_message points to reassembly_buffer, which is managed separately @@ -1673,11 +1681,15 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // Validate filters before processing char filter_error[512] = {0}; - if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) { + int validation_result = validate_filter_array(filters, filter_error, sizeof(filter_error)); + if (validation_result <= 0) { DEBUG_TRACE("REQ rejected: filter validation failed - %s", filter_error); send_notice_message(wsi, pss, filter_error); DEBUG_WARN("REQ rejected: invalid filters"); - record_malformed_request(pss); + // Only record as malformed if it's a true error (0), not benign error (-1) + if (validation_result == 0) { + record_malformed_request(pss); + } cJSON_Delete(filters); cJSON_Delete(json); free(message); @@ -1756,10 +1768,14 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // Validate filters before processing char filter_error[512] = {0}; - if (!validate_filter_array(filters, filter_error, sizeof(filter_error))) { + int validation_result = validate_filter_array(filters, filter_error, sizeof(filter_error)); + if (validation_result <= 0) { send_notice_message(wsi, pss, filter_error); DEBUG_WARN("COUNT rejected: invalid filters"); - record_malformed_request(pss); + // Only record as malformed if it's a true error (0), not benign error (-1) + if (validation_result == 0) { + record_malformed_request(pss); + } cJSON_Delete(filters); cJSON_Delete(json); free(message); @@ -2871,6 +2887,10 @@ int is_valid_hex_string(const char* str, size_t expected_len) { /** * Validate a filter array for REQ and COUNT messages + * Returns: + * 1 = valid + * 0 = invalid (malformed, should count toward rate limit) + * -1 = invalid but benign (e.g., kind 99999 from NDK ping, should not count toward rate limit) */ int validate_filter_array(cJSON* filters, char* error_message, size_t error_size) { if (!filters || !cJSON_IsArray(filters)) { @@ -2884,6 +2904,8 @@ int validate_filter_array(cJSON* filters, char* error_message, size_t error_size return 0; } + int has_kind_99999 = 0; // Track if we encounter kind 99999 (NDK ping) + // Validate each filter object for (int i = 0; i < filter_count; i++) { cJSON* filter = cJSON_GetArrayItem(filters, i); @@ -2964,6 +2986,15 @@ int validate_filter_array(cJSON* filters, char* error_message, size_t error_size return 0; } int kind_val = (int)cJSON_GetNumberValue(kind); + + // Special case: kind 99999 is used by NDK (Nostr Development Kit) for ping/connectivity checks + // We reject it but don't count it as a malformed request to avoid rate limiting NDK clients + if (kind_val == 99999) { + has_kind_99999 = 1; + snprintf(error_message, error_size, "error: invalid kind value %d (NDK ping)", kind_val); + continue; // Continue checking other kinds + } + if (kind_val < 0 || kind_val > MAX_KIND_VALUE) { snprintf(error_message, error_size, "error: invalid kind value %d", kind_val); return 0; @@ -3041,5 +3072,10 @@ int validate_filter_array(cJSON* filters, char* error_message, size_t error_size } } + // If we found kind 99999 (NDK ping), return -1 to indicate benign error + if (has_kind_99999) { + return -1; + } + return 1; // All filters valid }