Linux ip_fragment IP分片与ip_defrag重组超时
IP分片(ip_fragment)和IP重组(ip_defrag)是IPv4协议栈中处理数据报大于MTU的核心机制。分片发生在发送路径(net/ipv4/ip_output.c),重组发生在接收路径(net/ipv4/ip_fragment.c)。两者通过IP头的Identification字段、Fragment Offset和MF标志协同工作。
一、 ip_fragment发送分片
当IP层发现skb长度超过MTU且IP头中未设置DF标志时,调用ip_fragment进行软件分片。分片入口在 ip_finish_output 中:
static int ip_finish_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
unsigned int mtu;
mtu = ip_skb_dst_mtu(skb);
if (skb_is_gso(skb))
return ip_finish_output_gso(net, sk, skb, mtu);
if (skb->len > mtu && !skb_is_gso(skb))
return ip_fragment(net, sk, skb, mtu);
return ip_finish_output2(net, sk, skb);
}
ip_fragment 的核心逻辑是将一个大数据skb拆分为多个小skb,每个分片携带原始IP头的一部分,并调整Identification、偏移和MF标志:
int ip_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
unsigned int mtu)
{
struct iphdr *iph = ip_hdr(skb);
int ptr = sizeof(struct iphdr) + iphoptlen;
int hlen = ptr; /* IP头+选项长度 */
int offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;
int not_last_frag = 0;
struct sk_buff *skb2;
int err = 0;
/* 计算每个分片的数据长度(8字节对齐) */
int mtu_per_frag = (mtu - hlen) & ~7;
/* 遍历所有数据,按mtu_per_frag分片 */
while (skb->len > hlen) {
/* 计算当前分片的长度 */
if (skb->len > hlen + mtu_per_frag) {
/* 非最后分片 */
skb2 = alloc_skb(hlen + mtu_per_frag + ll_header_size,
GFP_ATOMIC);
if (!skb2) {
err = -ENOMEM;
goto fail;
}
/* 拷贝IP头 */
skb_copy_from_linear_data(skb, skb2->data, hlen);
skb2->network_header = skb2->data;
skb_put(skb2, hlen + mtu_per_frag);
/* 拷贝数据 */
skb_copy_bits(skb, hlen, skb2->data + hlen, mtu_per_frag);
skb_trim(skb, hlen);
} else {
/* 最后分片,直接引用原始skb */
skb2 = skb_get(skb);
skb_trim(skb, hlen);
}
/* 设置IP分片头字段 */
iph2 = ip_hdr(skb2);
iph2->frag_off = htons((offset >> 3));
if (not_last_frag)
iph2->frag_off |= htons(IP_MF);
/* 发送分片 */
err = ip_local_out(net, sk, skb2);
if (err)
goto fail;
offset += mtu_per_frag;
not_last_frag = 1;
}
}
这里的关键约束是 mtu_per_frag 必须被8整除,因为分片偏移值以8字节为单位。对于最后一个分片,MF标志清0,其他分片MF=1。
二、 IP ID分配
每个IP数据报的Identification字段由 ip_select_ident 分配,分片时所有分片共享相同的ID:
void __ip_select_ident(struct net *net, struct iphdr *iph, int segs)
{
u32 id;
/* 获取per-net的IP ID计数器 */
id = atomic_inc_return(&net->ipv4.id);
iph->id = htons((u16)id);
}
ip_fragment中所有分片使用相同的IP ID。IP ID是16位计数器,溢出时会回绕,但这不影响分片重组,因为接收方根据(源地址, 目的地址, 协议, ID)四元组区分不同数据报的分片。
三、 ip_defrag重组入口
接收端分片重组发生在 IP层的 ip_local_deliver 函数中:
int ip_local_deliver(struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
/* 如果IP MF标志或偏移非0,说明是分片报文 */
if (ip_is_fragment(iph)) {
skb = ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER);
if (!skb)
return 0;
}
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
net, NULL, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
ip_defrag 将分片插入到per-net的分片哈希表中,等待所有分片到齐后重组为一个完整数据报。
四、 分片哈希表与重组缓存
ip_defrag 使用 struct inet_frags 和 struct frag_queue 管理分片缓存。每个网络命名空间维护一个哈希表:
struct netns_frags {
struct inet_frags f;
struct hlist_head hash[INETFRAGS_HASHSZ];
atomic_long_t mem;
struct work_struct frag_mem_work;
};
新的分片到达时,ip_defrag 查找或创建 frag_queue:
static struct frag_queue *ip_frag_find(struct net *net, struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
struct frag_queue *fq;
struct hlist_head *chain;
u32 hash;
/* 计算哈希值:使用(源IP,目的IP,协议,ID) */
hash = ipqhashfn(iph->saddr, iph->daddr, iph->id,
iph->protocol);
chain = &net->ipv4.frags.hash[hash & (INETFRAGS_HASHSZ - 1)];
/* 在哈希链中查找已有的frag_queue */
hlist_for_each_entry_rcu(fq, chain, q.node) {
if (fq->q.key == hash &&
fq->iph.saddr == iph->saddr &&
fq->iph.daddr == iph->daddr &&
fq->iph.id == iph->id &&
fq->iph.protocol == iph->protocol)
goto found;
}
/* 未找到,创建新的frag_queue */
fq = ip_frag_create(net, skb, hash);
if (!fq)
return NULL;
found:
return fq;
}
五、 重组超时机制
分片重组有严格的时间限制,Linux默认30秒超时。超时由inet_frag_secret_rebuild关联的定时器驱动:
static void ip_expire(struct timer_list *t)
{
struct frag_queue *fq = from_timer(fq, t, q.timer);
struct net *net;
net = fq->q.net;
sub_frag_mem_limit(fq->q.net, fq->q.len);
/* 发送ICMP Time Exceeded通知源端 */
if (fq->q.flags & INET_FRAG_FIRST_IN) {
__ICMP_INC_STATS(net, ICMP_MIB_TIMEEXCEEDS);
icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_FRAGTIME, 0);
}
/* 释放frag_queue */
inet_frag_kill(&fq->q);
}
超时后,内核发送ICMP Time Exceeded(Code 1 - Fragment Reassembly Time Exceeded)给源端。per-net的frag超时通过 sysctl 调整:
net.ipv4.ipfrag_time = 30
重组队列的内存限制由两个参数控制:
net.ipv4.ipfrag_high_thresh = 4194304 (4MB)
net.ipv4.ipfrag_low_thresh = 3145728 (3MB)
当内存超过 high_thresh,内核开始丢弃分片缓存中的旧条目,直到降至 low_thresh。
六、 完整重组流程
分片插入 frag_queue 后,函数 ip_frag_queue 检查是否所有分片都已到达:
static int ip_frag_queue(struct frag_queue *fq, struct sk_buff *skb,
struct iphdr *iph)
{
int offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;
int end = offset + (skb->len - iph->ihl * 4);
/* 检查分片重叠,合并可能的重叠区域 */
if (prev && offset < prev_end) {
/* 重叠分片,修正offset */
offset = prev_end;
}
/* 将skb插入到frag_queue的rb树中 */
err = inet_frag_queue_insert(&fq->q, skb, offset, end);
if (err)
goto err;
fq->q.meat += skb->len - iph->ihl * 4;
/* 检查是否收到所有分片 */
if (fq->q.flags & INET_FRAG_FIRST_IN &&
fq->q.flags & INET_FRAG_LAST_IN &&
fq->q.meat == fq->q.len) {
/* 所有分片就绪,执行重组 */
return ip_frag_reasm(fq, skb);
}
return 0;
}
七、 ip_frag_reasm重组
当所有分片准备就绪,ip_frag_reasm 将 frag_queue 中的所有skb合并为一个skb:
int ip_frag_reasm(struct frag_queue *fq, struct sk_buff *prev)
{
struct sk_buff *head, *skb;
struct iphdr *iph;
int len;
/* 按offset顺序遍历rb树,将分片数据拷贝到head skb */
head = fq->q.fragments;
len = head->len - ip_hdrlen(head);
skb_walk_frags(head, skb) {
len += skb->len;
}
/* 更新IP头的总长度和分片相关字段 */
iph = ip_hdr(head);
iph->tot_len = htons(len);
iph->frag_off = 0;
head->ip_summed = CHECKSUM_NONE;
/* 释放frag_queue */
inet_frag_kill(&fq->q);
return 0;
}
重组后的skb重新进入IP层处理流,由 ip_local_deliver_finish 递交给传输层。
ip_fragment和ip_defrag构成了IPv4分片/重组的完整生命周期。分片端根据MTU拆分数据并设置正确的偏移和MF标志;重组端(接收端)通过哈希表管理积累的分片,在超时前完成重组并递交给上层。整个机制通过 sysctl ipfrag_time 和 ipfrag_high_thresh/ipfrag_low_thresh 进行资源控制,避免分片缓存耗尽系统内存。
Linux ip_fragment IP分片与ip_defrag重组超时
张小明
前端开发工程师
NuminaMath:符号-语义混合状态机驱动的AI数学推理新范式
1. 项目概述:这不是又一个“数学大模型”,而是一次对AI推理范式的重新校准“Inside NuminaMath: The AI Model that Took The First Place In the AI Math Olympiad”——这个标题里藏着三个极易被误读的关键词:“AI Math Olympiad”、“Firs…
编程智能体的上下文工程
编程代理的上下文配置指南 过去几个月,我们配置和丰富编程agent上下文的选项呈爆发式增长。Claude Code 在这一领域引领创新,但其他编程助手也在迅速跟进。强大的上下文工程已成为这些工具开发者体验的重要组成部分。 当然,上下文工程与所有…
StudyFetch:一个 AI 学习工具,怎么靠短视频做到 700 万用户
今天分享的是:StudyFetch,https://www.studyfetch.com/StudyFetch 是一个面向学生的 AI 学习平台。用户可以上传课件、教材、课堂笔记、视频或讲义,系统会把这些材料变成学习计划、测验、闪卡、课堂笔记,还配有一个叫 Spark.E 的 …
终极免费Flash浏览器指南:如何让经典Flash内容重获新生
终极免费Flash浏览器指南:如何让经典Flash内容重获新生 【免费下载链接】CefFlashBrowser Flash浏览器 / Flash Browser 项目地址: https://gitcode.com/gh_mirrors/ce/CefFlashBrowser Flash技术虽已停止支持,但无数经典游戏、教育课件和企业系统…
银河麒麟NetworkManager接管 ifcfg-eth0配置
新建/编辑network文件 在银河麒麟系统中是没有/etc/sysconfig/network-scripts/ifcfg-<接口名>路径的,需要从/sysconfig文件夹开始,一级一级完全新建。 同时,建议按照以下网卡配置文件格式编辑 关键开关:NM_CONTROLLED=yes 是NetworkManage接管的核心开关,设为no则…
第四卷:橡皮泥江湖(拓扑学)
第四卷:橡皮泥江湖(拓扑学) 作者:乖乖数学 核心隐喻:橡皮泥、面团、绳结、器皿,可揉、可捏、可拉、可扭 核心心法:形变不拘尺寸曲直,神存只看连通孔洞 师傅语录:度量是外…