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


網絡子係統16_arp傳輸接收接口

//	鄰居子係統的配置屬性:
//	1.ARP_ANNOUNCE用於生成arp請求時,源ip的選擇 
//		1.1 內核中產生的請求,使用arp_send傳輸arp_solicitation請求
//  	1.2 用戶空間中產生的請求,arp_solicition調用neigh_app_ns來通知相應的用戶空間程序,需要生成一個solicitation

//	2.ARP_IGNORE 當ARP請求的目的地址為本機地址時,根據目的地地址與接收接口的關係,來決定是否應答
//	3.ARP_FILTER 當ARP請求的目的地址為本機地址時,處理一台主機有多個NIC連接到同一LAN,並且配置同一子網時,控製一個接口是對入口ARP應答。常見於虛擬服務器
//	4.medium-id  當ARP請求的目的地址非本機地址時,處理ip子網橫跨不同的lan,且該子網中提供arp請求的主機有多快nic,設置這些nic是否在同一lan

//	
//	發送arp消息
//		arp協議是ip->l2地址的轉換協議,可以運行在多種共享介質的l2鏈路類型
//	參數:
//		type 	: 	arp頭中的操作碼
//		ptype 	:	l2幀頭中填寫的l3協議類型

//	函數主要任務:
//		1.檢查是否需要進行地址解析
//		2.創建arp封包
//		3.發送數據包
1.1 void arp_send(int type, int ptype, u32 dest_ip, 
	      struct net_device *dev, u32 src_ip, 
	      unsigned char *dest_hw, unsigned char *src_hw,
	      unsigned char *target_hw)
{
	struct sk_buff *skb;

	//表明該接口不能執行arp	
	if (dev->flags&IFF_NOARP)
		return;
	//創建arp封包
	skb = arp_create(type, ptype, dest_ip, dev, src_ip,
			 dest_hw, src_hw, target_hw);
	if (skb == NULL) {
		return;
	}
	//傳輸出去
	arp_xmit(skb);
}

//	創建arp封包
//	調用路徑:
//		arp_send->arp_create
//	參數:
//		type  :		arp頭中的操作碼
//		ptype :		l2幀頭中填寫的l3協議類型

//		dest_ip, src_ip, target_hw, src_hw, arp協議中填寫的內容
//		dest_hw, l2幀頭填寫的目標地址

//		dev   :		出口設備

//	函數主要任務:
//		1.填充l2幀頭
//		2.填寫arp協議字段
1.2 struct sk_buff *arp_create(int type, int ptype, u32 dest_ip,
			   struct net_device *dev, u32 src_ip,
			   unsigned char *dest_hw, unsigned char *src_hw,
			   unsigned char *target_hw)
{
	struct sk_buff *skb;
	struct arphdr *arp;
	unsigned char *arp_ptr;

	//封包大小 = arp頭 + (arp源ip地址,源mac地址,目的ip地址,目的mac地址) + l2頭
	skb = alloc_skb(sizeof(struct arphdr)+ 2*(dev->addr_len+4)
				+ LL_RESERVED_SPACE(dev), GFP_ATOMIC);
	if (skb == NULL)
		return NULL;
	//移動skb->data,騰出mac的長度
	skb_reserve(skb, LL_RESERVED_SPACE(dev));//處理設備相關的l2鏈路類型
	//初始化l3頭指針
	skb->nh.raw = skb->data;
	//移動skb->tail到skb->data + arp報文長度
	arp = (struct arphdr *) skb_put(skb,sizeof(struct arphdr) + 2*(dev->addr_len+4));
	skb->dev = dev;//設置出口設備
	skb->protocol = htons(ETH_P_ARP);//skb的協議類型
	if (src_hw == NULL)
		src_hw = dev->dev_addr;//如果沒有提供源地址,則設置為出口設備的l2地址
	if (dest_hw == NULL)
		dest_hw = dev->broadcast;//如果沒有目的地址,則設置為出口設備的廣播地址
	//填充mac頭
	if (dev->hard_header &&
	    dev->hard_header(skb,dev,ptype,dest_hw,src_hw,skb->len) < 0)
		goto out;
	//填充協議字段
	switch (dev->type) {
	default:
		arp->ar_hrd = htons(dev->type);//arp頭的硬件類型
		arp->ar_pro = htons(ETH_P_IP);//arp頭的協議類型,ip協議
		break;
		....

	}

	arp->ar_hln = dev->addr_len;//硬件地址長度
	arp->ar_pln = 4;//協議地址長度
	arp->ar_op = htons(type);//操作碼

	arp_ptr=(unsigned char *)(arp+1);//arp_ptr指向協議頭後的第一個字節

	memcpy(arp_ptr, src_hw, dev->addr_len);//拷貝出口設備地址到源地址
	arp_ptr+=dev->addr_len;//移動指針
	memcpy(arp_ptr, &src_ip,4);//拷貝源ip到源ip
	arp_ptr+=4;
	//拷貝目地l2地址
	if (target_hw != NULL)
		memcpy(arp_ptr, target_hw, dev->addr_len);
	else
		memset(arp_ptr, 0, dev->addr_len);
	arp_ptr+=dev->addr_len;
	memcpy(arp_ptr, &dest_ip, 4);

	return skb;

out:
	kfree_skb(skb);
	return NULL;
}

