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