mTCP: A User-level TCP Stack
Mainack Mondal
Sandip Chakraborty
CS 60203
Autumn 2026
Outline
Motivation: Short Connections Everywhere
Client
Server
SYN
SYN-ACK
ACK + request
response
FIN
FIN-ACK
ACK
setup
data
teardown
Short flows dominate many workloads
So the per-connection cost matters most
Why the Linux TCP Stack Does Not Scale
Core 0
app thread
Core 1
app thread
Core 2
app thread
Core 3
app thread
One listen socket, one accept queue
protected by one lock
One file descriptor table per process
"lowest free number" rule forces sharing
Every core competes for the same queue and table.
Shared resources
Broken locality
System call overhead
Heavy sockets
Result: for short connections, most CPU time goes to kernel overhead, not to the application.
Earlier Fixes Were Partial
System | Accept queue | Connection locality | System call batching | Packet I/O batching | API |
Linux (3.x) | Shared | No | No | No | BSD sockets |
Linux + SO_REUSEPORT (3.9+) | Per core | Not guaranteed | No | No | BSD sockets |
Affinity-Accept (EuroSys 2012) | Per core | Yes | No | No | BSD sockets |
MegaPipe (OSDI 2012) | Per core | Yes | Yes | No | New lightweight socket API |
mTCP (NSDI 2014) | Per core | Yes | No system calls on the fast path | Yes | BSD-like, at user level |
All earlier systems keep the kernel TCP stack, so they still pay kernel costs for every packet. mTCP removes the kernel from the fast path.
The Base Idea of mTCP
Linux
mTCP
Application
System calls
Kernel TCP/IP
NIC driver
NIC
Application
mTCP library
TCP/IP in user space
Packet I/O library
PSIO, DPDK, netmap
Thin kernel driver
setup only
NIC
red dashed line: user/kernel boundary
1. Move TCP to user space
2. Partition everything per core
3. Batch everything
mTCP Architecture
Core 0
App thread
your server code
job queue ↓
event queue ↑
mTCP thread
all TCP processing
Packet I/O
user-level library
RX/TX queue 0
Core 1
App thread
your server code
job queue ↓
event queue ↑
mTCP thread
all TCP processing
Packet I/O
user-level library
RX/TX queue 1
Core 2
App thread
your server code
job queue ↓
event queue ↑
mTCP thread
all TCP processing
Packet I/O
user-level library
RX/TX queue 2
NIC with RSS: each flow is hashed to one queue
Key Concept 1: Per-Core Partitioning
Flow A
Flow B
Flow C
Flow D
NIC
RSS hash of the 4-tuple
Core 0
own listen queue, fd table, TCP state, timers, buffers
Core 1
own listen queue, fd table, TCP state, timers, buffers
Core 2
own listen queue, fd table, TCP state, timers, buffers
Core 3
own listen queue, fd table, TCP state, timers, buffers
All packets of one connection always reach the same core.
Key Concept 2: Batching at Every Level
Linux: one system call (user → kernel → user) per operation
app
kernel
app
kernel
app
kernel
app
kernel
app
kernel
app
kernel
each switch costs a mode change, and pollutes caches and TLBs
mTCP: work is queued, then handled in one go
App thread
queues many jobs; no system call
mTCP thread
handles all jobs and arrived packets; sends one packet batch
App thread
handles all new events
Packet batching
Job and event batching
The trade-off
Key Concept 3: The mTCP Thread Loop
1. Receive
a batch of packets from the NIC queue
2. TCP processing
state machine, ACKs, reassembly
3. Post events
new connection, data ready, closed
4. Take app jobs
send, connect, close requests
5. Timers
retransmit, timeouts, TIME_WAIT
6. Send
one batch of packets out
one loop per core, runs again and again
Programming Model
Create one mTCP context per core
Create socket, bind, listen
mTCP versions of the usual calls
Wait for events
epoll-like, per core
Accept, read, write
non-blocking, mTCP versions
loop
Same shape as an epoll-based server on Linux.
Familiar API
Rules to follow
Ported in the paper
Results from the Paper (NSDI 2014)
25x
faster than Linux for small message transactions on short connections
3x
faster than MegaPipe, the best earlier research system
33 to 320%
faster real applications (SSLShader to lighttpd) than on Linux
Where the gain comes from
Where the gain is small
mTCP vs Linux TCP vs XDP
Aspect | Linux TCP stack | XDP | mTCP |
What it is | Full TCP/IP in the kernel | Packet hook in the NIC driver | Full TCP/IP in user space |
Handles TCP connections? | Yes | No: per-packet decisions only | Yes |
Kernel work per packet | Full stack | Driver only | None on the fast path |
Sharing the NIC | All applications | Yes; other packets go to the stack | Owned by one application |
CPU use when idle | Low | Low | Polling cores stay busy |
Safety | Trusted kernel code | Verifier-checked program | App and stack in one process |
Best for | General use | Filters, load balancers, forwarding | Servers with many short connections |
They solve different problems and can be combined: AF_XDP can serve as the packet I/O layer under a user-level stack.
Applications and Legacy
Web servers and proxies
Load generators
Middleboxes
Runs on modern I/O
Influence on industry
Influence on research
Limitations of mTCP
Design trade-offs
Practical issues
Lesson that lasted: share nothing across cores, batch fixed costs, and keep the kernel off the fast path. Later systems kept these ideas and added better protection.
Summary
References