Linux 内核源码阅读——ipv4
综述
在 Linux 内核中,IPv4 协议的实现主要分布在 net/ipv4/
目录下。以下是一些关键的源文件及其作用:
1. 协议栈核心
net/ipv4/ip_input.c
:处理接收到的 IPv4 数据包(输入路径)。net/ipv4/ip_output.c
:处理 IPv4 数据包的发送(输出路径)。net/ipv4/ip_forward.c
:实现 IP 数据包的转发逻辑。
2. 地址管理
net/ipv4/devinet.c
:管理 IPv4 地址,包括添加、删除和查询接口地址。net/ipv4/af_inet.c
:实现 IPv4 协议族的socket
操作,如socket()
、bind()
、connect()
等。
3. 路由子系统
net/ipv4/route.c
:核心的路由查找和管理逻辑。net/ipv4/fib_frontend.c
、fib_trie.c
:实现基于前缀树(Trie)的 FIB(Forwarding Information Base)路由表。
4. 传输层交互
net/ipv4/tcp_ipv4.c
:IPv4 版本的 TCP 处理。net/ipv4/udp.c
:IPv4 版本的 UDP 处理。net/ipv4/raw.c
:处理 IPv4 原始套接字(Raw Sockets)。
5. 其他重要模块
net/ipv4/ip_fragment.c
:处理 IP 数据包的分片和重组。net/ipv4/icmp.c
:实现 ICMP(Internet Control Message Protocol)。net/ipv4/igmp.c
:实现 IGMP(Internet Group Management Protocol)。net/ipv4/netfilter/
目录:Linux 内核 Netfilter(防火墙和 NAT)相关代码。
收发包
关键数据结构
iphdr
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4, // IP头长度 (单位:4字节)
version:4; // IP版本(例如 IPv4)
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4, // IP版本(例如 IPv4)
ihl:4; // IP头长度 (单位:4字节)
#else
#error "Please fix <asm/byteorder.h>" // 如果没有定义大小端模式,编译时会报错
#endif
__u8 tos; // 服务类型(Type of Service,TOS),用于指定数据包的优先级和路由
__u16 tot_len; // 总长度(包括头部和数据部分),单位字节
__u16 id; // 标识符,用于标识分片的所有部分
__u16 frag_off; // 分片偏移和标志,指示是否是分片,以及分片的位置
__u8 ttl; // 生存时间(Time To Live),指定数据包能在网络上生存的最大跳数
__u8 protocol; // 上层协议类型(例如 ICMP、TCP、UDP 等)
__u16 check; // 校验和,用于错误检查
__u32 saddr; // 源 IP 地址
__u32 daddr; // 目标 IP 地址
/*The options start here. */
};
收包主要接口ip_rcv
/**
* ip_rcv - 处理接收到的 IPv4 数据包
* @skb: 指向 socket buffer 的指针,包含接收到的数据包
* @dev: 指向接收到数据包的网络设备的指针
* @pt: 指向 packet_type 结构体的指针,描述数据包类型
*
* 该函数用于接收并处理从网络设备接收到的 IPv4 数据包。它解析 IP 头部,
* 检查数据包的有效性,并根据协议类型(如 TCP、UDP 或 ICMP)将数据包
* 传递给相应的协议栈进行处理。如果数据包不可达或格式无效,返回错误。
* 如果数据包处理成功,返回 0。
*
* 返回值:
* - 0:表示数据包处理成功,已传递给适当的协议处理函数
* - 负值:表示错误,例如数据包格式无效或目标不可达
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt);
@startuml
start
:网络数据包到达;
:调用 ip_rcv 函数;
:调用 ip_rcv_finish 完成处理;
:调用 ip_route_input 查找路由;
if (路由(缓存)查找成功?) then (是)
if (是本地包?) then (是)
:调用 ip_local_deliver 处理本地包;
:将数据包交给本地协议栈处理;
else (否)
if (是多播包?) then (是)
:调用 ip_route_input_mc 处理多播包;
:处理多播包;
else (否)
:调用 ip_route_input_slow 进行慢路径处理;
:处理路由查找未命中的情况;
note right
插入缓存表rt_hash_table
end note
endif
endif
else (否)
:调用 ip_route_input_slow 进行慢路径处理;
:处理路由查找未命中的情况;
note right
插入缓存表rt_hash_table
end note
endif
stop
@enduml
本机包:ip_local_deliver
其他包,进行转发:ip_forward
发包主要接口ip_output
该函数的主要任务是:
- 处理 IP 头部(如检查和更新校验和)
- 执行 IP 选项 处理
- 可能需要进行 分片
- 最终调用 底层链路层接口 进行发送
int ip_output(struct sk_buff *skb)
主要流程
@startuml
start
:调用 ip_output(skb);
if (需要分片?) then (是)
:调用 ip_fragment(skb);
:对每个分片调用 ip_finish_output(skb);
else (否)
:调用 ip_finish_output(skb);
endif
:进入 ip_finish_output2;
:添加链路层头部(如以太网头部);
if (存在 hh 缓存?) then (否)
:发送 ARP 请求;
:等待 ARP 响应;
:缓存邻居信息;
endif
:调用 hh->hh_output(skb);
stop
@enduml
设备管理
事件通知处理函数——inetdev_event
处理什么事件?
NETDEV_REGISTER ——注册接口
NETDEV_UP —— 接口UP
NETDEV_DOWN —— 接口DOWN
NETDEV_CHANGEMTU —— 改变MTU
NETDEV_UNREGISTER ——注销接口
NETDEV_CHANGENAME —— 更改接口名
接口UP处理
分为普通接口和环回口处理
- MTU 检查
- 环回设备的特殊处理
- 启动多播(Multicast)功能
RTNetlink 消息
RTNetlink
是 Linux 内核用于网络配置和管理的接口,它通过 Netlink sockets 向用户空间传递网络相关的信息。
关键结构
static struct rtnetlink_link inet_rtnetlink_table[RTM_MAX - RTM_BASE + 1] = {
[4] = { .doit = inet_rtm_newaddr, }, /* 处理新的 IP 地址添加消息 */
[5] = { .doit = inet_rtm_deladdr, }, /* 处理删除 IP 地址的消息 */
[6] = { .dumpit = inet_dump_ifaddr, }, /* 处理获取 IP 地址的消息 */
[8] = { .doit = inet_rtm_newroute, }, /* 处理新的路由添加消息 */
[9] = { .doit = inet_rtm_delroute, }, /* 处理删除路由的消息*/
[10] = { .doit = inet_rtm_getroute, .dumpit = inet_dump_fib, },
#ifdef CONFIG_IP_MULTIPLE_TABLES /* 多个路由表支持 */
[16] = { .doit = inet_rtm_newrule, },
[17] = { .doit = inet_rtm_delrule, },
[18] = { .dumpit = inet_dump_rules, },
#endif
};
主要流程
@startuml
start
:module_init(inet_init);
floating note left: module_init内核模块的初始化入口点
:inet_init;
note right
初始化网络协议栈中与 IP 协议相关
end note
:ip_init;
note right
初始化 IPv4 协议栈
end note
:ip_rt_init;
:devinet_init;
note right
初始化与设备相关的网络功能
end note
:rtnetlink_links[PF_INET] = inet_rtnetlink_table;
note left
注册 IPv4 相关的 Netlink 消息处理表
end note
stop
@enduml
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);