From 716daafce3eaa5ebfd246c1ef9915a2972b4c6fd Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 21 May 2026 06:20:45 +0530 Subject: fix: TLS allocation, stack overflow, DNS bind — mint now reachable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: mbedtls SSL buffers (16KB) couldn't allocate from fragmented internal RAM (largest block 8KB). Fix by lowering SPIRAM_MALLOC_ALWAYSINTERNAL from 16KB to 4KB, allowing SSL buffers to go to PSRAM. Additional fixes: - DNS server binds to AP IP only (prevents self-hijacking) - start_services() moved from esp_timer (2KB stack) to dedicated FreeRTOS task (16KB stack) — was causing stack overflow with dynamic buffers - Mint health probe now logs errors and exposes last_err in API - Debug endpoint shows internal/PSRAM heap breakdown and DNS resolve test - Enabled CONFIG_MBEDTLS_DYNAMIC_BUFFER for better memory management - Board C config: mint_url → testnut-nutshell.mints.orangesync.tech --- main/dns_server.c | 2 +- main/mint_health.c | 19 +++++++++++++-- main/mint_health.h | 1 + main/tollgate_api.c | 47 +++++++++++++++++++++++++++++++++++++ main/tollgate_main.c | 66 +++++++++++++++++++++++++++++++++++----------------- sdkconfig | 6 ++--- sdkconfig.defaults | 6 ++++- 7 files changed, 119 insertions(+), 28 deletions(-) diff --git a/main/dns_server.c b/main/dns_server.c index 15a729f..b84a4cf 100644 --- a/main/dns_server.c +++ b/main/dns_server.c @@ -161,7 +161,7 @@ static void dns_server_task(void *arg) struct sockaddr_in bind_addr = { .sin_family = AF_INET, .sin_port = htons(DNS_PORT), - .sin_addr.s_addr = INADDR_ANY, + .sin_addr.s_addr = s_ap_ip.addr, }; if (bind(sock, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) { ESP_LOGE(TAG, "Failed to bind DNS socket"); diff --git a/main/mint_health.c b/main/mint_health.c index 5853a39..26d73a6 100644 --- a/main/mint_health.c +++ b/main/mint_health.c @@ -7,9 +7,12 @@ #include "freertos/semphr.h" #include #include +#include static const char *TAG = "mint_health"; +static int s_last_probe_err = 0; + static mint_status_t s_mints[MINT_HEALTH_MAX]; static int s_mint_count = 0; static bool s_running = false; @@ -60,16 +63,26 @@ static bool probe_mint(const char *url) .crt_bundle_attach = esp_crt_bundle_attach, }; esp_http_client_handle_t client = esp_http_client_init(&config); - if (!client) return false; + if (!client) { + ESP_LOGE(TAG, "probe: init failed for %s", probe_url); + s_last_probe_err = -1; + return false; + } esp_err_t err = esp_http_client_open(client, 0); if (err != ESP_OK) { + ESP_LOGE(TAG, "probe: open failed for %s err=0x%x", probe_url, err); + int sc = esp_http_client_get_status_code(client); + ESP_LOGE(TAG, "probe: status=%d errno=%d(%s)", sc, errno, strerror(errno)); + s_last_probe_err = err; esp_http_client_cleanup(client); return false; } int content_length = esp_http_client_fetch_headers(client); int status = esp_http_client_get_status_code(client); + ESP_LOGI(TAG, "probe: %s -> status=%d len=%d", probe_url, status, content_length); + s_last_probe_err = 0; char *resp = NULL; if (content_length > 0 && content_length < 8192) { @@ -100,6 +113,7 @@ static void run_probes(void) bool ok = probe_mint(s_mints[i].url); s_mints[i].last_probe_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; s_mints[i].last_http_status = ok ? 200 : 0; + s_mints[i].last_err = ok ? 0 : s_last_probe_err; if (ok) { s_mints[i].consecutive_successes++; @@ -111,7 +125,7 @@ static void run_probes(void) } } else { if (s_mints[i].reachable) { - ESP_LOGW(TAG, "Mint UNREACHABLE: %s", s_mints[i].url); + ESP_LOGW(TAG, "Mint UNREACHABLE: %s err=0x%x", s_mints[i].url, s_last_probe_err); } s_mints[i].reachable = false; s_mints[i].consecutive_successes = 0; @@ -137,6 +151,7 @@ static void run_initial_probes(void) bool ok = probe_mint(s_mints[i].url); s_mints[i].last_probe_ms = (int64_t)xTaskGetTickCount() * portTICK_PERIOD_MS; s_mints[i].last_http_status = ok ? 200 : 0; + s_mints[i].last_err = ok ? 0 : s_last_probe_err; if (ok) { s_mints[i].consecutive_successes = MINT_HEALTH_RECOVERY_THRESHOLD; diff --git a/main/mint_health.h b/main/mint_health.h index f047d6a..33413db 100644 --- a/main/mint_health.h +++ b/main/mint_health.h @@ -16,6 +16,7 @@ typedef struct { uint8_t consecutive_successes; int64_t last_probe_ms; int last_http_status; + int last_err; } mint_status_t; typedef void (*mint_health_changed_cb)(void); diff --git a/main/tollgate_api.c b/main/tollgate_api.c index af91093..5ac0543 100644 --- a/main/tollgate_api.c +++ b/main/tollgate_api.c @@ -5,6 +5,11 @@ #include "session.h" #include "captive_portal.h" #include "firewall.h" +#include "lwip/dns.h" +#include "lwip/netdb.h" +#include "esp_http_client.h" +#include "esp_crt_bundle.h" +#include "esp_heap_caps.h" #include "nucula_wallet.h" #include "mint_health.h" #include "market.h" @@ -510,6 +515,12 @@ static esp_err_t api_get_mints(httpd_req_t *req) cJSON *obj = cJSON_CreateObject(); cJSON_AddStringToObject(obj, "url", mints[i].url); cJSON_AddBoolToObject(obj, "reachable", mints[i].reachable); + cJSON_AddNumberToObject(obj, "status", mints[i].last_http_status); + if (mints[i].last_err) { + char errbuf[16]; + snprintf(errbuf, sizeof(errbuf), "0x%x", mints[i].last_err); + cJSON_AddStringToObject(obj, "last_err", errbuf); + } cJSON_AddItemToArray(arr, obj); } char *json = cJSON_PrintUnformatted(arr); @@ -686,6 +697,8 @@ extern bool s_start_services_called; extern bool s_start_ap_services_called; extern bool s_sta_got_ip; extern bool s_ap_started; +extern esp_ip4_addr_t s_sta_ip; +extern esp_ip4_addr_t s_sta_gw; static esp_err_t api_get_debug(httpd_req_t *req) { @@ -700,6 +713,40 @@ static esp_err_t api_get_debug(httpd_req_t *req) cJSON_AddBoolToObject(root, "ap_started", s_ap_started); cJSON_AddNumberToObject(root, "free_heap", (double)esp_get_free_heap_size()); cJSON_AddNumberToObject(root, "min_free_heap", (double)esp_get_minimum_free_heap_size()); + cJSON_AddNumberToObject(root, "free_internal", (double)heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + cJSON_AddNumberToObject(root, "largest_internal", (double)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)); + cJSON_AddNumberToObject(root, "free_spiram", (double)heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + + char dns0[16], dns1[16], dns2[16]; + const ip_addr_t *d0 = dns_getserver(0); + const ip_addr_t *d1 = dns_getserver(1); + const ip_addr_t *d2 = dns_getserver(2); + snprintf(dns0, sizeof(dns0), IPSTR, IP2STR(&(esp_ip4_addr_t){.addr=d0->addr})); + snprintf(dns1, sizeof(dns1), IPSTR, IP2STR(&(esp_ip4_addr_t){.addr=d1->addr})); + snprintf(dns2, sizeof(dns2), IPSTR, IP2STR(&(esp_ip4_addr_t){.addr=d2->addr})); + cJSON_AddStringToObject(root, "dns0", dns0); + cJSON_AddStringToObject(root, "dns1", dns1); + cJSON_AddStringToObject(root, "dns2", dns2); + + char sta_ip_str[16], sta_gw_str[16]; + snprintf(sta_ip_str, sizeof(sta_ip_str), IPSTR, IP2STR(&s_sta_ip)); + snprintf(sta_gw_str, sizeof(sta_gw_str), IPSTR, IP2STR(&s_sta_gw)); + cJSON_AddStringToObject(root, "sta_ip", sta_ip_str); + cJSON_AddStringToObject(root, "sta_gw", sta_gw_str); + + struct addrinfo hints = {0}, *res = NULL; + hints.ai_family = AF_INET; + int dns_rc = getaddrinfo("testnut-compat.mints.orangesync.tech", NULL, &hints, &res); + if (dns_rc == 0 && res) { + struct sockaddr_in *addr = (struct sockaddr_in *)res->ai_addr; + char resolved[16]; + snprintf(resolved, sizeof(resolved), IPSTR, IP2STR(&(esp_ip4_addr_t){.addr=addr->sin_addr.s_addr})); + cJSON_AddStringToObject(root, "dns_resolve", resolved); + } else { + cJSON_AddStringToObject(root, "dns_resolve", dns_rc == 0 ? "no-addr" : "FAIL"); + } + if (res) freeaddrinfo(res); + char *json = cJSON_PrintUnformatted(root); httpd_resp_set_type(req, "application/json"); httpd_resp_sendstr(req, json); diff --git a/main/tollgate_main.c b/main/tollgate_main.c index 23b4a00..ebfa52e 100644 --- a/main/tollgate_main.c +++ b/main/tollgate_main.c @@ -8,7 +8,6 @@ #include "esp_system.h" #include "nvs_flash.h" #include "esp_netif.h" -#include "esp_timer.h" #include "lwip/netif.h" #include "lwip/dns.h" #include "esp_sntp.h" @@ -60,6 +59,8 @@ volatile bool s_start_services_called = false; volatile bool s_start_ap_services_called = false; volatile bool s_sta_got_ip = false; volatile bool s_ap_started = false; +volatile esp_ip4_addr_t s_sta_ip = {0}; +volatile esp_ip4_addr_t s_sta_gw = {0}; static void start_services(void); static void stop_services(void); @@ -134,13 +135,12 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base, } } -static void services_start_timer_cb(void *arg) +static void services_start_task(void *pvParameters) { start_services(); + vTaskDelete(NULL); } -static esp_timer_handle_t s_services_timer; - static void ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { @@ -149,15 +149,15 @@ static void ip_event_handler(void *arg, esp_event_base_t event_base, ESP_LOGI(TAG, "Got IP:" IPSTR ", GW:" IPSTR, IP2STR(&event->ip_info.ip), IP2STR(&event->ip_info.gw)); s_retry_count = 0; s_sta_got_ip = true; + s_sta_ip.addr = event->ip_info.ip.addr; + s_sta_gw.addr = event->ip_info.gw.addr; xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); - const esp_timer_create_args_t timer_cfg = { - .callback = services_start_timer_cb, - .name = "svc_start", - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_cfg, &s_services_timer)); - ESP_ERROR_CHECK(esp_timer_start_once(s_services_timer, 3000000)); - ESP_LOGI(TAG, "services_start_timer scheduled (3s)"); + static TaskHandle_t s_svc_task = NULL; + if (s_svc_task == NULL) { + xTaskCreate(services_start_task, "svc_start", 16384, NULL, 5, &s_svc_task); + ESP_LOGI(TAG, "services_start_task spawned (3s delay)"); + } esp_sntp_stop(); esp_sntp_setoperatingmode(SNTP_OPMODE_POLL); @@ -187,6 +187,7 @@ static void publish_wifistr_task(void *pvParameters) static void start_services(void) { + vTaskDelay(pdMS_TO_TICKS(3000)); ESP_LOGI(TAG, ">>> start_services() called"); if (s_services_mutex) xSemaphoreTake(s_services_mutex, portMAX_DELAY); if (s_services_running) { @@ -204,6 +205,34 @@ static void start_services(void) const ip_addr_t *dns_addr = dns_getserver(0); upstream_dns.addr = dns_addr->addr; + if (upstream_dns.addr == ap_ip_info.ip.addr) { + ESP_LOGW(TAG, "DNS[0] is our own AP IP — trying DNS[1] and STA gateway"); + const ip_addr_t *dns1 = dns_getserver(1); + if (dns1->addr != 0 && dns1->addr != ap_ip_info.ip.addr) { + upstream_dns.addr = dns1->addr; + dns_setserver(0, dns1); + ESP_LOGI(TAG, "Fixed DNS[0] to " IPSTR " from DNS[1]", IP2STR(&upstream_dns)); + } else { + esp_netif_dns_info_t sta_dns; + if (esp_netif_get_dns_info(s_sta_netif, ESP_NETIF_DNS_MAIN, &sta_dns) == ESP_OK + && sta_dns.ip.u_addr.ip4.addr != 0 + && sta_dns.ip.u_addr.ip4.addr != ap_ip_info.ip.addr) { + upstream_dns.addr = sta_dns.ip.u_addr.ip4.addr; + ip_addr_t lwip_dns = {0}; + lwip_dns.addr = sta_dns.ip.u_addr.ip4.addr; + dns_setserver(0, &lwip_dns); + ESP_LOGI(TAG, "Fixed DNS[0] to " IPSTR " from STA netif", IP2STR(&upstream_dns)); + } else { + ESP_LOGE(TAG, "No valid upstream DNS found! Mint verification will fail."); + } + } + } + + ESP_LOGI(TAG, "DNS config: [0]=" IPSTR " [1]=" IPSTR " [2]=" IPSTR, + IP2STR(&(esp_ip4_addr_t){.addr=dns_getserver(0)->addr}), + IP2STR(&(esp_ip4_addr_t){.addr=dns_getserver(1)->addr}), + IP2STR(&(esp_ip4_addr_t){.addr=dns_getserver(2)->addr})); + firewall_init(ap_ip_info.ip); session_manager_init(); @@ -305,10 +334,10 @@ static void wifi_create_ap_netif(void) { s_ap_netif = esp_netif_create_default_wifi_ap(); - const tollgate_config_t *cfg = tollgate_config_get(); - esp_ip4_addr_t ap_ip = cfg->ap_ip; - esp_ip4_addr_t ap_gw = cfg->ap_ip; - esp_ip4_addr_t ap_mask; + const tollgate_config_t *cfg = tollgate_config_get(); + esp_ip4_addr_t ap_ip = cfg->ap_ip; + esp_ip4_addr_t ap_gw = {0}; + esp_ip4_addr_t ap_mask; IP4_ADDR(&ap_mask, 255, 255, 255, 0); strncpy(s_ap_ip_str, cfg->ap_ip_str, sizeof(s_ap_ip_str) - 1); @@ -425,12 +454,7 @@ void app_main(void) if (tollgate_config_get_wifi(&(wifi_config_t){0}) != ESP_OK) { ESP_LOGI(TAG, "No STA network configured, starting services immediately"); - const esp_timer_create_args_t timer_cfg = { - .callback = services_start_timer_cb, - .name = "svc_start_fallback", - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_cfg, &s_services_timer)); - ESP_ERROR_CHECK(esp_timer_start_once(s_services_timer, 3000000)); + xTaskCreate(services_start_task, "svc_start_fb", 16384, NULL, 5, NULL); } while (1) { diff --git a/sdkconfig b/sdkconfig index 75f2a12..9ba9857 100644 --- a/sdkconfig +++ b/sdkconfig @@ -1095,7 +1095,7 @@ CONFIG_SPIRAM_BOOT_INIT=y # CONFIG_SPIRAM_USE_CAPS_ALLOC is not set CONFIG_SPIRAM_USE_MALLOC=y CONFIG_SPIRAM_MEMTEST=y -CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096 # CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP is not set CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 # CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set @@ -1682,13 +1682,13 @@ CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 -# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set +CONFIG_MBEDTLS_DYNAMIC_BUFFER=y +# CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA is not set # CONFIG_MBEDTLS_DEBUG is not set # # mbedTLS v3.x related # -# CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 is not set # CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set # CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set # CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set diff --git a/sdkconfig.defaults b/sdkconfig.defaults index e2e1f4e..bed04fe 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -37,11 +37,15 @@ CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" # mbedTLS (needed for HTTPS to mint) CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y +CONFIG_MBEDTLS_DYNAMIC_BUFFER=y +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=4096 +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 # PSRAM (ESP32-S3 has 8MB) CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y CONFIG_SPIRAM_SPEED_80M=y CONFIG_SPIRAM_USE_MALLOC=y -CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096 CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +CONFIG_MBEDTLS_DYNAMIC_BUFFER=y -- cgit v1.2.3