heap-memoryfreeesp8266natmemory-fragmentation

Calling a routine on ESP8266 decreases progressively the Free Heap


I am using a routine printIPs to print on serial and on a display the last byte of the IP of all connected stations to a ESP8266 used as NAT. The whole schetch works if I do not call that routine. If I do call it, I get a progressive free heap reduction that leads to "esp Fatal exception 29(StoreProhibitedCause)". I guess that I should allocate better the structures to avoid memory fragmentation. Here is the routune:

void printIPs() {
  //This part is to list the last byte of the connected stations.
  unsigned char softap_stations_cnt = 0;
  struct station_info *stat_info;
  struct ip4_addr *IPaddress;
  uint32 uintaddress;
  byte mordisco;

  //This part is to list the last byte of the connected stations.
  softap_stations_cnt = wifi_softap_get_station_num(); // Count of stations which are connected to ESP8266 soft-AP
  stat_info = wifi_softap_get_station_info();
  if (stat_info != NULL) {
    Serial.print("Finding number of Station IPs connected: ");
    Serial.println(softap_stations_cnt);
    Serial.println("Printing the last byte of the Station IPs connected:");
    display.print(" - IPs: ");
    while (stat_info != NULL) {
      IPaddress = &stat_info->ip;
      uintaddress = IPaddress->addr;
      mordisco = (uintaddress >> 24);
      Serial.print(mordisco);
      Serial.print(" ");
      display.print(mordisco);
      display.print(" ");
      stat_info = STAILQ_NEXT(stat_info, next);
    }
  }
}

Although I think the issue can be solved looking at the routine only, for completiness, here is the whole schetch:


// NAPT example released to public domain

#if LWIP_FEATURES && !LWIP_IPV6

#define HAVE_NETDUMP 0

#include <ESP8266WiFi.h>
#include <lwip/napt.h>
#include <lwip/dns.h>

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

extern "C" {
#include "user_interface.h"
#include "wpa2_enterprise.h"
#include "c_types.h"
}

// SSID to connect to
char ssid[] = "SSIDNAME";
char username[] = "";
char identity[] = "";
char password[] = "";
String ExtenderSSID = "OTHERSSID";
String ExtenderPW = "";
int c = 0;

uint8_t target_esp_mac[6] = {0x24, 0x0a, 0xc4, 0x9a, 0x58, 0x28};

#define NAPT 1000
#define NAPT_PORT 10

#if HAVE_NETDUMP

#include <NetDump.h>

void dump(int netif_idx, const char* data, size_t len, int out, int success) {
  (void)success;
  Serial.print(out ? F("out ") : F(" in "));
  Serial.printf("%d ", netif_idx);

  // optional filter example: if (netDump_is_ARP(data))
  {
    netDump(Serial, data, len);
    // netDumpHex(Serial, data, len);
  }
}
#endif

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Wire.setClock(100000);
  Wire.begin(5, 4); //SDA, SCL
  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  display.clearDisplay();
  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0, 0);            // Start at top-left corner
  display.print("Heap on start: ");
  display.println(ESP.getFreeHeap());
  display.println("Creating Connection info for Station WiFi...");
  display.display();
  delay(2000);

  // init oled done
  Serial.printf("\n\nNAPT Range extender\n");
  Serial.printf("Heap on start: %d\n", ESP.getFreeHeap());

#if HAVE_NETDUMP
  phy_capture = dump;
