upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-17 16:39:31 +0530
committerYour Name <you@example.com>2026-05-17 16:39:31 +0530
commit0c2c67b463d6a90aaa0bb69bf3c91dba1d9ec3ec (patch)
treeafd9d9bca2d184825ebf7413ec31830e14131030 /main
parent3342c8e7b4f645c75470d3d893d09037a672cfd2 (diff)
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
Diffstat (limited to 'main')
-rw-r--r--main/firewall.c51
-rw-r--r--main/firewall.h6
-rw-r--r--main/lwip_tollgate_hooks.h10
-rw-r--r--main/session.c48
-rw-r--r--main/session.h10
-rw-r--r--main/tollgate_api.c25
-rw-r--r--main/tollgate_main.c1
7 files changed, 37 insertions, 114 deletions
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 @@
6#include "lwip/lwip_napt.h" 6#include "lwip/lwip_napt.h"
7#include "lwip/etharp.h" 7#include "lwip/etharp.h"
8#include "lwip/netif.h" 8#include "lwip/netif.h"
9#include "lwip/prot/ip4.h"
9#include <string.h> 10#include <string.h>
10 11
11#define MAX_CLIENTS 10 12#define MAX_CLIENTS 10
12 13
13static const char *TAG = "firewall"; 14static const char *TAG = "firewall";
14static esp_ip4_addr_t s_ap_ip; 15static esp_ip4_addr_t s_ap_ip;
15static bool s_nat_enabled = false;
16 16
17typedef struct { 17typedef struct {
18 uint32_t ip; 18 uint32_t ip;
@@ -22,11 +22,6 @@ typedef struct {
22static fw_client_t s_clients[MAX_CLIENTS]; 22static fw_client_t s_clients[MAX_CLIENTS];
23static int s_client_count = 0; 23static int s_client_count = 0;
24 24
25static struct netif *get_ap_netif(void)
26{
27 return netif_get_by_index(NETIF_NO_INDEX);
28}
29
30esp_err_t firewall_get_mac_for_ip(uint32_t client_ip, char *mac_out, size_t mac_out_size) 25esp_err_t firewall_get_mac_for_ip(uint32_t client_ip, char *mac_out, size_t mac_out_size)
31{ 26{
32 wifi_sta_list_t sta_list; 27 wifi_sta_list_t sta_list;
@@ -66,38 +61,25 @@ esp_err_t firewall_init(esp_ip4_addr_t ap_ip)
66 s_ap_ip = ap_ip; 61 s_ap_ip = ap_ip;
67 memset(s_clients, 0, sizeof(s_clients)); 62 memset(s_clients, 0, sizeof(s_clients));
68 s_client_count = 0; 63 s_client_count = 0;
69 ESP_LOGI(TAG, "Firewall initialized with AP IP=" IPSTR, IP2STR(&s_ap_ip)); 64 ip_napt_enable(s_ap_ip.addr, 1);
65 ESP_LOGI(TAG, "Firewall initialized with AP IP=" IPSTR " (NAT always on, per-client filter)", IP2STR(&s_ap_ip));
70 return ESP_OK; 66 return ESP_OK;
71} 67}
72 68
73static void update_nat(void) 69int tollgate_ip4_canforward_filter(struct pbuf *p, u32_t dest_addr_hostorder)
74{ 70{
75 bool should_enable = (s_client_count > 0); 71 (void)dest_addr_hostorder;
76 if (should_enable && !s_nat_enabled) { 72 if (p->len < IP_HLEN) return -1;
77 ip_napt_enable(s_ap_ip.addr, 1); 73 struct ip_hdr *iphdr = (struct ip_hdr *)p->payload;
78 s_nat_enabled = true; 74 uint32_t src_ip_h = lwip_ntohl(iphdr->src.addr);
79 ESP_LOGI(TAG, "NAT enabled (client authenticated)"); 75 uint32_t ap_subnet = lwip_ntohl(s_ap_ip.addr) & 0xFFFFFF00;
80 } else if (!should_enable && s_nat_enabled) { 76 if ((src_ip_h & 0xFFFFFF00) != ap_subnet) {
81 ip_napt_enable(s_ap_ip.addr, 0); 77 return 1;
82 s_nat_enabled = false;
83 ESP_LOGI(TAG, "NAT disabled (no authenticated clients)");
84 } 78 }
85} 79 if (firewall_is_client_allowed(iphdr->src.addr)) {
86 80 return 1;
87void firewall_enable_nat(void) 81 }
88{ 82 return 0;
89 if (s_nat_enabled) return;
90 ip_napt_enable(s_ap_ip.addr, 1);
91 s_nat_enabled = true;
92 ESP_LOGI(TAG, "NAT enabled");
93}
94
95void firewall_disable_nat(void)
96{
97 if (!s_nat_enabled) return;
98 ip_napt_enable(s_ap_ip.addr, 0);
99 s_nat_enabled = false;
100 ESP_LOGI(TAG, "NAT disabled");
101} 83}
102 84
103static fw_client_t *find_client_by_ip(uint32_t client_ip) 85static fw_client_t *find_client_by_ip(uint32_t client_ip)
@@ -137,7 +119,6 @@ void firewall_grant_access(uint32_t client_ip)
137 s_client_count++; 119 s_client_count++;
138 120
139 dns_server_set_client_authenticated(client_ip, true); 121 dns_server_set_client_authenticated(client_ip, true);
140 update_nat();
141 122
142 esp_ip4_addr_t ip_addr = { .addr = client_ip }; 123 esp_ip4_addr_t ip_addr = { .addr = client_ip };
143 ESP_LOGI(TAG, "Access granted to " IPSTR " mac=%s", IP2STR(&ip_addr), 124 ESP_LOGI(TAG, "Access granted to " IPSTR " mac=%s", IP2STR(&ip_addr),
@@ -154,7 +135,6 @@ void firewall_revoke_access(uint32_t client_ip)
154 s_clients[i] = s_clients[s_client_count - 1]; 135 s_clients[i] = s_clients[s_client_count - 1];
155 s_client_count--; 136 s_client_count--;
156 dns_server_set_client_authenticated(client_ip, false); 137 dns_server_set_client_authenticated(client_ip, false);
157 update_nat();
158 return; 138 return;
159 } 139 }
160 } 140 }
@@ -166,7 +146,6 @@ void firewall_revoke_all(void)
166 dns_server_set_client_authenticated(s_clients[i].ip, false); 146 dns_server_set_client_authenticated(s_clients[i].ip, false);
167 } 147 }
168 s_client_count = 0; 148 s_client_count = 0;
169 update_nat();
170 ESP_LOGI(TAG, "All client access revoked"); 149 ESP_LOGI(TAG, "All client access revoked");
171} 150}
172 151
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 @@
6#include <stdbool.h> 6#include <stdbool.h>
7#include <stdint.h> 7#include <stdint.h>
8 8
9struct pbuf;
10
9#define FW_MAX_MAC_LEN 18 11#define FW_MAX_MAC_LEN 18
10 12
11esp_err_t firewall_init(esp_ip4_addr_t ap_ip); 13esp_err_t firewall_init(esp_ip4_addr_t ap_ip);
12void firewall_enable_nat(void);
13void firewall_disable_nat(void);
14void firewall_grant_access(uint32_t client_ip); 14void firewall_grant_access(uint32_t client_ip);
15void firewall_revoke_access(uint32_t client_ip); 15void firewall_revoke_access(uint32_t client_ip);
16void firewall_revoke_all(void); 16void firewall_revoke_all(void);
@@ -20,4 +20,6 @@ int firewall_client_count(void);
20 20
21esp_err_t firewall_get_mac_for_ip(uint32_t client_ip, char *mac_out, size_t mac_out_size); 21esp_err_t firewall_get_mac_for_ip(uint32_t client_ip, char *mac_out, size_t mac_out_size);
22 22
23int tollgate_ip4_canforward_filter(struct pbuf *p, uint32_t dest_addr_hostorder);
24
23#endif 25#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 @@
1#ifndef LWIP_TOLLGATE_HOOKS_H
2#define LWIP_TOLLGATE_HOOKS_H
3
4#include "lwip/pbuf.h"
5
6int tollgate_ip4_canforward_filter(struct pbuf *p, u32_t dest_addr_hostorder);
7
8#define LWIP_HOOK_IP4_CANFORWARD(p, addr) tollgate_ip4_canforward_filter(p, addr)
9
10#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 @@
7#include "freertos/task.h" 7#include "freertos/task.h"
8#include <string.h> 8#include <string.h>
9 9
10#define SPENT_SECRETS_MAX 100
11
12static const char *TAG = "session"; 10static const char *TAG = "session";
13static session_t s_sessions[SESSION_MAX_CLIENTS]; 11static session_t s_sessions[SESSION_MAX_CLIENTS];
14static int s_session_count = 0; 12static int s_session_count = 0;
15 13
16static char s_spent_secrets[SPENT_SECRETS_MAX][65];
17static int s_spent_count = 0;
18
19static int64_t get_time_ms(void) 14static int64_t get_time_ms(void)
20{ 15{
21 return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; 16 return (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS;
@@ -25,7 +20,6 @@ esp_err_t session_manager_init(void)
25{ 20{
26 memset(s_sessions, 0, sizeof(s_sessions)); 21 memset(s_sessions, 0, sizeof(s_sessions));
27 s_session_count = 0; 22 s_session_count = 0;
28 s_spent_count = 0;
29 ESP_LOGI(TAG, "Session manager initialized"); 23 ESP_LOGI(TAG, "Session manager initialized");
30 return ESP_OK; 24 return ESP_OK;
31} 25}
@@ -37,27 +31,14 @@ static void populate_mac(session_t *session, uint32_t client_ip)
37 } 31 }
38} 32}
39 33
40session_t *session_create(uint32_t client_ip, uint64_t allotment_ms, 34session_t *session_create(uint32_t client_ip, uint64_t allotment_ms)
41 const char *spent_secrets[], int secret_count)
42{ 35{
43 session_t *existing = session_find_by_ip(client_ip); 36 session_t *existing = session_find_by_ip(client_ip);
44 if (existing) { 37 if (existing) {
45 session_extend(existing, allotment_ms); 38 session_extend(existing, allotment_ms);
46 for (int i = 0; i < secret_count && s_spent_count < SPENT_SECRETS_MAX; i++) {
47 strncpy(s_spent_secrets[s_spent_count], spent_secrets[i], 64);
48 s_spent_secrets[s_spent_count][64] = '\0';
49 s_spent_count++;
50 }
51 return existing; 39 return existing;
52 } 40 }
53 41
54 for (int i = 0; i < secret_count; i++) {
55 if (session_is_secret_spent(spent_secrets[i])) {
56 ESP_LOGW(TAG, "Duplicate secret rejected");
57 return NULL;
58 }
59 }
60
61 if (s_session_count >= SESSION_MAX_CLIENTS) { 42 if (s_session_count >= SESSION_MAX_CLIENTS) {
62 for (int i = 0; i < SESSION_MAX_CLIENTS; i++) { 43 for (int i = 0; i < SESSION_MAX_CLIENTS; i++) {
63 if (!s_sessions[i].active || session_is_expired(&s_sessions[i])) { 44 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,
73 s_sessions[i].allotment_ms = allotment_ms; 54 s_sessions[i].allotment_ms = allotment_ms;
74 s_sessions[i].start_time_ms = get_time_ms(); 55 s_sessions[i].start_time_ms = get_time_ms();
75 s_sessions[i].active = true; 56 s_sessions[i].active = true;
76 s_sessions[i].spent_secret_count = 0;
77 populate_mac(&s_sessions[i], client_ip); 57 populate_mac(&s_sessions[i], client_ip);
78 58
79 for (int j = 0; j < secret_count && j < 5; j++) {
80 strncpy(s_sessions[i].spent_secrets[s_sessions[i].spent_secret_count],
81 spent_secrets[j], 64);
82 s_sessions[i].spent_secrets[s_sessions[i].spent_secret_count][64] = '\0';
83 s_sessions[i].spent_secret_count++;
84 }
85
86 for (int j = 0; j < secret_count && s_spent_count < SPENT_SECRETS_MAX; j++) {
87 strncpy(s_spent_secrets[s_spent_count], spent_secrets[j], 64);
88 s_spent_secrets[s_spent_count][64] = '\0';
89 s_spent_count++;
90 }
91
92 s_session_count++; 59 s_session_count++;
93 firewall_grant_access(client_ip); 60 firewall_grant_access(client_ip);
94 61
@@ -104,10 +71,9 @@ session_t *session_create(uint32_t client_ip, uint64_t allotment_ms,
104 return NULL; 71 return NULL;
105} 72}
106 73
107session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes, 74session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes)
108 const char *spent_secrets[], int secret_count)
109{ 75{
110 session_t *s = session_create(client_ip, 0, spent_secrets, secret_count); 76 session_t *s = session_create(client_ip, 0);
111 if (s) { 77 if (s) {
112 s->allotment_bytes = allotment_bytes; 78 s->allotment_bytes = allotment_bytes;
113 s->bytes_consumed = 0; 79 s->bytes_consumed = 0;
@@ -170,14 +136,6 @@ bool session_is_expired(const session_t *session)
170 return elapsed >= (int64_t)session->allotment_ms; 136 return elapsed >= (int64_t)session->allotment_ms;
171} 137}
172 138
173bool session_is_secret_spent(const char *secret)
174{
175 for (int i = 0; i < s_spent_count; i++) {
176 if (strncmp(s_spent_secrets[i], secret, 64) == 0) return true;
177 }
178 return false;
179}
180
181void session_check_expiry(void) 139void session_check_expiry(void)
182{ 140{
183 for (int i = 0; i < SESSION_MAX_CLIENTS; i++) { 141 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 {
16 uint64_t allotment_bytes; 16 uint64_t allotment_bytes;
17 uint64_t bytes_consumed; 17 uint64_t bytes_consumed;
18 bool active; 18 bool active;
19 char spent_secrets[5][65];
20 int spent_secret_count;
21} session_t; 19} session_t;
22 20
23esp_err_t session_manager_init(void); 21esp_err_t session_manager_init(void);
24 22
25session_t *session_create(uint32_t client_ip, uint64_t allotment_ms, 23session_t *session_create(uint32_t client_ip, uint64_t allotment_ms);
26 const char *spent_secrets[], int secret_count);
27 24
28session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes, 25session_t *session_create_bytes(uint32_t client_ip, uint64_t allotment_bytes);
29 const char *spent_secrets[], int secret_count);
30 26
31void session_add_bytes(uint32_t client_ip, uint64_t bytes); 27void session_add_bytes(uint32_t client_ip, uint64_t bytes);
32 28
@@ -37,8 +33,6 @@ void session_extend(session_t *session, uint64_t additional_ms);
37 33
38bool session_is_expired(const session_t *session); 34bool session_is_expired(const session_t *session);
39 35
40bool session_is_secret_spent(const char *secret);
41
42void session_check_expiry(void); 36void session_check_expiry(void);
43 37
44void session_revoke(session_t *session); 38void 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)
224 return ESP_OK; 224 return ESP_OK;
225 } 225 }
226 226
227 for (int i = 0; i < token->proof_count; i++) {
228 if (session_is_secret_spent(token->proofs[i].secret)) {
229 free(token);
230 cJSON *notice = create_notice("error", "payment-error-token-spent", "Token already spent");
231 char *json = cJSON_PrintUnformatted(notice);
232 httpd_resp_set_status(req, "402 Payment Required");
233 httpd_resp_set_type(req, "application/json");
234 httpd_resp_send(req, json, strlen(json));
235 cJSON_free(json);
236 cJSON_Delete(notice);
237 return ESP_OK;
238 }
239 }
240
241 cashu_proof_state_t *states = malloc(CASHU_MAX_PROOFS * sizeof(cashu_proof_state_t)); 227 cashu_proof_state_t *states = malloc(CASHU_MAX_PROOFS * sizeof(cashu_proof_state_t));
242 if (!states) { 228 if (!states) {
243 free(token); 229 free(token);
@@ -299,16 +285,11 @@ static esp_err_t api_post_payment(httpd_req_t *req)
299 return ESP_OK; 285 return ESP_OK;
300 } 286 }
301 287
302 int secret_count = token->proof_count > 5 ? 5 : token->proof_count;
303 const char *secrets[5];
304 for (int i = 0; i < secret_count; i++) {
305 secrets[i] = token->proofs[i].secret;
306 }
307 session_t *session; 288 session_t *session;
308 if (is_bytes) { 289 if (is_bytes) {
309 session = session_create_bytes(client_ip, allotment, secrets, secret_count); 290 session = session_create_bytes(client_ip, allotment);
310 } else { 291 } else {
311 session = session_create(client_ip, allotment, secrets, secret_count); 292 session = session_create(client_ip, allotment);
312 } 293 }
313 if (!session) { 294 if (!session) {
314 free(states); 295 free(states);
@@ -498,7 +479,7 @@ esp_err_t tollgate_api_start(void)
498 config.server_port = 2121; 479 config.server_port = 2121;
499 config.ctrl_port = 32769; 480 config.ctrl_port = 32769;
500 config.max_uri_handlers = 10; 481 config.max_uri_handlers = 10;
501 config.stack_size = 32768; 482 config.stack_size = 16384;
502 483
503 esp_err_t ret = httpd_start(&s_api_server, &config); 484 esp_err_t ret = httpd_start(&s_api_server, &config);
504 if (ret != ESP_OK) { 485 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)
174 tollgate_api_stop(); 174 tollgate_api_stop();
175 dns_server_stop(); 175 dns_server_stop();
176 cvm_server_stop(); 176 cvm_server_stop();
177 firewall_disable_nat();
178 firewall_revoke_all(); 177 firewall_revoke_all();
179 s_services_running = false; 178 s_services_running = false;
180 if (s_services_mutex) xSemaphoreGive(s_services_mutex); 179 if (s_services_mutex) xSemaphoreGive(s_services_mutex);