Memory Leak if using WebSockets

Michael Schmidl
Posts: 1
Joined: Thu Aug 29, 2019 7:51 am

Memory Leak if using WebSockets

Postby Michael Schmidl » Thu Aug 29, 2019 8:03 am

Ladies and Gentlemen,

we use the ESP32 with the ESP-IDF SDK to provide an access point for our application. The communication between the mobile device with the ESP32 is using websockets to exchange information. The ESP32 is about to accept only one WiFi client and one websocket connection at a time.

The websocket handler is FreeRTOS task as follows:
  1. static void _taskCallbackWsServer(void *pvParameters)
  2. {
  3.     ( void ) pvParameters;
  4.  
  5.     //connection references
  6.     ESP_LOGI(TAGSocket, "ws server start");
  7.     struct netconn *conn;
  8.     struct netconn *newconn;
  9.  
  10.     //set up new TCP listener
  11.     conn = netconn_new(NETCONN_TCP);
  12.     netconn_bind(conn, NULL, WS_PORT);
  13.     netconn_listen(conn);
  14.  
  15.     //wait for connections
  16.     while (netconn_accept(conn, &newconn) == ERR_OK)
  17.     {
  18.         _wsServerNetconnServe(newconn);
  19.     }
  20.  
  21.     //close connection
  22.     netconn_close(conn);
  23.     netconn_delete(conn);
  24. }
