Linux 内核网络之网络层接收消息:分片组装

系统 Linux
在接收方,一个由发送方发出的原始 IP 数据报,其所有分片将被重新组合,然后才能提交给上层协议。

在接收方,一个由发送方发出的原始 IP 数据报,其所有分片将被重新组合,然后才能提交给上层协议。

而每个将被重新组合的 IP 数据报都用一个 ipq 结构实例来表示。

struct ipq {
//用来将ipq_hash散列表链接成双向链表
struct hlist_node list;
/*用来将ipq连接到全局链表ipq_lru_list链表中。ipq_lru_list 用于垃圾收集,
当ip组装模块消耗内存大于规定的上限时,会遍历该链表清除符合条件的分片*/
struct list_head lru_list; /* lru list member */
u32 user; //标识分片来源: 来自网络其他主机或是本地环回接口的分片、含有路由警告选项的IP分片

//以下四个字段的值都来源于ip首部,用来唯一确定分片来自哪个ip数据报
__be32 saddr;
__be32 daddr;
__be16 id;
u8 protocol;

u8 last_in;
#define COMPLETE 4 //所有分片已到达,可以进行组装;
#define FIRST_IN 2 // 第一个分片到达,其特殊之处在于只有第一个分片包含了所有ip选项;
#define LAST_IN 1 //最后一个分片已到达,最后一个分片带有原始数据包的长度信息

//用来链接已经接收到的分片
struct sk_buff *fragments; /* linked list of received fragments */
/*当前已接收到分片中offset最大的那个分片的offset值加上其长度值,
即分片末尾处在整个原始数据报中的位置,
因此当收到最后一个分片后该字段值将更新为原始数据报的长度
*/
int len; /* total length of original datagram */
//已接收到的所有分片总长度,因此可以用len和meat来判断一个ip数据报的所有分片时否已到齐
int meat;
//自旋锁,在smp环境下,处理ipq及分片链时需上锁
spinlock_t lock;

atomic_t refcnt; //引用计数
//组装超时定时器,组装分片非常消耗资源,防止无休止等待分片的到达
struct timer_list timer; /* when will this queue expire? */
//记录最后一个分片的到达时间,在组装数据报时用该值作为时间戳
struct timeval stamp;
//接收最后一个分片的网络设备索引号。当分片组装失败时,用该设备发送组装失败icmp出错报文。
int iif;
//已接收到分片的计数器,可通过对端信息块peer中的分片计数器和该分片计数器来防止DoS攻击
unsigned int rid;
//记录发送方的一些信息
struct inet_peer *peer;
};

/* Hash table. */

#define IPQ_HASHSZ 64

/* Per-bucket lock is easy to add now. */
static struct hlist_head ipq_hash[IPQ_HASHSZ];

每个原始的 IP 数据报的所有分片以链表的形式保存在 ipq 结构 fragments 中。

在网络层中,会根据每个原始的 IP 数据报首部中的(saddr, daddr, id, protocol, ipfrag_hash_rnd)计算一个 hash 值,然后将 ipq 结构放到对应的 ipq_hash[hash] 散列表中。

因此,当一个新的分片 skb 到来时,根据(saddr, daddr, id, protocol, ipfrag_hash_rnd)计算出 hash 值,从 ipq_hash[hash] 散列表中找到 ipq 结构,然后把分片存放到 fragments 链表中。

当 IP 分片到达本地时,先调用 ip_defrag 进行重组。

//ip数据报输入到本地
int ip_local_deliver(struct sk_buff *skb)
{
/*
* Reassemble IP fragments.
*/
/*若接收到的ip报是分片,则调用ip_defrag进行重组,其标志为IP_DEFRAG_LOCAL_DELIVER*/
if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
/*返回0,表示ip数据包分片还未到齐,重组未完成,直接返回;
非0,返回已完成重组的ip数据报skb缓存的指针,传到传输层进行处理*/
skb = ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER);
if (!skb)
return 0;
}

