天天看點

Linux-網橋原理分析(四)7  網橋資料包的處理流程8 參考文獻

7  網橋資料包的處理流程

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

1.  在一個接口上接收的包不會再在那個接口上發送這個資料包;

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

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

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

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

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

Linux-網橋原理分析(四)7  網橋資料包的處理流程8 參考文獻

網絡資料包在軟終端處理時會進行網橋部分處理,大緻的處理流程如下(處理函數調用鍊):

Linux-網橋原理分析(四)7  網橋資料包的處理流程8 參考文獻

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 (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          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;      } 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;          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);                  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;               if (p->state == BR_STATE_DISABLED)         goto err;          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);          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) {                  if (br_should_route_hook) {             if (br_should_route_hook(pskb))                  return 0;             skb = *pskb;             dest = eth_hdr(skb)->h_dest;         }                  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;               br_fdb_update(p->br, p, eth_hdr(skb)->h_source);               if (br->dev->flags & IFF_PROMISC) {         struct sk_buff *skb2;                  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, skb, !passedup);         if (!passedup)             br_pass_frame_up(br, skb);         goto out;     }          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;     }          if (dst != NULL) {         br_forward(dst->dst, skb);         goto out;     }          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;     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) {          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;      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) {               if (skb->len > skb->dev->mtu && !skb_shinfo(skb)->tso_size)         kfree_skb(skb);     else { #ifdef CONFIG_BRIDGE_NETFILTER                  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

繼續閱讀