The function _wsServerNetconnServe() as used when a connection got accepted, is as follows:
  1. static void _wsServerNetconnServe(struct netconn *conn)
  2. {
  3.     //Netbuf
  4.     struct netbuf *inbuf;
  5.  
  6.     //message buffer
  7.     char *buf;
  8.  
  9.     //pointer to buffer (multi purpose)
  10.     char* p_buf;
  11.  
  12.     //Pointer to SHA1 input
  13.     char* p_SHA1_Inp;
  14.  
  15.     //Pointer to SHA1 result
  16.     char* p_SHA1_result;
  17.  
  18.     //multi purpose number buffer
  19.     uint16_t i;
  20.  
  21.     //will point to payload (send and receive
  22.     char* p_payload;
  23.  
  24.     //Frame header pointer
  25.     WS_frame_header_t* p_frame_hdr;
  26.  
  27.     //allocate memory for SHA1 input
  28.     p_SHA1_Inp = heap_caps_malloc(WS_CLIENT_KEY_L + sizeof(WS_sec_conKey),
  29.                                   MALLOC_CAP_8BIT);
  30.  
  31.     //allocate memory for SHA1 result
  32.     p_SHA1_result = heap_caps_malloc(SHA1_RES_L, MALLOC_CAP_8BIT);
  33.  
  34.     //Check if malloc suceeded
  35.     if ((p_SHA1_Inp != NULL) && (p_SHA1_result != NULL))
  36.     {
  37.  
  38.         //receive handshake request
  39.         if (netconn_recv(conn, &inbuf) == ERR_OK)
  40.         {
  41.             WebSocket_receivedMessages++;
  42.  
  43.             //read buffer
  44.             netbuf_data(inbuf, (void**) &buf, &i);
  45.  
  46.             //write static key into SHA1 Input
  47.             for (i = 0; i < sizeof(WS_sec_conKey); i++)
  48.                 p_SHA1_Inp[i + WS_CLIENT_KEY_L] = WS_sec_conKey[i];
  49.  
  50.             //find Client Sec-WebSocket-Key:
  51.             p_buf = strstr(buf, WS_sec_WS_keys);
  52.  
  53.             //check if needle "Sec-WebSocket-Key:" was found
  54.             if (p_buf != NULL)
  55.             {
  56.                 //get Client Key
  57.                 for (i = 0; i < WS_CLIENT_KEY_L; i++)
  58.                     p_SHA1_Inp[i] = *(p_buf + sizeof(WS_sec_WS_keys) + i);
  59.  
  60.                 // calculate hash
  61.                 esp_sha(SHA1, (unsigned char*) p_SHA1_Inp, strlen(p_SHA1_Inp),
  62.                         (unsigned char*) p_SHA1_result);
  63.  
  64.                 //hex to base64
  65.                 mbedtls_base64_encode((unsigned char*)0,0,(size_t*) &i,(unsigned char*) p_SHA1_result,
  66.                                       SHA1_RES_L);
  67.  
  68.                 size_t p_buf_len = i;
  69.                 p_buf = heap_caps_malloc(p_buf_len, MALLOC_CAP_8BIT);
  70.  
  71.                 mbedtls_base64_encode((unsigned char*)p_buf,p_buf_len,(size_t*) &i,(unsigned char*) p_SHA1_result,
  72.                                       SHA1_RES_L);
  73.  
  74.                 i = p_buf_len;
  75.  
  76.                 //free SHA1 input
  77.                 free(p_SHA1_Inp);
  78.  
  79.                 //free SHA1 result
  80.                 free(p_SHA1_result);
  81.  
  82.                 //allocate memory for handshake
  83.                 p_payload = heap_caps_malloc(
  84.                         sizeof(WS_srv_hs) + i - WS_SPRINTF_ARG_L,
  85.                         MALLOC_CAP_8BIT);
  86.  
  87.                 //check if malloc suceeded
  88.                 if ( (p_payload != NULL) && (p_buf != NULL))
  89.                 {
  90.                     //prepare handshake
  91.                     sprintf(p_payload, WS_srv_hs, i - 1, p_buf);
  92.  
  93.                     //send handshake
  94.                     WebSocket_sentMessages++;
  95.                     netconn_write(conn, p_payload, strlen(p_payload),
  96.                                   NETCONN_COPY);
  97.  
  98.                     //free base 64 encoded sec key
  99.                     free(p_buf);
  100.  
  101.                     //free handshake memory
  102.                     free(p_payload);
  103.  
  104.                     //set pointer to open WebSocket connection
  105.                     WS_conn = conn;
  106.  
  107.                     _requestSetStatusOperation();
  108.                     //ESP_ERROR_CHECK(esp_timer_start_once(_packetTimeout_timer, 500000 ) );
  109.  
  110.                     //Wait for new data
  111.                     while (netconn_recv(conn, &inbuf) == ERR_OK)
  112.                     {
  113.                         WebSocket_receivedMessages++;
  114.  
  115.                         //read data from inbuf
  116.                         netbuf_data(inbuf, (void**) &buf, &i);
  117.  
  118.                         //get pointer to header
  119.                         p_frame_hdr = (WS_frame_header_t*) buf;
  120.  
  121.                         //check if clients wants to close the connection
  122.                         if (p_frame_hdr->opcode == WS_OP_CLS)
  123.                             break;
  124.  
  125.                         //get payload length
  126.                         if (p_frame_hdr->payload_length <= WS_STD_LEN)
  127.                         {
  128.                             //get beginning of mask or payload
  129.                             p_buf = (char*) &buf[sizeof(WS_frame_header_t)];
  130.  
  131.                             //check if content is masked
  132.                             if (p_frame_hdr->mask)
  133.                             {
  134.                                 //allocate memory for decoded message
  135.                                 p_payload = heap_caps_malloc( p_frame_hdr->payload_length + 1,
  136.                                                               MALLOC_CAP_8BIT);
  137.  
  138.                                 //check if malloc succeeded
  139.                                 if (p_payload != NULL)
  140.                                 {
  141.                                     //decode playload
  142.                                     for (i = 0; i < p_frame_hdr->payload_length; i++)
  143.                                     {
  144.                                         p_payload[i] = ( (p_buf + WS_MASK_L)[i] ^ p_buf[i % WS_MASK_L] );
  145.                                     }
  146.  
  147.                                     //add 0 terminator
  148.                                     p_payload[p_frame_hdr->payload_length] = 0;
  149.                                 }
  150.                             }
  151.                             else
  152.                             {
  153.                                 //content is not masked
  154.                                 p_payload = p_buf;
  155.                             }
  156.  
  157.                             //do stuff
  158.                             if ((p_payload != NULL) && (p_frame_hdr->opcode == WS_OP_TXT))
  159.                             {
  160.                                 //prepare FreeRTOS message
  161.                                 WebSocket_frame_t __ws_frame;
  162.                                 __ws_frame.connection=conn;
  163.                                 __ws_frame.frame_header=*p_frame_hdr;
  164.                                 __ws_frame.payload_length=p_frame_hdr->payload_length;
  165.                                 __ws_frame.payload=p_payload;
  166.  
  167.                                 //send message
  168.                                 xQueueSendFromISR(_webSocketRxQueueHandle,&__ws_frame,0);
  169.                             }
  170.  
  171.                             //free payload buffer (in this demo done by the receive task)
  172.                             //                          if (p_frame_hdr->mask && p_payload != NULL)
  173.                             //                              free(p_payload);
  174.  
  175.                         }
  176.                         else //p_frame_hdr->payload_length<126
  177.                         {
  178.                             //get beginning of mask or payload
  179.                             p_buf = (char*) &buf[sizeof(WS_big_frame_header_t)];
  180.                             WS_big_frame_header_t* p_big_frame_hdr = (WS_big_frame_header_t*) buf;
  181.  
  182.                             //check if content is masked
  183.                             size_t payload_length = (p_big_frame_hdr->ext_payload_lengthMSB << 8) | p_big_frame_hdr->ext_payload_lengthLSB;// zeile von unten
  184.                             if (p_frame_hdr->mask)
  185.                             {
  186.                                 //allocate memory for decoded message
  187.                                 p_payload = heap_caps_malloc( payload_length + 1,
  188.                                                               MALLOC_CAP_8BIT);
  189.  
  190.                                 //check if malloc succeeded
  191.                                 if (p_payload != NULL)
  192.                                 {
  193.                                     //decode playload
  194.                                     for (i = 0; i < payload_length; i++)
  195.                                     {
  196.                                         p_payload[i] = ( (p_buf + WS_MASK_L)[i] ^ p_buf[i % WS_MASK_L] );
  197.                                     }
  198.  
  199.                                     //add 0 terminator
  200.                                     p_payload[payload_length] = 0;
  201.                                 }
  202.                             }
  203.                             else
  204.                             {
  205.                                 //content is not masked
  206.                                 p_payload = p_buf;
  207.                             }
  208.  
  209.                             //do stuff
  210.                             if ((p_payload != NULL) && (p_frame_hdr->opcode == WS_OP_TXT))
  211.                             {
  212.                                 //prepare FreeRTOS message
  213.                                 WebSocket_frame_t __ws_frame;
  214.                                 __ws_frame.connection       = conn;
  215.                                 __ws_frame.frame_header     = *p_frame_hdr;
  216.                                 __ws_frame.payload_length   = payload_length;
  217.                                 __ws_frame.payload          = p_payload;
  218.                                 //                              uint8_t ext_payload_lengthMSB: 8;
  219.                                 //                              uint8_t ext_payload_lengthLSB: 8;
  220.                                 //send message
  221.                                 xQueueSendFromISR(_webSocketRxQueueHandle,&__ws_frame,0);
  222.                             }
  223.                         }
  224.  
  225.                         //free input buffer
  226.                         netbuf_delete(inbuf);
  227.  
  228.                     } //while(netconn_recv(conn, &inbuf)==ERR_OK)
  229.                 } //p_payload!=NULL
  230.             } //check if needle "Sec-WebSocket-Key:" was found
  231.         } //receive handshake
  232.     } //p_SHA1_Inp!=NULL&p_SHA1_result!=NULL
  233.  
  234.     //release pointer to open WebSocket connection
  235.     WS_conn = NULL;
  236.  
  237.     //delete buffer
  238.     netbuf_delete(inbuf);
  239.  
  240.     // Close the connection
  241.     netconn_close(conn);
  242.  
  243.     //Delete connection
  244.     netconn_delete(conn);
  245. }
This works pretty good, but if a connection gets disconnected by the client (e.g. by closing the web browser or the mobile application), about 2K of RAM are not freed. If you new connection is established afterwards and closed again, another 2K are not freed.

After about 30..40 connects and disconnects, the ESP32 is not handling new connection also a lot of free memory is still available.

Any idea how to get rid of it?

Any help is appregiated.

with kind regards
Michael

ESP_Sprite
Posts: 9769
Joined: Thu Nov 26, 2015 4:08 am

Re: Memory Leak if using WebSockets

Postby ESP_Sprite » Fri Aug 30, 2019 3:11 am

Take a good, hard look at when you free your variables. For instance, p_SHA1_Inp is only freed in the good-weather-scenario; if there's anything that goes wrong, it never gets freed. For a more generic approach, you may want to look at the relevant esp-idf documentation chapter.

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 123 guests