1005 字
3 分钟
网络协议栈
一、网络协议栈概述
Linux 网络协议栈是内核中最复杂的子系统之一,它从上到下贯穿了从用户态 socket API 到网卡驱动的完整数据路径。理解网络协议栈不仅对网络工程师至关重要,对任何需要处理高性能网络 I/O 的开发者也同样重要。
协议栈分层模型
graph TB
subgraph 用户空间
A[应用程序] --> B[socket API]
end
subgraph 内核空间
B --> C[Socket 层<br>struct socket / struct sock]
C --> D[TCP/UDP 层<br>net/ipv4/tcp.c, net/ipv4/udp.c]
D --> E[IP 层<br>net/ipv4/ip_input.c, ip_output.c]
E --> F[网络设备接口<br>net/core/dev.c]
F --> G[设备驱动<br>drivers/net/]
end
subgraph 硬件
G --> H[网卡 NIC]
end
二、socket 与 sock:用户态与内核态的桥梁
Linux 中每个网络连接涉及两个关键结构:socket 和 sock。
struct socket(用户可见层)
socket 是 VFS 层的表示,与文件描述符关联:
struct socket { socket_state state; // socket 状态 short type; // SOCK_STREAM / SOCK_DGRAM 等 unsigned long flags; // 标志位 struct file *file; // 关联的 file 结构 struct sock *sk; // 指向内核层的 sock const struct proto_ops *ops; // 协议操作函数表};struct sock(内核实现层)
sock 是网络协议栈的核心结构,包含了连接的所有状态信息:
// include/net/sock.h(简化)struct sock { struct sock_common __sk_common; // 通用字段(地址、端口等) struct socket *sk_socket; // 反向指向 socket struct proto *sk_prot; // 协议操作函数表 struct net *sk_net; // 所属网络命名空间 rwlock_t sk_callback_lock; int sk_rcvbuf; // 接收缓冲区大小 int sk_sndbuf; // 发送缓冲区大小 struct sk_buff_head sk_receive_queue; // 接收队列 struct sk_buff_head sk_write_queue; // 发送队列 // ... 大量字段};对于 TCP 连接,sock 进一步扩展为 tcp_sock,包含 TCP 特有的状态(窗口、序列号、重传定时器等):
sock → inet_sock → tcp_sock → tcp_connection_sock三、sk_buff:网络数据包的内核表示
sk_buff(socket buffer)是网络数据包在内核中的表示,它是整个网络协议栈中传递的基本单位。
sk_buff 的核心设计
// include/linux/skbuff.h(简化)struct sk_buff { struct sk_buff *next; // 链表后继 struct sk_buff *prev; // 链表前驱
struct net_device *dev; // 接收/发送的网络设备 char cb[48]; // 控制缓冲区(各层私有数据)
unsigned int len; // 数据总长度 unsigned int data_len; // 分片数据长度 unsigned int truesize; // 实际占用内存
__u16 protocol; // 协议类型(ETH_P_IP 等)
sk_buff_data_t transport_header; // 传输层头偏移 sk_buff_data_t network_header; // 网络层头偏移 sk_buff_data_t mac_header; // 链路层头偏移
unsigned char *head; // 缓冲区起始 unsigned char *end; // 缓冲区结束 unsigned char *data; // 数据起始 unsigned char *tail; // 数据结束};sk_buff 的指针模型
sk_buff 使用四个指针管理数据区域,这种设计使得各层协议头可以高效地添加和移除:
head data tail end | | | | v v v v +----------+-----------+----------+ | headroom | data | tailroom| +----------+-----------+----------+ ↑ ↑ | | 预留空间 有效数据 (用于添加头部)push操作(添加协议头):data指针前移pull操作(移除协议头):data指针后移put操作(添加数据):tail指针后移
sk_buff 的克隆与零拷贝
为避免数据复制,sk_buff 支持克隆——多个 sk_buff 共享同一块数据区域:
struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t priority);// 克隆只复制 sk_buff 结构本身,不复制数据// 通过引用计数管理数据区域的释放四、TCP 连接管理
TCP 状态机
TCP 连接在其生命周期中经历多个状态转换:
stateDiagram-v2
[*] --> CLOSED
CLOSED --> SYN_SENT: 主动打开<br>发送 SYN
CLOSED --> SYN_RECV: 被动打开<br>收到 SYN,发送 SYN+ACK
SYN_SENT --> ESTABLISHED: 收到 SYN+ACK<br>发送 ACK
SYN_RECV --> ESTABLISHED: 收到 ACK
ESTABLISHED --> FIN_WAIT_1: 主动关闭<br>发送 FIN
ESTABLISHED --> CLOSE_WAIT: 收到 FIN<br>发送 ACK
FIN_WAIT_1 --> FIN_WAIT_2: 收到 ACK
FIN_WAIT_1 --> CLOSING: 收到 FIN<br>发送 ACK
FIN_WAIT_2 --> TIME_WAIT: 收到 FIN<br>发送 ACK
CLOSE_WAIT --> LAST_ACK: 发送 FIN
LAST_ACK --> CLOSED: 收到 ACK
CLOSING --> CLOSED: 收到 ACK
TIME_WAIT --> CLOSED: 2MSL 超时
三次握手的内核实现
// 客户端发起连接tcp_v4_connect() // 设置目标地址,发送 SYN → tcp_connect() // 构造 SYN 报文,启动重传定时器
// 服务端收到 SYNtcp_v4_rcv() // 收到 SYN 报文 → tcp_v4_do_rcv() → tcp_rcv_state_process() // 处于 LISTEN 状态 → tcp_v4_conn_request() // 发送 SYN+ACK,创建 mini_sock
// 第三次握手 ACKtcp_v4_rcv() // 收到 ACK 报文 → tcp_check_req() // 查找 mini_sock → tcp_child_process() // 创建完整的 tcp_sock,进入 ESTABLISHED四次挥手的内核实现
主动关闭方 被动关闭方 | | | FIN → | | (tcp_send_fin) | | | ← ACK | | (tcp_ack) | | | | ← FIN | | (tcp_send_fin) | ACK → | | (tcp_ack) | | | | TIME_WAIT (2MSL) | CLOSED | (tcp_time_wait) | | CLOSED |五、数据包接收路径(Rx)
当网卡收到数据包时,数据从硬件流向用户空间的完整路径:
sequenceDiagram
participant NIC as 网卡
participant DRV as 驱动
participant NAPI as NAPI
participant NETIF as netif_receive_skb
participant IP as IP 层
participant TCP as TCP 层
participant SOCK as Socket 缓冲区
participant USER as 用户空间
NIC->>DRV: 硬件中断
DRV->>NAPI: 禁用中断,调度 NAPI 轮询
NAPI->>NAPI: 轮询接收数据包
NAPI->>NETIF: netif_receive_skb()
NETIF->>IP: ip_rcv()
IP->>IP: 路由查找
IP->>TCP: tcp_v4_rcv() (本地交付)
TCP->>TCP: 序列号检查、窗口更新
TCP->>SOCK: 放入 sk_receive_queue
USER->>SOCK: read()/recvmsg() 从缓冲区取数据
NAPI 机制
NAPI(New API)是 Linux 网络设备的中断+轮询混合机制:
- 第一个包到达:网卡触发硬件中断,驱动在 ISR 中调度 NAPI 轮询
- 禁用中断,开始轮询:NAPI 的
poll函数批量处理接收队列中的数据包 - 处理完毕:重新启用中断,等待下一批数据
NAPI 的优势:
- 降低中断频率:高负载时避免中断风暴
- 批量处理:一次轮询处理多个包,提高缓存命中率
- 公平调度:NAPI 轮询在软中断上下文中执行,可被其他任务抢占
六、数据包发送路径(Tx)
用户空间发送数据到网卡的路径:
用户空间: send()/sendto()/write() ↓Socket 层: sock_sendmsg() → sock->ops->sendmsg() ↓TCP 层: tcp_sendmsg() → 构造 sk_buff,放入 sk_write_queue tcp_push() → tcp_write_xmit() → tcp_transmit_skb() ↓IP 层: ip_queue_xmit() → ip_route_output_flow() (路由查找) ip_output() → ip_finish_output() → neigh_output() ↓设备层: dev_queue_xmit() → __dev_xmit_skb() 选择发送队列 → 放入 qdisc ↓驱动层: ndo_start_xmit() → 映射 DMA,通知网卡发送 ↓硬件: 网卡 DMA 读取内存中的数据包,发送到网络七、Netfilter 与 iptables/nftables
Netfilter 是 Linux 内核的网络包过滤框架,它在网络协议栈的 5 个关键位置设置了钩子(hook)点:
graph LR
subgraph Netfilter 钩子点
A[PREROUTING] --> B[INPUT]
A --> C[FORWARD]
D[OUTPUT] --> E[POSTROUTING]
C --> E
end
subgraph 数据流
F[网卡收包] --> A
B --> G[本地进程]
H[本地进程发包] --> D
E --> I[网卡发包]
end
| 钩子点 | 位置 | 典型用途 |
|---|---|---|
NF_INET_PRE_ROUTING | 路由查找之前 | DNAT、早期过滤 |
NF_INET_LOCAL_IN | 本地交付之前 | INPUT 过滤 |
NF_INET_FORWARD | 转发过程中 | FORWARD 过滤 |
NF_INET_LOCAL_OUT | 本地发出之后 | OUTPUT 过滤 |
NF_INET_POST_ROUTING | 路由确定之后 | SNAT(源地址转换) |
# 查看 iptables 规则sudo iptables -L -n -v
# 查看 nftables 规则sudo nft list ruleset
# 添加规则:允许 SSH 入站sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT八、路由与邻居子系统
路由查找
内核通过 FIB(Forwarding Information Base)表进行路由查找:
// 路由查找核心流程ip_route_output_flow() // 主路由查找入口 → fib_lookup() // 查找 FIB 表 → fib_select_path() // 选择最优路径(ECMP 支持)# 查看路由表ip route show
# 查看路由缓存ip route get 8.8.8.8
# 查看邻居表(ARP/ND)ip neigh showARP 与邻居发现
ARP(IPv4)和 ND(IPv6)将 IP 地址映射为 MAC 地址:
# 查看 ARP 表ip neigh show# 192.168.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE
# 手动添加 ARP 条目sudo ip neigh add 192.168.1.100 lladdr 00:11:22:33:44:55 dev eth0九、网络命名空间
网络命名空间(netns)是容器网络隔离的基础,每个 netns 拥有独立的:
- 网络设备(虚拟和物理)
- IP 地址和路由表
- 端口号空间
- iptables/nftables 规则
- /proc/net 和 /sys/class/net 视图
# 创建网络命名空间sudo ip netns add testns
# 在命名空间中执行命令sudo ip netns exec testns ip link show
# 将网络接口移入命名空间sudo ip link set veth0 netns testns
# 列出所有命名空间ip netns list十、动手实践
1. 查看 TCP 连接状态
# 查看所有 TCP 连接ss -tnlp
# 查看特定状态的连接ss -t state time-wait
# 查看连接的详细定时器信息ss -tno2. 抓包分析
# 抓取 HTTP 流量sudo tcpdump -i eth0 -nn port 80
# 保存到文件用 Wireshark 分析sudo tcpdump -i eth0 -w capture.pcap
# 只抓 SYN 包sudo tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0'3. 查看网络统计
# TCP/UDP 统计cat /proc/net/snmp
# 网络设备统计cat /proc/net/dev
# 扩展的 TCP 统计cat /proc/net/netstat4. 查看 Netfilter 规则
# 查看 iptables 规则及计数sudo iptables -L -n -v
# 查看 NAT 规则sudo iptables -t nat -L -n -v
# 追踪连接sudo conntrack -L参考资料
- 《深入理解 Linux 内核》第 18 章
- 《深入理解 Linux 网络技术内幕》— Christian Benvenuti
- man 7 socket、man 7 tcp、man 7 netfilter
- Kernel Networking Documentation
- TCP Implementation in Linux
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时
相关文章 智能推荐
1
块设备与 I/O 栈
Linux内核 深入解析 Linux 块设备与 I/O 栈——bio 请求结构、通用块层与 blk-mq 多队列设计、I/O 调度器演进(CFQ→Deadline→BFQ)、I/O 合并与排序、NVMe 驱动模型——从 I/O 请求到磁盘的完整路径。
2
页缓存与 I/O 路径
Linux内核 深入解析 Linux 页缓存与 I/O 路径——Page Cache 与 address_space 结构、脏页写回机制、fsync 语义、Direct I/O、io_uring 异步 I/O 设计、预读机制——从 write() 到磁盘的完整数据旅程。
3
进程管理
Linux内核 深入解析 Linux 进程管理——task_struct 核心字段、进程状态机、fork-COW-exec 创建流程、clone 系统调用、僵尸与孤儿进程、进程组与会话——理解进程从创建到消亡的完整生命周期。
4
设备驱动模型
Linux内核 深入解析 Linux 设备驱动模型——kobject/kset/ktype 内核对象体系、sysfs 文件系统、字符/块/网络设备分类、设备号管理、udev 用户空间设备管理、内核模块机制、DMA 与流式映射。
5
内核架构全景
Linux内核 宏观视角下的 Linux 内核设计——用户空间与内核空间的隔离、七大子系统概览、执行上下文、内核通用数据结构,以及 /proc 和 /sys 文件系统——内核向用户空间暴露信息的窗口。






