上一節說到當判斷該數據報文是發送給本機的話那麼就會直接到ip_local_deliver()
進行處理
ip_local_deliver
對於收到的IP報文需要傳遞給更上層的協議去處理,但是如果收到的是IP分片的那麼就需要在往上層傳遞之前先進行重組,這些就是在ip_local_deliver()
函數裏面進行的。我們看下代碼:
/*
* Deliver IP Packets to the higher protocol layers.
*/
int ip_local_deliver(struct sk_buff *skb)
{
/*
* Reassemble IP fragments.
*/
struct net *net = dev_net(skb->dev);
//check if it is a fragment
if (ip_is_fragment(ip_hdr(skb))) {
//fragment recombination
if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
return 0;
}
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
net, NULL, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
首先使用ip_hdr()獲取ip頭部, 然後使用ip_is_fragment()來判斷當前IP報文是否是一個分組:
static inline bool ip_is_fragment(const struct iphdr *iph)
{
//check the ip packet is a fragment or not, why check IP_OFFSET??
return (iph->frag_off & htons(IP_MF | IP_OFFSET)) != 0;
}
在上面ip頭部結構裏面我們看到有一個16位的frag_off,其中前三位是標誌位,後13位是偏移位置,(如果不是一個分組 MF和OFFSET都是0,如果是是最後一個分組那麼MF==0 但是OFFSET不爲0)。那如果判斷是一個分組的話那麼就會調用ip_defrag()進行重組,重組成功就直接返回了,不會調用到ip_local_deliver_finish()。當最後一個報文過來或者是非分組的報文過來的話,ip_is_fragment()判斷不通過直接就會進入ip_local_deliver_finish()。
下面我們就看下ip_defrag()是如何進行ip分片重組的:
/* Process an incoming IP datagram fragment. */
int ip_defrag(struct net *net, struct sk_buff *skb, u32 user)
{
struct net_device *dev = skb->dev ? : skb_dst(skb)->dev;
int vif = l3mdev_master_ifindex_rcu(dev);
struct ipq *qp;
__IP_INC_STATS(net, IPSTATS_MIB_REASMREQDS);
skb_orphan(skb);
/* Lookup (or create) queue header */
qp = ip_find(net, ip_hdr(skb), user, vif);
if (qp) {
int ret;
spin_lock(&qp->q.lock);
ret = ip_frag_queue(qp, skb);
spin_unlock(&qp->q.lock);
ipq_put(qp);
return ret;
}
__IP_INC_STATS(net, IPSTATS_MIB_REASMFAILS);
kfree_skb(skb);
return -ENOMEM;
}
EXPORT_SYMBOL(ip_defrag);
上面的這個函數首先調用ip_find()去查找是否已經在分組隊列裏存在相應的queue,如果沒有那麼就新建一個queue等待收集之後的報文,如果已經存在那麼就返回queue的入口。ip_find()主要是調用iphashfn(),使用ip頭部裏的identifier、source addr、dest addr、protocol四個元素去進行hash計算。而iphashfn()調用的是jash_3words(), 該函數是高性能的查找算法。之後就是調用inet_frag_find()函數進行查找。
ip_local_deliver_finish
這邊會通過skb頭部裏面的協議類型在inet_protos裏面查找處理該協議的結構指針,是net_protocol指針類型。然後調用net_protocol->handler(),我們這邊只是跟蹤tcp報文,因爲這邊就是調用的tcp_v4_rcv(),報文處理流程進入到Inet Socket層。
static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
__skb_pull(skb, skb_network_header_len(skb));
rcu_read_lock();
{
//get the protocol of this packet
int protocol = ip_hdr(skb)->protocol;
const struct net_protocol *ipprot;
.....
//from inet_protos list to get the correct protocol struct depending on protocol as index
ipprot = rcu_dereference(inet_protos[protocol]);
if(ipprot) {
...
ret = ipprot->handler(skb);//call the Lay4 handler function.
...
}
}
....
}
上面的代碼只是對於正常的報文的流程進行分析,另外我們看到在source code中如果找不到相關的協議處理函數會通過icmp_send()
發送一個ICMP_DEST_UNREACH
的消息。