#endif

  // first, connect to STA so we can get a proper local DNS server
  WiFi.mode(WIFI_STA);
  display.clearDisplay();
  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0, 0);            // Start at top-left corner
  display.println("Created.");
  display.print("SDK version: ");
  display.println(system_get_sdk_version());
  display.print("Free Heap: ");
  display.println(ESP.getFreeHeap());
  display.println("Creating Station...");
  display.display();
  delay(2000);

  Serial.printf("SDK version: %s\n", system_get_sdk_version());
  Serial.printf("Free Heap: %4d\n", ESP.getFreeHeap());

  // Setting ESP into STATION mode only (no AP mode or dual mode)
  wifi_set_opmode(STATION_MODE);

  struct station_config wifi_config;

  memset(&wifi_config, 0, sizeof(wifi_config));
  strcpy((char*)wifi_config.ssid, ssid);
  strcpy((char*)wifi_config.password, password);

  wifi_station_set_config(&wifi_config);
  wifi_set_macaddr(STATION_IF, target_esp_mac);


  wifi_station_set_wpa2_enterprise_auth(1);

  // Clean up to be sure no old data is still inside
  wifi_station_clear_cert_key();
  wifi_station_clear_enterprise_ca_cert();
  wifi_station_clear_enterprise_identity();
  wifi_station_clear_enterprise_username();
  wifi_station_clear_enterprise_password();
  wifi_station_clear_enterprise_new_password();

  wifi_station_set_enterprise_identity((uint8*)identity, strlen(identity));
  wifi_station_set_enterprise_username((uint8*)username, strlen(username));
  wifi_station_set_enterprise_password((uint8*)password, strlen((char*)password));


  display.clearDisplay();
  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0, 0);            // Start at top-left corner
  display.print("Waiting to get link to ");
  display.print(ssid);
  display.println("...");
  display.print("SDK version: ");
  display.println(system_get_sdk_version());
  display.print("Free Heap: ");
  display.println(ESP.getFreeHeap());
  display.display();
  delay(2000);
  wifi_station_connect();
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }

  display.clearDisplay();
  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0, 0);            // Start at top-left corner
  display.println("Connected!");
  display.print("IP address: ");
  display.println(WiFi.localIP());
  display.print("Free Heap: ");
  display.println(String(ESP.getFreeHeap()));
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  display.print("DNS1: ");
  display.println(WiFi.dnsIP(0).toString());
  display.print("DNS2: ");
  display.println(WiFi.dnsIP(1).toString());
  display.display();
  delay(2000);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(500);
  }
  Serial.printf("\nSTA: %s (dns: %s / %s)\n", WiFi.localIP().toString().c_str(), WiFi.dnsIP(0).toString().c_str(), WiFi.dnsIP(1).toString().c_str());

  // By default, DNS option will point to the interface IP
  // Instead, point it to the real DNS server.
  // Notice that:
  // - DhcpServer class only supports IPv4
  // - Only a single IP can be set
  auto& server = WiFi.softAPDhcpServer();
  server.setDns(WiFi.dnsIP(0));

  display.clearDisplay();
  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0, 0);            // Start at top-left corner
  display.println("Creating AP... ");
  display.print("Free Heap: ");
  display.println(String(ESP.getFreeHeap()));
  display.display();
  delay(2000);
  WiFi.softAPConfig(  // enable AP, with android-compatible google domain
    IPAddress(172, 217, 28, 254), IPAddress(172, 217, 28, 254), IPAddress(255, 255, 255, 0));
  WiFi.softAP(ExtenderSSID, ExtenderPW);
  Serial.printf("AP: %s\n", WiFi.softAPIP().toString().c_str());

  display.clearDisplay();
  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0, 0);            // Start at top-left corner
  display.print("AP Created: ");
  display.println(WiFi.softAPIP().toString().c_str());
  display.print("Free Heap: ");
  display.println(String(ESP.getFreeHeap()));
  display.display();
  delay(2000);

  Serial.printf("Heap before: %d\n", ESP.getFreeHeap());
  err_t ret = ip_napt_init(NAPT, NAPT_PORT);
  Serial.printf("ip_napt_init(%d,%d): ret=%d (OK=%d)\n", NAPT, NAPT_PORT, (int)ret, (int)ERR_OK);
  if (ret == ERR_OK) {
    ret = ip_napt_enable_no(SOFTAP_IF, 1);
    Serial.printf("ip_napt_enable_no(SOFTAP_IF): ret=%d (OK=%d)\n", (int)ret, (int)ERR_OK);
    if (ret == ERR_OK) {
      Serial.printf("WiFi Network '%s' with defined password is now NATed behind '%s'\n", ExtenderSSID, ssid);
      display.clearDisplay();
      display.setTextSize(1);             // Normal 1:1 pixel scale
      display.setTextColor(SSD1306_WHITE);        // Draw white text
      display.setCursor(0, 0);            // Start at top-left corner
      display.print("WiFi Network ");
      display.println(ExtenderSSID);
      display.print("is NATed behind ");
      display.println(ssid);
      display.print("Free Heap: ");
      display.println(String(ESP.getFreeHeap()));
      display.display();

    }
  }
  Serial.printf("Heap after napt init: %d\n", ESP.getFreeHeap());
  if (ret != ERR_OK) {
    Serial.printf("NAT initialization failed\n");
    display.clearDisplay();
    display.setTextSize(1);             // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE);        // Draw white text
    display.setCursor(0, 0);            // Start at top-left corner
    display.println("NAT initialization failed");
    display.display();
  }
  delay(2000);
}