/*通过netfilter处理后,调用ip_local_deliver_finish将组装完的ip报文传送到传输层进行处理*/
return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}

分片组装

分片组装的流程如下:

ip_defrag
--> ip_find 在ipq散列表中查找分片所属的ipq,若找不到则新建一个ipq
--> ip_frag_queue 将分片插入到ipq分片链表的适当位置
--> ip_frag_reasm 原始数据报的所有分片全部到达,组装分片

具体实现细节如下

ip_defrag

/* Process an incoming IP datagram fragment. */
/* 对分片进行组装
skb : 新接收到的ip数据报
user : 分片来源
*/
struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user)
{
struct iphdr *iph = skb->nh.iph;
struct ipq *qp;
struct net_device *dev;

IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);

/* Start by cleaning up the memory. */
//若iqp散列表消耗的内存大于指定的值,则ip_evictor()清理分片
if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh)
ip_evictor();

//获取接收数据报的网络设备指针
dev = skb->dev;

/* Lookup (or create) queue header */
/*在ipq散列表中查找分片所属的ipq。若找不到则新建一个ipq,若返回为null,则说明
查找及创建失败*/
if ((qp = ip_find(iph, user)) != NULL) {
struct sk_buff *ret = NULL;

spin_lock(&qp->lock);
//将分片插入到ipq分片链表的适当位置
ip_frag_queue(qp, skb);

/*当ipq的第一个分片和最后一个分片都已收到,且若已接收数据报的总长度与原始数据报的长度相等,
则说明该原始数据报的所有分片都已到齐*/
if (qp->last_in == (FIRST_IN|LAST_IN) &&
qp->meat == qp->len)
//调用ip_frag_reasm组装分片
ret = ip_frag_reasm(qp, dev);

spin_unlock(&qp->lock);

//删除iqp及其所有分片
ipq_put(qp, NULL);

return ret;
}

IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);

//释放分片
kfree_skb(skb);
return NULL;
}

当一个分片到达后,按照偏移量插入到 ipq 分片链表的适当位置,如下图:

ip_find

在 ipq 散列表中查找分片所属的 ipq 结构如下:

//根据分片的ip首部以及user标志在ipq散列表中找对应的ipq,若没找到,则为其创建新的ipq 
static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
{
__be16 id = iph->id;
__be32 saddr = iph->saddr;
__be32 daddr = iph->daddr;
__u8 protocol = iph->protocol;
unsigned int hash;
struct ipq *qp;
struct hlist_node *n;

read_lock(&ipfrag_lock);
//计算hash值
hash = ipqhashfn(id, saddr, daddr, protocol);
/*遍历该桶的ipq链表查找对应的ipq,若找到则返回该ipq,否则说明这是一个新的ip数据报分片,
需要创建一个新的ipq*/
hlist_for_each_entry(qp, n, &ipq_hash[hash], list) {
if(qp->id == id &&
qp->saddr == saddr &&
qp->daddr == daddr &&
qp->protocol == protocol &&
qp->user == user) {
atomic_inc(&qp->refcnt);
read_unlock(&ipfrag_lock);
return qp;
}
}
read_unlock(&ipfrag_lock);
//返回新建的ipq
return ip_frag_create(iph, user);
}

ip_frag_create

创建 ipq 结构,并初始化其组装超时定时器。

/*每当接收到一个属于新的ip数据报的分片时,会为其创建对应的iqp,并初始化其组装超时定时器*/
static struct ipq *ip_frag_create(struct iphdr *iph, u32 user)
{
struct ipq *qp;

if ((qp = frag_alloc_queue()) == NULL)
goto out_nomem;

qp->protocol = iph->protocol;
qp->last_in = 0;
qp->id = iph->id;
qp->saddr = iph->saddr;
qp->daddr = iph->daddr;
qp->user = user;
qp->len = 0;
qp->meat = 0;
qp->fragments = NULL;
qp->iif = 0;
qp->peer = sysctl_ipfrag_max_dist ? inet_getpeer(iph->saddr, 1) : NULL;

/* Initialize a timer for this entry. */
init_timer(&qp->timer);
qp->timer.data = (unsigned long) qp; /* pointer to queue */
qp->timer.function = ip_expire; /* expire function */
spin_lock_init(&qp->lock);
atomic_set(&qp->refcnt, 1);

return ip_frag_intern(qp);

out_nomem:
LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
return NULL;
}

