1. 程式人生 > >Linux核心分析 - 網路:網橋原理分析

Linux核心分析 - 網路:網橋原理分析

網橋資料包的處理流程

    網橋處理包遵循以下幾條原則:

    1.  在一個介面上接收的包不會再在那個介面上傳送這個資料包;

    2.  每個接收到的資料包都要學習其源地址;

    3.  如果資料包是多播或廣播包,則要在同一個網段中除了接收埠外的其他所有埠傳送這個資料包,如果上層協議棧對多播包感興趣,則需要把資料包提交給上層協議棧;

    4.  如果資料包的目的MAC地址不能再CAM表中找到,則要在同一個網段中除了接收埠外的其他所有埠傳送這個資料包;

    5.  如果能夠在CAM表中查詢到目的MAC地址,則在特定的埠上傳送這個資料包,如果傳送埠和接收埠是同一埠則不傳送;

    網橋在整個網路子系統中處理可用下列簡圖說明:

    網路資料包在軟終端處理時會進行網橋部分處理,大致的處理流程如下(處理函式呼叫鏈):

7.1  netif_receive_skb

       netif_recerve_skb函式主要做三件事情:

       1.  如果有抓包程式(socket)需要skb,則將skb複製給他們;

       2.  處理橋接,即如果開啟了網橋,進行網橋處理;

       3. 將skb交給網路層;

int netif_receive_skb(struct sk_buff *skb)

{

   struct packet_type *ptype, *pt_prev;

   struct net_device *orig_dev;

   int ret = NET_RX_DROP;

   unsigned short type;

   /* if we've gotten here through NAPI, check netpoll */

   if (skb->dev->poll && netpoll_rx(skb))

       return NET_RX_DROP;

   if (!skb->tstamp.off_sec)

       net_timestamp(skb);

   if (!skb->input_dev)

       skb->input_dev = skb->dev;

   orig_dev = skb_bond(skb);

   __get_cpu_var(netdev_rx_stat).total++;

   skb->h.raw = skb->nh.raw = skb->data;

   skb->mac_len = skb->nh.raw - skb->mac.raw;

   pt_prev = NULL;

   rcu_read_lock();

#ifdef CONFIG_NET_CLS_ACT

   if (skb->tc_verd & TC_NCLS) {

       skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);

       goto ncls;

   }

#endif

   /*--yangxh mark:

       當網路裝置收到網路資料包時,最終會在軟體中斷環境裡呼叫此函式

          檢查該資料包是否有packet socket來接收該包,如果有則往該socket

          拷貝一份,由deliver_skb來完成。

   --*/

   list_for_each_entry_rcu(ptype, &ptype_all, list) {

       if (!ptype->dev || ptype->dev == skb->dev) {

           if (pt_prev)

               ret = deliver_skb(skb, pt_prev, orig_dev);

           pt_prev = ptype;

       }

   }

#ifdef CONFIG_NET_CLS_ACT

   if (pt_prev) {

       ret = deliver_skb(skb, pt_prev, orig_dev);

       pt_prev = NULL; /* noone else should process this after*/

   } else {

       skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);

   }

   ret = ing_filter(skb);

   if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN)) {

       kfree_skb(skb);

       goto out;

   }

   skb->tc_verd = 0;

ncls:

#endif

   handle_diverter(skb);

   /*--

       先試著將該資料包讓網橋函式來處理,如果該資料包的入口介面確實是網橋介面,

          則按網橋方式來處理,如果不是網橋介面的資料包,則不應該讓網橋來處理

   --*/

   if (handle_bridge(&skb, &pt_prev, &ret, orig_dev))

       goto out;

   /*--對該資料包轉發到它L3協議的處理函式--*/

   type = skb->protocol;

   list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {

       if (ptype->type == type &&

        (!ptype->dev || ptype->dev == skb->dev)) {

           if (pt_prev)

               ret = deliver_skb(skb, pt_prev, orig_dev);

           pt_prev = ptype;

       }

   }

   if (pt_prev) {

       ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);

   } else {

       kfree_skb(skb);

       /* Jamal, now you will not able to escape explaining

        * me how you were going to use this. :-)

        */

       ret = NET_RX_DROP;

   }

out:

   rcu_read_unlock();

   return ret;

}

