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


網絡子係統15_arp鄰居項初始化

//	初始化struct neighbour
//		當鄰居子係統新創一個neighbour時,鄰居子係統調用特定協議的初始化函數,初始化鄰居項。
//	調用路徑:neigh_create->arp_constructor
//	函數主要任務:
//		1.設置鄰居項的地址類型,a,b,c,d
//		2.使用與此鄰居項關聯的接口設備的neigh_param作為該鄰居項調整鄰居協議的參數。
//		3.根據與此鄰居項關聯的接口設備的信息,初始化鄰居項的狀態,以及ops
//			3.1 驅動沒有提供填充l2幀頭的函數,表明此接口不需要進行地址解析
//				3.1.1 設置鄰居項nud_state=NUD_NOARP,ops=arp_direct_ops
//			3.2 否則,根據鄰居l3地址類型,設置鄰居項狀態
//				3.2.1 多播地址,廣播地址,nud_state=NUD_NOARP
//				3.2.2 設備dev->flags表明無需進行地址解析,nud_state=NUD_NOARP
//				3.2.3 設備dev->flags表明回環設備,nud_state=NUD_NOARP
//			3.3 如果設備驅動提供l2幀頭緩存的能力:
//				3.3.1 使用arp_hh_ops
//			3.4 否則使用最通用的arp_generic_ops

//	注:根據鄰居項與本機的連接情況,為neighbour設置最合適操作函數
//		1.當鄰居項與本機之連時,無需arp解析,使用arp_direct_ops
//		2.當鄰居項需要進行地址解析時,而且到達此鄰居項的接口設備,提供l2幀頭緩存的能力,
//		則使用優化過的arp_hh_ops
		3.當1,2 均不滿足時,使用可以適用於一切情況下的arp_generic_ops

2.2 static int arp_constructor(struct neighbour *neigh)
{
	u32 addr = *(u32*)neigh->primary_key;//neighbour的key值,為ip地址
	struct net_device *dev = neigh->dev;//到達此鄰居通過的設備接口
	struct in_device *in_dev;//ipv4配置信息
	struct neigh_parms *parms;//調整鄰居協議的參數

	//a,b,c,d類地址
	neigh->type = inet_addr_type(addr);

	rcu_read_lock();
	//獲得設備的ipv4配置信息
	in_dev = rcu_dereference(__in_dev_get(dev));
	if (in_dev == NULL) {
		rcu_read_unlock();
		return -EINVAL;
	}
	//獲取此設備調整arp協議的參數控製塊
	parms = in_dev->arp_parms;
	//遞減引用計數
	__neigh_parms_put(neigh->parms);
	//克隆一份設備使用的控製參數,添加到鄰居項中
	neigh->parms = neigh_parms_clone(parms);
	rcu_read_unlock();
	//設備驅動沒有提供填充mac頭的回調函數
	if (dev->hard_header == NULL) {
		//設置當前鄰居項的狀態為NUD_NOARP,說明不需要進行地址解析
		neigh->nud_state = NUD_NOARP;
		//使用直連操作回調函數
		neigh->ops = &arp_direct_ops;
		//通過該鄰居項的輸出函數初始化為arp_direct_ops->queue_xmit,為dev_queue_xmit
		neigh->output = neigh->ops->queue_xmit;
	} else {
		//否則,需要進行地址解析

		//如果鄰居的地址類型為多播地址,則不需要進行地址解析
		if (neigh->type == RTN_MULTICAST) {
			neigh->nud_state = NUD_NOARP;
			//初始化多播地址鏈表
			arp_mc_map(addr, neigh->ha, dev, 1);
			//如果設備指示不能支持地址解析,或者設備為回環設備
		} else if (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) {
			//則不需要進行地址解析
			neigh->nud_state = NUD_NOARP;
			//鄰居的mac地址為dev的設備mac地址
			memcpy(neigh->ha, dev->dev_addr, dev->addr_len);
			//如果鄰居為廣播地址,或者設備為點到點連接
		} else if (neigh->type == RTN_BROADCAST || dev->flags&IFF_POINTOPOINT) {
			//則不需要進行地址解析
			neigh->nud_state = NUD_NOARP;
			//鄰居的mac地址為dev的設備的廣播地址
			memcpy(neigh->ha, dev->broadcast, dev->addr_len);
		}
		//設備提供了mac頭緩存回調函數
		if (dev->hard_header_cache)
			neigh->ops = &arp_hh_ops;//初始化為arp_hh_ops
		else
			neigh->ops = &arp_generic_ops;//負責初始化為通用例程
		if (neigh->nud_state&NUD_VALID)//如果當前鄰居狀態為有效狀態
			neigh->output = neigh->ops->connected_output;//則初始化鄰居的輸出函數為連接狀態下的輸出回調函數
		else
			neigh->output = neigh->ops->output;
	}
	return 0;
}