//	發送arp封包
//		銜接netfilter處理點NF_ARP_OUT
//	調用路徑: arp_send->arp_xmit
1.3 void arp_xmit(struct sk_buff *skb)
{
	//通過NETFILTER HOOK,最後由dev_queue_xmit傳輸出去
	NF_HOOK(NF_ARP, NF_ARP_OUT, skb, NULL, skb->dev, dev_queue_xmit);
}

//	arp協議接收例程
//		保存在ptype_base的hash表中,在netif_receive_skb中,根據l2幀頭中指定的l3協議號,調用此函數
//	調用路徑:netif_receive_skb->arp_rcv

//	函數主要任務:
//		1.報文合理性檢查:
//			1.1 在不支持arp的設備上,收到了arp報文,丟掉
//			1.2 非本機l2地址,如果開啟網橋功能,則由橋接處理
//			1.3 l2地址為回環地址
//		2.由於arp協議可能會修改sk_buff,如果skb被其他子係統引用,則拷貝一份
//		3.銜接netfilter處理點NF_ARP_IN

//	注:arp報文的l2地址,可以為廣播地址或l2多播地址,用於解析發送者自己的l2地址			
2.1 int arp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
	struct arphdr *arp;

	//使skb->data-tail足夠一個完整的arp報文
	if (!pskb_may_pull(skb, (sizeof(struct arphdr) +
				 (2 * dev->addr_len) +
				 (2 * sizeof(u32)))))
		goto freeskb;

	arp = skb->nh.arph;
	//報文有效性檢查:
	//	1.在不支持arp的設備上,收到了arp報文,丟掉
	//	2.非本機l2地址,如果開啟網橋功能,則由橋接處理
	//	3.l2地址為回環地址,l2地址可以為廣播地址或l2多播地址
	if (arp->ar_hln != dev->addr_len ||
	    dev->flags & IFF_NOARP |
	    skb->pkt_type == PACKET_OTHERHOST ||
	    skb->pkt_type == PACKET_LOOPBACK ||//回環
	    arp->ar_pln != 4)
		goto freeskb;
	//如果報文引用計數!=1,拷貝一份
	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
		goto out_of_mem;
	//NETFILTER HOOK,在執行實際的arp處理之前,執行HOOK調用
	return NF_HOOK(NF_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);

freeskb:
	kfree_skb(skb);
out_of_mem:
	return 0;
}