#else

void setup() {
  Serial.begin(115200);
  Serial.printf("\n\nNAPT not supported in this configuration\n");
}

#endif

void printIPs() {
  //This part is to list the last byte of the connected stations.
  unsigned char softap_stations_cnt = 0;
  struct station_info *stat_info;
  struct ip4_addr *IPaddress;
  uint32 uintaddress;
  byte mordisco;

  //This part is to list the last byte of the connected stations.
  softap_stations_cnt = wifi_softap_get_station_num(); // Count of stations which are connected to ESP8266 soft-AP
  stat_info = wifi_softap_get_station_info();
  if (stat_info != NULL) {
    Serial.print("Finding number of Station IPs connected: ");
    Serial.println(softap_stations_cnt);
    Serial.println("Printing the last byte of the Station IPs connected:");
    display.print(" - IPs: ");
    while (stat_info != NULL) {
      IPaddress = &stat_info->ip;
      uintaddress = IPaddress->addr;
      mordisco = (uintaddress >> 24);
      Serial.print(mordisco);
      Serial.print(" ");
      display.print(mordisco);
      display.print(" ");
      stat_info = STAILQ_NEXT(stat_info, next);
    }
  }
}

void loop() {
  delay(2000);
  display.clearDisplay();
  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0, 0);            // Start at top-left corner
  display.print("FH (");
  display.print(c);
  c = c + 1;
  if (c > 999) {
    c = 0;
  }
  Serial.print("C=" + String(c));
  display.print("): ");
  display.println(String(ESP.getFreeHeap()));
  Serial.println(" - Free Heap: " + String(ESP.getFreeHeap()));
  display.print("AP: ");
  display.println(WiFi.softAPIP().toString().c_str());
  display.print("Stations N: ");
  display.print(WiFi.softAPgetStationNum());
  Serial.print("Number of stations connected: ");
  Serial.println(WiFi.softAPgetStationNum());
  yield();

  printIPs();

  Serial.println("");
  display.display();
  yield();
}

Here a part of the serial output aroung the exception:

23:19:32.857 -> C=300 - Free Heap: 712
23:19:32.857 -> Number of stations connected: 2
23:19:32.857 -> Printing the last byte of the Station IPs connected:
23:19:32.857 -> 97 98 
23:19:34.848 -> C=301 - Free Heap: 664
23:19:34.848 -> Number of stations connected: 2
23:19:34.848 -> Printing the last byte of the Station IPs connected:
23:19:34.881 -> 97 98 
23:19:36.873 -> C=302 - Free Heap: 616
23:19:36.873 -> Number of stations connected: 2
23:19:36.873 -> Printing the last byte of the Station IPs connected:
23:19:36.873 -> 97 98 
23:19:38.896 -> C=303 - Free Heap: 440
23:19:38.896 -> Number of stations connected: 2
23:19:38.896 -> Printing the last byte of the Station IPs connected:
23:19:38.896 -> 97 98 
23:19:40.920 -> C=304 - Free Heap: 520
23:19:40.920 -> Number of stations connected: 2
23:19:40.920 -> Printing the last byte of the Station IPs connected:
23:19:40.920 -> 97 98 
23:19:40.986 -> Fatal exception 29(StoreProhibitedCause):
23:19:40.986 -> epc1=0x4000df64, epc2=0x00000000, epc3=0x00000000, excvaddr=0x00000000, depc=0x00000000

I had the same issue even when the same code was in the main loop and the variable declatation on the main header. I see that the free heap reduction, on the best case, is slowly progressive. It depends on the number of connected stations and their internet usage.


Solution

  • Your program has a "memory leak" and is allocating memory or calling a function that allocates memory, without ever freeing it.

    You already isolated the function that's leaking memory; a good way to debug it from here is to remove lines from the function until you find that the heap is no longer shrinking or to print the size of the heap after each line of code in order to find the line that's responsible for the allocation.

    In this case you'd find that the call to wifi_softap_get_station_info() is allocating memory that's never being freed.

    You need to call wifi_softap_free_station_info() once you're done - be sure to call this before your function printIPs() returns.

    It's important to call wifi_softap_free_station_info() rather than just calling free() on whatever wifi_softap_get_station_info() returns so that your code doesn't make assumptions about how wifi_softap_get_station_info() manages its memory.

    Both wifi_softap_get_station_info() and wifi_softap_free_station_info() are part of the underlying SDK on which the ESP8266 Arduino framework is built and unfortunately are less well-documented than the Arduino functions.