Linux 网络协议栈开发(五)—— 二层桥转发蓝图(上)

一、看一张桥转发时函数调用的一个基本蓝图。

Linux 网络协议栈开发(五)—— 二层桥转发蓝图(上)


这张图中,简单的展示了,数据的接收和发送,其中还包括netfilet的钩子点所处的位置。

需要说明的是:
1).我们先暂时忽略数据包从一开始是怎么从驱动进入到netif_receive_skb的,因为这个暂时不影响我们理解这幅图的流程。
2).由于桥转发的篇幅较大,图中没有标示出,数据包中途被丢弃的情况。约定数据包会发送成功。


现在数据包(skb)已经准备好了装备要闯关了

1.首先skb从驱动经过繁杂的路线走到了netif_receive_skb这个函数中经过一些小波折到达__netif_receive_skb_core中时遇到了第一个十字路口是看了看自己有没有skb->dev->rx_handler(注1)这个装备,如果有,则进入br_handle_frame(注2).如果没有则直接上协议栈。

  注1:桥下的设备注册过rx_handler函数,所以数据包会进入桥,br_pass_frame_up函数将原先的设备换成了桥设备, 而桥设备没有注册过rx_handler函数,所以数据包不会二次进入桥 
  注2:br_handle_frame我们在前几节提到过,是skb进入桥的入口函数,在br_add_if的时候会注册该函数

2.skb注定要经历一番劫难,刚进入br_handle_frame又将陷入两难境地,此时有两个入口,这两个是netfilter设下的连个hook点,分别是,NF_BR_PRE_ROUTING,和NF_BR_LOCAL_IN,两种路径,如果数据包只想去本地,则会选择NF_BR_LOCAL_IN入口,然后发往本地,如果暂时还确定不了,则先进入NF_BR_PRE_ROUTING入口.

3.进入NF_BR_PRE_ROUTING入口后,会遇到br_handle_frame_finish函数,这个函数决定了skb的何去何从

(1)如果是转发,则在经过NF_BR_FORWARD钩子点进入转发阶段的障碍,最后在进入NF_BR_POST_ROUTING,以及最终的dev_queue_xmit,实现最终转发。

(2)如果发往本地则重新进入NF_BR_LOCAL_IN最后在进入netif_receive_skb,进行转发。

skb在经过目前口述的磨练最终得以释放。

4.如果是如果是本地发出的数据包,经过NF_BR_LOCAL_OUT钩子点然后进入最后阶段的NF_BR_POST_ROUTING,进行相应的转发。


二、我们看看数据包如何在桥中进行转发的一些动作

  首先,上节的大蓝图中,标识除了,数据包是如何进入桥的,有一个很重要的函数br_handle_frame这个函数的初始注册地点是在桥添加接口的时候,注册在桥某一个接口上

[cpp] view plain copy
  1. int br_add_if(struct net_bridge *br, struct net_device *dev)  
  2. {  
  3.     ........  
  4.     /*注册设备接收帧函数*/  
  5.     err = netdev_rx_handler_register(dev, br_handle_frame, p);  
  6.     ........  
  7. }  
其次,那么__netif_receive_skb_core是怎样让数据包进入桥的呢?我们看看上面提到的netdev_rx_handler_register函数,具体做了什么

[cpp] view plain copy
  1. int netdev_rx_handler_register(struct net_device *dev,  
  2.                    rx_handler_func_t *rx_handler,  
  3.                    void *rx_handler_data)  
  4. {  
  5.     ASSERT_RTNL();  
  6.   
  7.     if (dev->rx_handler)  
  8.         return -EBUSY;  
  9.     /* Note: rx_handler_data must be set before rx_handler */  
  10.     /*将dev->rx_handler_data,指向rx_handler_data(上面的p是桥端口信息)*/  
  11.     rcu_assign_pointer(dev->rx_handler_data, rx_handler_data);  
  12.     /*将dev->rx_handle指针指向rx_handler*/  
  13.     rcu_assign_pointer(dev->rx_handler, rx_handler);  
  14.   
  15.     return 0;  
  16. }  

看完这个函数,我们就明白了为什么在__netif_receive_skb_core中可以用skb->dev->rx_handle将数据包传入br_handle_frame函数,也就是将数据包传入了桥。

值得注意的是:上面的dev是桥下的设备,不是桥设备,桥设备(比如br0)是没有注册rx_handle这个函数的

好,了解到,桥的注册函数和如何接收数据包后,然后一起来看看br_handle_frame是如何操作的