ip_frag_intern

//将新建的ipq插入到ipq散列表中和ipq_lru_list中
static struct ipq *ip_frag_intern(struct ipq *qp_in)
{
struct ipq *qp;
unsigned int hash;

write_lock(&ipfrag_lock);
//计算hash值
hash = ipqhashfn(qp_in->id, qp_in->saddr, qp_in->daddr,
qp_in->protocol);

qp = qp_in;

/* 安装ipq的组装超时定时器,定时为 sysctl_ipfrag_time */
if (!mod_timer(&qp->timer, jiffies + sysctl_ipfrag_time))
atomic_inc(&qp->refcnt);

//递增ipq的引用计数
atomic_inc(&qp->refcnt);
//将ipq插入到ipq散列表和lru_list中
hlist_add_head(&qp->list, &ipq_hash[hash]);
INIT_LIST_HEAD(&qp->lru_list);
list_add_tail(&qp->lru_list, &ipq_lru_list);

//对ipq的数量进行计数
ip_frag_nqueues++;
write_unlock(&ipfrag_lock);
return qp;
}

ip_frag_queue

/* Add new segment to existing queue. */
/*将分片skb添加到iqp指定的ipq分片链表中
qp : 将添加ip分片到ipq
skb : 接收到 待添加到ipq中的ip分片
*/
static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
struct sk_buff *prev, *next;
int flags, offset;
int ihl, end;
//对分片已全部接收到的ipq,则释放该分片后返回
if (qp->last_in & COMPLETE)
goto err;

/* 若不是有本地生成的分片,则调用ip_frag_too_far检测该分片是否存在DoS攻击嫌疑,
。若受到攻击,则调用ip_frag_reinit释放所有的ipq所有分片*/
if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
unlikely(ip_frag_too_far(qp)) && unlikely(ip_frag_reinit(qp))) {
ipq_kill(qp);
goto err;
}
//取出ip首部中的标志位、片偏移及首部长度字段,并计算片偏移值和首部长度值
offset = ntohs(skb->nh.iph->frag_off);
flags = offset & ~IP_OFFSET;
//ip首部中的片偏移字段为13位,表示的是8字节的倍数
offset &= IP_OFFSET;
offset <<= 3; /* offset is in 8-byte chunks */
ihl = skb->nh.iph->ihl * 4;

/* Determine the position of this fragment. */
//计算分片末尾处在原始数据报中的位置
end = offset + skb->len - ihl;

