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