QEMU Network Internals¶
This document provides detailed technical information about the UART-based IP tunnel implementation for ESP32 QEMU emulation.
Note
This is advanced technical documentation. For usage instructions, see QEMU Emulator Guide.
Architecture Overview¶
┌─────────────────────────────────────────────────────────────────┐
│ HOST (Linux) │
│ │
│ ┌──────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ ping │─────▶│ tun0 │◀────▶│ TUN Bridge │ │
│ │ curl │ │ 192.168. │ │ (Python) │ │
│ └──────────┘ │ 100.1/24 │ └─────────────────┘ │
│ └─────────────┘ │ │
│ │ TCP:5556 │
└──────────────────────────────────────────────────┼──────────────┘
│
│ QEMU chardev
┌──────────────────────────────────────────────────┼──────────────┐
│ ESP32 QEMU │ │
│ ▼ │
│ ┌────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Web │ │ lwIP │ │ UART1 │ │
│ │ Server │◀───▶│ Stack │◀───▶│ Driver │ │
│ │ HTTP │ │ 192.168. │ │ │ │
│ │ │ │ 100.2/24 │ └─────────────┘ │
│ └────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Frame Format¶
Ethernet Frame Encapsulation¶
┌───────────────┬────────────────────────────────────────────┐
│ Frame Length │ Ethernet Frame │
│ (2 bytes) │ (14-byte header + IP) │
│ Big Endian │ │
├───────────────┼────────────────────────────────────────────┤
│ [HI] [LO] │ [DST_MAC:6][SRC_MAC:6][TYPE:2][IP PACKET] │
└───────────────┴────────────────────────────────────────────┘
Example ICMP Echo Request (98 bytes total):
Length: 0x00 0x62 (98 bytes)
Ethernet:
Dst MAC: 02:00:00:00:00:02 (ESP32)
Src MAC: 02:00:00:00:00:01 (Host)
Type: 0x08 0x00 (IPv4)
IP:
Version/IHL: 0x45 (IPv4, 20 byte header)
Protocol: 0x01 (ICMP)
Src IP: 192.168.100.1 (0xC0 0xA8 0x64 0x01)
Dst IP: 192.168.100.2 (0xC0 0xA8 0x64 0x02)
ICMP:
Type: 0x08 (Echo Request)
Code: 0x00
Initialization Sequence¶
System Startup¶
Initialize Network Stack (
wifi_manager_sim.c)esp_netif_init()esp_event_loop_create_default()
Initialize UART Tunnel Driver (
netif_uart_tunnel_sim.c)Hardware Setup:
Configure UART1: 115200 baud, GPIO 17/16
uart_driver_install()with 2KB RX/TX buffers
Create esp_netif:
ESP_NETIF_INHERENT_DEFAULT_ETH()configesp_netif_new()
Configure Static IP:
IP: 192.168.100.2
Gateway: 192.168.100.1
Netmask: 255.255.255.0
Direct lwIP Integration:
Get lwIP netif handle
Set MAC: 02:00:00:00:00:02
Set flags: ETHARP | ETHERNET | BROADCAST
Register linkoutput callback
Set as default netif
Add static ARP entry for gateway
Start RX Task
FreeRTOS task polls UART for incoming frames
Priority 5 (high priority for network responsiveness)
Packet Flow¶
Outgoing (TX): ESP32 → Host¶
Application Layer
Application calls lwIP functions (e.g.,
send(),httpd_resp_send())
lwIP TCP/IP Stack
TCP/UDP processing
IP header generation
Routing decision (uses UART netif)
Ethernet Layer
lwIP calls
linkoutputcallbackFunction:
netif_output()innetif_uart_tunnel_sim.c
Frame Preparation
Extract Ethernet header from pbuf (14 bytes)
Get payload length
Prepare 2-byte length prefix (big-endian)
UART Transmission
Write length prefix to UART1
Write Ethernet frame to UART1
QEMU chardev forwards to TCP socket
TUN Bridge (Python)
Receives frame from TCP socket
Writes frame to TUN device
Linux kernel processes as Ethernet frame
Host Network Stack
Routes to appropriate application (curl, browser, etc.)
Incoming (RX): Host → ESP32¶
Host Application
Application sends packet (e.g., HTTP request)
Linux Network Stack
Routes to TUN device (192.168.100.2)
TUN Bridge (Python)
Reads Ethernet frame from TUN device
Writes length-prefixed frame to TCP socket
QEMU Chardev
Forwards data to emulated UART1
ESP32 UART Driver
RX interrupt triggers
Data copied to FreeRTOS queue
RX Task (
uart_rx_task)Reads 2-byte length prefix
Reads Ethernet frame (up to 1518 bytes)
Validates frame length and format
lwIP Input
Allocates pbuf
Copies frame data to pbuf
Calls
netif->input()→tcpip_input()
lwIP TCP/IP Stack
Ethernet processing
IP routing
TCP/UDP handling
Application Layer
Application receives data via socket
Critical Implementation Details¶
Buffer Management¶
// RX buffer allocation
#define UART_RX_BUF_SIZE 2048
#define MAX_ETH_FRAME_SIZE 1518
uint8_t frame_buffer[MAX_ETH_FRAME_SIZE];
Strategy:
Fixed-size buffer on stack for frame assembly
pbuf allocation only after complete frame received
Minimizes heap fragmentation
UART Configuration¶
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
};
Performance:
115200 baud ≈ 14.4 KB/s theoretical max
Practical throughput: ~10 KB/s (overhead + framing)
Adequate for HTTP, slow for large transfers
Error Handling¶
// Length validation
if (frame_len < 14 || frame_len > MAX_ETH_FRAME_SIZE) {
ESP_LOGW(TAG, "Invalid frame length: %d", frame_len);
continue; // Skip malformed frame
}
Robustness:
Frame length validation prevents buffer overflows
Timeout on incomplete frames (1 second)
Automatic recovery from malformed packets
MAC Address Assignment¶
static const uint8_t esp32_mac[6] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x02};
static const uint8_t host_mac[6] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
Design:
Locally administered MAC addresses (bit 1 set)
Static assignment for predictable ARP behavior
No MAC learning required
Static ARP Entry¶
// Add gateway ARP entry
ip4_addr_t gw_addr;
IP4_ADDR(&gw_addr, 192, 168, 100, 1);
etharp_add_static_entry(&gw_addr, (struct eth_addr*)host_mac);
Why Static ARP:
Prevents ARP requests over UART (reduces overhead)
Immediate connectivity without ARP handshake
Simplifies bridge implementation
Performance Characteristics¶
Throughput¶
HTTP GET small file: ~5-8 KB/s
HTTP GET 1MB file: ~10 KB/s sustained
Ping latency: 3-8 ms typical
Bottlenecks¶
UART Bandwidth: 115200 baud limit
Frame Overhead: 2-byte length + 14-byte Ethernet header per packet
QEMU Emulation: CPU overhead vs. real hardware
Optimization Opportunities¶
Increase Baud Rate: 230400 or 460800 (requires TUN bridge update)
Jumbo Frames: Support 9KB frames (requires lwIP MTU change)
DMA: Use UART DMA for reduced CPU overhead
Zero-Copy: Direct pbuf allocation in UART callback
Debugging Network Issues¶
Enable Verbose Logging¶
// In netif_uart_tunnel_sim.c
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
Capture UART Traffic¶
# Monitor UART1 in separate terminal
./tools/view_uart1.sh
Analyze with tcpdump¶
# Capture TUN device traffic
sudo tcpdump -i tun0 -w capture.pcap
# Analyze with Wireshark
wireshark capture.pcap
Check Frame Alignment¶
# Look for length/frame mismatches in logs
grep "RX:" /tmp/qemu_uart*.log | head -20
Known Issues and Limitations¶
Current Limitations¶
UART Speed: 115200 baud limits throughput to ~10 KB/s
No WiFi Emulation: Direct IP connectivity, no AP/STA simulation
Single Interface: Cannot emulate multiple network interfaces
No Packet Loss: Reliable UART, no wireless error simulation
Future Improvements¶
Higher Baud Rate: 460800+ for better throughput
WiFi Event Simulation: Emulate connection/disconnection events
Packet Loss Simulation: Inject errors for robustness testing
Multiple Interfaces: Support AP + STA simultaneously
Source Code Reference¶
Key Files¶
main/components/netif_uart_tunnel/netif_uart_tunnel_sim.c- ESP32 drivermain/components/web_server/wifi_manager_sim.c- Network initializationtools/serial_tun_bridge.py- Host-side TUN bridgetools/run-qemu-network.sh- Launch script