From 8a2307a5ced6da94cc674602219d5a68a1246264 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 15 May 2026 13:06:25 +0530 Subject: initiall commit --- main/CMakeLists.txt | 9 ++ main/config.c | 142 +++++++++++++++++++++++++++ main/config.h | 39 ++++++++ main/dns_server.c | 270 ++++++++++++++++++++++++++++++++++++++++++++++++++++ main/dns_server.h | 13 +++ main/firewall.c | 96 +++++++++++++++++++ main/firewall.h | 18 ++++ 7 files changed, 587 insertions(+) create mode 100644 main/CMakeLists.txt create mode 100644 main/config.c create mode 100644 main/config.h create mode 100644 main/dns_server.c create mode 100644 main/dns_server.h create mode 100644 main/firewall.c create mode 100644 main/firewall.h (limited to 'main') diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..2c94ff1 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register(SRCS "tollgate_main.c" + "config.c" + "dns_server.c" + "captive_portal.c" + "firewall.c" + INCLUDE_DIRS "." + REQUIRES esp_wifi esp_event esp_netif nvs_flash esp_http_server + lwip json esp_http_client esp_tls log + PRIV_REQUIRES lwip) diff --git a/main/config.c b/main/config.c new file mode 100644 index 0000000..f78bc8b --- /dev/null +++ b/main/config.c @@ -0,0 +1,142 @@ +#include "config.h" +#include "esp_log.h" +#include "esp_spiffs.h" +#include "cJSON.h" +#include + +static const char *TAG = "tollgate_config"; +static tollgate_config_t g_config; + +esp_err_t tollgate_config_init(void) +{ + memset(&g_config, 0, sizeof(g_config)); + g_config.max_retry = 5; + g_config.ap_channel = 1; + g_config.ap_max_conn = 4; + g_config.price_per_step = 21; + g_config.step_size_ms = 60000; + + esp_vfs_spiffs_conf_t conf = { + .base_path = "/spiffs", + .partition_label = NULL, + .max_files = 5, + .format_if_mount_failed = true, + }; + esp_err_t ret = esp_vfs_spiffs_register(&conf); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount SPIFFS: %s", esp_err_to_name(ret)); + return ret; + } + + FILE *f = fopen("/spiffs/config.json", "r"); + if (!f) { + ESP_LOGW(TAG, "No config.json found, generating default"); + const char *default_json = "{" + "\"wifi_networks\":[" + "{\"ssid\":\"EnterSSID-2.4GHz\",\"password\":\"c03rad0r123!\"}" + "]," + "\"ap_ssid\":\"TollGate\"," + "\"ap_password\":\"\"," + "\"ap_channel\":1," + "\"mint_url\":\"https://nofee.testnut.cashu.space\"," + "\"lnurl_url\":\"https://redeem.cashu.me/.well-known/lnurlp/tollgate\"," + "\"price_per_step\":21," + "\"step_size_ms\":60000" + "}"; + f = fopen("/spiffs/config.json", "w"); + if (f) { + fputs(default_json, f); + fclose(f); + } + f = fopen("/spiffs/config.json", "r"); + } + + if (!f) { + ESP_LOGE(TAG, "Failed to open config.json"); + return ESP_FAIL; + } + + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); + char *buf = malloc(fsize + 1); + if (!buf) { + fclose(f); + return ESP_ERR_NO_MEM; + } + fread(buf, 1, fsize, f); + buf[fsize] = '\0'; + fclose(f); + + cJSON *root = cJSON_Parse(buf); + free(buf); + if (!root) { + ESP_LOGE(TAG, "Failed to parse config.json"); + return ESP_FAIL; + } + + cJSON *networks = cJSON_GetObjectItem(root, "wifi_networks"); + if (networks && cJSON_IsArray(networks)) { + int count = cJSON_GetArraySize(networks); + if (count > TOLLGATE_MAX_WIFI_NETWORKS) count = TOLLGATE_MAX_WIFI_NETWORKS; + for (int i = 0; i < count; i++) { + cJSON *net = cJSON_GetArrayItem(networks, i); + cJSON *ssid = cJSON_GetObjectItem(net, "ssid"); + cJSON *pass = cJSON_GetObjectItem(net, "password"); + if (ssid && pass) { + strncpy(g_config.networks[i].ssid, ssid->valuestring, sizeof(g_config.networks[i].ssid) - 1); + strncpy(g_config.networks[i].password, pass->valuestring, sizeof(g_config.networks[i].password) - 1); + g_config.network_count++; + } + } + } + + cJSON *ap_ssid = cJSON_GetObjectItem(root, "ap_ssid"); + if (ap_ssid) strncpy(g_config.ap_ssid, ap_ssid->valuestring, sizeof(g_config.ap_ssid) - 1); + else strncpy(g_config.ap_ssid, "TollGate", sizeof(g_config.ap_ssid) - 1); + + cJSON *ap_pass = cJSON_GetObjectItem(root, "ap_password"); + if (ap_pass) strncpy(g_config.ap_password, ap_pass->valuestring, sizeof(g_config.ap_password) - 1); + + cJSON *ap_ch = cJSON_GetObjectItem(root, "ap_channel"); + if (ap_ch) g_config.ap_channel = ap_ch->valueint; + + cJSON *mint = cJSON_GetObjectItem(root, "mint_url"); + if (mint) strncpy(g_config.mint_url, mint->valuestring, sizeof(g_config.mint_url) - 1); + + cJSON *lnurl = cJSON_GetObjectItem(root, "lnurl_url"); + if (lnurl) strncpy(g_config.lnurl_url, lnurl->valuestring, sizeof(g_config.lnurl_url) - 1); + + cJSON *price = cJSON_GetObjectItem(root, "price_per_step"); + if (price) g_config.price_per_step = price->valueint; + + cJSON *step = cJSON_GetObjectItem(root, "step_size_ms"); + if (step) g_config.step_size_ms = step->valueint; + + cJSON_Delete(root); + ESP_LOGI(TAG, "Config loaded: AP='%s', %d WiFi networks, price=%d sats/%dms", + g_config.ap_ssid, g_config.network_count, g_config.price_per_step, g_config.step_size_ms); + return ESP_OK; +} + +const tollgate_config_t *tollgate_config_get(void) +{ + return &g_config; +} + +esp_err_t tollgate_config_get_wifi(wifi_config_t *wifi_config) +{ + if (g_config.network_count == 0) return ESP_ERR_NOT_FOUND; + int idx = g_config.current_network % g_config.network_count; + memset(wifi_config, 0, sizeof(wifi_config_t)); + strncpy((char *)wifi_config->sta.ssid, g_config.networks[idx].ssid, sizeof(wifi_config->sta.ssid) - 1); + strncpy((char *)wifi_config->sta.password, g_config.networks[idx].password, sizeof(wifi_config->sta.password) - 1); + wifi_config->sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; + return ESP_OK; +} + +esp_err_t tollgate_config_get_next_wifi(wifi_config_t *wifi_config) +{ + g_config.current_network = (g_config.current_network + 1) % g_config.network_count; + return tollgate_config_get_wifi(wifi_config); +} diff --git a/main/config.h b/main/config.h new file mode 100644 index 0000000..d26b7ae --- /dev/null +++ b/main/config.h @@ -0,0 +1,39 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "esp_err.h" +#include "esp_wifi.h" + +#define TOLLGATE_MAX_WIFI_NETWORKS 5 +#define TOLLGATE_MAX_MINT_URLS 3 +#define TOLLGATE_MAX_AP_SSID_LEN 32 +#define TOLLGATE_MAX_AP_PASS_LEN 64 + +typedef struct { + char ssid[32]; + char password[64]; +} wifi_network_t; + +typedef struct { + wifi_network_t networks[TOLLGATE_MAX_WIFI_NETWORKS]; + int network_count; + int current_network; + int max_retry; + + char ap_ssid[TOLLGATE_MAX_AP_SSID_LEN]; + char ap_password[TOLLGATE_MAX_AP_PASS_LEN]; + uint8_t ap_channel; + uint8_t ap_max_conn; + + char mint_url[256]; + char lnurl_url[256]; + int price_per_step; + int step_size_ms; +} tollgate_config_t; + +esp_err_t tollgate_config_init(void); +const tollgate_config_t *tollgate_config_get(void); +esp_err_t tollgate_config_get_wifi(wifi_config_t *wifi_config); +esp_err_t tollgate_config_get_next_wifi(wifi_config_t *wifi_config); + +#endif diff --git a/main/dns_server.c b/main/dns_server.c new file mode 100644 index 0000000..f7977c6 --- /dev/null +++ b/main/dns_server.c @@ -0,0 +1,270 @@ +#include "dns_server.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "lwip/sockets.h" +#include "lwip/netdb.h" +#include +#include + +#define MAX_AUTH_IPS 10 +#define MAX_PENDING 50 +#define DNS_BUF_SIZE 512 +#define DNS_PORT 53 +#define DNS_TASK_STACK 4096 +#define DNS_TASK_PRIO 5 +#define DNS_FORWARD_TIMEOUT_MS 2000 +#define NXDOMAIN_TTL 30 + +static const char *TAG = "dns_server"; + +#pragma pack(push, 1) +typedef struct { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +} dns_header_t; +#pragma pack(pop) + +#pragma pack(push, 1) +typedef struct { + uint16_t name; + uint16_t type; + uint16_t class; + uint32_t ttl; + uint16_t len; + uint32_t addr; +} dns_answer_t; +#pragma pack(pop) + +typedef struct { + uint32_t ip; +} auth_entry_t; + +static auth_entry_t s_auth_list[MAX_AUTH_IPS]; +static int s_auth_count = 0; +static TaskHandle_t s_dns_task = NULL; +static volatile bool s_dns_running = false; +static esp_ip4_addr_t s_ap_ip; +static esp_ip4_addr_t s_upstream_dns; + +static bool is_authenticated(uint32_t ip) +{ + for (int i = 0; i < s_auth_count; i++) { + if (s_auth_list[i].ip == ip) return true; + } + return false; +} + +static void parse_dns_name(const uint8_t *buf, int buf_len, int offset, char *out, int out_len) +{ + int pos = offset; + int out_pos = 0; + int jumped = 0; + int jump_pos = 0; + while (pos < buf_len && out_pos < out_len - 1) { + uint8_t len = buf[pos]; + if (len == 0) break; + if ((len & 0xC0) == 0xC0) { + if (!jumped) jump_pos = pos + 2; + pos = ((len & 0x3F) << 8) | buf[pos + 1]; + jumped = 1; + continue; + } + if (out_pos > 0 && out_pos < out_len - 1) out[out_pos++] = '.'; + pos++; + for (int i = 0; i < len && pos < buf_len && out_pos < out_len - 1; i++) { + out[out_pos++] = buf[pos++]; + } + } + out[out_pos] = '\0'; +} + +static int build_nxdomain(uint8_t *response, int req_len) +{ + memcpy(response, response, req_len); + dns_header_t *hdr = (dns_header_t *)response; + hdr->flags = htons(0x8403); + hdr->ancount = 0; + hdr->nscount = 0; + hdr->arcount = 0; + return req_len; +} + +static int build_redirect_response(uint8_t *response, int req_len) +{ + memcpy(response, response, req_len); + dns_header_t *hdr = (dns_header_t *)response; + hdr->flags = htons(0x8180); + hdr->ancount = htons(1); + hdr->nscount = 0; + hdr->arcount = 0; + int resp_len = req_len; + dns_answer_t ans; + ans.name = htons(0xC00C); + ans.type = htons(1); + ans.class = htons(1); + ans.ttl = htonl(NXDOMAIN_TTL); + ans.len = htons(4); + ans.addr = s_ap_ip.addr; + memcpy(response + resp_len, &ans, sizeof(ans)); + resp_len += sizeof(ans); + return resp_len; +} + +static int forward_dns(const uint8_t *req, int req_len, uint8_t *resp, int resp_buf_len, + const struct sockaddr_in *client_addr, uint16_t txn_id) +{ + int upstream_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (upstream_sock < 0) return -1; + + struct timeval tv = { .tv_sec = DNS_FORWARD_TIMEOUT_MS / 1000, .tv_usec = (DNS_FORWARD_TIMEOUT_MS % 1000) * 1000 }; + setsockopt(upstream_sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + + struct sockaddr_in upstream_addr = { + .sin_family = AF_INET, + .sin_port = htons(DNS_PORT), + .sin_addr.s_addr = s_upstream_dns.addr, + }; + + sendto(upstream_sock, req, req_len, 0, (struct sockaddr *)&upstream_addr, sizeof(upstream_addr)); + + int n = recvfrom(upstream_sock, resp, resp_buf_len, 0, NULL, NULL); + close(upstream_sock); + + if (n > 0) { + if (n >= sizeof(dns_header_t)) { + dns_header_t *hdr = (dns_header_t *)resp; + hdr->id = htons(txn_id); + } + } + return n; +} + +static void dns_server_task(void *arg) +{ + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + ESP_LOGE(TAG, "Failed to create DNS socket"); + s_dns_running = false; + vTaskDelete(NULL); + return; + } + + struct sockaddr_in bind_addr = { + .sin_family = AF_INET, + .sin_port = htons(DNS_PORT), + .sin_addr.s_addr = INADDR_ANY, + }; + if (bind(sock, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) { + ESP_LOGE(TAG, "Failed to bind DNS socket"); + close(sock); + s_dns_running = false; + vTaskDelete(NULL); + return; + } + + ESP_LOGI(TAG, "DNS server started on port %d, AP IP=" IPSTR ", upstream DNS=" IPSTR, + DNS_PORT, IP2STR(&s_ap_ip), IP2STR(&s_upstream_dns)); + + uint8_t rx_buf[DNS_BUF_SIZE]; + uint8_t tx_buf[DNS_BUF_SIZE + sizeof(dns_answer_t)]; + + while (s_dns_running) { + struct sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + int n = recvfrom(sock, rx_buf, sizeof(rx_buf), 0, + (struct sockaddr *)&client_addr, &client_len); + if (n < (int)sizeof(dns_header_t)) continue; + + uint32_t client_ip = client_addr.sin_addr.s_addr; + dns_header_t *hdr = (dns_header_t *)rx_buf; + uint16_t txn_id = ntohs(hdr->id); + bool is_query = (ntohs(hdr->flags) & 0x8000) == 0; + uint16_t qdcount = ntohs(hdr->qdcount); + + if (!is_query || qdcount == 0) continue; + + int q_offset = sizeof(dns_header_t); + while (q_offset < n && rx_buf[q_offset] != 0) { + q_offset += rx_buf[q_offset] + 1; + } + if (q_offset + 5 > n) continue; + uint16_t qtype = (rx_buf[q_offset + 1] << 8) | rx_buf[q_offset + 2]; + int req_len = q_offset + 5; + + if (is_authenticated(client_ip)) { + int resp_len = forward_dns(rx_buf, req_len, tx_buf, sizeof(tx_buf), &client_addr, txn_id); + if (resp_len > 0) { + sendto(sock, tx_buf, resp_len, 0, (struct sockaddr *)&client_addr, client_len); + } + } else { + if (qtype == 1) { + int resp_len = build_redirect_response(rx_buf, req_len); + memcpy(tx_buf, rx_buf, resp_len); + dns_header_t *resp_hdr = (dns_header_t *)tx_buf; + resp_hdr->id = htons(txn_id); + sendto(sock, tx_buf, resp_len, 0, (struct sockaddr *)&client_addr, client_len); + } else if (qtype == 28) { + int resp_len = build_nxdomain(rx_buf, req_len); + memcpy(tx_buf, rx_buf, resp_len); + dns_header_t *resp_hdr = (dns_header_t *)tx_buf; + resp_hdr->id = htons(txn_id); + sendto(sock, tx_buf, resp_len, 0, (struct sockaddr *)&client_addr, client_len); + } else { + int resp_len = forward_dns(rx_buf, req_len, tx_buf, sizeof(tx_buf), &client_addr, txn_id); + if (resp_len > 0) { + sendto(sock, tx_buf, resp_len, 0, (struct sockaddr *)&client_addr, client_len); + } + } + } + } + + close(sock); + ESP_LOGI(TAG, "DNS server stopped"); + vTaskDelete(NULL); +} + +esp_err_t dns_server_start(esp_ip4_addr_t ap_ip, esp_ip4_addr_t upstream_dns) +{ + if (s_dns_running) return ESP_OK; + s_ap_ip = ap_ip; + s_upstream_dns = upstream_dns; + s_dns_running = true; + xTaskCreate(dns_server_task, "dns_server", DNS_TASK_STACK, NULL, DNS_TASK_PRIO, &s_dns_task); + return ESP_OK; +} + +void dns_server_stop(void) +{ + s_dns_running = false; + vTaskDelay(pdMS_TO_TICKS(200)); + s_dns_task = NULL; +} + +void dns_server_set_client_authenticated(uint32_t client_ip, bool authenticated) +{ + if (authenticated) { + if (is_authenticated(client_ip)) return; + if (s_auth_count < MAX_AUTH_IPS) { + s_auth_list[s_auth_count].ip = client_ip; + s_auth_count++; + } + } else { + for (int i = 0; i < s_auth_count; i++) { + if (s_auth_list[i].ip == client_ip) { + s_auth_list[i] = s_auth_list[s_auth_count - 1]; + s_auth_count--; + return; + } + } + } +} + +bool dns_server_is_running(void) +{ + return s_dns_running; +} diff --git a/main/dns_server.h b/main/dns_server.h new file mode 100644 index 0000000..9cae10b --- /dev/null +++ b/main/dns_server.h @@ -0,0 +1,13 @@ +#ifndef DNS_SERVER_H +#define DNS_SERVER_H + +#include "esp_err.h" +#include "esp_netif.h" +#include + +esp_err_t dns_server_start(esp_ip4_addr_t ap_ip, esp_ip4_addr_t upstream_dns); +void dns_server_stop(void); +void dns_server_set_client_authenticated(uint32_t client_ip, bool authenticated); +bool dns_server_is_running(void); + +#endif diff --git a/main/firewall.c b/main/firewall.c new file mode 100644 index 0000000..9ef3be0 --- /dev/null +++ b/main/firewall.c @@ -0,0 +1,96 @@ +#include "firewall.h" +#include "dns_server.h" +#include "esp_log.h" +#include "lwip/lwip_napt.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; +} fw_client_t; + +static fw_client_t s_clients[MAX_CLIENTS]; +static int s_client_count = 0; + +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)); + return ESP_OK; +} + +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"); +} + +void firewall_grant_access(uint32_t client_ip) +{ + for (int i = 0; i < s_client_count; i++) { + if (s_clients[i].ip == client_ip) return; + } + if (s_client_count >= MAX_CLIENTS) { + ESP_LOGW(TAG, "Max clients reached, cannot grant access"); + return; + } + s_clients[s_client_count].ip = client_ip; + s_client_count++; + dns_server_set_client_authenticated(client_ip, true); + + esp_ip4_addr_t ip_addr = { .addr = client_ip }; + ESP_LOGI(TAG, "Access granted to " IPSTR, IP2STR(&ip_addr)); +} + +void firewall_revoke_access(uint32_t client_ip) +{ + for (int i = 0; i < s_client_count; i++) { + if (s_clients[i].ip == client_ip) { + s_clients[i] = s_clients[s_client_count - 1]; + s_client_count--; + dns_server_set_client_authenticated(client_ip, false); + esp_ip4_addr_t ip_addr = { .addr = client_ip }; + ESP_LOGI(TAG, "Access revoked for " IPSTR, IP2STR(&ip_addr)); + return; + } + } +} + +void firewall_revoke_all(void) +{ + for (int i = 0; i < s_client_count; i++) { + dns_server_set_client_authenticated(s_clients[i].ip, false); + } + s_client_count = 0; + ESP_LOGI(TAG, "All client access revoked"); +} + +bool firewall_is_client_allowed(uint32_t client_ip) +{ + for (int i = 0; i < s_client_count; i++) { + if (s_clients[i].ip == client_ip) return true; + } + return false; +} + +int firewall_client_count(void) +{ + return s_client_count; +} diff --git a/main/firewall.h b/main/firewall.h new file mode 100644 index 0000000..91a89b0 --- /dev/null +++ b/main/firewall.h @@ -0,0 +1,18 @@ +#ifndef FIREWALL_H +#define FIREWALL_H + +#include "esp_err.h" +#include "esp_netif.h" +#include +#include + +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); +bool firewall_is_client_allowed(uint32_t client_ip); +int firewall_client_count(void); + +#endif -- cgit v1.2.3