//	處理arp報文
//	調用路徑:arp_rcv->netfilter->arp_process
//	函數主要任務:
//		1.入口設備需要配置有ipv4信息
//		2.arp合理性檢查:
//			2.1 arp隻處理l3協議為ip的地址解析
//			2.2 arp隻處理l2協議為以太網,IEEE802的地址解析
//			2.3 arp報文請求解析的目標ip地址不能為多播地址,或回環地址地址
//		3.入口設備的l2類型應該與arp報文中指定的l2類型相同
//		4.arp操作合法性檢查:
//			4.1 隻處理arp_request
//			4.2 隻處理arp_reply
//		5.處理重複地址檢查:
//			5.1 如果arp報文中沒有指定源ip地址,則此arp報文用於重複地址檢查
//			5.2 應答重複地址檢查的條件:
//				5.2.1 目的ip地址為本機
//				5.2.2 arp ignore配置為可可應答
//			5.2 使用源ip,目的ip均為arp報文中指定的目的ip進行應答,然後退出
//		6.目標ip為本機的arp request
//			6.1 判斷目標ip為本機的辦法:
//					通過路由子係統路由該目的地址,可達,且路由項指出路由類型為本機
//			6.2 被動學習,接收到arp request觸發創建一個新鄰居項
//			6.3 根據arp ignore,arp filter 決定是否進行應答
//			6.4 應答此arp, 然後退出
//		7.目的ip非本機,處理arp代理
//		8.更新鄰居項:
//			8.1 存在對應的鄰居項
//			8.2 如果為arp應答,且目的ip為本機,則說明鄰居項可達,更新鄰居項狀態為REACHABLE
//			8.3 否則更新鄰居狀態為STALE, 表明鄰居項有可用的l2地址,但是可可到達性需要確認。

//		注:arp ignore為全局配置,arp filter為接口配置,對代理的arp請求可以被延遲處理
2.2 static int arp_process(struct sk_buff *skb)
{
	struct net_device *dev = skb->dev;
	struct in_device *in_dev = in_dev_get(dev);
	struct arphdr *arp;
	unsigned char *arp_ptr;
	struct rtable *rt;
	unsigned char *sha, *tha;
	u32 sip, tip;
	u16 dev_type = dev->type;
	int addr_type;
	struct neighbour *n;
	//獲取輸入設備的ipv4配置信息
	if (in_dev == NULL)
		goto out;
	//skb的以太網頭,此指針由驅動程序移動跨過l2地址時,完成更新
	arp = skb->nh.arph;
	//設備類型
	switch (dev_type) {
	default:	
		if (arp->ar_pro != htons(ETH_P_IP) ||//arp隻處理ip協議
		    htons(dev_type) != arp->ar_hrd)//接收此skb的設備的設備類型,不是arp報文中指定的設備類型
			goto out;
		break;
		...

		if ((arp->ar_hrd != htons(ARPHRD_ETHER) &&//arp報文中指定的硬件地址類型不是以太網,或者IEEE802
		     arp->ar_hrd != htons(ARPHRD_IEEE802)) ||
		    arp->ar_pro != htons(ETH_P_IP))//非ip協議
			goto out;
		break;
		...
	}

	//處理的操作碼隻限定在ARP應答與請求
	if (arp->ar_op != htons(ARPOP_REPLY) &&
	    arp->ar_op != htons(ARPOP_REQUEST))
		goto out;
	//獲取源mac,源ip,目的mac,目的ip
	arp_ptr= (unsigned char *)(arp+1);
	sha	= arp_ptr;
	arp_ptr += dev->addr_len;
	memcpy(&sip, arp_ptr, 4);
	arp_ptr += 4;
	tha	= arp_ptr;
	arp_ptr += dev->addr_len;
	memcpy(&tip, arp_ptr, 4);
	//arp不處理回環地址與多播地址
	if (LOOPBACK(tip) || MULTICAST(tip))
		goto out;

	//如果設備類型為DLCI
	//源地址為設備的廣播地址ff:ff:ff:ff:ff:ff
	if (dev_type == ARPHRD_DLCI)
		sha = dev->broadcast;

	//源ip沒有指定,說明此arp請求用於重複地址檢查
	if (sip == 0) {
		//arp請求 && 目的ip為
		if (arp->ar_op == htons(ARPOP_REQUEST) &&
		    inet_addr_type(tip) == RTN_LOCAL &&//本機ip
		    !arp_ignore(in_dev,dev,sip,tip))//判斷是否忽略此arp
			arp_send(ARPOP_REPLY,ETH_P_ARP,tip,dev,tip,sha,dev->dev_addr,dev->dev_addr);//arp報文的源ip,目的ip均為arp請求中的目的ip
		goto out;
	}
	//ARP請求,處理無端arp,更新鄰居項
	if (arp->ar_op == htons(ARPOP_REQUEST) &&
	    ip_route_input(skb, tip, sip, 0, dev) == 0) {//路由目的地址與源地址

		rt = (struct rtable*)skb->dst;//獲取查詢的路由信息
		addr_type = rt->rt_type;

		if (addr_type == RTN_LOCAL) {//本機ip
			n = neigh_event_ns(&arp_tbl, sha, &sip, dev);//被動學習
			if (n) {//
				int dont_send = 0;

				if (!dont_send)
					dont_send |= arp_ignore(in_dev,dev,sip,tip);
				if (!dont_send && IN_DEV_ARPFILTER(in_dev))//ARP_FILTER為接口選項,隻能開啟或者關閉,設置該選項後,隻有在內核知道如何到達發送方的ip地址
					//並且隻有到達發送方ip地址的設備為接收此ARP_REQUEST的設備,才進行應答
					//該選項主要用於當一台主機有多個NIC連接到同一LAN,且配置在同一個IP子網上時,控製一個接口是否對arp請求進行應答
					dont_send |= arp_filter(sip,tip,dev); 
				if (!dont_send)//沒有過濾到此arp,則進行應答
					arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);

				neigh_release(n);
			}
			goto out;
		} else if (IN_DEV_FORWARD(in_dev)) {//非本機ip,但本地接口具有轉發功能
			if ((rt->rt_flags&RTCF_DNAT) ||//進一步檢查是否為代理
			    (addr_type == RTN_UNICAST  && rt->u.dst.dev != dev &&
			     (arp_fwd_proxy(in_dev, rt) || pneigh_lookup(&arp_tbl, &tip, dev, 0)))) {
				n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
				if (n)
					neigh_release(n);

				if (skb->stamp.tv_sec == LOCALLY_ENQUEUED || 
				    skb->pkt_type == PACKET_HOST ||
				    in_dev->arp_parms->proxy_delay == 0) {
					arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
				} else {
					pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);
					in_dev_put(in_dev);
					return 0;
				}
				goto out;
			}
		}
	}
	//存在鄰居項
	n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
	...
	//存在對應的鄰居項,此skb為鄰居可達性的證據,更新鄰居狀態
	if (n) {
		int state = NUD_REACHABLE;//更新鄰居項的可達性
		int override;
		//判斷距離上一次修改鄰居項的屬性是否已經經曆了n->parms->locktime這麼久
		override = time_after(jiffies, n->updated + n->parms->locktime);

		//非arp應答,或者封包目的地非本機
		if (arp->ar_op != htons(ARPOP_REPLY) ||
		    skb->pkt_type != PACKET_HOST)
			state = NUD_STALE;
		//更新鄰居項
		neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 0);
		neigh_release(n);
	}

