IP重組 ip重組這部分 4.19內核與3.10內核有些差別,4.9.134以後內核中不使用低水位和工作隊列了,同時使用了rhashtable 替代了 hash bucket的概念,在3.10內核中使用1024個hash bucket, 每個bucket中最多存放128個分片隊列,在4.19內核中所 ...
IP重組
ip重組這部分 4.19內核與3.10內核有些差別,4.9.134以後內核中不使用低水位和工作隊列了,同時使用了rhashtable 替代了 hash bucket的概念,在3.10內核中使用1024個hash bucket, 每個bucket中最多存放128個分片隊列,在4.19內核中所有的分片隊列都保存在可動態調整的rhashtable 中,同時不再使用低水位和工作隊列對ip 分片進行回收
4.19內核中,在記憶體中會分配一個reassembly buffer用於IP分片的重組。同時,也定義了一系列的參數用於控制IP分片處理過程:
net.ipv4.ipfrag_high_thresh
: 用於IP分片重組的最大記憶體用量(預設為4194304 ,即4Mb)。
net.ipv4.ipfrag_time
: IP分片在記憶體中的保留時間(預設30,單位:秒)。
對應上述網路協議棧的內核參數,內核層定義了結構體netns_frags,包含分片重組功能需要的全局控制信息,其定義如下:
struct netns_frags {
struct percpu_counter mem ____cacheline_aligned_in_smp;
/* sysctls */
int timeout;
int high_thresh;
int low_thresh;
int max_dist;
struct inet_frags *f;
struct rhashtable rhashtable ____cacheline_aligned_in_smp;
atomic_long_t mem ____cacheline_aligned_in_smp;
};
其中rhashtable為分片隊列(inet_frag_queue)所在的hash表,IP分片包在內核中根據IP報頭的4個欄位計算得到一個hash值(key值),每個hash值對應一個分片隊列,在實現分片包重組功能時,IP層需要先緩存收到的所有分片包,等待同一個IP報文的所有分片包都到達後,把它們重組成一個大包再提交給L4(TCP/UDP... ...)協議。
當收到新的ip分片包時,將查找是否存在同一數據包的分片隊列。首先檢查當前記憶體中所有待重組分片包占用的記憶體(frag_mem_limit)是否高於高水位(net.ipv4.ipfrag_high_thresh),如果高於則丟棄分片包;否則接著對接收到的分片包與rhashtable表中緩存的分片隊列進行匹配(即從rhashtable表查找分片隊列)將屬於同一數據包的分片包放在同一個分片隊列中,如果一個數據包的所有分片包都接收完成,那麼將進入數據包的重構流程;如果匹配失敗,說明該分片屬於一個新的數據包,那麼進入分片隊列新建流程。分片隊列的接收查找函數inet_frag_find定義如下:
struct inet_frag_queue *inet_frag_find(struct netns_frags *nf, void *key)
{
struct inet_frag_queue *fq = NULL, *prev;
//①高水位判斷
if (!nf->high_thresh || frag_mem_limit(nf) > nf->high_thresh)
return NULL;
rcu_read_lock();
prev = rhashtable_lookup(&nf->rhashtable, key, nf->f->rhash_params); //② 查找rhashtable中的分片隊列
if (!prev)
fq = inet_frag_create(nf, key, &prev); //③ 創建新分片隊列
if (prev && !IS_ERR(prev)) {
fq = prev;
if (!refcount_inc_not_zero(&fq->refcnt))
fq = NULL;
}
rcu_read_unlock();
return fq;
}
在分片隊列的新建流程中,將從slab中分配一段空間,相應增加分片包占用的記憶體,同時設置定時器(超時時常為30秒)用來檢查重組結果,如果定時器超時未重組成功,該分片包也將丟棄。分片包的新建函數inet_frag_alloc定義如下:
static struct inet_frag_queue *inet_frag_alloc(struct netns_frags *nf,
struct inet_frags *f,
void *arg)
{
struct inet_frag_queue *q;
q = kmem_cache_zalloc(f->frags_cachep, GFP_ATOMIC);
if (!q)
return NULL;
... ...
add_frag_mem_limit(nf, f->qsize); //①增加分片報文占用記憶體
setup_timer(&q->timer, //②設置超時定時器
f->frag_expire, (unsigned long)q);
... ...
return q;
}
int ip_defrag(struct net *net, struct sk_buff *skb, u32 user)
{
... ...
qp = ip_find(net, ip_hdr(skb), user, vif); //①查找分片隊列
if (qp) {
... ...
ret = ip_frag_queue(qp, skb); //②分片隊列入隊操作
... ...
return ret;
}
kfree_skb(skb);
return -ENOMEM;
}
如果一個數據包的所有分片包都已接收,則需將所有分片包整合獲得原始數據包,並將整合後的數據包提交給高層協議。同時,處理與分片包相關的數據結構,譬如更新當前分片包占用的記憶體(frag_mem_limit),停止與分片包相關的定時器等。數據包的重構函數ip_frag_reasm定義如下:
static int ip_frag_reasm(struct ipq *qp, struct sk_buff *skb,
struct sk_buff *prev_tail, struct net_device *dev)
{
... ...
ipq_kill(qp); //①減少分片包引用計數
... ...
sub_frag_mem_limit(qp->q.net, //②減少分片包占用記憶體
head->truesize);
... ...
}
所以,一個分片包的接收通常經歷了查找分片、緩存、重組、釋放等階段,下圖是分片包的接收流程。
圖1 4.19分片包接收流程
根據分析,內核中待重組的分片包占用記憶體量由高水位(net.ipv4.ipfrag_high_thresh)閾值和分片保留時間(net.ipv4.ipfrag_time)來控制,如果待重組分片包記憶體占用高於高水位(high_thresh),那麼新收到的數據包分片將會直接丟棄, 如果分片包超過最大保留時間(ipfrag_time),那麼已經收到的數據包也會被丟棄。
附3.10 ip重組
本文來自博客園,作者:StepForwards,轉載請註明原文鏈接:https://www.cnblogs.com/forwards/p/18279336