mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1005 字
3 分钟
网络协议栈
2024-07-24

一、网络协议栈概述#

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 中每个网络连接涉及两个关键结构:socketsock

struct socket(用户可见层)#

socket 是 VFS 层的表示,与文件描述符关联:

include/linux/net.h
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 报文,启动重传定时器
// 服务端收到 SYN
tcp_v4_rcv() // 收到 SYN 报文
tcp_v4_do_rcv()
tcp_rcv_state_process() // 处于 LISTEN 状态
tcp_v4_conn_request() // 发送 SYN+ACK,创建 mini_sock
// 第三次握手 ACK
tcp_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 网络设备的中断+轮询混合机制:

  1. 第一个包到达:网卡触发硬件中断,驱动在 ISR 中调度 NAPI 轮询
  2. 禁用中断,开始轮询:NAPI 的 poll 函数批量处理接收队列中的数据包
  3. 处理完毕:重新启用中断,等待下一批数据

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 show

ARP 与邻居发现#

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 -tno

2. 抓包分析#

# 抓取 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/netstat

4. 查看 Netfilter 规则#

# 查看 iptables 规则及计数
sudo iptables -L -n -v
# 查看 NAT 规则
sudo iptables -t nat -L -n -v
# 追踪连接
sudo conntrack -L

参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

网络协议栈
https://blog.souloss.com/posts/linux-internals/network-protocol-stack/
作者
Souloss
发布于
2024-07-24
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时