ip分片重組函式-ip_frag_queue
阿新 • • 發佈:2018-12-11
在講解此函式前 有個概念需要再次說明一下,即分片重疊的問題
比如在沒有接收到新來的分片時候 分片佇列的分佈如下:
當在接收到一個分片的時候,遍歷連結串列(已經升序排序好)後找到插入位置後根據重疊與非重疊 有如下情況:
(1)不發生重疊的正常情況: 比如新分片的offset=300 len=99 則插入情況如下:
(2)發生前重疊 比如新分片的offset=280 len=119 則插入的情況如下:
(3)傳送後重疊 比如新分片的offet=300 len=280 則插入的情況如下:
以上圖就是對程式碼中覆蓋部分的總結,根據圖我們來看下面程式碼的重疊處理部分會容易理解一些
/* 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; //碎片重組已經完成 if (qp->q.last_in & INET_FRAG_COMPLETE) goto err; /* IPCB(skb)->flags只有在本機發送IPv4分片時被置位,那麼這裡的檢查應該是預防收到本機自己發出的IP分片。 */ if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&//檢查skb中的分片標誌 //關於ip_frag_too_far:該函式主要保證了來自同一個peer(相同的源地址)不會佔用過多的IP分片佇列 unlikely(ip_frag_too_far(qp)) && //重新初始化該佇列 unlikely(err = ip_frag_reinit(qp))) { ipq_kill(qp); goto err; } //計算碎片的偏移量和標誌 offset = ntohs(ip_hdr(skb)->frag_off); flags = offset & ~IP_OFFSET; offset &= IP_OFFSET; offset <<= 3; /* offset is in 8-byte chunks */ //獲得ip頭的長度 ihl = ip_hdrlen(skb); /* Determine the position of this fragment. */ //獲得報文總的長度 end = offset + skb->len - ihl; err = -EINVAL; //如果是最後一個分片 if ((flags & IP_MF) == 0) { /* If we already have some bits beyond end * or have different end, the segment is corrrupted. */ //末端小於之前獲得的總長度 則出現問題 //之前已經收到了一個最後分片,且這次判斷的末端不等於之前獲得的值 則出錯 if (end < qp->q.len || ((qp->q.last_in & INET_FRAG_LAST_IN) && end != qp->q.len)) goto err; qp->q.last_in |= INET_FRAG_LAST_IN; //標誌這是最後一個分片 qp->q.len = end;//更新總長度 } else //不是最後一個分片 { //檢查是否8位元組對齊 if (end&7) { //除最後一個分片外 其餘分片均為8位元組對齊 end &= ~7; if (skb->ip_summed != CHECKSUM_UNNECESSARY)//沒有設定跳過校驗和計算 skb->ip_summed = CHECKSUM_NONE;//傳輸層自己計算校驗和 } //此分片尾部是否超過之前獲得的報文總長度 if (end > qp->q.len) { /* Some bits beyond end -> corruption. */ if (qp->q.last_in & INET_FRAG_LAST_IN)//最後一個分片已經接受到 在來的分片是錯誤的 goto err; qp->q.len = end;//更新報文的總長度 } } //表示空的ip分片 if (end == offset) goto err; err = -ENOMEM; //將skb->data移到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? */ //遍歷ipq中的fragments連結串列,每個skb的cb成員記錄著當前skb進行ip重組的 //時候所需要的偏移,fragments中的skb都是按照offset升序排好的,所以,找到 //第一項offset大於當前IP包偏移的資料包就可以了 prev = NULL; for (next = qp->q.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. */ //在上面的迴圈中找到了插入的位置 但可能發生重疊, if (prev) { //若沒發生重疊 很顯然offset的大小 應該在FRAG_CB(prev)->offset + prev->len 之後也就是i值會小於0 /*若發生了重疊 offset的大小 在FRAG_CB(prev)->offset和FRAG_CB(prev)->offset + prev->len之間 則i的值會大於0 從上面的迴圈可知,此分片應該插入到prev指向的位置後面比如 prev的offset=100 len=150 若此分片的offset的值 在100+150的後面 i值就會小於0 沒有重疊發生 若此分片的offset的值 在100,350之間 i的值會大於0 發生了重疊 插入位置 前重疊 \|/ ------------------------------------------------------ 分片1 | (與分片2發生重疊)分片2 | 分片3 | ------------------------------------------------------ */ int i = (FRAG_CB(prev)->offset + prev->len) - offset; if (i > 0)//發生了重疊 { offset += i; //將偏移完後移動i 跑到prev報文區間的最右邊 err = -EINVAL; if (end <= offset) //此分片移動後的offset是否超過了此分片的最大長度 goto err;//偏移超過了分片的最大長度 出錯 err = -ENOMEM; if (!pskb_pull(skb, i))//將skb的資料指標減少i個位元組 改變偏移值 goto err; if (skb->ip_summed != CHECKSUM_UNNECESSARY)//沒有設定跳過校驗和計算 skb->ip_summed = CHECKSUM_NONE;//傳輸層自己計算校驗和 } } err = -ENOMEM; //此分片與佇列中存在的分片發生後面的重疊 後重疊 /* 插入位置 \|/ ------------------------------------------------------------------------ 分片1 | 分片2 | (重疊與分片3甚至後面的分片發生重疊) 分片3 | ------------------------------------------------------------------------ */ //此分片的最大報文長度 大於後面分片的起始位置 發生後重疊 while (next && FRAG_CB(next)->offset < end) { //計算重疊的位元組數 int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */ if (i < next->len)//是否只重疊了此next分片 { /* Eat head of the next overlapped fragment * and leave the loop. The next ones cannot overlap. */ //將覆蓋的i個位元組在next分片中剔除 //將data指標減少i個位元組 if (!pskb_pull(next, i)) goto err; FRAG_CB(next)->offset += i;//更新next新的偏移 往後移動i個位元組 qp->q.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->q.fragments = next; qp->q.meat -= free_it->len; //釋放覆蓋的整個分片 frag_kfree_skb(qp->q.net, free_it, NULL); } } //設定新的偏移值 FRAG_CB(skb)->offset = offset; /* Insert this fragment in the chain of fragments. */ //將skb加入到佇列中 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; } qp->q.stamp = skb->tstamp;//記錄這個包接收的時間戳 qp->q.meat += skb->len; //增加已經獲得的報文的長度 atomic_add(skb->truesize, &qp->q.net->mem); //說明是第一個分片 設定第一個分片的標誌 if (offset == 0) qp->q.last_in |= INET_FRAG_FIRST_IN; //如果收到了第一個分片和最後一個分片 並且報文長度也也等於原始的ip報文長度 //則對所有的分片進行組裝 if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) && qp->q.meat == qp->q.len) return ip_frag_reasm(qp, prev, dev);//根據所有的分段建立一個新的資料包 //ip分片還未完全收齊 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; }