/* Is this the final fragment? */
/*若是最后一个分片*/
if ((flags & IP_MF) == 0) {
/* If we already have some bits beyond end
* or have different end, the segment is corrrupted.
*/
/* 先对分片进行检查,若其末尾小于原始包长度,
或者ipq已有LAST_IN标志且分片末尾不等于原始包长度,则出错*/
if (end < qp->len ||
((qp->last_in & LAST_IN) && end != qp->len))
goto err;

//设置LAST_IN标志,将完整数据报长度存储在ipq的len字段中
qp->last_in |= LAST_IN;
qp->len = end;
} else {
//不是最后一个分片,其数据长度又不是8字节对齐,则将其截为8字节对齐
if (end&7) {
end &= ~7;

/* 若需要计算校验和,则强制设置有软件来计算校验和,因为截断了ip有效负载,改变了长度,
需重新计算校验和 */
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
/*在最后一个分片没有到达的情况下,若当前分片的末尾在整个数据报中的位置大于ipq中的len值,则更新len。
因为ipq中的len字段始终保持所有已接收到的分片中分片末尾在数据报中的位置的最大值,而只有在收到最后一个分片
后。len值才是整个数据报的长度*/
if (end > qp->len) {
/* Some bits beyond end -> corruption. */
//若此数据报有异常,则直接丢弃
if (qp->last_in & LAST_IN)
goto err;

qp->len = end;
}
}

//若分片的数据区长度为0,则该分片异常,直接丢弃
if (end == offset)
goto err;

//调用pskb_pull去掉ip首部,只保留数据部分
if (pskb_pull(skb, ihl) == NULL)
goto err;

//将skb数据区长度调整为 end-offset, ip有效负载长度
if (pskb_trim_rcsum(skb, end-offset))
goto err;

/* Find out which fragments are in front and at the back of us
* in the chain of fragments so far. We must know where to put
* this fragment, right?
*/
/*确定分片在分片链表中的位置。因为各分片很可能不按照顺序到达,而ipq分片链表上的分片
是按照分片偏移值从小到大的顺序连接在一起的*/
prev = NULL;
for(next = qp->fragments; next != NULL; next = next->next) {
if (FRAG_CB(next)->offset >= offset)
break; /* bingo! */
prev = next;
}

/* We found where to put this one. Check for overlap with
* preceding fragment, and, if needed, align things so that
* any overlaps are eliminated.
*/
/*检测和上一个分片的数量是否有重叠,i是重叠部分的长度,若有重叠,调用pskb_pull去掉重叠的部分*/
if (prev) {
int i = (FRAG_CB(prev)->offset + prev->len) - offset;

if (i > 0) {
offset += i;
if (end <= offset)
goto err;
if (!pskb_pull(skb, i))
goto err;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
}

/*若和后一个分片有重叠,则还需判断重叠部分的数据长度是否超过下一个分片的数据长度,若没有超过则调整
下一个分片,超过这需要释放下一个分片后再检查与后面第二个分片的数据是否有重叠,如此反复,直到完成后面对
所有分片的检测。调整分片的偏移值、已接收分片总长度等*/
while (next && FRAG_CB(next)->offset < end) {
int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */

if (i < next->len) {
/* Eat head of the next overlapped fragment
* and leave the loop. The next ones cannot overlap.
*/
if (!pskb_pull(next, i))
goto err;
FRAG_CB(next)->offset += i;
qp->meat -= i;
if (next->ip_summed != CHECKSUM_UNNECESSARY)
next->ip_summed = CHECKSUM_NONE;
break;
} else {
struct sk_buff *free_it = next;

/* Old fragment is completely overridden with
* new one drop it.
*/
next = next->next;

if (prev)
prev->next = next;
else
qp->fragments = next;

qp->meat -= free_it->len;
frag_kfree_skb(free_it, NULL);
}
}

//记录当前分片的偏移值
FRAG_CB(skb)->offset = offset;

/* Insert this fragment in the chain of fragments. */
//将当前的分片插入到ipq分片队列中的相应位置
skb->next = next;
if (prev)
prev->next = skb;
else
qp->fragments = skb;

if (skb->dev)
qp->iif = skb->dev->ifindex;
skb->dev = NULL;
//更新ipq的时间戳
skb_get_timestamp(skb, &qp->stamp);
//累计该分片已收到的分片总长度
qp->meat += skb->len;

//累计分片组装模块所占的内存
atomic_add(skb->truesize, &ip_frag_mem);

//若片偏移值为0,说明当前分片为第一个分片,设置FIRST_IN
if (offset == 0)
qp->last_in |= FIRST_IN;

write_lock(&ipfrag_lock);
//调整所属ipq在ipq_lru_list中的位置,这是为了在占用内存超过阈值时可以先释放最久未用的那些分片
list_move_tail(&qp->lru_list, &ipq_lru_list);
write_unlock(&ipfrag_lock);

return;

err:
kfree_skb(skb);
}

对于其中计算分片末尾处在原始数据报中的位置的地方。

//计算分片末尾处在原始数据报中的位置end = offset + skb->len - ihl;

有关ihl、offset、len 和 end 的关系,如下图:

ip_frag_reasm

/*组装已到齐的所有分片。当原始数据报所有分片都已到齐时,调用此函数组装分片
qp : 存储待组装分片队列的ipq
dev : 输入分片的网络设备
*/
static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev)
{
struct iphdr *iph;
struct sk_buff *fp, *head = qp->fragments;
int len;
int ihlen;

/*组装前,先将此ipq节点从ipq散列表和ipq_lru_list表中断开,并删除定时器*/
ipq_kill(qp);

BUG_TRAP(head != NULL);
BUG_TRAP(FRAG_CB(head)->offset == 0);

/* Allocate a new buffer for the datagram. */
//计算原始数据报包括ip首部的总长度,
ihlen = head->nh.iph->ihl*4;
len = ihlen + qp->len;

//若该长度值超过64k则丢弃
if(len > 65535)
goto out_oversize;

/* Head of list must not be cloned. */
/*在组装分片时,所有的分片都会组装到第一个分片上,因此第一个分片不能时克隆的,
若是克隆的,则需为分片组装重新分配一个skb*/
if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
goto out_nomem;

/* If the first fragment is fragmented itself, we split
* it to two chunks: the first with data and paged part
* and the second, holding only fragments. */
/*分片队列的第一个skb不能既带有数据,又带有分片,也即是其frag_list上不能有分片skb,
若有则重新分配一个skb。最终效果时,head自身不包括数据,其frag_list上连接着所有分片的skb。
这也是skb的一种表现形式,不一定是一个连续的数据块,但最终会调用skb_linearize()将这些数据都复制到
一个连续的数据块中*/
if (skb_shinfo(head)->frag_list) {
struct sk_buff *clone;
int i, plen = 0;

if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
goto out_nomem;
clone->next = head->next;
head->next = clone;
skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
skb_shinfo(head)->frag_list = NULL;
for (i=0; i<skb_shinfo(head)->nr_frags; i++)
plen += skb_shinfo(head)->frags[i].size;
clone->len = clone->data_len = head->data_len - plen;
head->data_len -= clone->len;
head->len -= clone->len;
clone->csum = 0;
clone->ip_summed = head->ip_summed;
atomic_add(clone->truesize, &ip_frag_mem);
}

/*把所有分片组装起来,即将分片连接到第一个skb中的frag_list上,同时还需要遍历所有分片,重新计算ip数据报长度以及校验和等。*/
skb_shinfo(head)->frag_list = head->next;
skb_push(head, head->data - head->nh.raw);
atomic_sub(head->truesize, &ip_frag_mem);

for (fp=head->next; fp; fp = fp->next) {
head->data_len += fp->len;
head->len += fp->len;
if (head->ip_summed != fp->ip_summed)
head->ip_summed = CHECKSUM_NONE;
else if (head->ip_summed == CHECKSUM_COMPLETE)
head->csum = csum_add(head->csum, fp->csum);
head->truesize += fp->truesize;
atomic_sub(fp->truesize, &ip_frag_mem);
}

head->next = NULL;
head->dev = dev;
skb_set_timestamp(head, &qp->stamp);

//重置首部长度、片偏移、标志位和总长度
iph = head->nh.iph;
iph->frag_off = 0;
iph->tot_len = htons(len);
IP_INC_STATS_BH(IPSTATS_MIB_REASMOKS);
//既然各分片都已处理完,释放ipq的分片队列
qp->fragments = NULL;
return head;

out_nomem:
LIMIT_NETDEBUG(KERN_ERR "IP: queue_glue: no memory for gluing "
"queue %p\n", qp);
goto out_fail;
out_oversize:
if (net_ratelimit())
printk(KERN_INFO
"Oversized IP packet from %d.%d.%d.%d.\n",
NIPQUAD(qp->saddr));
out_fail:
IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
return NULL;
}

