聲明:未經本人同意,嚴禁一切形式轉載!!! |
---|
文章目錄
- 1. NF_IP_LOCAL_IN這部分的作用
- 2. ip_local_deliver接口
-
- 2.1 分片重組子產品~整體架構
- 2.2 分片重組時資料組織結構
- 2.3 分片封包重組完畢後的資料結構
- 2.4 相關函數
-
- 2.4.1 ip_local_deliver()函數
- 2.4.2 ip_defrag()函數
- 2.4.3 ip_find()函數
- 2.4.4 ip_frag_queue()函數
- 2.4.5 ip_frag_reasm()函數
- 3. ip_local_deliver_finish接口
-
- 3.1 實作邏輯
- 3.1 相關函數
-
- 3.1.1 ip_local_deliver_finish()
本篇文章主要介紹的内容為NF_IP_LOCAL_IN鈎子前後的處理邏輯。
1. NF_IP_LOCAL_IN這部分的作用
這部分的主要作用是:接收并處理發往本地的IP封包,由于這裡依然屬于IP層,處理的邏輯比較簡單:
- IP封包分片重組
- 原始套接字相關處理
- 将封包去除頭部後,傳遞給傳輸層處理
下面會這幾個功能做一個簡單的介紹。
2. ip_local_deliver接口
該函數的作用:
- IP封包的分片重組,函數接口為:
ip_defrag()
- 進入NF_IP_LOCAL_IN HOOK點
- 在HOOK點之後進入
ip_local_deliver_finish()
此函數的核心功能便是:傳說中的IP封包分片重組。
2.1 分片重組子產品~整體架構
2.2 分片重組時資料組織結構
2.3 分片封包重組完畢後的資料結構
2.4 相關函數
分片重組功能涉及的函數有:
函數名稱 | 作用 |
---|---|
ip_defrag | 分片重組封包核心處理函數,其他相關函數皆在此函數中直接或者間接調用 |
ip_find | 查找分片封包對應的隊列頭,如果沒有找到,則建立一個分片封包隊列頭部節點(描述資訊) |
ip_frag_create | 根據(sip,dip,id,proto)建立一個分片封包頭部節點并插入到隊列中 |
ip_frag_intern | 将建立的分片封包頭部節點插入到全局分片封包哈希表中 |
ip_frag_queue | 将分片放入插入相應分片隊列的合适位置(根據封包中的偏移量進行排序) |
ip_frag_reasm | 分片封包重組。(不容易了解!!!) |
ipq_put | 無人引用時,釋放分片封包頭部節點以及緩存的所有分片封包 |
ip_frag_destroy | 釋放分片封包頭部節點以及緩存的所有分片封包 |
ip_evictor | 當緩存的分片封包總大小超過設定的門檻值時,根據LRU釋放一部分緩存的分片封包空間 |
ipq_kill | 設定标記位,聲明此節點即将被釋放 |
下面對上表中的幾個重要函數做一個說明。可以結合上面的處理邏輯和數組組織結構一起更容易了解。
2.4.1 ip_local_deliver()函數
/*
* Deliver IP Packets to the higher protocol layers.
*/
int ip_local_deliver(struct sk_buff *skb)
{
/*
* Reassemble IP fragments.
*/
if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
skb = ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER);/*分片重組之後的封包*/
if (!skb)
return 0;
}
return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
2.4.2 ip_defrag()函數
此函數是分片重組的核心函數接口.
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. */
if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh)/*緩存的封包超過系統分片門檻值時釋放一部分緩存*/
ip_evictor();
dev = skb->dev;
/* Lookup (or create) queue header */
if ((qp = ip_find(iph, user)) != NULL) {/*查找分片節點,隻有出錯時才可能為null*/
struct sk_buff *ret = NULL;
spin_lock(&qp->lock);
ip_frag_queue(qp, skb);/*将此分片封包根據序号插入到分片隊列中*/
if (qp->last_in == (FIRST_IN|LAST_IN) &&
qp->meat == qp->len)
ret = ip_frag_reasm(qp, dev);/*分片封包重組*/
spin_unlock(&qp->lock);
ipq_put(qp, NULL);/**/
return ret;
}
IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
kfree_skb(skb);
return NULL;
}
2.4.3 ip_find()函數
/* 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 iphdr *iph, u32 user)
{
__u16 id = iph->id;
__u32 saddr = iph->saddr;
__u32 daddr = iph->daddr;
__u8 protocol = iph->protocol;
unsigned int hash = ipqhashfn(id, saddr, daddr, protocol);
struct ipq *qp;
read_lock(&ipfrag_lock);
for(qp = ipq_hash[hash]; qp; qp = qp->next) {
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);
return ip_frag_create(hash, iph, user);/*未緩存過此類分片封包,則建立分片緩存*/
}
2.4.4 ip_frag_queue()函數
/* Add new segment to existing queue. */
static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)/*每一個分片封包一個隊列,在隊列中根據偏移進行排序*/
{
struct sk_buff *prev, *next;
int flags, offset;
int ihl, end;
if (qp->last_in & COMPLETE)/*此qp即将被釋放*/
goto err;
offset = ntohs(skb->nh.iph->frag_off);
flags = offset & ~IP_OFFSET;
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.此段已損壞
*/
if (end < qp->len ||
((qp->last_in & LAST_IN) && end != qp->len))/*格式檢查*/
goto err;
qp->last_in |= LAST_IN;/*分片全部收到标記位*/
qp->len = end;/*所有的分片封包總長度*/
} else {
if (end&7) {/*必須是8的倍數??? why*/
end &= ~7;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
if (end > qp->len) {/*qp->len可以當做是一個滑動視窗*/
/* Some bits beyond end -> corruption. */
if (qp->last_in & LAST_IN)/*不是最後一個封包*/
goto err;
qp->len = end;
}
}
if (end == offset)
goto err;
if (pskb_pull(skb, ihl) == NULL)
goto err;
if (pskb_trim(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?
*/
prev = NULL;
for(next = qp->fragments; next != NULL; next = next->next) {/*為分片封包在隊列中找到合适的位置*/
if (FRAG_CB(next)->offset >= offset)/*1、2、3、4、5、6、8、9*/
break; /* bingo! */ /*------------------7-----*/
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) {
int i = (FRAG_CB(prev)->offset + prev->len) - offset;
/*i<0時說明存在空洞,如何處理呢?*/
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 fragmnet 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. */
skb->next = next;
if (prev)
prev->next = skb;
else
qp->fragments = skb;
if (skb->dev)
qp->iif = skb->dev->ifindex;
skb->dev = NULL;
qp->stamp = skb->stamp;/*更新時間戳*/
qp->meat += skb->len;
atomic_add(skb->truesize, &ip_frag_mem);
if (offset == 0)
qp->last_in |= FIRST_IN;
write_lock(&ipfrag_lock);
list_move_tail(&qp->lru_list, &ipq_lru_list);/*維護LRU隊列,将該節點移到末尾*/
write_unlock(&ipfrag_lock);
return;
err:
kfree_skb(skb);
}
2.4.5 ip_frag_reasm()函數
/* Build a new IP datagram from all its fragments. */
/*分片封包重組*/
/*
*需要特别說明的是:這個函數并沒有直接将多個分片封包重組成一個大封包
*而是利用了
*/
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_kill(qp);/*設定标記位,表明此分片封包空間即将被釋放(無需區分空間不足還是接收完畢)*/
BUG_TRAP(head != NULL);
BUG_TRAP(FRAG_CB(head)->offset == 0);
/* Allocate a new buffer for the datagram. */
ihlen = head->nh.iph->ihl*4;
len = ihlen + qp->len;/*分片封包總長度+一個IP頭部長度*/
if(len > 65535)/*封包最長為65535*/
goto out_oversize;
/* Head of list must not be cloned. */
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. */
if (skb_shinfo(head)->frag_list) {/*如果第一個分片封包的skb不是連續的,需要特别處理*/
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_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_HW)
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;
head->stamp = qp->stamp;/*時間戳,為最後一個封包的時間戳*/
iph = head->nh.iph;
iph->frag_off = 0;
iph->tot_len = htons(len);
IP_INC_STATS_BH(IPSTATS_MIB_REASMOKS);
qp->fragments = NULL;/*這裡指針已經釋放了!!!!!!也就是說分片重組的封包在這裡并沒有被釋放,而是在其他地方釋放*/
return head;
out_nomem:
NETDEBUG(if (net_ratelimit())
printk(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;
}
3. ip_local_deliver_finish接口
此函數的主要作用包括:
- IP層封包即将交由傳輸層進行處理,是以先将IP頭部去除;
- 檢查是否存在原始套接字監控此協定封包,如果有,則複制一份skb,交由所有監控此協定的原始套接字(交給原始套接字處理前需要再添加上IP頭部);
- 傳遞給相應的傳輸層協定進行後續處理;
如果不考慮傳輸層封包解析處理,則涉及的函數比較少;其中比較多的就是原始套接字相關的函數接口,這部分尚未整理學習,這裡不再詳細說明。我們隻需要知道的是:在這裡有一個原始套接字處理的過程即可。
3.1 實作邏輯
- 關于“為什麼在傳輸層不支援此協定時,需要再次判斷是否有原始套接字監控??而隻有在沒有被監聽的情況下才能提示“協定不可達”呢???”
就目前我所知道的而言,有一部分封包不需要無法通過監聽固定端口來接收封包,如OSPF協定的hello封包,沒有對應的傳輸層端口。處理這類封包的方式通常是:在應用層通過原始套接字進行擷取,是以這裡在處理時有這麼一個邏輯也就在情理之中了;畢竟該封包已經被正确的接收,隻不過接收的對象比較特殊而已,是以這種情況也不能提示“協定不可達”。
3.1 相關函數
這裡隻介紹
ip_local_deliver_finish()
函數。
3.1.1 ip_local_deliver_finish()
static inline int ip_local_deliver_finish(struct sk_buff *skb)/*分片重組後的封包*/
{
int ihl = skb->nh.iph->ihl*4;
#ifdef CONFIG_NETFILTER_DEBUG
nf_debug_ip_local_deliver(skb);
#endif /*CONFIG_NETFILTER_DEBUG*/
__skb_pull(skb, ihl);/*去ip頭*/
/* Free reference early: we don't need it any more, and it may
hold ip_conntrack module loaded indefinitely. */
nf_reset(skb);
/* Point into the IP datagram, just past the header. */
skb->h.raw = skb->data;/*IP資料部分*/
rcu_read_lock();
{
/* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
int protocol = skb->nh.iph->protocol;/*擷取四層協定*/
int hash;
struct sock *raw_sk;
struct net_protocol *ipprot;
resubmit:
hash = protocol & (MAX_INET_PROTOS - 1);/*以協定作為hash檢索原始套接字和傳輸層處理接口*/
raw_sk = sk_head(&raw_v4_htable[hash]);/*擷取原始套接字連結清單中的第一個sock結構*/
/* If there maybe a raw socket we must check - if not we
* don't care less
*/
if (raw_sk)/*有原始套接字在監控此協定封包,則複制一份skb給原始套接字*/
raw_v4_input(skb, skb->nh.iph, hash);
if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {/*擷取傳輸層協定操作結構體*/
int ret;
if (!ipprot->no_policy &&
!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
goto out;
}
/*交由傳輸層繼續處理此封包,注意這裡ip頭部已經去除*/
ret = ipprot->handler(skb);/*常見的有UDP,TCP, ICMP,ESP, AH, IGMP, ... ...*/
if (ret < 0) {
protocol = -ret;
goto resubmit;
}
IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
} else {
/*這裡為何在沒有原始套接字時才提示不可達呢?
*因為有一部分封包隻能通過原始套接字接收,如ospf協定的hello封包,沒有接收接口資訊
*有的直接通過原始套接字将hello封包抓取到應用層進行處理。這裡應該處理的是這種情況!!!
**/
if (!raw_sk) {/*此協定不識别,又無原始套接字在監控,傳回協定不可達(好像是協定不可達)*/
if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
icmp_send(skb, ICMP_DEST_UNREACH,
ICMP_PROT_UNREACH, 0);
}
} else
IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
kfree_skb(skb);
}
}
out:
rcu_read_unlock();
return 0;
}