TCP/IP協議--IP層ip_local_deliver實現

上一節說到當判斷該數據報文是發送給本機的話那麼就會直接到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的消息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章