从数据包到路由决策 - 揭秘 IP 层的核心机制
系列:Linux 网络子系统源码剖析篇号:第 5 篇 内核版本:Linux 5.10 LTS重点模块:IP 接收、路由查找、FIB trie
📋 本篇导读
你将学到
IP 报文接收的完整流程
IP 头校验与选项处理
路由子系统的核心数据结构
FIB trie 路由查找算法
路由缓存机制
转发与本地接收决策
分片重组实现
策略路由原理
前置知识
已阅读第 1-4 篇文章
了解 IP 协议基础
理解路由原理
熟悉数据结构(树、哈希表)
阅读时间
约 70-80 分钟
🎯 IP 层在网络子系统中的位置
网络协议栈分层
IP 层的核心职责
📦 IP 报文格式
IPv4 头结构
字段说明:
• Version (4 bits):版本号,IPv4 = 4
• IHL (4 bits):头长度,单位 4 字节,最小 5(20 字节)
• Type of Service (8 bits):服务类型(TOS/DSCP)
• Total Length (16 bits):总长度,包括头和数据
• Identification (16 bits):标识,用于分片重组
• Flags (3 bits):
- Bit 0: 保留,必须为 0
- Bit 1: DF (Don't Fragment),禁止分片
- Bit 2: MF (More Fragments),还有更多分片
• Fragment Offset (13 bits):分片偏移,单位 8 字节
• Time to Live (8 bits):生存时间,每跳减 1
• Protocol (8 bits):上层协议(6=TCP, 17=UDP, 1=ICMP)
• Header Checksum (16 bits):头校验和
• Source Address (32 bits):源 IP 地址
• Destination Address (32 bits):目的 IP 地址
• Options (可变):IP 选项
IP 头在内核中的表示
源码位置:include/uapi/linux/ip.h
📥 IP 报文接收流程
接收流程总览
ip_rcv() - IP 接收入口
源码位置:net/ipv4/ip_input.c
/**
* ip_rcv - IP 层接收函数
* @skb: 接收到的数据包
* @dev: 接收设备
* @pt: 协议类型
* @orig_dev: 原始设备
*
* 这是 IP 层的入口函数
* 在协议分发时被调用
*/
IP 头校验和计算
IP 选项处理
/**
* ip_rcv_options - 处理 IP 选项
* @skb: 数据包
* @dev: 接收设备
*
* 返回:0 成功,非 0 失败
*/
🗺️ 路由子系统
路由系统架构
核心数据结构
FIB trie 数据结构
查找 192.168.1.100:
1. 从根开始
2. 匹配 192.168.*
3. 匹配 *.1.*
4. 找到 eth0
最长前缀匹配:
• 192.168.1.100 匹配 192.168.1.0/24(最长)
• 也匹配 192.168.0.0/16(较短)
• 选择最长的匹配
🔍 路由查找流程
ip_rcv_finish() - 路由查找入口
源码位置:net/ipv4/ip_input.c
/**
* ip_rcv_finish - IP 接收完成处理
* @net: 网络命名空间
* @sk: socket(通常为 NULL)
* @skb: 数据包
*
* 在 Netfilter PREROUTING 钩子之后调用
* 主要任务是路由查找
*/
/**
* ip_rcv_finish_core - 路由查找核心
*/
ip_route_input_noref() - 路由查找
源码位置:net/ipv4/route.c
/**
* ip_route_input_noref - 输入路由查找
* @skb: 数据包
* @daddr: 目的 IP 地址
* @saddr: 源 IP 地址
* @tos: 服务类型
* @devin: 输入设备
*
* 查找输入路由,决定如何处理数据包
*/
/**
* ip_route_input_slow - 慢速路由查找
*
* 完整的路由查找和 rtable 创建
*/
FIB 查找算法
/**
* fib_lookup - FIB 查找
* @net: 网络命名空间
* @flp: 查找键
* @res: 查找结果
* @flags: 标志
*
* 在 FIB 表中查找路由
*/
/**
* fib_table_lookup - 在 FIB trie 中查找
* @tb: 路由表
* @flp: 查找键
* @res: 查找结果
* @fib_flags: 标志
*
* 这是最核心的查找函数
* 实现最长前缀匹配算法
*/
多路径路由选择
/**
* fib_select_multipath - 选择多路径路由的下一跳
* @res: 路由结果
* @hash: 哈希值
*
* 当有多个下一跳时,选择一个
* 支持负载均衡
*/
📨 本地接收与转发
本地接收流程
ip_local_deliver() 实现
源码位置:net/ipv4/ip_input.c
/**
* ip_local_deliver - 本地接收
* @skb: 数据包
*
* 将数据包上送到传输层
*/
/**
* ip_local_deliver_finish - 本地接收完成
* @net: 网络命名空间
* @sk: socket
* @skb: 数据包
*
* 根据协议号分发到传输层
*/
转发流程
/**
* ip_forward - IP 转发
* @skb: 数据包
*
* 将数据包转发到其他主机
*/
🧩 分片与重组
分片机制
注意:
• Offset 单位是 8 字节
• MF (More Fragments):1=还有分片,0=最后一个
• 所有分片的 Identification 字段相同
分片重组实现
源码位置:net/ipv4/ip_fragment.c
🎛️ 策略路由
策略路由概念
路由规则数据结构
/**
* struct fib_rule - 路由规则
* 源码位置:include/net/fib_rules.h
*/
策略路由查找
/**
* __fib_lookup - 策略路由查找
* @net: 网络命名空间
* @flp: 查找键
* @res: 查找结果
* @flags: 标志
*
* 遍历路由规则,在匹配的表中查找路由
*/
/**
* fib4_rule_match - 检查规则是否匹配
* @rule: 规则
* @fl: 流信息
* @flags: 标志
*
* 返回:true 表示匹配,false 表示不匹配
*/
/**
* fib4_rule_action - 执行规则动作
* @rule: 规则
* @flp: 查找键
* @flags: 标志
* @arg: 查找参数
*
* 根据规则动作执行相应操作
*/
策略路由配置示例
# 查看路由规则
ip rule list
# 默认规则:
# 0: from all lookup local
# 32766: from all lookup main
# 32767: from all lookup default
# 添加规则:来自 192.168.1.0/24 的流量查找表 100
ip rule add from 192.168.1.0/24 table 100 priority 100
# 添加规则:fwmark 为 1 的流量查找表 101
ip rule add fwmark 1 table 101 priority 101
# 添加规则:入口设备为 eth0 的流量查找表 102
ip rule add iif eth0 table 102 priority 102
# 添加规则:TOS 为 0x10 的流量查找表 103
ip rule add tos 0x10 table 103 priority 103
# 创建路由表
ip route add default via 192.168.1.1 table 100
ip route add default via 192.168.2.1 table 101
# 删除规则
ip rule del from 192.168.1.0/24 table 100
# 应用场景:
# 1. 多出口负载均衡
# 2. 基于源地址的路由选择
# 3. VPN 分流
# 4. QoS 路由
📊 性能优化
路由缓存(已废弃)
早期内核(< 3.6):
• 使用路由缓存(route cache)
• 每次查找结果缓存到哈希表
• 后续相同查找直接返回缓存
• 问题:
- 容易被攻击(缓存溢出)
- 维护开销大
- 多核扩展性差
现代内核(>= 3.6):
• 废弃路由缓存
• 每次都查找 FIB trie
• 优化:
- FIB trie 查找非常快(O(log n))
- 使用 RCU 无锁读取
- per-CPU 缓存
- 早期解复用(Early Demux)
早期解复用(Early Demux)
/**
* 早期解复用优化
*
* 对于已建立的连接,直接找到 socket
* 避免路由查找
*/
/**
* tcp_v4_early_demux - TCP 早期解复用
* @skb: 数据包
*
* 尝试找到对应的 TCP socket
*/
FIB trie 优化
性能数据(典型场景):
• 路由表大小:100,000 条
• 查找时间:< 1 微秒
• 吞吐量:> 10 Mpps(单核)
• 内存占用:~10 MB
📝 总结
核心要点回顾
数据流总结
❓ 常见问题(FAQ)
Q1: 为什么废弃了路由缓存?
A: 路由缓存存在几个严重问题:
安全问题:容易被攻击导致缓存溢出
性能问题:维护缓存的开销很大
扩展性问题:多核环境下锁竞争严重
FIB trie 查找已经足够快,不需要缓存
Q2: 早期解复用(Early Demux)有什么好处?
A:
Q3: 如何选择合适的路由算法?
A:
Q4: 分片重组的性能影响有多大?
A:
分片会显著降低性能(50-80%)
需要额外的内存和 CPU
建议启用 PMTU 发现,避免分片
现代网络应该尽量避免分片
Q5: 策略路由的性能开销如何?
A:
规则数量少(< 10 条):开销很小
规则数量多(> 100 条):可能影响性能
建议:
将常用规则放在前面
使用精确匹配而非范围匹配
定期清理无用规则
🔗 参考资料
内核文档
Documentation/networking/ip-sysctl.txt - IP 参数配置
Documentation/networking/routing.txt - 路由文档
Documentation/networking/policy-routing.txt - 策略路由
源码位置
推荐阅读
《TCP/IP Illustrated, Volume 1》- IP 协议详解
《Understanding Linux Network Internals》- 第 30-35 章
RFC 791 - Internet Protocol
RFC 1122 - Requirements for Internet Hosts
🎯 下一篇预告
第 6 篇:IP 层实现(下)- 发送与分片
我们将深入分析:
IP 报文发送流程(ip_queue_xmit)
IP 头构造
IP 分片实现
PMTU 发现机制
ICMP 协议实现
IP 选项处理
敬请期待!
作者:肇中
💡 提示:本文基于 Linux 5.10 LTS 内核,不同版本可能有差异。
📧 反馈:如有问题或建议,欢迎交流讨论。
⭐ 如果觉得有帮助,欢迎分享给更多人!