linux内核协议栈 IPv4分片重组Ⅰ之相关数据结构

it2026-08-14  8

目录

1 IP分片重组控制信息数据结构

1.1 网络命名空间 struct netns_ipv4

1.2 IPv4分片哈希表 struct inet_frags

1.3 IP分片队列 struct ipq [ inet_frag_queue]

1.4 IPv4分片关系图

2 IPv4分片初始化 ipfrag_init

2.1 ip4_frag_match()

2.2 ip4_frag_init()


1 IP分片重组控制信息数据结构

1.1 网络命名空间 struct netns_ipv4

在网络命名空间中,为IPv4分片重组定义了frags变量,用于保存分片重组功能需要的一些全局控制信息。

struct net { ... struct netns_ipv4 ipv4; } struct netns_ipv4 { ... struct netns_frags frags; } struct netns_frags { // 当前待重组的IP报文个数,是哈希表中IP分片队列的个数,并非IP分片的个数 int nqueues; // 当前保存的所有待重组分片占用的全部内存,不仅仅是分片本身的大小,还有为了管理而增加的额外开销, // 该值不允许超过下面的上限值high_thresh,超过后,后续的IP片段将会由于没有内存而被丢弃 atomic_t mem; // 所有IP片段组成一个LRU列表,便于在内存紧张时进行片段回收 struct list_head lru_list; // 配置参数/proc/sys/net/ipv4/ipfrag_time,默认30s int timeout; // 配置参数/proc/sys/net/ipv4/ipfrag_high_thresh int high_thresh; // 配置参数/proc/sys/net/ipv4/ipfrag_low_thresh int low_thresh; };

1.2 IPv4分片哈希表 struct inet_frags

在代码实现上,内核将片段重组功能进行了抽象剥离,非协议相关的部分在net/ipv4/inet_fragment.c(不知道为什么还在ipv4目录下)中实现,IPv4相关的在net/ipv4/ip_fragment.c中实现。在相关函数的命令上也很好区分,以ip_xxx开头的是IPv4相关的,以inet_xxx相关的,是非协议相关的。

在实现重组功能时,IP层显然需要先缓存所有收到的IP片段,等同一个IP报文的所有片段都到达后把它们重组到一起再递交给L4协议。所以,IPv4协议定义了哈希表用于保存当前已收到的所有分片。

// IPv4协议用于分片重组的哈希表信息(全局) static struct inet_frags ip4_frags; #define INETFRAGS_HASHSZ 64 struct inet_frags { // 所有待重组的分段组织在该哈希表中 struct hlist_head hash[INETFRAGS_HASHSZ]; // 保护哈希表的读写锁 rwlock_t lock; // 参与哈希值计算的一个随机数 u32 rnd; // 哈希表中保存的元素的大小,用于队列元素的创建过程。对于IPv4,是sizeof(struct ipq) int qsize; // 配置参数/proc/sys/net/ipv4/ipfrag_secret_interval,内核初始化为10分钟 int secret_interval; struct timer_list secret_timer; // 一组回调函数,在分片重组过程中被回调 unsigned int (*hashfn)(struct inet_frag_queue *); // 当分配一个新的IP分片队列时回调,用于初始化该新建的IP分片队列 void (*constructor)(struct inet_frag_queue *q, void *arg); void (*destructor)(struct inet_frag_queue *); void (*skb_free)(struct sk_buff *); // 用于判断arg指定的IP分片是否属于一个IP分片队列q(见ip_find()) int (*match)(struct inet_frag_queue *q, void *arg); // 分片队列超时清理回调函数 void (*frag_expire)(unsigned long data); };

1.3 IP分片队列 struct ipq [ inet_frag_queue]

IPv4 在哈希表中保存的结构实际上是 struct ipq,该结构保存了属于同一个 IP 报文的所有 IP 片段。

/* Describe an entry in the "incomplete datagrams" queue. */ struct ipq { // 通用的IP分片队列 struct inet_frag_queue q; // user是用来标识分片重组是由谁发起的,因为正常理解来看,重组应该由IP层在数据传递过程中发起, // 但是一些防火墙功能,必须要处理完整的IP报文,所以也存在提前进行组装的可能 u32 user; // 来自IP报头的几个字段 __be32 saddr; __be32 daddr; __be16 id; u8 protocol; // 记录IP分片的输入网卡索引 int iif; unsigned int rid; // 和IP层的IP地址管理有关,先忽略 struct inet_peer *peer; }; struct inet_frag_queue { // 将IP分片队列接入全局的哈希表ip4_frag.hash中 struct hlist_node list; // 指向网络命名空间中的net->ipv4.frags struct netns_frags *net; // 将IP分片以LRU链表组织(inet.ipv4.frags.lru_list),方便内存紧张时的回收处理 struct list_head lru_list; /* lru list member */ spinlock_t lock; // 引用计数,每个IP片段都会持有一个该队列的引用计数 atomic_t refcnt; // 一个IP报文,如果在指定时间内(/proc/sys/net/ipv4/ipfrag_time)内不能完成重组,则所有片段都会丢弃 struct timer_list timer; /* when will this queue expire? */ // IP分片列表,已经按照offset顺序排好了 struct sk_buff *fragments; /* list of received fragments */ // 上一次收到IP分片的时间戳 ktime_t stamp; // 当前收到的该IP报文的最大偏移量,随着片段的接收,该值会不断更新,实际一个IP报文 // 有多少字节只能在收到最后一个片段后才能知道 int len; /* total length of orig datagram */ // 当前已经收到的IP分片的数据量总和 int meat; // 可取下面三个标记,分别表示第一个分片、最后一个分片、以及是否全部分片接收完成 __u8 last_in; /* first/last segment arrived? */ #define COMPLETE 4 #define FIRST_IN 2 #define LAST_IN 1 };

关于user,内核定义了如下可能的值,我们这里只关注IP_DEFRAG_LOCAL_DELIVER这一种最常规的场景:

enum ip_defrag_users { IP_DEFRAG_LOCAL_DELIVER, IP_DEFRAG_CALL_RA_CHAIN, IP_DEFRAG_CONNTRACK_IN, IP_DEFRAG_CONNTRACK_OUT, IP_DEFRAG_VS_IN, IP_DEFRAG_VS_OUT, IP_DEFRAG_VS_FWD };

1.4 IPv4分片关系图

上面这些数据结构之间的组织关键件下图:

2 IPv4分片初始化 ipfrag_init

在IPv4初始化过程中,会调用ipfrag_init()对IP分片子模块进行初始化,不过这部分初始化只和IP片段的重组有关系,发送过程中的IP分段并不涉及。

static struct pernet_operations ip4_frags_ops = { .init = ipv4_frags_init_net, .exit = ipv4_frags_exit_net, }; // IPv4协议用于分片重组的哈希表信息 static struct inet_frags ip4_frags; void __init ipfrag_init(void) { // 初始化网络命名空间中的IPv4分片重组控制信息 register_pernet_subsys(&ip4_frags_ops); // 初始化全局哈希表ip4_frags ip4_frags.hashfn = ip4_hashfn; ip4_frags.constructor = ip4_frag_init; ip4_frags.destructor = ip4_frag_free; ip4_frags.skb_free = NULL; ip4_frags.qsize = sizeof(struct ipq); ip4_frags.match = ip4_frag_match; ip4_frags.frag_expire = ip_expire; // 超时时间为10分钟 ip4_frags.secret_interval = 10 * 60 * HZ; inet_frags_init(&ip4_frags); } void inet_frags_init(struct inet_frags *f) { int i; for (i = 0; i < INETFRAGS_HASHSZ; i++) INIT_HLIST_HEAD(&f->hash[i]); rwlock_init(&f->lock); // 生成随机数,并直接启动定时器,定时间隔由secret_interval指定,定时器函数是inet_frag_secret_rebuild() f->rnd = (u32) ((num_physpages ^ (num_physpages>>7)) ^ (jiffies ^ (jiffies >> 6))); setup_timer(&f->secret_timer, inet_frag_secret_rebuild, (unsigned long)f); f->secret_timer.expires = jiffies + f->secret_interval; add_timer(&f->secret_timer); } =====================网络命令空间初始化==================================== static int ipv4_frags_init_net(struct net *net) { /* * Fragment cache limits. We will commit 256K at one time. Should we * cross that limit we will prune down to 192K. This should cope with * even the most extreme cases without allowing an attacker to * measurably harm machine performance. */ net->ipv4.frags.high_thresh = 256 * 1024; net->ipv4.frags.low_thresh = 192 * 1024; /* * Important NOTE! Fragment queue must be destroyed before MSL expires. * RFC791 is wrong proposing to prolongate timer each fragment arrival * by TTL. */ net->ipv4.frags.timeout = IP_FRAG_TIME; // 初始化frags中的其它几个字段 inet_frags_init_net(&net->ipv4.frags); // 在/proc/sys/net/ipv4/目录下创建控制参数文件 return ip4_frags_ctl_register(net); } void inet_frags_init_net(struct netns_frags *nf) { nf->nqueues = 0; atomic_set(&nf->mem, 0); INIT_LIST_HEAD(&nf->lru_list); }

2 IPv4分配函数实现

如上,全局的ip4_frags中有一组回调函数,这组回调函数影响了IP层分片重组功能的实现。

2.1 ip4_frag_match()

该函数用于判断一个IP片段是否属于一个IP分片队列,调用场景见ip_find()。

static int ip4_frag_match(struct inet_frag_queue *q, void *a) { struct ipq *qp; struct ip4_create_arg *arg = a; // 对于IPv4,实际的IP分片队列结构是ipq qp = container_of(q, struct ipq, q); // 很好理解 return (qp->id == arg->iph->id && qp->saddr == arg->iph->saddr && qp->daddr == arg->iph->daddr && qp->protocol == arg->iph->protocol && qp->user == arg->user); }

2.2 ip4_frag_init()

每当新建一个IP分片队列后,则回调该函数,见inet_frag_alloc()。

static void ip4_frag_init(struct inet_frag_queue *q, void *a) { struct ipq *qp = container_of(q, struct ipq, q); struct ip4_create_arg *arg = a; qp->protocol = arg->iph->protocol; qp->id = arg->iph->id; qp->saddr = arg->iph->saddr; qp->daddr = arg->iph->daddr; qp->user = arg->user; qp->peer = sysctl_ipfrag_max_dist ? inet_getpeer(arg->iph->saddr, 1) : NULL; }
最新回复(0)