组装的过程就是把所有分片组装起来,即将分片连接到第一个 skb 中的 frag_list 上,并返回组装后的数据报文。

ipq 散列表的重组

所有的分片重组都是通过 ipq 散列表进行的。随着后续 ipq 的添加或删除,使得散列表中的 ipq 的分布变得不均匀,处理性能会大大降低,因此需要定时对散列表进行重新组装。这样做同时也是为了防御 DoS 攻击。

散列表的定时重组是通过 ipfrag_secret_timer定时器实现的,在 ipfrag_init() 中对 ipfrag_secret_timer 定时器的初始化。在该函数中还初始化了 ipfrag_hash_rnd 变量,该变量主要用来与 IP 首部中的源地址、目的地址等构成 ipq 散列表的关键字。每次重组时都会将 ipfrag_hash_rnd 更新为一个新的随机值,并重新设置 ipfrag_secret_timer 定时器,时间跨度为 10 min。

ipfrag_init

void ipfrag_init(void)
{
ipfrag_hash_rnd = (u32) ((num_physpages ^ (num_physpages>>7)) ^
(jiffies ^ (jiffies >> 6)));

init_timer(&ipfrag_secret_timer);
ipfrag_secret_timer.function = ipfrag_secret_rebuild;
ipfrag_secret_timer.expires = jiffies + sysctl_ipfrag_secret_interval;
add_timer(&ipfrag_secret_timer);
}

