Linux ip_fragment IP分片与ip_defrag重组超时

发布时间:2026/6/13 7:48:13

Linux ip_fragment IP分片与ip_defrag重组超时 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其他分片MF1。二、 IP ID分配每个IP数据报的Identification字段由 ip_select_ident 分配分片时所有分片共享相同的IDvoid __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_queuestatic 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 ExceededCode 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合并为一个skbint 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 进行资源控制避免分片缓存耗尽系统内存。

相关新闻