diff options
Diffstat (limited to 'PLAN.md')
| -rw-r--r-- | PLAN.md | 75 |
1 files changed, 71 insertions, 4 deletions
| @@ -286,7 +286,7 @@ Publishes TollGate node to Nostr as kind 38787 (wifistr): | |||
| 286 | | 37 | 5 consecutive payments | Loop | All authenticated | TODO | | 286 | | 37 | 5 consecutive payments | Loop | All authenticated | TODO | |
| 287 | | 38 | Stress: rapid pay/expire | Loop with short sessions | No crash/leak | TODO | | 287 | | 38 | Stress: rapid pay/expire | Loop with short sessions | No crash/leak | TODO | |
| 288 | 288 | ||
| 289 | ### Phase 4: ESP32 TollGate Client Detection + Auto-Payment — IN PROGRESS | 289 | ### Phase 4: ESP32 TollGate Client Detection + Auto-Payment — COMPLETE |
| 290 | 290 | ||
| 291 | **Goal:** ESP32 detects upstream TollGate when connected as STA, automatically pays for internet access using on-device wallet. Enables ESP32→OpenWRT (Scenario 4) and ESP32→ESP32 (Scenario 5) auto-payment. | 291 | **Goal:** ESP32 detects upstream TollGate when connected as STA, automatically pays for internet access using on-device wallet. Enables ESP32→OpenWRT (Scenario 4) and ESP32→ESP32 (Scenario 5) auto-payment. |
| 292 | 292 | ||
| @@ -354,7 +354,7 @@ IDLE → [STA got IP] → DETECTING → [kind=10021 found] → NEEDS_PAY | |||
| 354 | 354 | ||
| 355 | Pre-association price discovery via Wi-Fi Vendor IE beacons (OUI `0x54:0x47`) is deferred to a future phase. The client currently uses HTTP-based discovery after connection. | 355 | Pre-association price discovery via Wi-Fi Vendor IE beacons (OUI `0x54:0x47`) is deferred to a future phase. The client currently uses HTTP-based discovery after connection. |
| 356 | 356 | ||
| 357 | ### Phase 5: Lightning Auto-Payout — NOT STARTED | 357 | ### Phase 5: Lightning Auto-Payout — COMPLETE |
| 358 | 358 | ||
| 359 | **Goal:** When wallet balance exceeds a configurable threshold, automatically pay out to Lightning addresses via LNURL-pay + Cashu NUT-05 melt. | 359 | **Goal:** When wallet balance exceeds a configurable threshold, automatically pay out to Lightning addresses via LNURL-pay + Cashu NUT-05 melt. |
| 360 | 360 | ||
| @@ -420,7 +420,7 @@ Wraps `Wallet::request_melt_quote()` + `Wallet::melt_tokens()` (NUT-05). | |||
| 420 | | 47 | Melt with fee tolerance | Integration | Invoice paid, change received | TODO | | 420 | | 47 | Melt with fee tolerance | Integration | Invoice paid, change received | TODO | |
| 421 | | 48 | Full payout cycle | E2E | Wallet drains to min_balance | TODO | | 421 | | 48 | Full payout cycle | E2E | Wallet drains to min_balance | TODO | |
| 422 | 422 | ||
| 423 | ### Phase 6: Bytes-Based Billing — NOT STARTED | 423 | ### Phase 6: Bytes-Based Billing — COMPLETE |
| 424 | 424 | ||
| 425 | **Goal:** Support both time-based (milliseconds) and data-based (bytes) billing metrics. Mirrors the Go implementation's dual-metric system. | 425 | **Goal:** Support both time-based (milliseconds) and data-based (bytes) billing metrics. Mirrors the Go implementation's dual-metric system. |
| 426 | 426 | ||
| @@ -473,7 +473,7 @@ uint64_t bytes_consumed; | |||
| 473 | | 51 | NAPT byte counting | Integration | Counters match actual traffic | TODO | | 473 | | 51 | NAPT byte counting | Integration | Counters match actual traffic | TODO | |
| 474 | | 52 | Bytes metric end-to-end | E2E | Client disconnected after data cap | TODO | | 474 | | 52 | Bytes metric end-to-end | E2E | Client disconnected after data cap | TODO | |
| 475 | 475 | ||
| 476 | ### Phase 7: ContextVM Server (MCP over Nostr) — NOT STARTED | 476 | ### Phase 7: ContextVM Server (MCP over Nostr) — COMPLETE |
| 477 | 477 | ||
| 478 | **Goal:** Remote configuration of ESP32 TollGate via ContextVM — services communicate over Nostr using public keys as addresses. Exposes configuration as MCP tools accessible by both humans and AI agents. | 478 | **Goal:** Remote configuration of ESP32 TollGate via ContextVM — services communicate over Nostr using public keys as addresses. Exposes configuration as MCP tools accessible by both humans and AI agents. |
| 479 | 479 | ||
| @@ -524,6 +524,73 @@ Only accept commands from owner npub (derived from nsec in config.json). | |||
| 524 | 524 | ||
| 525 | ## Total: 56 Tests across 7 phases | 525 | ## Total: 56 Tests across 7 phases |
| 526 | 526 | ||
| 527 | ## Post-Phase 7: Bug Fixes & Architecture Improvements | ||
| 528 | |||
| 529 | ### Per-Client NAT Filtering (Multi-Client Isolation Fix) | ||
| 530 | |||
| 531 | **Problem:** When client A's session expires but client B is still active, NAPT stays enabled globally. Client A's existing TCP/UDP connections in the NAT table continue routing — their traffic reaches the internet even though they're unauthenticated. | ||
| 532 | |||
| 533 | **Solution:** Use lwIP's `LWIP_HOOK_IP4_CANFORWARD` hook to filter forwarded packets by source IP. This fires in `ip4_forward()` **before** NAPT translation, so unauthorized clients are blocked at the IP layer. | ||
| 534 | |||
| 535 | **Architecture Change:** | ||
| 536 | - **Before:** NAT is a global toggle — ON when any client is auth'd, OFF when none are | ||
| 537 | - **After:** NAT stays always ON. Per-client filtering happens in the lwIP forwarding path via `LWIP_HOOK_IP4_CANFORWARD` | ||
| 538 | |||
| 539 | **Files:** | ||
| 540 | |||
| 541 | | File | Action | Description | | ||
| 542 | |------|--------|-------------| | ||
| 543 | | `main/lwip_tollgate_hooks.h` | Create | Defines `LWIP_HOOK_IP4_CANFORWARD` macro | | ||
| 544 | | `CMakeLists.txt` | Modify | Inject hook header into lwIP compilation | | ||
| 545 | | `main/firewall.c` | Modify | Add filter function, remove global NAT toggle | | ||
| 546 | | `main/firewall.h` | Modify | Expose filter function declaration | | ||
| 547 | | `main/tollgate_main.c` | Modify | Remove `firewall_disable_nat()` from stop_services | | ||
| 548 | |||
| 549 | **Implementation:** | ||
| 550 | |||
| 551 | 1. `lwip_tollgate_hooks.h` — hook header included by lwIP: | ||
| 552 | ```c | ||
| 553 | #define LWIP_HOOK_IP4_CANFORWARD(p, addr) tollgate_ip4_canforward_filter(p, addr) | ||
| 554 | ``` | ||
| 555 | |||
| 556 | 2. `CMakeLists.txt` — inject into lwIP build (follows ESP-IDF vlan example pattern): | ||
| 557 | ```cmake | ||
| 558 | idf_component_get_property(lwip lwip COMPONENT_LIB) | ||
| 559 | target_compile_options(${lwip} PRIVATE "-I${PROJECT_DIR}/main") | ||
| 560 | target_compile_definitions(${lwip} PRIVATE "-DESP_IDF_LWIP_HOOK_FILENAME=\"lwip_tollgate_hooks.h\"") | ||
| 561 | ``` | ||
| 562 | |||
| 563 | 3. `firewall.c` — filter function checks source IP against allowed client list: | ||
| 564 | ```c | ||
| 565 | int tollgate_ip4_canforward_filter(struct pbuf *p, u32_t dest_addr_hostorder) { | ||
| 566 | struct ip_hdr *iphdr = (struct ip_hdr *)p->payload; | ||
| 567 | uint32_t src_ip = lwip_ntohl(iphdr->src.addr); | ||
| 568 | return firewall_is_client_allowed(src_ip) ? 1 : 0; | ||
| 569 | } | ||
| 570 | ``` | ||
| 571 | |||
| 572 | 4. NAT management simplified: enable once during `firewall_init()`, never disable. Remove `update_nat()`, `firewall_enable_nat()`, `firewall_disable_nat()`. | ||
| 573 | |||
| 574 | **Key properties:** | ||
| 575 | - Hook fires only for **forwarded** packets (AP client → internet), not packets to ESP32 itself | ||
| 576 | - Captive portal (port 80) and API (port 2121) remain accessible to all clients | ||
| 577 | - DNS hijack continues independently — both layers enforce auth | ||
| 578 | - Return traffic: NAPT only creates DNAT entries for successfully forwarded outbound packets, so blocked clients don't get return traffic either | ||
| 579 | - Performance: linear scan of ≤10 clients per packet — negligible cost | ||
| 580 | |||
| 581 | ### Spent-Secret Cleanup | ||
| 582 | |||
| 583 | **Problem:** `session.c` tracks spent Cashu secrets locally in a static array (`s_spent_secrets[100][65]`). This is redundant — the mint is the authority on spent state, and `nucula_wallet_receive()` already swaps proofs (registering them as spent with the mint). | ||
| 584 | |||
| 585 | **Changes:** | ||
| 586 | - Remove `s_spent_secrets[]`, `s_spent_count`, `session_is_secret_spent()` from `session.c` | ||
| 587 | - Remove `spent_secrets` and `spent_secret_count` from `session_t` struct | ||
| 588 | - Remove `spent_secrets` params from `session_create()` / `session_create_bytes()` | ||
| 589 | - Remove local spent-secret check in `tollgate_api.c` — keep only mint `check_proof_states` check | ||
| 590 | - Update unit tests | ||
| 591 | |||
| 592 | **Rationale:** The mint's `cashu_check_proof_states()` already catches double-spends over HTTP. `nucula_wallet_receive()` → `swap()` registers proofs as spent and replaces them. After a successful receive, the old token is useless. Local tracking adds no security, wastes 6.5KB RAM, and is lost on reboot anyway. | ||
| 593 | |||
| 527 | ## Testing Infrastructure | 594 | ## Testing Infrastructure |
| 528 | 595 | ||
| 529 | ### Three-Layer Test Architecture | 596 | ### Three-Layer Test Architecture |