//	最通用的鄰居項操作
2.1 static struct neigh_ops arp_generic_ops = {
	.family =		AF_INET,//地址族
	.solicit =		arp_solicit,//協議提供,用於發送solicitation請求的回調函數
	.error_report =		arp_error_report,//當一個arp事物中發生錯誤時,arp_error_report函數就通知上層的網絡層
	.output =		neigh_resolve_output,//可用於所有情況下,它會檢查地址是否已經被解析過,在沒有被解析的情況下,啟動解析程序,如果地址還沒有準備好,
	//則會把封包保存在arp_queue中,並啟動解析程序。該函數為了保證接收方可到達,做好每一件必要的操作
	.connected_output =	neigh_connected_output,//當已經知道鄰居是可到達時,(NUD_CONNECTED態),使用該函數
	.hh_output =		dev_queue_xmit,//當地址已經解析過,並且整個封包頭已經根據上一次傳輸結果放入幀頭緩存時,就使用該函數
	.queue_xmit =		dev_queue_xmit,//之前的所有函數,除了hh_output函數外,都不會實際傳輸封包,他們所做的工作就是確保封包幀頭是編寫好的,然後當幀頭
	//緩存準備好時,調用queue_xmit執行傳輸。
};

//	驅動程序提供l2幀頭緩存功能,arp_hh_ops提高性能
3.1 static struct neigh_ops arp_hh_ops = {
	.family =		AF_INET,
	.solicit =		arp_solicit,
	.error_report =		arp_error_report,
	.output =		neigh_resolve_output,
	.connected_output =	neigh_resolve_output,
	.hh_output =		dev_queue_xmit,
	.queue_xmit =		dev_queue_xmit,
};

//	不需要進行地址解析時采用的操作
4.1 static struct neigh_ops arp_direct_ops = {
	.family =		AF_INET,
	.output =		dev_queue_xmit,//直接啟動設備的傳輸
	.connected_output =	dev_queue_xmit,
	.hh_output =		dev_queue_xmit,
	.queue_xmit =		dev_queue_xmit,
};


