網絡收包流程-網絡層處理流程ip_rcv(五)

        報文提交給內核協議棧處理後,最終會調用到__netif_receive_skb_core函數,如果報文沒有被網橋處理函數rx_handler消費掉,最終會交給ptype_base中註冊的協議處理,包括內核註冊的協議,也包括raw socket等創建的協議處理。本文將分析普通ipv4    報文的處理過程,處理入口函數爲ip_rcv函數。
主要調用流程:ip_rcv-->ip_rcv_finish-->ip_local_deliver-->ip_local_deliver_finish
分組向上穿過內核的路線如圖所示:

主要作用:
(1)類型爲ETH_P_IP類型的數據包,被傳遞到三層,調用ip_rcv函數
  (2)  ip_rcv完成基本的校驗( 主要檢查計算的校驗和與首部中存儲的校驗和是否一致)和處理工作後,經過PRE_ROUTING鉤子點
  (3) 經過PRE_ROUTING鉤子點之後,調用ip_rcv_finish完成數據包接收,包括選項處理,路由查詢,並且根據路由決定數據包是發往本機還是轉發

1.ip_rcv函數

/*
 *      Main IP Receive routine.
 */
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
        const struct iphdr *iph;
        u32 len;

        /* When the interface is in promisc. mode, drop all the crap
         * that it receives, do not try to analyse it.
         */
        if (skb->pkt_type == PACKET_OTHERHOST)//丟棄掉不是發往本機的報文,網卡開啓混雜模式會收到此類報文
                goto drop;


        IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);

        if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {//檢查是否skb爲share,是 則克隆報文
                IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
                goto out;
        }

        if (!pskb_may_pull(skb, sizeof(struct iphdr)))//確保skb還可以容納標準的報頭(即20字節)
                goto inhdr_error;

        iph = ip_hdr(skb);//得到IP頭

        /*
         *      RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
         *
         *      Is the datagram acceptable?
         *
         *      1.      Length at least the size of an ip header
         *      2.      Version of 4
         *      3.      Checksums correctly. [Speed optimisation for later, skip loopback checksums]
         *      4.      Doesn't have a bogus length
         */

        if (iph->ihl < 5 || iph->version != 4)//ip頭長度至少爲20字節(ihl>=5,後面計算頭長度會乘4),只支持v4
                goto inhdr_error;

        BUILD_BUG_ON(IPSTATS_MIB_ECT1PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_1);
        BUILD_BUG_ON(IPSTATS_MIB_ECT0PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_0);
        BUILD_BUG_ON(IPSTATS_MIB_CEPKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_CE);
        IP_ADD_STATS_BH(dev_net(dev),
                        IPSTATS_MIB_NOECTPKTS + (iph->tos & INET_ECN_MASK),
                        max_t(unsigned short, 1, skb_shinfo(skb)->gso_segs));

        if (!pskb_may_pull(skb, iph->ihl*4))//確保skb還可以容納實際的報頭(ihl*4)
                goto inhdr_error;

        iph = ip_hdr(skb);

        if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))//ip頭csum校驗
                goto csum_error;

        len = ntohs(iph->tot_len);//獲取ip分組總長,即ip首部加數據的長度
        if (skb->len < len) {//skb的實際總長度小於ip分組總長,則drop
                IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
                goto drop;
        } else if (len < (iph->ihl*4))//ip頭記錄的分組長度就大於數據總長,則出錯
                goto inhdr_error;

        /* Our transport medium may have padded the buffer out. Now we know it
         * is IP we can trim to the true length of the frame.
         * Note this now means skb->len holds ntohs(iph->tot_len).
         */
        if (pskb_trim_rcsum(skb, len)) {//去除多餘的字節
                IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
                goto drop;
        } else if (len < (iph->ihl*4))
                goto inhdr_error;

        /* Our transport medium may have padded the buffer out. Now we know it
         * is IP we can trim to the true length of the frame.
         * Note this now means skb->len holds ntohs(iph->tot_len).
         */
        if (pskb_trim_rcsum(skb, len)) {
                IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
                goto drop;
        }

        skb->transport_header = skb->network_header + iph->ihl*4;//設置傳輸層header

        /* Remove any debris in the socket control block */
        memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));//清空cb,即inet_skb_parm值

        /* Must drop socket now because of tproxy. */
        skb_orphan(skb);
		//調用netfilter,實現iptables功能,通過後調用ip_rcv_finish
        return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, NULL, skb,
                       dev, NULL,
                       ip_rcv_finish);

