On this page
Network Protocol Stack Overview
Coverage: sk_buff → NAPI → softirq (NET_RX/NET_TX) → protocol stack layers → socket API → net_device → multi-queue Kernel version: 2.6 ~ 6.x
Overview
The Linux network stack is the most widely used TCP/IP implementation in the world. From NIC DMA packet reception to the user-space read() return, packets go through 10+ layers of processing. This article traces this complete path, focusing on core data structures and design decisions.
sk_buff: Packet Descriptor
// include/linux/skbuff.h
;
The core design of sk_buff is the four-pointer layout of head/data/tail/end: data resides in the buffer allocated from head to end, while data to tail represents valid data. The regions head to data and tail to end are headroom and tailroom, respectively. As the protocol stack adds each layer's header, the data pointer moves backward, reusing the existing headroom—no copying is required.
NAPI: Hybrid Interrupt + Polling
// net/core/dev.c
// Problem: High-speed NICs (10Gbps+) receive millions of packets per second → one interrupt per packet → interrupt storm
// NAPI: First packet triggers an interrupt notification → interrupts disabled → polling for packet reception in softirq
// NIC Driver:
// 1. Packet received → disable RX interrupt → napi_schedule(&napi)
// 2. NET_RX_SOFTIRQ triggered → napi_poll()
// → Batch receive packets (one budget, typically 300 packets)
// → After processing a batch → if more packets remain → continue polling
// → If no more packets → enable RX interrupt → return to step 1
Complete Packet Reception Path
flowchart TD
DMA["NIC DMA<br/>→ Ring Buffer (RX ring)"]
DMA -->|"Hardware Interrupt"| IRQ["Driver Interrupt Handler<br/>napi_schedule()"]
IRQ -->|"NET_RX_SOFTIRQ"| POLL["napi_poll()<br/>Batch receive, up to 300 packets"]
POLL -->|"Per Packet"| NETIF["__netif_receive_skb()"]
NETIF --> XDP["XDP hook (bpf)<br/>Earliest processing point"]
NETIF --> RXH{"RX Handler<br/>List Check"}
RXH -->|"tcpdump"| AF_PACKET["AF_PACKET<br/>Copy to packet socket"]
RXH -->|"bridge/bonding/VLAN"| REDIR["Possible Redirection"]
NETIF --> DELIVER["deliver_skb()"]
DELIVER --> IP_RCV["ip_rcv()<br/>L3 (IPv4)"]
IP_RCV --> IP_LOCAL["ip_local_deliver()"]
IP_LOCAL --> NF{"netfilter<br/>PREROUTING"}
NF -->|"FORWARD"| FWD["Forward to other interfaces"]
NF -->|"INPUT"| IP_FINISH["ip_local_deliver_finish()"]
IP_FINISH --> TCP_RCV["tcp_v4_rcv()<br/>L4 (TCP)"]
TCP_RCV --> TCP_DO["tcp_v4_do_rcv()"]
TCP_DO --> STATE{"Connection State?"}
STATE -->|"ESTABLISHED"| FAST["tcp_rcv_established()<br/>Fast path ⚡"]
STATE -->|"LISTEN"| SLOW["tcp_rcv_state_process()"]
SLOW --> QUEUE["Data enqueued to receive queue"]
FAST --> QUEUE
QUEUE --> WAKE["sk_data_ready()<br/>Wake up blocked read() ✅"]
classDef hw fill:#fff3e0,stroke:#ef6c00
classDef irq fill:#ffebee,stroke:#c62828
classDef l2 fill:#e3f2fd,stroke:#1565c0
classDef l3 fill:#f3e5f5,stroke:#7b1fa2
classDef l4 fill:#e8f5e9,stroke:#2e7d32
classDef decision fill:#fff8e1,stroke:#f9a825
class DMA,IRQ hw
class POLL,NETIF,XDP,RXH,AF_PACKET,REDIR l2
class IP_RCV,IP_LOCAL,IP_FINISH,DELIVER,NF,FWD l3
class TCP_RCV,TCP_DO,FAST,SLOW,QUEUE,WAKE l4
class STATE decision
Multi-Queue and RSS
RSS (Receive Side Scaling):
NIC uses hash(src_ip, dst_ip, src_port, dst_port) % N
→ N receive queues → N CPUs
NAPI's napi_struct is per-queue:
→ Each queue has its own NAPI → Parallel packet reception
→ Packets from the same flow always go to the same queue → TCP ordering preserved
XPS (Transmit Packet Steering):
Multi-queue on the transmit side: similar concept but for TX
net_device: Network Device Abstraction
// include/linux/netdevice.h
;
Debugging
# Packet receive/transmit statistics
# Softirq statistics (NET_RX/NET_TX)
|
# NAPI statistics (per-queue polling)
|
# Drop reasons (drop counter)
References
- Source Code:
net/core/dev.c,include/linux/skbuff.h,include/linux/netdevice.h - Kernel Documentation:
Documentation/networking/ - LWN: "The Linux networking stack", "NAPI design"
Keywords: sk_buff, NAPI, NET_RX_SOFTIRQ, multi-queue, RSS, net_device, XDP