//	發送solicitation請求
5.1 static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb)
{
	u32 saddr = 0;
	u8  *dst_ha = NULL;
	struct net_device *dev = neigh->dev;
	u32 target = *(u32*)neigh->primary_key;
	int probes = atomic_read(&neigh->probes);//失敗的solicitation嚐試的次數
	//獲取設備上的ipv4配置信息
	struct in_device *in_dev = in_dev_get(dev);

	if (!in_dev)
		return;
	//當發送arp請求的主機有多個ip地址時,ANNOUNCE這個選項控製哪個地址應該放到solicitation請求的ARP頭中。
	switch (IN_DEV_ARP_ANNOUNCE(in_dev)) {
	default:
	case 0:	//任何本地ip都可以	
		//源地址為本機地址
		if (skb && inet_addr_type(skb->nh.iph->saddr) == RTN_LOCAL)
			saddr = skb->nh.iph->saddr;
		break;
	case 1:	//如果可能,選擇和目的地址位於同一子網內的地址	
		if (!skb)
			break;
		saddr = skb->nh.iph->saddr;
		if (inet_addr_type(saddr) == RTN_LOCAL) {
			//判斷skb中的源地址與目標地址是否在同一子網內
			if (inet_addr_onlink(in_dev, target, saddr))
				break;
		}
		saddr = 0;
		break;
	case 2:	//優先使用主地址
		break;
	}
	//遞減ipv4配置信息的引用
	if (in_dev)
		in_dev_put(in_dev);
	//說明需要優先使用主地址
	if (!saddr)
		saddr = inet_select_addr(dev, target, RT_SCOPE_LINK);
	//當前鄰居已經消耗盡證實一個地址可到達性測試的探測次數
	if ((probes -= neigh->parms->ucast_probes) < 0) {
		//當前狀態非VALID
		if (!(neigh->nud_state&NUD_VALID))
			printk(KERN_DEBUG "trying to ucast probe in NUD_INVALID\n");
		dst_ha = neigh->ha;//與primary_key表示的l3地址關聯的l2地址
		read_lock_bh(&neigh->lock);
	} else if ((probes -= neigh->parms->app_probes) < 0) {
#ifdef CONFIG_ARPD
		//如果使用arpd,則喚醒用戶態進程
		neigh_app_ns(neigh);
#endif
		return;
	}
	//發送arp報文
	arp_send(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,
		 dst_ha, dev->dev_addr, NULL);
	if (dst_ha)
		read_unlock_bh(&neigh->lock);
}

//	通知上層網絡協議arp事物錯誤
5.2 static void arp_error_report(struct neighbour *neigh, struct sk_buff *skb)
{
	//向路由子係統的路由項緩存通知鏈路失效
	dst_link_failure(skb);
	//釋放當前skb
	kfree_skb(skb);
}

//	可用於所有情況下的skb發送
5.3 int neigh_resolve_output(struct sk_buff *skb)
{
	//skb對應的路由項緩存
	struct dst_entry *dst = skb->dst;
	struct neighbour *neigh;
	int rc = 0;
	//skb沒有關聯的路由緩存或者路由緩存沒有有效的鄰居
	if (!dst || !(neigh = dst->neighbour))
		goto discard;
	//將skb->data移動到l3協議頭處
	__skb_pull(skb, skb->nh.raw - skb->data);
	//調整鄰居的狀態機,啟動定時器,並且skb沒有接管
	if (!neigh_event_send(neigh, skb)) {
		int err;
		struct net_device *dev = neigh->dev;
		//如果驅動程序提供了l2幀頭緩存,並且路由緩存沒有關聯的l2頭緩存
		if (dev->hard_header_cache && !dst->hh) {
			write_lock_bh(&neigh->lock);
			//路由項緩存沒有l2頭緩存
			if (!dst->hh)
				neigh_hh_init(neigh, dst, dst->ops->protocol);//初始化dst的l2幀頭緩存
			//填充skb的l2頭
			err = dev->hard_header(skb, dev, ntohs(skb->protocol),
					       neigh->ha, NULL, skb->len);
			write_unlock_bh(&neigh->lock);
		} else {
			read_lock_bh(&neigh->lock);
			//驅動沒有提供幀頭緩存,調用驅動提供回調函數,填充skb的l2幀頭
			//此時dst依然沒有對應的l2幀頭緩存
			err = dev->hard_header(skb, dev, ntohs(skb->protocol),
					       neigh->ha, NULL, skb->len);
			read_unlock_bh(&neigh->lock);
		}
		if (err >= 0)
		{
			//準備好l2頭之後,調用queue_xmit進行傳輸
			rc = neigh->ops->queue_xmit(skb);
		}
		else
			goto out_kfree_skb;
	}
out:
	return rc;
discard:
	NEIGH_PRINTK1("neigh_resolve_output: dst=%p neigh=%p\n",
		      dst, dst ? dst->neighbour : NULL);
out_kfree_skb:
	rc = -EINVAL;
	kfree_skb(skb);
	goto out;
}

