網絡子係統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