閱讀533 返回首頁    go 阿裏雲 go 技術社區[雲棲]


網絡子係統42_ip協議數據幀的接收

//參考 深入理解linux網絡技術內幕

                                    

//	注冊l3協議
//		ptype_all鏈表,鏈接所有ETH_P_ALL類型的l3協議
//		ptype_base哈希表,非ETH_P_ALL類型的l3協議
//	注:l3協議可以使用相同的協議號

1.1 void dev_add_pack(struct packet_type *pt)
{
	int hash;
	//ptype_all ptype_base共用一把鎖 ptype_lock
	spin_lock_bh(&ptype_lock);
	//ETH_P_ALL類型的l3協議,從外接收到的數據幀,和從本地發送的數據幀,都會向ptype_all鏈表中的l3協議,傳遞一份
	if (pt->type == htons(ETH_P_ALL)) {
		netdev_nit++;
		list_add_rcu(&pt->list, &ptype_all);
	} else {
		hash = ntohs(pt->type) & 15;//ptype_base有16個bucket
		list_add_rcu(&pt->list, &ptype_base[hash]);//鏈接到bucket的鏈表中
	}
	spin_unlock_bh(&ptype_lock);
}


//	ip協議控製塊
//		每個l3協議,通過struct packet_type描述自己
1.2 static struct packet_type ip_packet_type = {
	.type = __constant_htons(ETH_P_IP),//.type使用網絡字節序,大端模式
	.func = ip_rcv,//ip接收函數
};


//	ip協議數據接收
//	調用路徑:netif_receive_skb->ip_rcv

//	函數主要任務:
//		1.使處理程序擁有獨立的skb
//		2.檢驗ip數據包的有效性
//		3.處理l2填充

//	ip數據包有效性:
//		1.報頭長度
//		2.協議號
//		3.ip校驗和
//		4.ip報文總長度

//	處理l2填充:
//		1.當skb中指示的報文總長度大於ip報頭指示的數據包總長度時,說明發生了l2填充
//		2.以ip報頭指示的數據包總長度為正確,去掉末尾多餘的填充數據,同時使l4校驗和失效	

1.3 int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
	struct iphdr *iph;
	//skb->pkt_type由接收設備的驅動程序,調用eth_type_trans設定
	//當設備處於混雜模式下,對於非本機,非廣播,非多播的數據幀,設置其pkt_type=PACKET_OTHERHOST
	//ip協議不處理l2地址非本機的封包,此類型的封包應該在ip協議之前被bonding,或者網橋來處理
	if (skb->pkt_type == PACKET_OTHERHOST)
		goto drop;

	//更新snmp統計信息
	IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);
	//如果skb引用計數不為1,則拷貝一份
	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
		IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
		goto out;
	}
	//使skb->data到skb->tail之間足夠20字節的ip頭(此時不包括選項字段)
	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
		goto inhdr_error;

	iph = skb->nh.iph;//ip頭

	//報文有效性檢查
	//	1.報頭長度
	//	2.協議號
	//	3.ip校驗和
	//	4.ip報文總長度
	if (iph->ihl < 5 || iph->version != 4)
		goto inhdr_error; 

	//ihl以4字節為單位,表示ip頭的長度
	if (!pskb_may_pull(skb, iph->ihl*4))
		goto inhdr_error;

	//對ip頭和選項(如果存在的話),計算校驗和
	if (ip_fast_csum((u8 *)iph, iph->ihl) != 0)
		goto inhdr_error; 

	{
		__u32 len = ntohs(iph->tot_len); //ip報文總長(包括ip報頭,選項,ip有效載荷)
		if (skb->len < len || len < (iph->ihl<<2))//skb中l3數據總長度小於接收到的長度,或者接收到的長度不足ip報頭和選項的長度
			goto inhdr_error;

		//處理l2填充:
		//	1.當skb中指示的報文總長度大於ip報頭指示的數據包總長度時,說明發生了l2填充
		//	2.以ip報頭指示的數據包總長度為正確,去掉末尾多餘的填充數據,同時使l4校驗和失效	
		if (skb->len > len) {
			__pskb_trim(skb, len);
			if (skb->ip_summed == CHECKSUM_HW)//如果校驗和已經由硬件求得,但是由於對數據進行了切割操作
				skb->ip_summed = CHECKSUM_NONE;//則指示需要網絡層通過軟件再進行校驗和計算
		}
	}

	return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
		       ip_rcv_finish);//經過netfilter鉤子函數的處理,最終由ip_rcv_finish完成處理


inhdr_error:
	IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
        kfree_skb(skb);
out:
        return NET_RX_DROP;
}


//	ip協議數據接收

//	函數主要任務:
//		1.路由數據包
//		2.如果ip報文存在選項,分析ip選項
//		3.處理源路由選項
//		4.根據路由子係統對skb->dst的設置,繼續處理skb

1.4 static inline int ip_rcv_finish(struct sk_buff *skb)
{
	struct net_device *dev = skb->dev;
	struct iphdr *iph = skb->nh.iph;


	if (skb->dst == NULL) {//目標路由未設置
		if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))//輸入路徑上的路由,設置skb->dst
			goto drop; 
	}
	...


	if (iph->ihl > 5) {//需要處理ip選項
		struct ip_options *opt;
		//當ip報頭的長度大於20字節,說明有選項需要處理,此時通過skb_cow,如果緩存區和別人共享,就會做出該緩衝區的副本,因為對緩存區具有排他擁有權
		if (skb_cow(skb, skb_headroom(skb))) {//是必要的,因為需要處理選項,就可能會修改ip報頭,複製緩存區時,保證副本預留16字節的l2頭
			IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
			goto drop;
		}
		iph = skb->nh.iph;
		//在報文接收路徑的多處,由不同函數對各個選項進行處理
		if (ip_options_compile(NULL, skb))//此處分析ip選項,初始化skb->cb中ip_options結構,為後邊處理ip選項做準備。
			goto inhdr_error;

		opt = &(IPCB(skb)->opt);
		if (opt->srr) {//ip_options_compile分析ip報文中的選項,設置opt->srr為源路由選項相對於ip頭的起始位置
			struct in_device *in_dev = in_dev_get(dev);
			if (in_dev) {
				if (!IN_DEV_SOURCE_ROUTE(in_dev)) {//入口設備的inet配置不支持源路由選項
					
					in_dev_put(in_dev);
					goto drop;//直接丟棄封包
				}
				in_dev_put(in_dev);
			}
			if (ip_options_rcv_srr(skb))//處理源路由選項
				goto drop;
		}
	}
	//skb->dst->input處理,input函數指針由ip_route_input,根據目標地址,進行設置。
	return dst_input(skb);


inhdr_error:
	IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
        kfree_skb(skb);
        return NET_RX_DROP;
}


最後更新:2017-04-03 14:53:37

  上一篇:go [曆年IT筆試題]2014微軟校園招聘筆試試題
  下一篇:go 瀏覽器小覽【歡迎討論】