IPv4之分片重组(一)

本文探讨了IP分片重组中的数据结构,包括IP分片控制信息、哈希表及队列(struct ipq)。详细介绍了IPv4在net/ipv4/inet_fragment.c和ip_fragment.c中的实现,并阐述了初始化过程,如ip_frag_init()。还解析了关键函数ip4_frag_match()和ip4_frag_init()的作用。
摘要由CSDN通过智能技术生成

这篇笔记先来看看IP分片重组功能涉及的一些数据结构以及核心函数,下一篇笔记则将这些过程串起来,看看整个分片重组过程是如何进行的。

数据结构

IP分片重组控制信息

在网络命名空间中,为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;
};

IPv4分片哈希表

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

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

#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);
};
// IPv4协议用于分片重组的哈希表信息
static struct inet_frags ip4_frags;

IP分片队列: struct ipq

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

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
};

/* 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;
};

关于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
};

上面这些数据结构之间的组织关键件下图:
在这里插入图片描述

初始化

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

ip_frag_init()

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);
}

网络命名空间相关初始化

void inet_frags_init_net(struct netns_frags *nf)
{
	nf->nqueues = 0;
	atomic_set(&nf->mem, 0);
	INIT_LIST_HEAD(&nf->lru_list);
}

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);
}

static struct pernet_operations ip4_frags_ops = {
	.init = ipv4_frags_init_net,
	.exit = ipv4_frags_exit_net,
};

IPv4关键函数实现

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

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);
}

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;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值