这篇笔记来看看分片重组功能额外的一些实现细节,包括:
- IP分片队列的回收;
- 哈希表的重新散列;
IP分片队列的回收
首先,最正常的情况就是收到了所有的IP分片,然后将其组装成一个完整的IP报文,然后递交给上层。情况最差的,就是在规定的时间内没有收到全部的分片,这时需要有老化机制,使得之前缓存的片段能够被释放,否则对系统是一种负担。
重组完成后的IP分片队列回收
在ip_frag_reasm()中,并没有看到有对IP分片队列的明确回收操作,相关的两个动作如下:
/* Build a new IP datagram from all its fragments. */
static int ip_frag_reasm(struct ipq *qp, struct sk_buff *prev, struct net_device *dev)
{
...
// 实现见下文
ipq_kill(qp);
// 分片队列后面的skb全部移除
qp->q.fragments = NULL;
...
}
ipq_kill()
void inet_frag_kill(struct inet_frag_queue *fq, struct inet_frags *f)
{
// 停止定时器,如果停止成功则递减引用计数,因为在安装定时器时增加了引用计数
// 这里需要对应操作
if (del_timer(&fq->timer))
atomic_dec(&fq->refcnt);
// 对于没有设置COMPLETE标记的IP分片队列,将其从哈希表和LRU表中移除,并且递减其引用计数,然后设置CPMPLETE,
// 如果不设置该标记,后续无法进行释放。重组场景调用ipq_kill()时还并没有设置COMPLETE标记
if (!(fq->last_in & COMPLETE)) {
fq_unlink(fq, f);
atomic_dec(&fq->refcnt);
fq->last_in |= COMPLETE;
}
}
EXPORT_SYMBOL(inet_frag_kill);
/* Kill ipq entry. It is not destroyed immediately,
* because caller (and someone more) holds reference count.
*/
static void ipq_kill(struct ipq *ipq)
{
inet_frag_kill(&ipq->q, &ip4_frags);
}
如上代码所示,对于正常重组后的IP分片队列,这里仅仅是递减了其引用计数,并没有执行直接的释放动作,所以需要回忆其从创建到重组过程中引用计数的变化:
创建时
static struct inet_frag_queue *inet_frag_alloc(struct netns_frags *nf, struct inet_frags *f, void *arg)
{
struct inet_frag_queue *q;
...
// 初始化引用计数为1
atomic_set(&q->refcnt, 1);
...
}
启动定时器/插入哈希表时
创建IP分片队列后,会立即启动其组装超时定时器,此时也会增加引用计数,因为定时器超时函数也会引用该IP分片队列。
在将IP分片队列放入全局的哈希表和LRU表后,也会增加引用计数。
static struct inet_frag_queue *inet_frag_intern(struct netns_frags *nf,
struct inet_frag_queue *qp_in, struct inet_frags *f