7.2  Br_handle_frame

      1.  如果skb的目的Mac地址與接收該skb的網口的Mac地址相同,則結束橋接處理過程(返回到net_receive_skb函式後,這個skb會最終 被提交給網路層);

      2.  否則,呼叫到br_handle_frame_finish函式將報文轉發,然後釋放skb(返回到net_receive_skb函式後,這個skb就不會往網路層提交了);

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;

   

   /*--網橋狀態為disable,返回錯誤,丟棄資料包--*/

   if (p->state == BR_STATE_DISABLED)

       goto err;

   /*--源MAC地址為非法,返回錯誤,丟棄資料包--*/

   if (!is_valid_ether_addr(eth_hdr(skb)->h_source))

       goto err;

       

   /*--如果網橋狀態處於學習狀態,則更新資料庫--*/

   if (p->state == BR_STATE_LEARNING)

       br_fdb_update(p->br, p, eth_hdr(skb)->h_source);

   /*--如果是STP的BPDU資料包,則進入STP處理--*/

   if (p->br->stp_enabled &&

    !memcmp(dest, bridge_ula, 5) &&

    !(dest[5] & 0xF0)) {

       if (!dest[5]) {

           NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,

               NULL, br_stp_handle_bpdu);

           return 1;

       }

   }    

   else if (p->state == BR_STATE_FORWARDING) {

       /*--如果該介面處於Forwarding狀態,並且該報文必需要走L3層進行轉發,

           則直接返回--*/

       if (br_should_route_hook) {

           if (br_should_route_hook(pskb))

               return 0;

           skb = *pskb;

           dest = eth_hdr(skb)->h_dest;

       }

       /*--

           當用核心建立一個網橋的同時也會建立一個虛擬的網路裝置,它的名字

           為網橋的名字,儲存在p->br->dev指標裡。P->br->dev和port_list裡面的

           介面共同組成一個網橋。如果該報文是要發往此接,則標記skb->pkt_type為

           PACKET_HOST。因為報文最終是要傳送到p->br->dev的輸送佇列裡面,

           正如一般的網絡卡驅動程式將資料包送往到某個net_device的輸入佇列一樣,

           這樣bridge功能充當了虛擬網絡卡(如例子中的br0)驅動,

           應當設定skb->pkt_type為PACKET_HOST,

           表明資料包是要傳送該介面,而非是因為開啟混雜模式而接收到的。

       --*/

       if (!compare_ether_addr(p->br->dev->dev_addr, dest))

           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;

}

7.3Br_handle_frame_finish

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;

   /*--

       對所有報的源MAC地址進行學習,這是網橋的特點之一,

         通過對源地址的學習來建立MAC地址到埠的對映。

   --*/

   /* insert into forwarding database after filtering to avoid spoofing */

   br_fdb_update(p->br, p, eth_hdr(skb)->h_source);

   

   /*--如果網橋的虛擬網絡卡處於混雜模式,那麼每個接收到的資料包都需要克隆一份送到

       AF_PACKET協議處理體(網路軟中斷函式net_rx_action中ptype_all鏈的處理)--*/

   if (br->dev->flags & IFF_PROMISC) {

       struct sk_buff *skb2;

       /*--skb2非空,表明要發往本機,br_pass_frame_up函式完成發往本機的工作--*/

       skb2 = skb_clone(skb, GFP_ATOMIC);

       if (skb2 != NULL) {

           passedup = 1;

           br_pass_frame_up(br, skb2);

       }

   }

   if (dest[0] & 1) {

       /*--此報文是廣播或組播報文,

           由br_flood_forward函式把報文向所有埠轉發出去

           如果本地協議棧已經發過了,則算了,不再發送--*/

       br_flood_forward(br, skb, !passedup);

       if (!passedup)

           br_pass_frame_up(br, skb);

       goto out;

   }

   /*--__br_fdb_get函式先查MAC-埠對映表,表中每項是通過結構

       struct net_bridge_fdb_entry來描述的,這一步是網橋的關鍵。

       這個報文應從哪個介面轉發出去就看它了。

       如果這個報文應發往本機,那麼skb置空。不需要再轉發了

       因為發往本機介面從邏輯上來說本身就是一個轉發,後續有上層協議棧處理

   --*/

   dst = __br_fdb_get(br, dest);

   if (dst != NULL && dst->is_local) {

       if (!passedup)

           br_pass_frame_up(br, skb);

       else

           kfree_skb(skb);

       goto out;

   }

   /*--找到MAC對映,則發往對應的目的埠--*/

   if (dst != NULL) {

       br_forward(dst->dst, skb);

       goto out;

   }

   /*--dst==NULL,沒有賺到對映,則廣播--*/

   br_flood_forward(br, skb, 0);

out:

   return 0;

}

7.4  Br_pass_frame_up

