6.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Build
Requires DPDK 24.11 built with meson at ~/share/dpdk-study/sources/dpdk-24.11/build.
make
The binary is ./build/dpdk_ws_client. The Makefile uses pkg-config (with PKG_CONFIG_PATH pointing to DPDK's uninstalled .pc files) to get compiler/linker flags. The binary includes an RPATH pointing to the DPDK lib directory, so no LD_LIBRARY_PATH is needed at runtime. Key flags: -DALLOW_EXPERIMENTAL_API, -Wl,--allow-multiple-definition, -O2 -g.
Test/benchmark binaries (standalone, no DPDK needed)
gcc -O2 -o test/test_ws_protocol test/test_ws_protocol.c -lpthread
gcc -O2 -o test/bench_tcp test/bench_tcp.c -lpthread
Architecture
This is a DPDK user-space TCP/IP stack with a WebSocket client (RFC 6455) layered on top. It runs entirely in userspace — no kernel TCP.
Lcore split
- lcore0 (main thread): DPDK RX/TX burst from NIC, WS receive polling, application logic (
main.c) - lcore1 (pkt_process): Protocol processing loop — TCP state machine, ARP/ICMP, timer maintenance, TX output engine (
tcp_core.c)
Data crosses lcores via two rte_ring SP/SC queues: ring->in (RX → pkt_process) and ring->out (pkt_process → TX).
Layer stack (bottom-up)
DPDK rte_eth_rx_burst / rte_eth_tx_burst
→ pkt_process dispatch: ARP / ICMP / TCP (ng_tcp_process)
→ TCP 11-state FSM (tcp_input.c) + congestion control (tcp_output.c) + timers (tcp_timer.c)
→ stream->rcvbuf / stream->sndbuf (rte_ring per-TCB)
→ POSIX-like socket API: nsocket / nconnect / nsend / nrecv / nclose (tcp_socket.c)
→ WebSocket: ws_connect (HTTP Upgrade) / ws_send_frame / ws_recv_frame (ws_client.c)
→ Application (main.c)
Critical design details
- Blocking I/O via condition variables:
nconnect()andnrecv()block the app thread usingpthread_cond_wait. The pkt_process lcore signalsstream->condwhen state changes or data arrives.nconnect()additionally services DPDK RX/TX inline while waiting for the handshake to complete. - TCB lookup is O(1): 256-bucket hash table on the 4-tuple (sip, dip, sport, dport). Additionally, an
fd → TCBdirect array (fd_to_tcb[]) provides O(1) lookup from socket fd. - Client→Server frames are MASKED, Server→Client frames are unmasked (RFC 6455). Masking key is 4 random bytes XORed over the payload.
- TCP send path:
nsend()segments into MSS-sizedng_tcp_fragmentstructs, enqueues to stream->sndbuf ring.ng_tcp_out()(called from pkt_process) dequeues them, builds Ethernet/IP/TCP headers (with option bytes fromfragment->option[]), resolves MAC via ARP table, sends to ring->out. - TCP receive path:
ng_tcp_input()validates sequence numbers against the receive window, handles in-order data by enqueueing tostream->rcvbufand wakingnrecv(). Out-of-order segments go toofo_headsorted linked list (max 64 entries), drained when the gap fills. - TCP timers checked per-iteration in pkt_process: RTO with exponential backoff, delayed ACK (200ms), keepalive (2h idle), zero-window probe (1s interval, max 10).
- Congestion control: RFC 5681 slow start / congestion avoidance / fast retransmit (3 dupacks) / fast recovery. cwnd initialized to 2 MSS (~2920 bytes).
- ARP: Linked-list ARP table in
arp.h. ARP requests rate-limited to 10/s per destination. Missing MAC causes the fragment to be re-enqueued to sndbuf. - Globals:
gDpdkPortId,gLocalIp(default192.168.0.115),gSrcMac. Singletons:tcpInstance(),ringInstance().
Key source files
| File | Purpose |
|---|---|
src/main.c |
Entry point, EAL init, cmdline parsing, main loop (RX/TX + WS poll + ping) |
src/tcp_core.h |
All structs (TCB, fragment, hash table), constants, function declarations |
src/tcp_core.c |
Hash table, packet encoding, ARP/ICMP helpers, pkt_process loop, port init, ng_tcp_out |
src/tcp_input.c |
TCP option parsing, sequence validation, 11-state FSM, out-of-order queue |
src/tcp_output.c |
Congestion control (slow start/CA/fast retx/recovery), option building |
src/tcp_timer.c |
RTT estimation (RFC 6298), RTO with backoff, delayed ACK, keepalive, ZWP |
src/tcp_socket.c |
POSIX-like socket API with condition-variable blocking |
src/ws_client.c |
WebSocket handshake, frame encode/decode (masked send, unmasked recv), ping/pong |
src/ws_client.h |
WebSocket opcodes, API declarations |
src/arp.h |
ARP table (linked list), LL_ADD/LL_REMOVE macros |
Tests
test/test_ws_protocol.c— WebSocket protocol test using kernel TCP sockets. Connects to a WS server (Python or Java), tests handshake, echo, subscribe/push. No DPDK needed. Usage:./test_ws_protocol [server_ip] [port]test/bench_tcp.c— Microbenchmarks for the data-path hot paths: ring buffer SP/SC throughput, hash table lookup, WS frame encode/decode, memcpy baseline. CPU-only, no NIC.test/ws_test_server.py— Python async WS echo/subscribe server for manual testing. Dependencies:websockets. Usage:python3 test/ws_test_server.py [port]
Runtime
# 1. Hugepages
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
mkdir -p /mnt/huge && mount -t hugetlbfs nodev /mnt/huge
# 2. Bind NIC (use DPDK 24.11 usertools)
modprobe vfio-pci
sudo python3 ~/share/dpdk-study/sources/dpdk-24.11/usertools/dpdk-devbind.py --bind=vfio-pci <pci_addr>
# 3. Run (RPATH baked in, no LD_LIBRARY_PATH needed)
./build/dpdk_ws_client -l 0-1 -n 2 -- -a <SERVER_IP> -p <PORT> -w /ws
# With subscription and local IP:
./build/dpdk_ws_client -l 0-1 -n 2 -- \
-a 192.168.1.100 -p 8080 -w /ws \
-s '{"action":"subscribe","topic":"market_data"}' \
-l 10.0.0.10
CLI parameters (after --)
| Flag | Default | Description |
|---|---|---|
-a, --addr |
192.168.1.100 |
Server IPv4 |
-p, --port |
8080 |
Server port |
-w, --path |
/ws |
WebSocket path |
-s, --subscribe |
(none) | Subscribe JSON message |
-k, --keepalive |
30 |
Ping interval in seconds (0=off) |
-l, --local-ip |
192.168.0.115 |
Local/stub IPv4 |
Port init
ng_init_port() configures a single port (index 0) with 1 RX queue (128 descriptors) and 1 TX queue (512 descriptors), promiscuous mode enabled. DPDK port ID is the global gDpdkPortId (default 0). In DPDK 24.11, max_rx_pkt_len was replaced by mtu in rte_eth_rxmode.
AWS deployment notes
- Use
c5n.large+ (ENA PMD) orc5n.metal(SR-IOV) - Verify ENA PMD is enabled:
grep ENA $RTE_SDK/x86_64-native-linux-gcc/.config - Security groups must allow the target TCP port