out:
	if (in_dev)
		in_dev_put(in_dev);
	kfree_skb(skb);
	return 0;
}

//檢查是否忽略此arp
2.3 static int arp_ignore(struct in_device *in_dev, struct net_device *dev,
		      u32 sip, u32 tip)
{
	int scope;
	//查看此ipv4上配置的屬性
	switch (IN_DEV_ARP_IGNORE(in_dev)) {
	case 0:	//對任何本機的地址進行應答
		return 0;
	case 1://目的ip配置到接收報文的接口上,才應答
		sip = 0;
		scope = RT_SCOPE_HOST;//本地地址
		break;
	case 2:	
		scope = RT_SCOPE_HOST;//目的ip配置到接收報文的接口上,並且源ip與本機在同一子網
		break;
	case 3:	//不應答本子網地址
		sip = 0;
		scope = RT_SCOPE_LINK;
		dev = NULL;
		break;
	case 4:	//保留
	case 5:
	case 6:
	case 7:
		return 0;
	case 8://不應答
		return 1;
	default:
		return 0;
	}
	//確定tip是否屬於scope指定的範圍
	return !inet_confirm_addr(dev, sip, tip, scope);
}

最後更新:2017-04-03 15:22:03

  上一篇:go 2013-09-28
  下一篇:go objective-c下的消息機製