csum_error:
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_CSUMERRORS);
inhdr_error:
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
drop:
        kfree_skb(skb);
out:
        return NET_RX_DROP;
}

2.ip_rcv_finish函數
作用:
1)、確定數據包是轉發還是在本機協議棧上傳,如果是轉發要確定輸出網絡設備和下一個接受棧的地址。
2)、解析和處理部分IP選項。


static int ip_rcv_finish(struct sock *sk, struct sk_buff *skb)
{
        const struct iphdr *iph = ip_hdr(skb);
        struct rtable *rt;
        int err;

        /* if ingress device is enslaved to an L3 master device pass the
         * skb to its handler for processing
         */
        skb = l3mdev_ip_rcv(skb);
        if (!skb)
                return NET_RX_SUCCESS;

        if (sysctl_ip_early_demux && !skb_dst(skb) && skb->sk == NULL) {
                const struct net_protocol *ipprot;
                int protocol = iph->protocol;//得到傳輸層協議
			/* 找到early_demux函數,如是tcp協議就調用,tcp_v4_early_demux */
                ipprot = rcu_dereference(inet_protos[protocol]);
                if (ipprot && ipprot->early_demux) {//對於socket報文,可以通過socket快速獲取路由表
                        err = ipprot->early_demux(skb);/* 調用該函數,將路由信息緩存到_skb->refdst */

	      if (unlikely(err))
                                goto drop_error;
                        /* must reload iph, skb->head might have changed */
                        iph = ip_hdr(skb);//重新獲取ip頭
                }
        }
	
        /*
         *      Initialise the virtual path cache for the packet. It describes
         *      how the packet travels inside Linux networking.
         */
		/* 1.  爲數據包初始化虛擬路徑緩存,它描述了數據包是如何在linux網絡中傳播的 ;
		    2.  通常從外界接收的數據包,skb->dst不會包含路由信息,暫時還不知道在何處會設置這個字段;
		    3.  skb->dst該數據域包含了如何到達目的地址的路由信息,如果該數據域是NULL,就通過路由子系統函數ip_route_input_noref路由,ip_route_input_noref的輸入參數有源IP地址、目的IP地址、服務類型、接受數據包的網絡設備,根據這5個參數決策路由。*/
        if (!skb_valid_dst(skb)) {
			// 路由查詢,決定後續處理:向上傳遞( ip_local_deliver)、轉發(ip_forward)、丟棄
                err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
                                           iph->tos, skb->dev);
                if (unlikely(err))
                        goto drop_error;
        }

#ifdef CONFIG_IP_ROUTE_CLASSID
        if (unlikely(skb_dst(skb)->tclassid)) {
                struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);
                u32 idx = skb_dst(skb)->tclassid;
                st[idx&0xFF].o_packets++;//更新接收數據包數量
                st[idx&0xFF].o_bytes += skb->len;//更新接收數據包的長度
                st[(idx>>16)&0xFF].i_packets++;
                st[(idx>>16)&0xFF].i_bytes += skb->len;
        }
#endif

        if (iph->ihl > 5 && ip_rcv_options(skb))
                goto drop;

        rt = skb_rtable(skb);//得到路由表項,統計組播和廣播報文
        if (rt->rt_type == RTN_MULTICAST) {
                IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST,
                             skb->len);
        } else if (rt->rt_type == RTN_BROADCAST)
                IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST,
                                skb->len);

        return dst_input(skb);/*ip_rcv_finish的結束是調用了dst_input,實際是調用存放在skb->dst->input的數據域。該函數確定了下一步對數據包的處理,根據數據包的目的地地址,skb->dst->input字段的信息主要由路由處理流程確定,可能是往本地協議棧上傳就調用 ip_local_deliver,如果是轉發就調用ip_forward */

drop:
        kfree_skb(skb);
        return NET_RX_DROP;

drop_error:
        if (err == -EXDEV)
                NET_INC_STATS_BH(dev_net(skb->dev), LINUX_MIB_IPRPFILTER);
        goto drop;
}

 

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