From 0c2c67b463d6a90aaa0bb69bf3c91dba1d9ec3ec Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 17 May 2026 16:39:31 +0530 Subject: feat: per-client NAT filtering via LWIP_HOOK_IP4_CANFORWARD - Add lwip_tollgate_hooks.h defining LWIP_HOOK_IP4_CANFORWARD macro - Inject hook into lwIP build via CMakeLists.txt ESP_IDF_LWIP_HOOK_FILENAME - Filter forwarded packets by source IP against firewall allowed list - Only filter packets from AP subnet (10.192.45.0/24), allow all others - Fix byte order bug: use network byte order for firewall_is_client_allowed - NAT always enabled, removed global NAT toggle functions - Remove spent-secret tracking from session.c (mint is authority) - Remove unused get_ap_netif() function - Reduce API server stack from 32KB to 16KB (fixes ESP_ERR_HTTPD_TASK) - Add esp_random.h stub for unit tests - All 186 unit tests passing - Verified on hardware: block->pay->allow->revoke->block E2E works --- main/firewall.c | 51 ++++++++++++++-------------------------------- main/firewall.h | 6 ++++-- main/lwip_tollgate_hooks.h | 10 +++++++++ main/session.c | 48 +++---------------------------------------- main/session.h | 10 ++------- main/tollgate_api.c | 25 +++-------------------- main/tollgate_main.c | 1 - 7 files changed, 37 insertions(+), 114 deletions(-) create mode 100644 main/lwip_tollgate_hooks.h (limited to 'main') diff --git a/main/firewall.c b/main/firewall.c index f349ab1..8d535b4 100644 --- a/main/firewall.c +++ b/main/firewall.c @@ -6,13 +6,13 @@ #include "lwip/lwip_napt.h" #include "lwip/etharp.h" #include "lwip/netif.h" +#include "lwip/prot/ip4.h" #include #define MAX_CLIENTS 10 static const char *TAG = "firewall"; static esp_ip4_addr_t s_ap_ip; -static bool s_nat_enabled = false; typedef struct { uint32_t ip; @@ -22,11 +22,6 @@ typedef struct { static fw_client_t s_clients[MAX_CLIENTS]; static int s_client_count = 0; -static struct netif *get_ap_netif(void) -{ - return netif_get_by_index(NETIF_NO_INDEX); -} - esp_err_t firewall_get_mac_for_ip(uint32_t client_ip, char *mac_out, size_t mac_out_size) { wifi_sta_list_t sta_list; @@ -66,38 +61,25 @@ esp_err_t firewall_init(esp_ip4_addr_t ap_ip) s_ap_ip = ap_ip; memset(s_clients, 0, sizeof(s_clients)); s_client_count = 0; - ESP_LOGI(TAG, "Firewall initialized with AP IP=" IPSTR, IP2STR(&s_ap_ip)); + ip_napt_enable(s_ap_ip.addr, 1); + ESP_LOGI(TAG, "Firewall initialized with AP IP=" IPSTR " (NAT always on, per-client filter)", IP2STR(&s_ap_ip)); return ESP_OK; } -static void update_nat(void) +int tollgate_ip4_canforward_filter(struct pbuf *p, u32_t dest_addr_hostorder) { - bool should_enable = (s_client_count > 0); - if (should_enable && !s_nat_enabled) { - ip_napt_enable(s_ap_ip.addr, 1); - s_nat_enabled = true; - ESP_LOGI(TAG, "NAT enabled (client authenticated)"); - } else if (!should_enable && s_nat_enabled) { - ip_napt_enable(s_ap_ip.addr, 0); - s_nat_enabled = false; - ESP_LOGI(TAG, "NAT disabled (no authenticated clients)"); + (void)dest_addr_hostorder; + if (p->len < IP_HLEN) return -1; + struct ip_hdr *iphdr = (struct ip_hdr *)p->payload; + uint32_t src_ip_h = lwip_ntohl(iphdr->src.addr); + uint32_t ap_subnet = lwip_ntohl(s_ap_ip.addr) & 0xFFFFFF00; + if ((src_ip_h & 0xFFFFFF00) != ap_subnet) { + return 1; } -} - -void firewall_enable_nat(void) -{ - if (s_nat_enabled) return; - ip_napt_enable(s_ap_ip.addr, 1); - s_nat_enabled = true; - ESP_LOGI(TAG, "NAT enabled"); -} - -void firewall_disable_nat(void) -{ - if (!s_nat_enabled) return; - ip_napt_enable(s_ap_ip.addr, 0); - s_nat_enabled = false; - ESP_LOGI(TAG, "NAT disabled"); + if (firewall_is_client_allowed(iphdr->src.addr)) { + return 1; + } + return 0; } static fw_client_t *find_client_by_ip(uint32_t client_ip) @@ -137,7 +119,6 @@ void firewall_grant_access(uint32_t client_ip) s_client_count++; dns_server_set_client_authenticated(client_ip, true); - update_nat(); esp_ip4_addr_t ip_addr = { .addr = client_ip }; ESP_LOGI(TAG, "Access granted to " IPSTR " mac=%s", IP2STR(&ip_addr), @@ -154,7 +135,6 @@ void firewall_revoke_access(uint32_t client_ip) s_clients[i] = s_clients[s_client_count - 1]; s_client_count--; dns_server_set_client_authenticated(client_ip, false); - update_nat(); return; } } @@ -166,7 +146,6 @@ void firewall_revoke_all(void) dns_server_set_client_authenticated(s_clients[i].ip, false); } s_client_count = 0; - update_nat(); ESP_LOGI(TAG, "All client access revoked"); } diff --git a/main/firewall.h b/main/firewall.h index e5d492a..f177eaa 100644 --- a/main/firewall.h +++ b/main/firewall.h @@ -6,11 +6,11 @@ #include #include +struct pbuf; + #define FW_MAX_MAC_LEN 18 esp_err_t firewall_init(esp_ip4_addr_t ap_ip); -void firewall_enable_nat(void); -void firewall_disable_nat(void); void firewall_grant_access(uint32_t client_ip); void firewall_revoke_access(uint32_t client_ip); void firewall_revoke_all(void); @@ -20,4 +20,6 @@ int firewall_client_count(void); esp_err_t firewall_get_mac_for_ip(uint32_t client_ip, char *mac_out, size_t mac_out_size); +int tollgate_ip4_canforward_filter(struct pbuf *p, uint32_t dest_addr_hostorder); + #endif diff --git a/main/lwip_tollgate_hooks.h b/main/lwip_tollgate_hooks.h new file mode 100644 index 0000000..76017be --- /dev/null +++ b/main/lwip_tollgate_hooks.h @@ -0,0 +1,10 @@ +#ifndef LWIP_TOLLGATE_HOOKS_H +#define LWIP_TOLLGATE_HOOKS_H + +#include "lwip/pbuf.h" + +int tollgate_ip4_canforward_filter(struct pbuf *p, u32_t dest_addr_hostorder); + +#define LWIP_HOOK_IP4_CANFORWARD(p, addr) tollgate_ip4_canforward_filter(p, addr) + +#endif diff --git a/main/session.c b/main/session.c index 4854163..9b4380c 100644 --- a/main/session.c +++ b/main/session.c @@ -7,15 +7,10 @@ #include "freertos/task.h" #include -#define SPENT_SECRETS_MAX 100 - static const char *TAG = "session"; static session_t s_sessions[SESSION_MAX_CLIENTS]; static int s_session_count = 0; -static char s_spent_secrets[SPENT_SECRETS_MAX][65]; -static int s_spent_count = 0; - static int64_t get_time_ms(void) { return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; @@ -25,7 +20,6 @@ esp_err_t session_manager_init(void) { memset(s_sessions, 0, sizeof(s_sessions)); s_session_count = 0; - s_spent_count = 0; ESP_LOGI(TAG, "Session manager initialized"); return ESP_OK; } @@ -37,27 +31,14 @@ static void populate_mac(session_t *session, uint32_t client_ip) } } -session_t *session_create(uint32_t client_ip, uint64_t allotment_ms, - const char *spent_secrets[], int secret_count) +session_t *session_create(uint32_t client_ip, uint64_t allotment_ms) { session_t *existing = session_find_by_ip(client_ip); if (existing) { session_extend(existing, allotment_ms); - for (int i = 0; i < secret_count && s_spent_count < SPENT_SECRETS_MAX; i++) { - strncpy(s_spent_secrets[s_spent_count], spent_secrets[i], 64); - s_spent_secrets[s_spent_count][64] = '\0'; - s_spent_count++; - } return existing; } - for (int i = 0; i < secret_count; i++) { - if (session_is_secret_spent(spent_secrets[i])) { - ESP_LOGW(TAG, "Duplicate secret rejected"); - return NULL; - } - } - if (s_session_count >= SESSION_MAX_CLIENTS) { for (int i = 0; i < SESSION_MAX_CLIENTS; i++) { if (!s_sessions[i].active || session_is_expired(&s_sessions[i])) { @@ -73,22 +54,8 @@ session_t *session_create(uint32_t client_ip, uint64_t allotment_ms, s_sessions[i].allotment_ms = allotment_ms; s_sessions[i].start_time_ms = get_time_ms(); s_sessions[i].active = true; - s_sessions[i].spent_secret_count = 0; populate_mac(&s_sessions[i], client_ip); - for (int j = 0; j < secret_count && j < 5; j++) { - strncpy(s_sessions[i].spent_secrets[s_sessions[i].spent_secret_count], - spent_secrets[j], 64); - s_sessions[i].spent_secrets[s_sessions[i].spent_secret_count][64] = '\0'; - s_sessions[i].spent_secret_count++; - } - - for (int j = 0; j < secret_count && s_spent_count < SPENT_SECRETS_MAX; j++) { - strncpy(s_spent_secrets[s_spent_count], spent_secrets[j], 64); - s_spent_secrets[s_spent_count][64] = '\0'; - s_spent_count++; - } - s_session_count++; firewall_grant_access(client_ip); @@ -104,10 +71,9 @@ session_t *session_create(uint32_t client_ip, uint64_t allotment_ms, return NULL; } -session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes, - const char *spent_secrets[], int secret_count) +session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes) { - session_t *s = session_create(client_ip, 0, spent_secrets, secret_count); + session_t *s = session_create(client_ip, 0); if (s) { s->allotment_bytes = allotment_bytes; s->bytes_consumed = 0; @@ -170,14 +136,6 @@ bool session_is_expired(const session_t *session) return elapsed >= (int64_t)session->allotment_ms; } -bool session_is_secret_spent(const char *secret) -{ - for (int i = 0; i < s_spent_count; i++) { - if (strncmp(s_spent_secrets[i], secret, 64) == 0) return true; - } - return false; -} - void session_check_expiry(void) { for (int i = 0; i < SESSION_MAX_CLIENTS; i++) { diff --git a/main/session.h b/main/session.h index 6282f5a..ea5b476 100644 --- a/main/session.h +++ b/main/session.h @@ -16,17 +16,13 @@ typedef struct { uint64_t allotment_bytes; uint64_t bytes_consumed; bool active; - char spent_secrets[5][65]; - int spent_secret_count; } session_t; esp_err_t session_manager_init(void); -session_t *session_create(uint32_t client_ip, uint64_t allotment_ms, - const char *spent_secrets[], int secret_count); +session_t *session_create(uint32_t client_ip, uint64_t allotment_ms); -session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes, - const char *spent_secrets[], int secret_count); +session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes); void session_add_bytes(uint32_t client_ip, uint64_t bytes); @@ -37,8 +33,6 @@ void session_extend(session_t *session, uint64_t additional_ms); bool session_is_expired(const session_t *session); -bool session_is_secret_spent(const char *secret); - void session_check_expiry(void); void session_revoke(session_t *session); diff --git a/main/tollgate_api.c b/main/tollgate_api.c index 25e7dd2..650b0f3 100644 --- a/main/tollgate_api.c +++ b/main/tollgate_api.c @@ -224,20 +224,6 @@ static esp_err_t api_post_payment(httpd_req_t *req) return ESP_OK; } - for (int i = 0; i < token->proof_count; i++) { - if (session_is_secret_spent(token->proofs[i].secret)) { - free(token); - cJSON *notice = create_notice("error", "payment-error-token-spent", "Token already spent"); - char *json = cJSON_PrintUnformatted(notice); - httpd_resp_set_status(req, "402 Payment Required"); - httpd_resp_set_type(req, "application/json"); - httpd_resp_send(req, json, strlen(json)); - cJSON_free(json); - cJSON_Delete(notice); - return ESP_OK; - } - } - cashu_proof_state_t *states = malloc(CASHU_MAX_PROOFS * sizeof(cashu_proof_state_t)); if (!states) { free(token); @@ -299,16 +285,11 @@ static esp_err_t api_post_payment(httpd_req_t *req) return ESP_OK; } - int secret_count = token->proof_count > 5 ? 5 : token->proof_count; - const char *secrets[5]; - for (int i = 0; i < secret_count; i++) { - secrets[i] = token->proofs[i].secret; - } session_t *session; if (is_bytes) { - session = session_create_bytes(client_ip, allotment, secrets, secret_count); + session = session_create_bytes(client_ip, allotment); } else { - session = session_create(client_ip, allotment, secrets, secret_count); + session = session_create(client_ip, allotment); } if (!session) { free(states); @@ -498,7 +479,7 @@ esp_err_t tollgate_api_start(void) config.server_port = 2121; config.ctrl_port = 32769; config.max_uri_handlers = 10; - config.stack_size = 32768; + config.stack_size = 16384; esp_err_t ret = httpd_start(&s_api_server, &config); if (ret != ESP_OK) { diff --git a/main/tollgate_main.c b/main/tollgate_main.c index 2670f05..41dbbed 100644 --- a/main/tollgate_main.c +++ b/main/tollgate_main.c @@ -174,7 +174,6 @@ static void stop_services(void) tollgate_api_stop(); dns_server_stop(); cvm_server_stop(); - firewall_disable_nat(); firewall_revoke_all(); s_services_running = false; if (s_services_mutex) xSemaphoreGive(s_services_mutex); -- cgit v1.2.3