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


網絡子係統34_網橋設備的傳輸與接收

//	網橋設備驅動程序的hard_start_xmit函數

//	函數主要任務:
//		1.廣播或多播地址,在所有端口上擴散
//		2.存在轉發項,在指定端口上發送
//		3.沒有找到轉發項,在所有端口上擴散
1.1 int br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
{
	struct net_bridge *br = netdev_priv(dev);
	const unsigned char *dest = skb->data;
	struct net_bridge_fdb_entry *dst;
	//更新網橋設備的統計信息
	br->statistics.tx_packets++;
	br->statistics.tx_bytes += skb->len;
	//更新l2頭位置
	skb->mac.raw = skb->data;
	skb_pull(skb, ETH_HLEN);

	rcu_read_lock();
	if (dest[0] & 1)//l2地址的第一個字節的第一位為1,則為多播地址 
		br_flood_deliver(br, skb, 0);//在所有端口上發送此報文
	else if ((dst = __br_fdb_get(br, dest)) != NULL)//如果轉發數據中存在目標地址的記錄
		br_deliver(dst->dst, skb);//通過與目標地址相關的端口發送
	else
		br_flood_deliver(br, skb, 0);

	rcu_read_unlock();
	return 0;
}

//	在所有端口擴散skb
//	參數:__packet_hook, __br_deliver

//	調用路徑:br_dev_xmit->br_flood_deliver->br_flood
1.3 static void br_flood(struct net_bridge *br, struct sk_buff *skb, int clone,
	void (*__packet_hook)(const struct net_bridge_port *p, 
			      struct sk_buff *skb))
{
	struct net_bridge_port *p;
	struct net_bridge_port *prev;
	//指示是否複製一份skb,br_dev_xmit->br_flood_deliver->br_flood此調用路徑clone=0
	if (clone) {
		struct sk_buff *skb2;

		if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL) {
			br->statistics.tx_dropped++;
			return;
		}

		skb = skb2;
	}

	prev = NULL;
	//遍曆所有的端口
	list_for_each_entry_rcu(p, &br->port_list, list) {
		if (should_deliver(p, skb)) {//skb的輸出設備非此端口,並且此端口處於轉發模式
			if (prev != NULL) {//在第一次運行時,prev=NULL,直接複製prev=p
				struct sk_buff *skb2;

				if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL) {//克隆sk_buff,共享packet data
					br->statistics.tx_dropped++;
					kfree_skb(skb);
					return;
				}

				__packet_hook(prev, skb2);//調用__br_deliver,執行一係列的netfilter調用點,最後由dev_queue_xmit完成傳輸
			}

			prev = p;
		}
	}

	if (prev != NULL) {
		__packet_hook(prev, skb);//對最後一個端口調用__br_deliver
		return;
	}

	kfree_skb(skb);
}

//	在網橋端口傳輸
//	調用路徑:br_flood->hook->br_dev_queue_push_xmit
1.6 int br_dev_queue_push_xmit(struct sk_buff *skb)
{
	if (skb->len > skb->dev->mtu)//如果skb的長度大於出口設備的mtu,則直接丟棄封包 
		kfree_skb(skb);
	else {
		skb_push(skb, ETH_HLEN);
		dev_queue_xmit(skb);//輸出
	}

	return 0;
}

//	判斷端口是否應該發送數據幀
//	端口發送數據幀的條件:
//		1.skb的入口設備非此端口
//		2.端口處於轉發模式
1.7 static inline int should_deliver(const struct net_bridge_port *p, 
				 const struct sk_buff *skb)
{
	if (skb->dev == p->dev ||//skb的接收端口為此端口
	    p->state != BR_STATE_FORWARDING)//端口非轉發模式
		return 0;

	return 1;
}


//	網橋入口數據幀處理
//	調用路徑:netif_receive_skb->handle_bridge
//
//	由網橋接收入口數據的條件:
//		1. 入口設備非回環地址
//		2. 入口設備為網橋端口
2.1 static __inline__ int handle_bridge(struct sk_buff **pskb,
				    struct packet_type **pt_prev, int *ret)
{
	struct net_bridge_port *port;

	if ((*pskb)->pkt_type == PACKET_LOOPBACK ||
	    (port = rcu_dereference((*pskb)->dev->br_port)) == NULL)//如果設備為網橋的端口,則dev->br_port為網橋端口控製塊
		return 0;

	if (*pt_prev) {
		*ret = deliver_skb(*pskb, *pt_prev);
		*pt_prev = NULL;
	} 
	//此設備為網橋的端口
	return br_handle_frame_hook(port, pskb);
}


//	網橋處理入口流量
//	參數:
//		p,接收到此入口流量的端口
//		pskb, 入口數據幀
//
//	返回值:
//		0,網橋沒有處理此pskb,netif_receive_skb繼續處理pskb
//		1,網橋已經處理此pskb,netif_receive_skb無需繼續處理此pskb
//
//	調用路徑 : netif_receive_skb->handle_bridge->br_handle_frame
//	
//	函數主要任務:
//		1.檢查分包合法性:
//			1.1 接收端口開啟狀態
//			1.2 源地址非多播或廣播地址
//		2.向轉發數據庫添加轉發項
//		3.識別配置bpdu,處理配置bpdu
//		4.決定入口流量被路由還是橋接
//		5.如果數據幀需要被橋接,則由橋接處理程序繼續處理。

//	注:
//		1.隻有轉發態(BR_STATE_FORWARDING),學習態(BR_STATE_LEARNING)的網橋端口,可以向轉發數據庫添加轉發項。
//		2.bpdu的識別辦法:以太網幀的目的地址為[0x01, 0x80, 0xc2, 0x00, 0x00, 0x00]
//		3.決定入口流量被路由還是橋接,通過ebtable_broute決定
//		4.隻有轉發態(BR_STATE_FORWARDING)的網橋端口,可以處理普通入口流量。
			