[cpp] view plain copy
  1. /* 
  2.  * Return NULL if skb is handled 
  3.  * note: already called with rcu_read_lock 
  4.  */  
  5. rx_handler_result_t br_handle_frame(struct sk_buff **pskb)  
  6. {  
  7.     struct net_bridge_port *p;  
  8.     struct sk_buff *skb = *pskb;  
  9.     const unsigned char *dest = eth_hdr(skb)->h_dest;  
  10.     br_should_route_hook_t *rhook;  
  11.   
  12.     if (unlikely(skb->pkt_type == PACKET_LOOPBACK))  
  13.         return RX_HANDLER_PASS;  
  14.     /*判断是否是有效的mac地址,即不是多播地址也不是全00地址*/  
  15.     if (!is_valid_ether_addr(eth_hdr(skb)->h_source))  
  16.         goto drop;  
  17.     /*判断是否是共享数据包,若果是则clone该数据包*/  
  18.     skb = skb_share_check(skb, GFP_ATOMIC);  
  19.     if (!skb)  
  20.         return RX_HANDLER_CONSUMED;  
  21.     /*获取数据包网桥端口的一些信息*/  
  22.     p = br_port_get_rcu(skb->dev);  
  23.     /*BPDU是网桥之间交流的报文,目标mac是 01:80:C2:00:00:00*/  
  24.     if (unlikely(is_link_local_ether_addr(dest))) {  
  25.         u16 fwd_mask = p->br->group_fwd_mask_required;  
  26.   
  27.         /* 
  28.          * See IEEE 802.1D Table 7-10 Reserved addresses 
  29.          * 
  30.          * Assignment               Value 
  31.          * Bridge Group Address     01-80-C2-00-00-00 
  32.          * (MAC Control) 802.3      01-80-C2-00-00-01 
  33.          * (Link Aggregation) 802.3 01-80-C2-00-00-02 
  34.          * 802.1X PAE address       01-80-C2-00-00-03 
  35.          * 
  36.          * 802.1AB LLDP         01-80-C2-00-00-0E 
  37.          * 
  38.          * Others reserved for future standardization 
  39.          */  
  40.         switch (dest[5]) {  
  41.         case 0x00:  /* Bridge Group Address */  
  42.             /* If STP is turned off, 
  43.                then must forward to keep loop detection */  
  44.             if (p->br->stp_enabled == BR_NO_STP ||  
  45.                 fwd_mask & (1u << dest[5]))  
  46.                 goto forward;  
  47.             *pskb = skb;  
  48.             __br_handle_local_finish(skb);  
  49.             return RX_HANDLER_PASS;  
  50.   
  51.         case 0x01:  /* IEEE MAC (Pause) */  
  52.             goto drop;  
  53.   
  54.         case 0x0E:  /* 802.1AB LLDP */  
  55.             fwd_mask |= p->br->group_fwd_mask;  
  56.             if (fwd_mask & (1u << dest[5]))  
  57.                 goto forward;  
  58.             *pskb = skb;  
  59.             __br_handle_local_finish(skb);  
  60.             return RX_HANDLER_PASS;  
  61.   
  62.         default:  
  63.             /* Allow selective forwarding for most other protocols */  
  64.             fwd_mask |= p->br->group_fwd_mask;  
  65.             if (fwd_mask & (1u << dest[5]))  
  66.                 goto forward;  
  67.         }  
  68.   
  69.         /* Deliver packet to local host only */  
  70.         NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, dev_net(skb->dev),  
  71.             NULL, skb, skb->dev, NULL, br_handle_local_finish);  
  72.         return RX_HANDLER_CONSUMED;  
  73.     }  
  74.   
  75. forward:  
  76.     switch (p->state) {  
  77.     case BR_STATE_FORWARDING:  
  78.         /*ebtables获取路由的hook点*/  
  79.         rhook = rcu_dereference(br_should_route_hook);  
  80.         if (rhook) {/*如果是转发状态,则转发数据包,然后返回*/  
  81.             if ((*rhook)(skb)) {  
  82.                 *pskb = skb;  
  83.                 return RX_HANDLER_PASS;  
  84.             }  
  85.             dest = eth_hdr(skb)->h_dest;  
  86.         }  
  87.         /* fall through */  
  88.     case BR_STATE_LEARNING:  
  89.         /*目的地址是否是设备链路层地址 */  
  90.         if (ether_addr_equal(p->br->dev->dev_addr, dest))  
  91.             skb->pkt_type = PACKET_HOST;  
  92.         /*将数据包送入数据帧处理函数br_handle_frame_finish*/  
  93.         NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING,  
  94.             dev_net(skb->dev), NULL, skb, skb->dev, NULL,  
  95.             br_handle_frame_finish);  
  96.         break;  
  97.     default:  
  98. drop:  
  99.         kfree_skb(skb);  
  100.     }  
  101.     return RX_HANDLER_CONSUMED;  
  102. }  
在br_handle_frame主要做一件事,就是将数据包放进那个钩子点。

说明:br_handle_frame函数中有两个hook函数,br_handle_local_finish和br_handle_frame_finish这两个函数只有在netfilter因其他原因没有丢弃或者消化该帧时才会被调用,ebtables也能查看帧。ebtables是一个架构,能提供一些netfilter所没有的提供的额外功能,尤其是,ebtables可以过滤和修改任何类型的帧,而非仅限于那些携带ip封包的帧。