//調用路徑neigh_resolve_output->neigh_event_send
5.4 static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{	
	//更新當前鄰居最近一次被使用的時間
	neigh->used = jiffies;
	//鄰居狀態已經開始處理solicitation
	if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
		return __neigh_event_send(neigh, skb);
	return 0;
}

//調用路徑neigh_resolve_output->neigh_event_send->__neigh_event_send
//在skb被該函數接管後,返回1,否則返回0
5.5 int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
	int rc;
	unsigned long now;

	write_lock_bh(&neigh->lock);

	rc = 0;
	//鄰居沒有未決的solicitation,或者已經開始執行solicitation
	if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
		goto out_unlock_bh;

	now = jiffies;
	//鄰居不需要可到底性確認,並且沒有待決的solicitation請求
	if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {
		//鄰居項配置參指示,仍然可以使用用戶空間解析地址,或者多播解析地址
		if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
			//設置鄰居項的試探次數為鄰居項調整參數的單播次數
			atomic_set(&neigh->probes, neigh->parms->ucast_probes);
			neigh->nud_state     = NUD_INCOMPLETE;//設置鄰居項有未決的solicitation請求
			neigh_hold(neigh);//增加引用計數
			neigh->timer.expires = now + 1;//調整到期時間為下一個jiffies
			add_timer(&neigh->timer);
		} else {
			neigh->nud_state = NUD_FAILED;//設置鄰居不可達
			write_unlock_bh(&neigh->lock);

			if (skb)
				kfree_skb(skb);
			return 1;
		}
	} else if (neigh->nud_state & NUD_STALE) {//鄰居項已經有一段時間沒有被確認過了
		NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);
		neigh_hold(neigh);//增加引用計數
		neigh->nud_state = NUD_DELAY;//鄰居項使用舊的l2地址,進入時間窗
		neigh->timer.expires = jiffies + neigh->parms->delay_probe_time;//設置時間窗的長度
		add_timer(&neigh->timer);//啟動定時器
	}

	if (neigh->nud_state == NUD_INCOMPLETE) {//如果已經發送了solicitation請求,但還沒有收到應答
		if (skb) {
			if (skb_queue_len(&neigh->arp_queue) >=
			    neigh->parms->queue_len) {
				struct sk_buff *buff;
				buff = neigh->arp_queue.next;
				__skb_unlink(buff, &neigh->arp_queue);
				kfree_skb(buff);
			}
			__skb_queue_tail(&neigh->arp_queue, skb);//將skb添加到arp_queue中,等待收到solicitation應答
		}
		rc = 1;
	}
out_unlock_bh:
	write_unlock_bh(&neigh->lock);
	return rc;
}

//使用hh_cache,盡快完成skb發送
5.5 int neigh_connected_output(struct sk_buff *skb)
{
	int err;
	struct dst_entry *dst = skb->dst;
	struct neighbour *neigh = dst->neighbour;
	struct net_device *dev = neigh->dev;
	//將skb->data調整到l3頭
	__skb_pull(skb, skb->nh.raw - skb->data);

	read_lock_bh(&neigh->lock);
	//填充skb的l2頭
	err = dev->hard_header(skb, dev, ntohs(skb->protocol),
			       neigh->ha, NULL, skb->len);
	read_unlock_bh(&neigh->lock);
	if (err >= 0)
		err = neigh->ops->queue_xmit(skb);//委托給ops->queue_xmit完成最後的傳輸
	else {
		err = -EINVAL;
		kfree_skb(skb);
	}
	return err;
}

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

  上一篇:go 網絡子係統7_l2、l3接口
  下一篇:go android 輕鬆實現在線即時聊天【圖片、語音、表情、文字】等!含源碼!