2.2 int br_handle_frame(struct net_bridge_port *p, struct sk_buff **pskb)
{
	struct sk_buff *skb = *pskb;
	const unsigned char *dest = eth_hdr(skb)->h_dest;

	if (p->state == BR_STATE_DISABLED)//端口被關閉
		goto err;

	if (eth_hdr(skb)->h_source[0] & 1)//源地址為廣播地址,說明錯誤
		goto err;

	if (p->state == BR_STATE_LEARNING ||//如果當前端口處於學習狀態,或者轉發狀態
	    p->state == BR_STATE_FORWARDING)
		br_fdb_insert(p->br, p, eth_hdr(skb)->h_source, 0);//在轉發數據庫中添加一條新轉發項,或者更新現有的轉發項

	if (p->br->stp_enabled &&//網橋的stp協議開啟
	    !memcmp(dest, bridge_ula, 5) &&//網橋組播地址的前5個字節
	    !(dest[5] & 0xF0)) {//bridge_ula[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 },通過入口幀的目標地址識別bpdu數據幀
		if (!dest[5]) {
			NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev, 
				NULL, br_stp_handle_bpdu);//入口bpdu的數據幀,可以被任何端口接收,隻要這個端口沒有通過管理性手段關閉
			return 1;
		}
	}

	else if (p->state == BR_STATE_FORWARDING) {//當前端口處於轉發狀態,一個封包是被路由還是橋接,通過br_should_route_hook函數決定
		if (br_should_route_hook) {//在ebtable_broute模塊中被設置
			if (br_should_route_hook(pskb)) 
				return 0;
			skb = *pskb;
			dest = eth_hdr(skb)->h_dest;
		}

		if (!memcmp(p->br->dev->dev_addr, dest, ETH_ALEN))//目標地址為本地
			skb->pkt_type = PACKET_HOST;

		NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
			br_handle_frame_finish);//默認情況下,被綁定的設備所接收到的網絡流量會分配給它的指定網橋
		return 1;
	}

err:
	kfree_skb(skb);
	return 1;
}

//	橋接入口流量

//	調用路徑: br_handle_frame->br_handle_frame_finish

//	函數主要任務:
//		1.網橋端口均處於混雜模式,向網橋本地接收模塊傳遞一份此數據幀
//		2.多播或廣播目的地址,在除接收端口外的所有端口,擴散此入口數據幀,退出
//		3.如果,數據幀目的地址為本機,向網橋本地接收模塊傳遞一份此數據幀,退出
//		4.否則,如果網橋知道能到達目的地址的輸出端口,從輸出端口發送此數據幀,退出
//		5.否則,如果網橋不知道使用哪個輸出端口,向所有端口擴散,退出
2.3 int br_handle_frame_finish(struct sk_buff *skb)
{
	const unsigned char *dest = eth_hdr(skb)->h_dest;
	struct net_bridge_port *p = skb->dev->br_port;
	struct net_bridge *br = p->br;
	struct net_bridge_fdb_entry *dst;
	int passedup = 0;

	if (br->dev->flags & IFF_PROMISC) {//設備處於混雜模式
		struct sk_buff *skb2;

		skb2 = skb_clone(skb, GFP_ATOMIC);//複製一份sk_buff,共享packet data
		if (skb2 != NULL) {
			passedup = 1;
			br_pass_frame_up(br, skb2);//向上層協議傳遞skb
		}
	}

	if (dest[0] & 1) {//以太網多播地址
		br_flood_forward(br, skb, !passedup);//從各個端口擴散此skb
		if (!passedup)
			br_pass_frame_up(br, skb);//如果處於混雜模式,則已經向上傳遞了一份skb
		goto out;
	}

	dst = __br_fdb_get(br, dest);//非多播地址的skb,查詢轉發數據庫
	if (dst != NULL && dst->is_local) {//本地地址
		if (!passedup)
			br_pass_frame_up(br, skb);//向上層協議傳遞一份skb,由於網橋端口為混雜模式,之前會傳遞一份,所以此處不會再傳遞
		else
			kfree_skb(skb);
		goto out;
	}

	if (dst != NULL) {
		br_forward(dst->dst, skb);//非本地地址,從通往目標地址的端口發送此skb
		goto out;
	}

	br_flood_forward(br, skb, 0);//非以太網多播,沒有明確的出口設備,從所有端口上擴散

out:
	return 0;
}

//	網橋向本地傳遞流量
//	調用路徑:br_handle_frame_finish->br_pass_frame_up

//	函數主要任務:
//		1.skb->dev設置為網橋設備,為skb重新走一遍netif_receive_skb

//	注:網橋端口處於混雜模式,所有數據幀均會向上傳遞,並且一個數據幀,隻會向本機傳遞一次。
2.4 static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
{
	struct net_device *indev;

	//增加統計數據
	br->statistics.rx_packets++;
	br->statistics.rx_bytes += skb->len;

	indev = skb->dev;
	skb->dev = br->dev;//修改接收此skb的設備為網橋設備

	NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,
			br_pass_frame_up_finish);
}

//	網橋向本地傳遞流量
//		使用積壓設備的方式
//	調用路徑:br_pass_frame_up->br_pass_frame_up_finish
2.5 static int br_pass_frame_up_finish(struct sk_buff *skb)
{
	//掛載積壓設備到poll_list,skb到input_pkt_queueu, 觸發接收軟中斷
	netif_rx(skb);
	return 0;
}

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

  上一篇:go 壓力測試工具ab.exe簡介
  下一篇:go Java常用類庫--日期操作類