This is a project experience shared by a colleague of mine. They developed an industrial gateway based on STM32F767 for a factory, which is used for:
- Collecting data from multiple Modbus/485 sensors
- Uploading data to the dispatch server via TCP/UDP protocol
- Supporting up to 32 devices connected simultaneously (LwIP + RAW API)
Initial small-scale tests went smoothly, but once it went live, the customer reported:
“As soon as more than 10 devices are connected, the gateway starts dropping connections and packets, and in severe cases, the entire system freezes, requiring a power cycle to restart.”
After testing:
- CPU usage skyrocketed with the 8th connection
- From the 12th connection, LwIP began to continuously report
<span>pbuf_alloc failed</span> - From the 15th connection, the system fell into a loop of packet loss and crashed completely within seconds
🚨 Fault Phenomenon: Increasing Connection Count → Network Packet Loss → LwIP Anomalies → System Hang
LwIP logs continuously output:
mem_malloc: could not allocate memory
tcp_alloc: pbuf allocation failed
pbuf_alloc: Out of pbufs
Further manifestations:
- TCP connections are constantly being reset
- UDP packet reception failure rate > 80%
- CPU usage is not high, but system response slows down or even hangs
- Sometimes even the watchdog cannot recover
🔍 Preliminary Diagnosis: LwIP Default Memory Pool Cannot Handle High Concurrency, Severe Memory Fragmentation
The LwIP protocol stack uses multiple memory pools (mempools) to manage different objects:
| Memory Pool | Usage |
|---|---|
<span>PBUF_POOL</span> |
Network packet buffer |
<span>TCP_SEG</span> |
TCP segment structure |
<span>NETCONN</span>, <span>RAW_PCB</span> |
Connection control block |
<span>MEM</span> |
Heap memory (e.g., <span>mem_malloc()</span>) |
We enabled the following default configurations (generated by CubeMX):
#define MEM_SIZE 16000
#define MEMP_NUM_PBUF 16
#define PBUF_POOL_SIZE 16
This is sufficient for small data volumes, but when the number of connections reaches 10+, each connection occupies multiple <span>pbuf</span>, quickly leading to:
“pbuf pool exhausted, PBUF_ALLOC failed → LwIP cannot receive → packet loss → connection reset → crash”
We also found:
- LwIP defaults to using
<span>mem_malloc()</span>to allocate structures like TCP_SEG, leading to rapid memory fragmentation - Resource contention occurs during multi-task
<span>recv()</span>, causing dynamic memory fragmentation in FreeRTOS
🧠 The Truth Revealed: LwIP’s Default Configuration Cannot Handle High Concurrency, Custom Memory Pools are Essential!
By analyzing LwIP mem_stats and runtime memory distribution, we confirmed:
- Exhaustion of pbuf_pool is the root cause
<span>mem_malloc()</span>frequently allocates small objects, leading to severe fragmentation<span>MEMP_NUM_TCP_SEG</span>is too small, causing TCP backlog and blocking transmission
✅ Solution: Three Steps to Build an STM32 Gateway Capable of Withstanding 10 Times the Connection Load
🧱 Step 1: Expand LwIP Memory Pools, Dedicated Pools for Each Object
We modified <span>lwipopts.h</span>:
#define MEM_SIZE 64000
#define MEMP_NUM_PBUF 128
#define MEMP_NUM_TCP_SEG 64
#define MEMP_NUM_NETCONN 32
#define PBUF_POOL_SIZE 128
#define PBUF_POOL_BUFSIZE 512
Reserving multiple pbufs + TCP SEG for each connection to avoid resource contention
Results:
- Single connection usage is more elastic
- No more occurrences of
<span>pbuf_alloc failed</span> - Memory fragmentation significantly reduced
🧠 Step 2: Enable LwIP Custom Memory Pool Mechanism (memp_std) + Fixed-Length Allocation
We enabled:
#define MEM_USE_POOLS 1
#define MEMP_USE_CUSTOM_POOLS 1
And customized multiple memory block pools in <span>lwippools.h</span>:
LWIP_MEMPOOL_DECLARE(RXBUF, 64, 512, "RX Buffers")
LWIP_MEMPOOL_DECLARE(TXBUF, 64, 512, "TX Buffers")
Benefits:
- Each type of data structure manages its own memory pool
- Faster allocation speed (fixed-length blocks)
- Avoids dynamic fragmentation from
<span>malloc()</span>
🛡️ Step 3: Network Task Isolation + Receive Queue Rate Limiting to Prevent “Storm Attacks”
We implemented rate limiting for the network task structure:
- Each connection can occupy a maximum of 8 pbufs
- Using message queues to block recv(), preventing CPU idling
- Automatically disconnecting abnormal connections (e.g., not reading data for a long time)
We also added a connection monitoring thread:
if (conn->recv_q_len > 8 || conn->idle_time > 10s) {
netconn_close(conn);
netconn_delete(conn);
}
To prevent individual devices from “dragging down the entire system”
🧪 Optimized Testing Results
We conducted a simulated “storm attack test”: connecting 60 TCP clients simultaneously, sending 1000 small packets per second.
| Metric | Before Optimization | After Optimization |
|---|---|---|
| Maximum Connection Count | 10~12 | 60+ |
| Packet Loss Rate | > 75% | < 0.5% |
| LwIP Memory Overflow | Frequent | 0 times |
| CPU Usage | High Fluctuation | Stable |
| System Stable Running Time | Minutes | Continuous 72 hours without crash |
| Customer Satisfaction | ❌ Suspended Cooperation | ✅ Resumed and Expanded Orders |
🧰 Final Thoughts
While LwIP is lightweight, it is not a “plug-and-play” universal TCP/IP solution:
- ✅ High concurrency scenarios must redesign
<span>lwipopts.h</span> - ✅ Use
<span>MEMP</span>custom pools instead of<span>malloc()</span>to avoid fragmentation - ✅ Network tasks need rate limiting and isolation to prevent “global failure”
- ✅ Add monitoring threads to detect and clean up abnormal connections
Otherwise, you will face:“A disaster scene where too many connections lead to system avalanche!”