linux核心協議棧 IPv4分片重組Ⅱ之 ip_defrag() 介面
Table of Contents
2.1.1 雜湊查詢分片佇列 inet_frag_find()
2.1.2 新建IP分片佇列 inet_frag_create()
2.2.1 所有分片均已收到重組ip報文 ip_frag_reasm()
1 IP片段接收入口 ip_local_deliver()
在接收路徑上的 ip_local_deliver() 函式中,此時已經確認資料包是給本機的,會首先呼叫 ip_defrag() 判斷是否是一個完整的IP報文,即是否需要進行重組,如果一切 ok,那麼繼續過防火牆的 LOCAL_IN 點,繼續資料包的接收流程。
int ip_local_deliver(struct sk_buff *skb)
{
// 第一個if條件成立,說明收到的skb是一個IP片段;
// ip_defrag()返回非0表示此資料包不完整,需要等更多的資料包才能完成重組,所以現在還不能遞交,接收流程結束
if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
return 0;
}
return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);
}
顯然,IP報文重組的核心在ip_defrag()中。
2 IP片段重組 ip_defrag()
ip_defrag()將分片重組邏輯分割的非常清晰:
- ip_find() 查詢全域性雜湊表,找到(或者新建)該IP分片所屬的 IP分片佇列struct ipq;
- 新收到了IP片段,所以呼叫 ip_frag_queue() 嘗試對該IP分片佇列中的片段進行重組。
/* Process an incoming IP datagram fragment. */
int ip_defrag(struct sk_buff *skb, u32 user)
{
struct ipq *qp;
struct net *net;
IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);
net = skb->dev ? skb->dev->nd_net : skb->dst->dev->nd_net;
// 後面很可能需要分配記憶體來儲存新來的分片,所以先檢查分片佔用記憶體是否已經超過了最大上限,
// 如果超過了則呼叫ip_evictor()進行記憶體清理
if (atomic_read(&net->ipv4.frags.mem) > net->ipv4.frags.high_thresh)
ip_evictor(net);
// 查詢該IP片段所屬IP分片佇列(每一個IP資料包有一個該佇列),如果沒有則新建一個
if ((qp = ip_find(net, ip_hdr(skb), user)) != NULL) {
int ret;
spin_lock(&qp->q.lock);
// 嘗試進行分片重組,如果能夠重組出一個完整的IP報文,則返回非0,這樣資料包就會傳遞給L4協議
ret = ip_frag_queue(qp, skb);
spin_unlock(&qp->q.lock);
ipq_put(qp);
return ret;
}
// 新建IP分片佇列失敗,則丟包
IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
kfree_skb(skb);
return -ENOMEM;
}
2.1 查詢IP分片佇列 ip_find()
// ip_find()內部的一個臨時傳參用的結構
struct ip4_create_arg {
struct iphdr *iph;
u32 user;
};
/* Find the correct entry in the "incomplete datagrams" queue for
* this IP datagram, and create new one, if nothing is found.
*/
static inline struct ipq *ip_find(struct net *net, struct iphdr *iph, u32 user)
{
struct inet_frag_queue *q;
struct ip4_create_arg arg;
unsigned int hash;
arg.iph = iph;
arg.user = user;
// 根據ipid、源IP、目的IP、L4協議號以及初始化時生成的一個隨機數共5個資訊計算hash值
hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);
// 查詢雜湊表,檢查是否有該片段所屬報文對應的inet_frag_queue佇列,如果沒有那麼函式會新建
q = inet_frag_find(&net->ipv4.frags, &ip4_frags, &arg, hash);
if (q == NULL)
goto out_nomem;
return container_of(q, struct ipq, q);
out_nomem:
LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
return NULL;
}
2.1.1 雜湊查詢分片佇列 inet_frag_find()
struct inet_frag_queue *inet_frag_find(struct netns_frags *nf, struct inet_frags *f, void *key, unsigned int hash)
{
struct inet_frag_queue *q;
struct hlist_node *n;
// 遍歷衝突鏈,嘗試尋找匹配的,如果找到,增加引用計數並返回
read_lock(&f->lock);
hlist_for_each_entry(q, n, &f->hash[hash], list) {
if (q->net == nf && f->match(q, key)) {
// 找到了對應的IP分片佇列,說明該分片不是第一個分片
atomic_inc(&q->refcnt);
read_unlock(&f->lock);
return q;
}
}
read_unlock(&f->lock);
// 沒找到則新建一個,注意此時並未持有雜湊表的讀寫鎖
return inet_frag_create(nf, f, key, hash);
}
2.1.2 新建IP分片佇列 inet_frag_create()
inet_frag_create()流程新建一個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, unsigned int hash, void *arg)
{
struct inet_frag_queue *qp;
#ifdef CONFIG_SMP
struct hlist_node *n;
#endif
write_lock(&f->lock);
#ifdef CONFIG_SMP
/* With SMP race we have to recheck hash table, because
* such entry could be created on other cpu, while we
* promoted read lock to write lock.
*/
hlist_for_each_entry(qp, n, &f->hash[hash], list) {
if (qp->net == nf && f->match(qp, arg)) {
atomic_inc(&qp->refcnt);
write_unlock(&f->lock);
qp_in->last_in |= COMPLETE;
inet_frag_put(qp_in, f);
return qp;
}
}
#endif
qp = qp_in;
// 啟動該IP分片佇列的定時器,超時時間來自系統引數(見網路名稱空間中的frags)
if (!mod_timer(&qp->timer, jiffies + nf->timeout))
atomic_inc(&qp->refcnt);
// 將新的IP分片佇列加入雜湊表中,並累加計數器
atomic_inc(&qp->refcnt);
hlist_add_head(&qp->list, &f->hash[hash]);
// 將qp加入LRU連結串列
list_add_tail(&qp->lru_list, &nf->lru_list);
nf->nqueues++;
write_unlock(&f->lock);
return qp;
}
static struct inet_frag_queue *inet_frag_alloc(struct netns_frags *nf, struct inet_frags *f, void *arg)
{
struct inet_frag_queue *q;
// 按照指定大小進行分配,對於IPv4,就是sizeof(struct ipq)
q = kzalloc(f->qsize, GFP_ATOMIC);
if (q == NULL)
return NULL;
// 呼叫回撥進行初始化,對於IPv4,這裡是ip4_frag_init()
f->constructor(q, arg);
// 記憶體消耗記賬
atomic_add(f->qsize, &nf->mem);
// 建立定時器,但是並不啟動,啟動是在inet_frag_intern()中完成的
setup_timer(&q->timer, f->frag_expire, (unsigned long)q);
spin_lock_init(&q->lock);
atomic_set(&q->refcnt, 1);
q->net = nf;
return q;
}
static struct inet_frag_queue *inet_frag_create(struct netns_frags *nf, struct inet_frags *f,
void *arg, unsigned int hash)
{
struct inet_frag_queue *q;
// 分配IP分片佇列並對其進行初始化
q = inet_frag_alloc(nf, f, arg);
if (q == NULL)
return NULL;
// 將新建的IP分片佇列放入全域性的IP分片重組雜湊表中
return inet_frag_intern(nf, q, f, hash, arg);
}
2.2 重組IP報文 ip_frag_queue()
在上面ip_find()的處理過程中,僅僅是拿到了該IP分片對應的IP分片佇列,並沒有將新收到的IP分片新增到佇列中,是否新增以及它們是否能夠重組成一個IP報文是由ip_frag_queue()實現的。
/* Add new segment to existing queue. */
static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
struct sk_buff *prev, *next;
struct net_device *dev;
int flags, offset;
int ihl, end;
int err = -ENOENT;
// 如果該IP報文已經重組完畢,但是又收到了屬於它的片段,那麼新收到的IP分片一定是個重複分片,丟棄
if (qp->q.last_in & COMPLETE)
goto err;
if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
unlikely(ip_frag_too_far(qp)) && unlikely(err = ip_frag_reinit(qp)))
{
ipq_kill(qp);
goto err;
}
// offset記錄偏移量,位元組為單位,flags記錄MF和DF標記
offset = ntohs(ip_hdr(skb)->frag_off);
flags = offset & ~IP_OFFSET;
offset &= IP_OFFSET;
offset <<= 3; /* offset is in 8-byte chunks */
ihl = ip_hdrlen(skb);
// end記錄的是該IP分片最後一個位元組在整個IP報文中的偏移量
end = offset + skb->len - ihl;
err = -EINVAL;
if ((flags & IP_MF) == 0) {
// MF標記為0,說明該IP片段是IP報文的最後一個分片
// q.len記錄的是當前收到的該IP報文的最大偏移量,所以下面兩種情況分別是:
// cond1成立,表示新收到這個片段雖然說自己是該IP報文的最後一個片段,
// 但是其最後一個位元組之前已經收過了,這顯然是一種傳輸錯誤;
// cond2成立,之前已經收到了最後一個IP分片,現在又收到了一個最後分片(可能是重傳),
// 但是其最後一個位元組的偏移量不正確,這顯然是一種錯誤
if (end < qp->q.len || ((qp->q.last_in & LAST_IN) && end != qp->q.len))
goto err;
// 標記最後一個片段接收成功,更新q.len
qp->q.last_in |= LAST_IN;
qp->q.len = end;
} else {
// IP分片不是IP報文的最後一個分片,其end必須是8位元組對齊的,這是由offset首部格式決定的
if (end&7) {
end &= ~7;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
if (end > qp->q.len) {
// 不是最後一個分片,但是其最後一個位元組的偏移超過了報文總長度,顯然是錯誤
if (qp->q.last_in & LAST_IN)
goto err;
// 偏移量ok,更新q.len
qp->q.len = end;
}
}
// 上面調整過end的值,或者該IP分片沒有攜帶資料,那麼是一種錯誤
if (end == offset)
goto err;
err = -ENOMEM;
// 調整skb,刪除IP首部,只保留資料部分
if (pskb_pull(skb, ihl) == NULL)
goto err;
// 校驗和調整
err = pskb_trim_rcsum(skb, end - offset);
if (err)
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?
*/
prev = NULL;
// 根據offset,找到該分片的插入點,最後將插入到prev的後面
for (next = qp->q.fragments; next != NULL; next = next->next) {
if (FRAG_CB(next)->offset >= offset)
break; /* bingo! */
prev = next;
}
// 檢查新接收的skb和prev是否有重疊
if (prev) {
int i = (FRAG_CB(prev)->offset + prev->len) - offset;
// 因為prev->offset肯定是小於offset的,如果prev報文的最後一個位元組的
// 偏移量超過了offset(i > 0),說明二者一定有重疊,需要調整
if (i > 0) {
// 調整策略就是將新收到的skb前面的一部分刪除
offset += i;
err = -EINVAL;
// 刪除後,新的報文已經沒有資料了,這種情況是新收到的skb之前已經完全收過了
if (end <= offset)
goto err;
err = -ENOMEM;
// 將新收到的skb的前i位元組資料刪除
if (!pskb_pull(skb, i))
goto err;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
}
err = -ENOMEM;
// 檢查新接收的skb和next是否有重疊
while (next && FRAG_CB(next)->offset < end) {
int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */
if (i < next->len) {
// i < next->len說明next末尾還有一部分資料是不重疊的,更新next
/* 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->q.meat -= i;
if (next->ip_summed != CHECKSUM_UNNECESSARY)
next->ip_summed = CHECKSUM_NONE;
break;
} else {
// next中所有位元組都是重複的,刪除它
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->q.fragments = next;
qp->q.meat -= free_it->len;
frag_kfree_skb(qp->q.net, free_it, NULL);
}
}
FRAG_CB(skb)->offset = offset;
// 將新收到的skb插入IP分片佇列中
skb->next = next;
if (prev)
prev->next = skb;
else
qp->q.fragments = skb;
// 記錄輸入裝置索引
dev = skb->dev;
if (dev) {
qp->iif = dev->ifindex;
skb->dev = NULL;
}
// 更新時間戳和meat
qp->q.stamp = skb->tstamp;
qp->q.meat += skb->len;
// 記憶體記賬
atomic_add(skb->truesize, &qp->q.net->mem);
// 偏移量為0,說明是第一個IP片段,設定FIRST_IN標記
if (offset == 0)
qp->q.last_in |= FIRST_IN;
// 所有片段都已經收到,重組IP報文
if (qp->q.last_in == (FIRST_IN | LAST_IN) && qp->q.meat == qp->q.len)
return ip_frag_reasm(qp, prev, dev);
// 重組條件不滿足,更新LRU連結串列
write_lock(&ip4_frags.lock);
list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);
write_unlock(&ip4_frags.lock);
return -EINPROGRESS;
err:
kfree_skb(skb);
return err;
}
2.2.1 所有分片均已收到重組ip報文 ip_frag_reasm()
當ip_frag_queue()發現一個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)
{
struct iphdr *iph;
struct sk_buff *fp, *head = qp->q.fragments;
int len;
int ihlen;
int err;
// 將IP分片佇列從雜湊表中摘下來,遞減IP分片佇列引用計數,以及停止相關定時器
ipq_kill(qp);
/* Make the one we just received the head. */
if (prev) {
head = prev->next;
fp = skb_clone(head, GFP_ATOMIC);
if (!fp)
goto out_nomem;
fp->next = head->next;
prev->next = fp;
skb_morph(head, qp->q.fragments);
head->next = qp->q.fragments->next;
kfree_skb(qp->q.fragments);
qp->q.fragments = head;
}
BUG_TRAP(head != NULL);
BUG_TRAP(FRAG_CB(head)->offset == 0);
// 計算整個IP報文的總長度
ihlen = ip_hdrlen(head);
len = ihlen + qp->q.len;
// 整個IP報文長度不能超過65535
err = -E2BIG;
if (len > 65535)
goto out_oversize;
/* Head of list must not be cloned. */
// 組裝時,所有的IP片段會被拷貝到第一個IP片段上,第一個IP片段不能是克隆的
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. */
// 第一個IP片段不能有分片,如果有,把它拆成兩部分,第一部分沒有片段,片段部分拷貝到新建的skb中
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, &qp->q.net->mem);
}
// 將所有的IP分片連結到第一個IP片段的frag_list中,並且重新計算校驗和以及長度資訊
skb_shinfo(head)->frag_list = head->next;
skb_push(head, head->data - skb_network_header(head));
atomic_sub(head->truesize, &qp->q.net->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, &qp->q.net->mem);
}
head->next = NULL;
head->dev = dev;
head->tstamp = qp->q.stamp;
// 重新設定IP首部的一些欄位
iph = ip_hdr(head);
iph->frag_off = 0;
iph->tot_len = htons(len);
IP_INC_STATS_BH(IPSTATS_MIB_REASMOKS);
qp->q.fragments = NULL;
return 0;
out_nomem:
LIMIT_NETDEBUG(KERN_ERR "IP: queue_glue: no memory for gluing queue %p\n", qp);
err = -ENOMEM;
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 err;
}
相關文章
- LTE的核心網技術之核心網和基站之間的介面協議協議
- TCP/IP協議棧在Linux核心中的執行時序分析TCP協議Linux
- zstack協議棧協議
- Linux TCP/IP協議棧全過程LinuxTCP協議
- 1、zstack協議棧協議
- 【網路協議】TCP分段與IP分片協議TCP
- linux下bluetooth程式設計(二)blueZ協議棧Linux程式設計協議
- 為什麼要從IPv4協議升級到IPv6協議?協議
- 計算機網路學習筆記(10) TCP/IP協議棧 之TELNET協議計算機網路筆記TCP協議
- etcd套路(二)etcd核心之raft協議Raft協議
- zookeeper核心之ZAB協議就這麼簡單!協議
- ovs + kernel datapath 的分片與重組流程
- 【無線通訊篇 | Zstack協議棧】CC2530 Zigbee Zstack協議棧組網專案及詳細講解篇協議
- Zigbee學習(一)之新建Zstack協議棧工程協議
- TI的TCP/IP協議棧--NDK .TCP協議
- [計算機網路]協議棧計算機網路協議
- JavaWeb第五講 Web核心基礎之HTTP協議JavaWebHTTP協議
- 組播協議詳解協議
- 轉換協議位元組協議
- IPIDEA分享,為什麼要從IPv4協議升級到IPv6協議?Idea協議
- 在Linux中,TCP/IP協議棧的工作原理是什麼?LinuxTCP協議
- ZigBee協議棧Zstack介紹協議
- Zstack協議棧結構的初步解析協議
- PDP分組資料協議協議
- 網路協議之:socket協議詳解之Datagram Socket協議
- 微信開發介面API協議API協議
- Java介面 == Objective-C協議JavaObject協議
- 網路基礎與協議入門——(2)TCP協議重點協議TCP
- 網路基礎與協議入門——(1)HTTP協議重點協議HTTP
- Python 之requests封裝通用http協議介面請求Python封裝HTTP協議
- HCNP Routing&Switching之組播技術-組播協議IGMP協議
- HCNP Routing&Switching之組播技術-組播路由協議PIM路由協議
- Linux之SSH協議知識點總結Linux協議
- linux核心的優先順序繼承協議(pip)和μC/OSII2.52核心的優先順序置頂協議(pcp)Linux繼承協議
- 網路協議之:socket協議詳解之Unix domain Socket協議AI
- 物聯網實驗2 協議棧剖析協議
- TCP/TP協議棧(逐漸更新版)TCP協議
- Linux協議有哪些面試重點?Linux運維入門學習Linux協議面試運維