在上個函式Br_handle_frame_finish中如果報文是需要發往本地協議棧處理的,則由函式Br_pass_frame_up實現:

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;/*--報文中的dev被賦予網橋本身的虛擬dev--*/

   NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,

           br_pass_frame_up_finish);

}

這段程式碼非常簡單,對net_bridge的資料統計進行更新以後,再更新skb->dev,最後通過NF_HOOK在NF_BR_LOCAL_IN掛接點上呼叫回了netif_receive_skb;

在netif_receive_skb函式中,呼叫了handle_bridge函式,重新觸發了網橋處理流程,現在發往網橋虛擬裝置的資料包又回到了netif_receive_skb,那麼網橋的處理過程會不會又被呼叫呢?在linux/net/bridge/br_if.c裡面可以看到br_add_if函式,實際上的操作是將某一網口加入網橋組,這個函式呼叫了new_nbp(br, dev); 用以填充net_bridge以及dev結構的重要成員,裡面將dev->br_port設定為一個新建的net_bridge_port結構,而上面的br_pass_frame_up函式將skb->dev賦成了br->dev,實際上skb->dev變成了網橋建立的虛擬裝置,這個裝置是網橋本身而不是橋組的某一埠,系統沒有為其呼叫br_add_if,所以這個net_device結構的br_port指標沒有進行賦值;br_port為空,不進入網橋處理流程 ;從而進入上層協議棧處理;

7.5  Br_forward

void br_forward(const struct net_bridge_port *to, struct sk_buff *skb)

{

   /*--should_deliver: 是否符合轉發條件

       __br_forward: 轉發--*/

   if (should_deliver(to, skb)) {

       __br_forward(to, skb);

       return;

   }

   kfree_skb(skb);

}

7.6 __br_forward

static void __br_forward(const struct net_bridge_port *to, struct sk_buff *skb)

{

   struct net_device *indev;

   indev = skb->dev;

   skb->dev = to->dev; /*--替換報文中的dev為轉發埠對應的dev--*/

   skb->ip_summed = CHECKSUM_NONE;

   NF_HOOK(PF_BRIDGE, NF_BR_FORWARD, skb, indev, skb->dev,

           br_forward_finish);

}

7.7   Br_forward_finish

int br_forward_finish(struct sk_buff *skb)

{

   NF_HOOK(PF_BRIDGE, NF_BR_POST_ROUTING, skb, NULL, skb->dev,

           br_dev_queue_push_xmit);

   return 0;

}

7.8   Br_dev_queue_push_xmit

int br_dev_queue_push_xmit(struct sk_buff *skb)

{

   /*--報文長度超過dev傳送的mtu限制,丟棄報文--*/

   /* drop mtu oversized packets except tso */

   if (skb->len > skb->dev->mtu && !skb_shinfo(skb)->tso_size)

       kfree_skb(skb);

   else {

#ifdef CONFIG_BRIDGE_NETFILTER

       /* ip_refrag calls ip_fragment, doesn't copy the MAC header. */

       nf_bridge_maybe_copy_header(skb);

#endif

       skb_push(skb, ETH_HLEN);

       dev_queue_xmit(skb);

   }

   return 0;

}

7.9 報文處理總結

進入橋的資料報文分為幾個型別,橋對應的處理方法也不同:

1.  報文是本機發送給自己的,橋不處理,交給上層協議棧;

2.  接收報文的物理介面不是網橋介面,橋不處理,交給上層協議棧;

3.  進入網橋後,如果網橋的狀態為Disable,則將包丟棄不處理;

4.  報文源地址無效(廣播,多播,以及00:00:00:00:00:00),丟包;

5.  如果是STP的BPDU包,進入STP處理,處理後不再轉發,也不再交給上層協議棧;

6.  如果是發給本機的報文,橋直接返回,交給上層協議棧,不轉發;

7.  需要轉發的報文分三種情況:

1) 廣播或多播,則除接收埠外的所有埠都需要轉發一份;

2) 單播並且在CAM表中能找到埠對映的,只需要網對映埠轉發一份即可;

3) 單播但找不到埠對映的,則除了接收埠外其餘埠都需要轉發;

8 參考文獻

1.   http://hi.baidu.com/_kouu/blog/item/ad2abf3ffa61cf3170cf6cd7.html

2.   http://hi.baidu.com/jrckkyy/blog/item/3bedbef37234d0c70b46e08b.html

3.   http://blog.csdn.net/linyt/archive/2010/01/15/5191512.aspx

4.   http://www.loosky.net/?p=307

5.   http://blog.csdn.net/zhaodm/archive/2006/12/25/1460041.aspx

6.   http://blog.chinaunix.net/u/12313/showart_246678.html