ipfrag_secret_rebuild

//对全局的ipq散列表进行重组
static void ipfrag_secret_rebuild(unsigned long dummy)
{
unsigned long now = jiffies;
int i;

write_lock(&ipfrag_lock);
//重新获取ipfrag_hash_rnd随机值
get_random_bytes(&ipfrag_hash_rnd, sizeof(u32));
//遍历ipq散列表中所有ipq,根据新的ipfrag_hash_rnd值把这些ipq重新连接到散列表对应的桶中
for (i = 0; i < IPQ_HASHSZ; i++) {
struct ipq *q;
struct hlist_node *p, *n;

hlist_for_each_entry_safe(q, p, n, &ipq_hash[i], list) {
unsigned int hval = ipqhashfn(q->id, q->saddr,
q->daddr, q->protocol);

if (hval != i) {
hlist_del(&q->list);

/* Relink to new hash chain. */
hlist_add_head(&q->list, &ipq_hash[hval]);
}
}
}
write_unlock(&ipfrag_lock);
//重新设置重构定时器的下次到期时间
mod_timer(&ipfrag_secret_timer, now + sysctl_ipfrag_secret_interval);
}

清除超时的 IP 分片

在复杂的网络环境下,一个 IP 数据报的分片有可能不能全部抵达目的地址,而该数据报已到达的分片会占用大量的资源,此外也为了防止抵御 DoS 攻击,因此需要设置一个时钟,一旦超时,数据报的分片还未全部到达,则将其已到达的分片全部清除。

每当收到一个属于新的 IP 数据报分片时,在为其创建 ipq 时,会初始化其超时定时器 ip_expire

/*每当接收到一个属于新的ip数据报的分片时,会为其创建对应的iqp,并初始化其组装超时定时器*/
static struct ipq *ip_frag_create(struct iphdr *iph, u32 user)
{
...

qp->timer.function = ip_expire; /* expire function */

...
}

ip_expire

//组装超时定时器例程,当定时器激活时,清除在规定时间内没有完成组装的ipq及其所有分片 
static void ip_expire(unsigned long arg)
{
struct ipq *qp = (struct ipq *) arg;

spin_lock(&qp->lock);
//当前已是COMPLETE状态,不做处理,直接跳到释放ipq及其所有的分片处
if (qp->last_in & COMPLETE)
goto out;

//将ipq从ipq散列表和ipq_lru_list链表中删除
ipq_kill(qp);

IP_INC_STATS_BH(IPSTATS_MIB_REASMTIMEOUT);
IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);

