一、看一张桥转发时函数调用的一个基本蓝图。
这张图中,简单的展示了,数据的接收和发送,其中还包括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
- int br_add_if(struct net_bridge *br, struct net_device *dev)
- {
- ........
- /*注册设备接收帧函数*/
- err = netdev_rx_handler_register(dev, br_handle_frame, p);
- ........
- }
其次,那么__netif_receive_skb_core是怎样让数据包进入桥的呢?我们看看上面提到的netdev_rx_handler_register函数,具体做了什么
[cpp] view plain copy
- int netdev_rx_handler_register(struct net_device *dev,
- rx_handler_func_t *rx_handler,
- void *rx_handler_data)
- {
- ASSERT_RTNL();
- if (dev->rx_handler)
- return -EBUSY;
- /* Note: rx_handler_data must be set before rx_handler */
- /*将dev->rx_handler_data,指向rx_handler_data(上面的p是桥端口信息)*/
- rcu_assign_pointer(dev->rx_handler_data, rx_handler_data);
- /*将dev->rx_handle指针指向rx_handler*/
- rcu_assign_pointer(dev->rx_handler, rx_handler);
- return 0;
- }
看完这个函数,我们就明白了为什么在__netif_receive_skb_core中可以用skb->dev->rx_handle将数据包传入br_handle_frame函数,也就是将数据包传入了桥。
值得注意的是:上面的dev是桥下的设备,不是桥设备,桥设备(比如br0)是没有注册rx_handle这个函数的
好,了解到,桥的注册函数和如何接收数据包后,然后一起来看看br_handle_frame是如何操作的
[cpp] view plain copy
- /*
- * Return NULL if skb is handled
- * note: already called with rcu_read_lock
- */
- rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
- {
- struct net_bridge_port *p;
- struct sk_buff *skb = *pskb;
- const unsigned char *dest = eth_hdr(skb)->h_dest;
- br_should_route_hook_t *rhook;
- if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
- return RX_HANDLER_PASS;
- /*判断是否是有效的mac地址,即不是多播地址也不是全00地址*/
- if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
- goto drop;
- /*判断是否是共享数据包,若果是则clone该数据包*/
- skb = skb_share_check(skb, GFP_ATOMIC);
- if (!skb)
- return RX_HANDLER_CONSUMED;
- /*获取数据包网桥端口的一些信息*/
- p = br_port_get_rcu(skb->dev);
- /*BPDU是网桥之间交流的报文,目标mac是 01:80:C2:00:00:00*/
- if (unlikely(is_link_local_ether_addr(dest))) {
- u16 fwd_mask = p->br->group_fwd_mask_required;
- /*
- * See IEEE 802.1D Table 7-10 Reserved addresses
- *
- * Assignment Value
- * Bridge Group Address 01-80-C2-00-00-00
- * (MAC Control) 802.3 01-80-C2-00-00-01
- * (Link Aggregation) 802.3 01-80-C2-00-00-02
- * 802.1X PAE address 01-80-C2-00-00-03
- *
- * 802.1AB LLDP 01-80-C2-00-00-0E
- *
- * Others reserved for future standardization
- */
- switch (dest[5]) {
- case 0x00: /* Bridge Group Address */
- /* If STP is turned off,
- then must forward to keep loop detection */
- if (p->br->stp_enabled == BR_NO_STP ||
- fwd_mask & (1u << dest[5]))
- goto forward;
- *pskb = skb;
- __br_handle_local_finish(skb);
- return RX_HANDLER_PASS;
- case 0x01: /* IEEE MAC (Pause) */
- goto drop;
- case 0x0E: /* 802.1AB LLDP */
- fwd_mask |= p->br->group_fwd_mask;
- if (fwd_mask & (1u << dest[5]))
- goto forward;
- *pskb = skb;
- __br_handle_local_finish(skb);
- return RX_HANDLER_PASS;
- default:
- /* Allow selective forwarding for most other protocols */
- fwd_mask |= p->br->group_fwd_mask;
- if (fwd_mask & (1u << dest[5]))
- goto forward;
- }
- /* Deliver packet to local host only */
- NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, dev_net(skb->dev),
- NULL, skb, skb->dev, NULL, br_handle_local_finish);
- return RX_HANDLER_CONSUMED;
- }
- forward:
- switch (p->state) {
- case BR_STATE_FORWARDING:
- /*ebtables获取路由的hook点*/
- rhook = rcu_dereference(br_should_route_hook);
- if (rhook) {/*如果是转发状态,则转发数据包,然后返回*/
- if ((*rhook)(skb)) {
- *pskb = skb;
- return RX_HANDLER_PASS;
- }
- dest = eth_hdr(skb)->h_dest;
- }
- /* fall through */
- case BR_STATE_LEARNING:
- /*目的地址是否是设备链路层地址 */
- if (ether_addr_equal(p->br->dev->dev_addr, dest))
- skb->pkt_type = PACKET_HOST;
- /*将数据包送入数据帧处理函数br_handle_frame_finish*/
- NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING,
- dev_net(skb->dev), NULL, skb, skb->dev, NULL,
- br_handle_frame_finish);
- break;
- default:
- drop:
- kfree_skb(skb);
- }
- return RX_HANDLER_CONSUMED;
- }
在br_handle_frame主要做一件事,就是将数据包放进那个钩子点。
说明:br_handle_frame函数中有两个hook函数,br_handle_local_finish和br_handle_frame_finish这两个函数只有在netfilter因其他原因没有丢弃或者消化该帧时才会被调用,ebtables也能查看帧。ebtables是一个架构,能提供一些netfilter所没有的提供的额外功能,尤其是,ebtables可以过滤和修改任何类型的帧,而非仅限于那些携带ip封包的帧。
上一节我们了解到,数据包如何走进桥,这一节我们简单看看,入口帧处理函数br_handle_frame_finish.
作用:br_handle_frame_finish函数主要是决策将不同类别的数据包做不同的分发路径。
其函数处理的过程如下图所示:
首先判断该数据包是否符合桥转发的条件:
(1)桥端口状态是否是开启状态,如果没有开启则丢掉数据包
(2)是否允许从该桥上转发,如果不允许,则直接返回0
获得桥转发的条件以后,开始判断数据包的类型:
(1)判断此时桥的标志为允许做哪些事情,学习还是扩展,如果学习的标志位被至位,则更新数据转发表。否则继续向下走
(2)根据多播或者广播报文的类型决定数据包的去留
(3)判断此时端口的状态,如果是学习状态,则将数据包丢弃
(要注意的是:桥的端口状态(和上面的flag不冲突,上面的flag表示网桥可以做的事情)state表示网桥端口所处于的状态)
在处理完一些需要预备的事情之后,就要为数据包的转发开始做准备了
(1)网桥设备是否处于混杂模式,如果是则建立副本,为发往本地做个备份
(注意的是,所有网桥端口绑定的设备都会处于混杂模式,因为 网桥运行必须此模式。但除非明确的对其进行配置,否则网桥自己是不会处于混杂模式的)
(2)在次判断广播还是多播地址
广播地址:仅仅设置副本,进行广播转发和发往本地
多播地址:先查多播地址转发表,如果存在,设置副本,进行多播转发,原始数据包指向NULL,如果已经传送至本地,则会释放副本,不进行本地转发,否则重新转发到本地
(3)不是广播或者多播
判断是否本地地址,如果是本地地址,则将原始数据包指向NULL,发往本地。否则进行数据包转发
[cpp] view plain copy
- /* note: already called with rcu_read_lock */
- int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
- {
- const unsigned char *dest = eth_hdr(skb)->h_dest;
- struct net_bridge_port *p = br_port_get_rcu(skb->dev);
- struct net_bridge *br;
- struct net_bridge_fdb_entry *dst;
- struct net_bridge_mdb_entry *mdst;
- struct sk_buff *skb2;
- bool unicast = true;
- u16 vid = 0;
- if (!p || p->state == BR_STATE_DISABLED)
- goto drop;
- /*判断是否允许进入桥内,如果没有开启vlan则所有的数据包都可以进入,
- 如果开启了vlan则根据vlan相应的规则,从桥上进行数据包转发*/
- if (!br_allowed_ingress(p->br, nbp_vlan_group_rcu(p), skb, &vid))
- goto out;
- /* insert into forwarding database after filtering to avoid spoofing */
- br = p->br;
- /*如果可以学习,则学习数据包的原地址*/
- if (p->flags & BR_LEARNING)
- br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, false);
- if (!is_broadcast_ether_addr(dest) && is_multicast_ether_addr(dest) &&
- br_multicast_rcv(br, p, skb, vid))
- goto drop;
- /*桥的端口状态(和上面的flag不冲突,上面的flag表示网桥可以做的事情)
- state表示网桥端口所处于的状态*/
- if (p->state == BR_STATE_LEARNING)
- goto drop;
- BR_INPUT_SKB_CB(skb)->brdev = br->dev;
- /* The packet skb2 goes to the local host (NULL to skip). */
- skb2 = NULL;
- if (br->dev->flags & IFF_PROMISC)
- skb2 = skb;
- dst = NULL;
- if (IS_ENABLED(CONFIG_INET) && skb->protocol == htons(ETH_P_ARP))
- br_do_proxy_arp(skb, br, vid, p);
- if (is_broadcast_ether_addr(dest)) {
- skb2 = skb;
- unicast = false;
- } else if (is_multicast_ether_addr(dest)) {
- mdst = br_mdb_get(br, skb, vid);
- if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
- br_multicast_querier_exists(br, eth_hdr(skb))) {
- if ((mdst && mdst->mglist) ||
- br_multicast_is_router(br))
- skb2 = skb;
- br_multicast_forward(mdst, skb, skb2);
- skb = NULL;
- if (!skb2)
- goto out;
- } else
- skb2 = skb;
- unicast = false;
- br->dev->stats.multicast++;
- } else if ((dst = __br_fdb_get(br, dest, vid)) &&
- dst->is_local) {
- skb2 = skb;
- /* Do not forward the packet since it's local. */
- skb = NULL;
- }
- if (skb) {
- if (dst) {
- dst->used = jiffies;
- br_forward(dst->dst, skb, skb2);
- } else
- br_flood_forward(br, skb, skb2, unicast);/*扩撒帧*/
- }
- /*将副本传入本地*/
- if (skb2)
- return br_pass_frame_up(skb2);
- out:
- return 0;
- drop:
- kfree_skb(skb);
- goto out;
- }
数据包发送有两个地方,一个是转发出去br_forward或者br_flood_forward,一个是发往本地br_pass_frame_up
二、数据包的转发
无论是在发往本地还是转发,有一个函数的功能是不能忽略的,就是br_handle_vlan函数
[cpp] view plain copy
- struct sk_buff *br_handle_vlan(struct net_bridge *br,
- struct net_bridge_vlan_group *vg,
- struct sk_buff *skb)
- {
- struct br_vlan_stats *stats;
- struct net_bridge_vlan *v;
- u16 vid;
- /* If this packet was not filtered at input, let it pass */
- if (!BR_INPUT_SKB_CB(skb)->vlan_filtered)
- goto out;
- /* At this point, we know that the frame was filtered and contains
- * a valid vlan id. If the vlan id has untagged flag set,
- * send untagged; otherwise, send tagged.
- */
- br_vlan_get_tag(skb, &vid);
- /*find vid from vlan group*/
- v = br_vlan_find(vg, vid);
- /* Vlan entry must be configured at this point. The
- * only exception is the bridge is set in promisc mode and the
- * packet is destined for the bridge device. In this case
- * pass the packet as is.
- */
- if (!v || !br_vlan_should_use(v)) {
- if ((br->dev->flags & IFF_PROMISC) && skb->dev == br->dev) {
- goto out;
- } else {
- kfree_skb(skb);
- return NULL;
- }
- }
- /*statistacs the vlan if flow and if the vlan_stats_enabled is true */
- if (br->vlan_stats_enabled) {
- stats = this_cpu_ptr(v->stats);
- u64_stats_update_begin(&stats->syncp);
- stats->tx_bytes += skb->len;
- stats->tx_packets++;
- u64_stats_update_end(&stats->syncp);
- }
- if (v->flags & BRIDGE_VLAN_INFO_UNTAGGED)
- skb->vlan_tci = 0;
- out:
- return skb;
- }
这个函数的作用很简单就是,数据包是否要带tag,过程:
在传递进来的vlan group中查找自己所处的vlan
如果该vlan不存在则判断当前模式是否是混杂模式和数据包的设备是否是桥下的设备,选择发包或者丢弃。
如果存在,且vlan是开启的,则统计vlan接口上的数据流量,最后根据vlan出口的标记位进行位运算判断是否要带tag.
然后我们来看一下上节提到的发往本地数据包的处理函数
[cpp] view plain copy
- static int br_pass_frame_up(struct sk_buff *skb)
- {
- struct net_device *indev, *brdev = BR_INPUT_SKB_CB(skb)->brdev;
- struct net_bridge *br = netdev_priv(brdev);
- struct net_bridge_vlan_group *vg;
- struct pcpu_sw_netstats *brstats = this_cpu_ptr(br->stats);
- /*统计该桥上的流量*/
- u64_stats_update_begin(&brstats->syncp);
- brstats->rx_packets++;
- brstats->rx_bytes += skb->len;
- u64_stats_update_end(&brstats->syncp);
- /*获取该桥上的vlan组*/
- vg = br_vlan_group_rcu(br);
- /* Bridge is just like any other port. Make sure the
- * packet is allowed except in promisc modue when someone
- * may be running packet capture.
- */
- if (!(brdev->flags & IFF_PROMISC) &&
- !br_allowed_egress(vg, skb)) {
- kfree_skb(skb);
- return NET_RX_DROP;
- }
- /*替换掉数据包中的设备信息改为桥设备*/
- indev = skb->dev;
- skb->dev = brdev;
- /*配置数据包vlan的相关信息*/
- skb = br_handle_vlan(br, vg, skb);
- if (!skb)
- return NET_RX_DROP;
- /*进入NF_BR_LOCAL_IN*/
- return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN,
- dev_net(indev), NULL, skb, indev, NULL,
- br_netif_receive_skb);
- }
这个函数所做的事情很简单,就是配置vlan的相关信息后,然后发往本地的netfilter钩子函数中
最后重新回到netif_recive_skb.如下函数:
[cpp] view plain copy
- static int
- br_netif_receive_skb(struct net *net, struct sock *sk, struct sk_buff *skb)
- {
- <span> </span>return netif_receive_skb(skb);
- }
再来看看数据包转发的函数
[cpp] view plain copy
- static void __br_forward(const struct net_bridge_port *to, struct sk_buff *skb)
- {
- struct net_bridge_vlan_group *vg;
- struct net_device *indev;
- if (skb_warn_if_lro(skb)) {
- kfree_skb(skb);
- return;
- }
- /*获取vlan组,这个组中有许多的vlanid,br_handle_vlan函数就是要在这个组中查找自己的vid*/
- vg = nbp_vlan_group_rcu(to);
- /*添加vlan的相关配置*/
- skb = br_handle_vlan(to->br, vg, skb);
- if (!skb)
- return;
- indev = skb->dev;
- skb->dev = to->dev;
- skb_forward_csum(skb);
- NF_HOOK(NFPROTO_BRIDGE, NF_BR_FORWARD,
- dev_net(indev), NULL, skb, indev, skb->dev,
- br_forward_finish);
- }
- int br_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
- {
- return NF_HOOK(NFPROTO_BRIDGE, NF_BR_POST_ROUTING,
- net, sk, skb, NULL, skb->dev,
- br_dev_queue_push_xmit);
- }
整个数据包转发的过程与转发到本地的过程类似,只不过所进入的netfilter钩子点不同.
整个分析中不包含数据包从本地发出的数据包