//若第一个分片已经到达,则发送分片组装超时ICMP出错报文
if ((qp->last_in&FIRST_IN) && qp->fragments != NULL) {
struct sk_buff *head = qp->fragments;
/* Send an ICMP "Fragment Reassembly Timeout" message. */
if ((head->dev = dev_get_by_index(qp->iif)) != NULL) {
icmp_send(head, ICMP_TIME_EXCEEDED, ICMP_EXC_FRAGTIME, 0);
dev_put(head->dev);
}
}
out:
spin_unlock(&qp->lock);

//释放ipq及其所有的ip分片
ipq_put(qp, NULL);
}

​垃圾收集

为了控制 IP 组装所占用的内存,设置了两个阈值 ipfrag_high_thresh 和 ipfrag_low_thresh 。当前 ipq 散列表占用的内存量存储在全局变量 ip_frag_mem 中,当 ip_frag_mem 大于 ipfrag_high_thresh 时,需要调用 ip_evictor() 对散列表进行清理,直到 ip_frag_mem 降低到 ipfrag_low_thresh 。这两个阈值可以在系统运行时通过 proc 文件系统修改。

ip_evictor

该方法主要对ipq中的分片进行条件性的清理。在所有的ipq中,若分片没有到齐,则被删除。

static void ip_evictor(void)
{
struct ipq *qp;
struct list_head *tmp;
int work;

/*在清理前再次对当前消耗的内存量做测试,若少于sysctl_ipfrag_low_thresh,则不进行清理*/
work = atomic_read(&ip_frag_mem) - sysctl_ipfrag_low_thresh;
if (work <= 0)
return;

while (work > 0) {
read_lock(&ipfrag_lock);
//若lru链表为空,解锁后返回
if (list_empty(&ipq_lru_list)) {
read_unlock(&ipfrag_lock);
return;
}
tmp = ipq_lru_list.next;
qp = list_entry(tmp, struct ipq, lru_list);
//递增ipq引用计数
atomic_inc(&qp->refcnt);
read_unlock(&ipfrag_lock);

//在删除分片前后要做同步保护
spin_lock(&qp->lock);
/*若分片还未到齐,则ipq从散列表及lru链表中删除。ipq_kill只删除不释放*/
if (!(qp->last_in&COMPLETE))
ipq_kill(qp);
spin_unlock(&qp->lock);

//ipq_put真正删除ipq及其所有分片
ipq_put(qp, &work);
IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
}
}
责任编辑:华轩 来源: 今日头条
相关推荐

2023-03-01 23:56:11

2023-03-01 23:53:30

Linuxshutdown进程

2023-03-10 14:56:37

Linuxconnect系统

2023-03-06 15:43:56

2024-08-22 14:47:50

开源Linux网络抓包工具

2009-07-16 09:02:38

LINUX 2.4.x网络安全LINUX开发

2020-10-29 15:05:31

Linux网络包代码

2021-09-17 11:59:21

tcpdump网络包Linux

2021-09-08 10:21:33

内核网络包Tcpdump

2010-07-19 10:05:52

ibmdwLinux

2023-05-12 07:27:24

Linux内核网络设备驱动

2015-12-14 14:11:26

网络虚拟化FlowVisor

2015-12-14 10:50:28

网络虚拟化FlowVisor

2015-12-14 11:47:23

网络虚拟化FlowVisor

2020-09-23 06:53:48

Linux内核架构

2022-08-22 08:45:57

Kafka网络层源码实现

2014-05-28 09:19:44

2013-12-18 14:44:10

2017-03-28 13:25:14

Linux网络数据包

2020-03-31 10:18:57

网络命令kalilinux
点赞
收藏

51CTO技术栈公众号