网路收报流程-网桥的处理流程(br网桥)(四)

25 篇文章 7 订阅

目录

 

1.网桥收发报文模型

2.网桥的初始化和相关数据结构关系

      1)通过br_init函数注册和初始化网桥功能

      2) 添加一个桥设备-br_add_bridge

      3)给网桥添加端口-br_add_if

     4)数据结构

3.网桥收包处理流程-br_handle_frame函数

1)br_handle_frame函数

2)br_handle_local_finish函数

3)br_handle_frame_finish 函数


1.网桥收发报文模型

      linux内核是通过一个虚拟的网桥设备来实现桥接的。这个虚拟设备可以绑定若干个以太网接口设备,从而将它们桥接起来。其网桥收发包模型如下所示:

网络收发报文的二种方式:
1)网桥转发给具体端口处理:
网桥设备br0绑定了eth0和eth1。对于网络协议栈的上层来说,只看得到br0,因为桥接是在数据链路层实现的,上层不需要关心桥接的细节。于是协议栈上层需要发送的报文被送到br0,网桥设备的处理代码再来判断报文该被转发到eth0或是eth1,或者两者皆是;反过来,从eth0或从eth1接收到的报文被提交给网桥的处理代码,在这里会判断报文该转发、丢弃、或提交到协议栈上层。
2)直接通过物理网卡处理
有时候eth0、eth1也可能会作为报文的源地址或目的地址,直接参与报文的发送与接收(从而绕过网桥)。


2.网桥的初始化和相关数据结构关系


      使用桥接功能,我们需要在编译内核时指定相关的选项,并让内核加载桥接模块。然后通过下列流程来初始化一个网桥设备:


      1)通过br_init函数注册和初始化网桥功能

      stp_proto_register(注册协议生成树收包函数)-->br_fdb_init(转发数据库初始化)-->register_pernet_subsys(在/proc目录下生成任何与bridge相关的目录,如果我们想在/proc下生成bridge相关的子目录或子文件)-->register_netdevice_notifier(注册通知连,主要针对桥转发表事件的相关信息)-->br_netlink_init(netlink的初始化)-->brioctl_set(用来处理ioctl命令的函数,比如添加和删除网桥)


      2) 添加一个桥设备-br_add_bridge


      这是一个用户态基于socket的ioctl的系统调用,用来处理ioctl命令的函数br_ioctl_deviceless_stub通过调用brioctl_set,将br_ioctl_deviceless_stub赋值给回调函数br_ioctl_hook,而br_ioctl_hook在sock_ioctl中使用。这样通过在应用层调用socket的ioctl函数,就能够进行网桥的添加与删除了。
      对于网桥添加的SIOCBRADDBR情形,就会触发br_ioctl_deviceless_stub函数来响应br_add_bridge函数来创建一个        net_device 类型的网桥设备,具体流程如下:
      br_add_bridge-->alloc_netdev (为设备分配一块内存,并且调用br_dev_setup函数初始化)-->dev->rtnl_link_ops = &br_link_ops(初始化dev的netlink链接通知操作函数)->register_netdev(注册一个网络设备)
br_dev_setup函数用来初始化桥设备所需要的基本数据,尤其是在br_netdev_ops中指定了很多的设备函数,包括启用,关闭网桥,修改mtu以及设备ioctl函数,添加或删除桥下设备等等一系列函数。具体操作实现如下:

void br_dev_setup(struct net_device *dev)
{
    struct net_bridge *br = netdev_priv(dev);
    eth_hw_addr_random(dev);
    ether_setup(dev);
    /*指定网络设备的管理钩子,关于各个钩子函数的作用以及用法, 请参见/linux/netdev.h中的net_device_ops结构体描述,这是很重要的一部分*/
    dev->netdev_ops = &br_netdev_ops;
    /*可选netdev操作, 关于各个钩子函数的作用以及用法, 请参见/linux/ethtool.h中的ethtool_ops结构体描述*/
    dev->destructor = br_dev_free;
    dev->ethtool_ops = &br_ethtool_ops;
    SET_NETDEV_DEVTYPE(dev, &br_type);
    /*IFF_EBRIDGE内核用来区别网桥设备和其他类型的设备*/
    dev->priv_flags = IFF_EBRIDGE | IFF_NO_QUEUE;
    dev->features = COMMON_FEATURES | NETIF_F_LLTX | NETIF_F_NETNS_LOCAL |
            NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_STAG_TX;
    dev->hw_features = COMMON_FEATURES | NETIF_F_HW_VLAN_CTAG_TX |
               NETIF_F_HW_VLAN_STAG_TX;
    dev->vlan_features = COMMON_FEATURES;

    br->dev = dev;
    spin_lock_init(&br->lock);
    INIT_LIST_HEAD(&br->port_list);
    spin_lock_init(&br->hash_lock);
    /*制定默认优先权*/
    br->bridge_id.prio[0] = 0x80;
    br->bridge_id.prio[1] = 0x00;

    ether_addr_copy(br->group_addr, eth_reserved_addr_base);

    br->stp_enabled = BR_NO_STP;
    br->group_fwd_mask = BR_GROUPFWD_DEFAULT;
    br->group_fwd_mask_required = BR_GROUPFWD_DEFAULT;

    br->designated_root = br->bridge_id;
    br->bridge_max_age = br->max_age = 20 * HZ;
    br->bridge_hello_time = br->hello_time = 2 * HZ;
    br->bridge_forward_delay = br->forward_delay = 15 * HZ;
    /*老化时间初值默认为5分钟*/
    br->ageing_time = BR_DEFAULT_AGEING_TIME;

    /*初始化该网桥的netfilter*/
    br_netfilter_rtable_init(br);
     /*初始化网桥的各类定时器,hello定时器,垃圾回收定时器等等*/
    br_stp_timer_init(br);
    /*多播初始化*/
    br_multicast_init(br);
}

      在上面的函数中,除了br_netdev_ops需要注意以外还有一个需要注意的函数br_netfilter_rtable_init(br);
      这个函数是用来初始化Bridging-Firewalling,在后续的内容中,可以看到Netfilter钩子(hook)在桥接程序用于处理入口和出口网络流量的主要位置。

      3)给网桥添加端口-br_add_if

         接口添加时如何实现的呢?如何调用到这个接口呢?
       上节我们在将添加一个桥设备的时候,有一个参数br_netdev_ops,这里面有很多的注册函数, 调用add_del_if SIOCDEVPRIVATE,调用old_dev_ioctl函数, 其实我们再看源码时就会发现,old_dev_ioctl函数后面其实也调用了add_del_if。add_del_if 最后在根据是添加还是删除接口的参数,进行相应的添加(SIOCBRADDIF),还是删除接口。

int br_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
    struct net_bridge *br = netdev_priv(dev);

    switch (cmd) {
    case SIOCDEVPRIVATE:
        return old_dev_ioctl(dev, rq, cmd);

    case SIOCBRADDIF:
    case SIOCBRDELIF:
        return add_del_if(br, rq->ifr_ifindex, cmd == SIOCBRADDIF);

    }

    br_debug(br, "Bridge does not support ioctl 0x%x\n", cmd);
    return -EOPNOTSUPP;
}

      该函数注册在,桥设备添加时候dev->netdev_ops = &br_netdev_ops; 在br_netdev_ops有一个函数指针.ndo_do_ioctl= br_dev_ioctl

int br_add_if(struct net_bridge *br, struct net_device *dev)
{
    struct net_bridge_port *p;
    int err = 0;
    unsigned br_hr, dev_hr;
    bool changed_addr;

    /* Don't allow bridging non-ethernet like devices, or DSA-enabled
     * master network devices since the bridge layer rx_handler prevents
     * the DSA fake ethertype handler to be invoked, so we do not strip off
     * the DSA switch tag protocol header and the bridge layer just return
     * RX_HANDLER_CONSUMED, stopping RX processing for these frames.
     */

    if ((dev->flags & IFF_LOOPBACK) ||
        dev->type != ARPHRD_ETHER || dev->addr_len != ETH_ALEN ||
        !is_valid_ether_addr(dev->dev_addr) ||
        netdev_uses_dsa(dev))
        return -EINVAL;

    /* No bridging of bridges */
    if (dev->netdev_ops->ndo_start_xmit == br_dev_xmit)
        return -ELOOP;

    /* Device is already being bridged  */
    if (br_port_exists(dev))
        return -EBUSY;

    /* No bridging devices that dislike that (e.g. wireless) */
    if (dev->priv_flags & IFF_DONT_BRIDGE)
        return -EOPNOTSUPP;

    /*分配一个新网桥端口并对其初始化*/
    p = new_nbp(br, dev);
    if (IS_ERR(p))
        return PTR_ERR(p);

    /*调用设备通知链,告诉网络有这样一个设备*/
    call_netdevice_notifiers(NETDEV_JOIN, dev);

    /**向设备添加或删除所有多播帧的接收。*/
    err = dev_set_allmulti(dev, 1);
    if (err)
        goto put_back;
  /*初始化一个kobject结构,并把它加入到kobject层次中
    err = kobject_init_and_add(&p->kobj, &brport_ktype, &(dev->dev.kobj),
                   SYSFS_BRIDGE_PORT_ATTR);
    if (err)
        goto err1;
    /*把链路添加到sysfs*/
    err = br_sysfs_addif(p);
    if (err)
        goto err2;

    err = br_netpoll_enable(p);
    if (err)
        goto err3;

    /*注册设备接收帧函数*/
    err = netdev_rx_handler_register(dev, br_handle_frame, p);
    if (err)
        goto err4;

    /*给该端口指派默认优先权*/
    dev->priv_flags |= IFF_BRIDGE_PORT;

    /*向上级设备添加主链路*/
    err = netdev_master_upper_dev_link(dev, br->dev, NULL, NULL);
    if (err)
        goto err5;

    /*禁用网络设备上的大型接收卸载(LRO)。 
    必须在RTNL下调用。 
    如果接收到的数据包可能转发到另一个接口, 则需要这样做。*/
    dev_disable_lro(dev);

    list_add_rcu(&p->list, &br->port_list);
    /*更新桥上的端口数,如果有更新,再进一步将其设为混杂模式*/
    nbp_update_port_count(br);
    /* 重新计算dev->features并发送通知(如果已更改)。 
    应该调用驱动程序或硬件依赖条件可能会改变影响功能。*/
    netdev_update_features(br->dev);

    br_hr = br->dev->needed_headroom;
    dev_hr = netdev_get_fwd_headroom(dev);
    if (br_hr < dev_hr)
        update_headroom(br, dev_hr);
    else
        netdev_set_rx_headroom(dev, br_hr);

    /*把dev的mac添加到转发数据库中*/
    if (br_fdb_insert(br, p, dev->dev_addr, 0))
        netdev_err(dev, "failed insert local address bridge forwarding table\n");

    /*初始化该桥端口的vlan*/
    err = nbp_vlan_init(p);
    if (err) {
        netdev_err(dev, "failed to initialize vlan filtering on this port\n");
        goto err6;
    }

    spin_lock_bh(&br->lock);
    /*更新网桥id*/
    changed_addr = br_stp_recalculate_bridge_id(br);
    /*设备是否启动,桥是否启动,设备上是否有载波信号(网桥没有载波状态,因为网桥是虚拟设备)*/
    if (netif_running(dev) && netif_oper_up(dev) &&
        (br->dev->flags & IFF_UP))
        /*启动网桥端口*/
        br_stp_enable_port(p);
    spin_unlock_bh(&br->lock);

    br_ifinfo_notify(RTM_NEWLINK, p);
    /*如果网桥的地址改变,则调用通知连相关的函数*/
    if (changed_addr)
        call_netdevice_notifiers(NETDEV_CHANGEADDR, br->dev);
    /*更新网桥mtu*/
    dev_set_mtu(br->dev, br_min_mtu(br));
    br_set_gso_limits(br);
    /*添加一个内核对象*/
    kobject_uevent(&p->kobj, KOBJ_ADD);

    return 0;

err6:
    list_del_rcu(&p->list);
    br_fdb_delete_by_port(br, p, 0, 1);
    nbp_update_port_count(br);
    netdev_upper_dev_unlink(dev, br->dev);

err5:
    dev->priv_flags &= ~IFF_BRIDGE_PORT;
    netdev_rx_handler_unregister(dev);
err4:
    br_netpoll_disable(p);
err3:
    sysfs_remove_link(br->ifobj, p->dev->name);
err2:
    kobject_put(&p->kobj);
    p = NULL; /* kobject_put frees */
err1:
    dev_set_allmulti(dev, -1);
put_back:
    dev_put(dev);
    kfree(p);
    return err;
}

4)数据结构

      完成这些操作后,内核中的数据结构关系如下图所示:网桥最主要有三个数据结构:struct net_bridge,struct net_bridge_port,struct net_bridge_fdb_entry,他们之间的关系如下图

结构体关联关系如下所示:

      其中最左边的net_device是一个代表网桥的虚拟设备结构,它关联了一个net_bridge结构,这是网桥设备所特有的数据结构。
      在net_bridge结构中,port_list成员下挂一个链表,链表中的每一个节点(net_bridge_port结构)关联到一个真实的网口设备的net_device。网口设备也通过其br_port指针做反向的关联(那么显然,一个网口最多只能同时被绑定到一个网桥)。
      net_bridge结构中还维护了一个hash表,是用来处理地址学习的。当网桥准备转发一个报文时,如 果可以在hash表中索引到一个net_bridge_fdb_entry结构,通过这个结构能找到一个网口设备的net_device,于是报文就应该从这个网口转发出去;否则,报文将从所有网口转发。

3.网桥收包处理流程-br_handle_frame函数

      如何判断一个skb是否需要做桥接相关的处理呢?skb->dev指向了接收这个skb的设备,如果这个net_device的rx_handler不为空,则表示这个net_device正在被桥接,于是调用到br_handle_frame函数,让桥接的代码来处理这个报文;
接下来主要讲讲__netif_receive_skb_core函数中网桥处理的流程,其中rx_handler指针函数就是 br_handle_frame。整体的流程大致如下:

另一个比较好的图:

      说明:br_handle_frame函数中有两个hook函数,br_handle_local_finish和br_handle_frame_finish这两个函数只有在netfilter因其他原因没有丢弃或者消化该帧时才会被调用,ebtables也能查看帧。ebtables是一个架构,能提供一些netfilter所没有的提供的额外功能,尤其是,ebtables可以过滤和修改任何类型的帧,而非仅限于那些携带ip封包的帧。
      br_handle_frame 主要有两个分支有NF_HOOK的调用的,如下: 
      |---link-local----      NF_HOOK(NFPROTO_BRIDGE,NF_BR_LOCAL_IN,..,br_handle_local_finish) 
      |---forward--          NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, ...,br_handle_frame_finish)
link-local :如果目的mac是本地链路地址,则会调用br_handle_local_finish。

1)br_handle_frame函数

/*
 * 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;
 	/*获取目的MAC地址*/
	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;

	if (!is_valid_ether_addr(eth_hdr(skb)->h_source)) //检测源mac地址的合法性:组播(包括广播)和全0的源mac地址是非法的
		goto drop;

	skb = skb_share_check(skb, GFP_ATOMIC);/*检测skb是否共享,如果是共享的,clone出一份新的skb,老的skb计数通过kfree_skb减1*/
/*clone skb的目的:
1.自己撒手,该skb由clone者负责打理!
2.但原来的skb可能被共享,如果需要修改skb,则会影响共享该sbk的其他函数,因此如果被共享,则克隆一份,再调用kfree_skb(实际只是skb->users--,相关数据并没有释放)*/
	if (!skb)
		return RX_HANDLER_CONSUMED;

	p = br_port_get_rcu(skb->dev);/*获取数据包网桥端口的一些信息*/
	if (p->flags & BR_VLAN_TUNNEL) { 
		if (br_handle_ingress_vlan_tunnel(skb, p,
						  nbp_vlan_group_rcu(p)))
			goto drop;
	}
/*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 */
/* 如果是STP的目的MAC地址,但是stp没有使能或者有转发标记,那么转发该报文,不然上送自身(STP报文的上送)*/
			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; // MAC Control帧不能通过网桥

		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:// 其他的保留MAC多播和普通数据帧一样处理
			/* Allow selective forwarding for most other protocols */
			fwd_mask |= p->br->group_fwd_mask;
			if (fwd_mask & (1u << dest[5]))
				goto forward;
		}
/*如果是linklocal地址,并且不是上面几种情况,则上送到自身*/
		/* Deliver packet to local host only */
		NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, NULL, skb,
			skb->dev, NULL, br_handle_local_finish);
		return RX_HANDLER_CONSUMED; /* consumed by filter */
	}

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, NULL, skb,
			skb->dev, NULL,
			br_handle_frame_finish);
		break;
	default:
drop:
		kfree_skb(skb);
	}
	return RX_HANDLER_CONSUMED;
}

2)br_handle_local_finish函数

       根据报文的源MAC来更新FDB表项,最终调用br_pass_frame_up将skb经由网桥处理(注意是网桥而不是网桥上的端口设备!)

/* note: already called with rcu_read_lock */
static int br_handle_local_finish(struct sock *sk, struct sk_buff *skb)
{
	struct net_bridge_port *p = br_port_get_rcu(skb->dev);

	__br_handle_local_finish(skb);

	BR_INPUT_SKB_CB(skb)->brdev = p->br->dev;
	br_pass_frame_up(skb);
	return 0;
}

      a)上面函数主要调用__br_handle_local_finish和br_pass_frame_up,先看看__br_handle_local_finish,它调用br_fdb_update根据报文的源MAC来更新FDB表项:

static void __br_handle_local_finish(struct sk_buff *skb)
{
	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
	u16 vid = 0;

	/* check if vlan is allowed, to avoid spoofing */
	if (p->flags & BR_LEARNING && br_should_learn(p, skb, &vid))
		br_fdb_update(p->br, p, eth_hdr(skb)->h_source, vid, false);
}

      科普:内核中,整个CAM表是用br->hash[hash_value]这个数组来存储的,其中hash_value是根据源MAC地址进行hash运算得出的一个值,这样,br->hash[hash]就指向了此源MAC地址对应的fdb项所在的链表的首部。这样说可能有点复杂,可用下图来表示:
br->hash[hash_0]->fdb1->fdb2->fdb3……
br->hash[hash_1]->fdb1->fdb2->fdb3……
br->hash[hash_2]->fdb1->fdb2->fdb3……
br->hash[hash_3]->fdb1->fdb2->fdb3……
……
其中的hash_0、hash_1……是通过对源MAC地址进行hash运算求出的。
    b)br_fdb_update函数:更新网络地址接口表CAM表,bridge每收到一个报文,就会根据报文的源MAC来更新FDB表项。

void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
                   const unsigned char *addr, u16 vid, bool added_by_user)
{
        struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)];
        struct net_bridge_fdb_entry *fdb;
        bool fdb_modified = false;

        /* some users want to always flood. */
        if (hold_time(br) == 0)
                return;

        /* ignore packets unless we are using this port */
//如果网桥的状态不是处于learning或者forward状态,则直接返回
        if (!(source->state == BR_STATE_LEARNING ||
              source->state == BR_STATE_FORWARDING))
                return;

/*若发现要update的mac地址所对应的fdb entry已经存在,函数还会判断这个fdb entry是否是local的。若是local的,说明br_handle_frame处理的入口数据包的mac地址是属于网桥端口的,这就说明了该网桥下的桥接端口出现了环路。这就是该函数的另一大功能,通过该函数我们能判断网桥下的端口是否环路了。*/
        fdb = fdb_find_rcu(head, addr, vid);
        if (likely(fdb)) {/*如果FDB表项已经存在,则更新*/
                /* attempt to update an entry for a local interface */
                if (unlikely(fdb->is_local)) {/*如果是本机FDB,表示接收错误*/
                        if (net_ratelimit())
                                br_warn(br, "received packet on %s with own address as source address (addr:%pM, vlan:%u)\n",
                                        source->dev->name, addr, vid);
                } else {
                        unsigned long now = jiffies;

                        /* fastpath: update of existing entry */
                        if (unlikely(source != fdb->dst)) {
                                fdb->dst = source; /*接口不统一,则需要更新当前地址所对应的端口*/
                                fdb_modified = true;
                                /* Take over HW learned entry */
                                if (unlikely(fdb->added_by_external_learn))
                                        fdb->added_by_external_learn = 0;
                        }
                        if (now != fdb->updated)
                                fdb->updated = now;//更新时间戳
                        if (unlikely(added_by_user))
                                fdb->added_by_user = 1;
                        if (unlikely(fdb_modified)) {
                                trace_br_fdb_update(br, source, addr, vid, added_by_user);
                                fdb_notify(br, fdb, RTM_NEWNEIGH);
                        }
                }
        } else {//若不存在fdb表中,则调用fdb_create,创建一个非local的表项,区别对待br_fdb_insert(网桥添加端口时会调用)创建一个local的fdb entry
                spin_lock(&br->hash_lock);
                if (likely(!fdb_find_rcu(head, addr, vid))) {
                        fdb = fdb_create(head, source, addr, vid, 0, 0);
                        if (fdb) {
                                if (unlikely(added_by_user))
                                        fdb->added_by_user = 1;
                                trace_br_fdb_update(br, source, addr, vid, added_by_user);
                                fdb_notify(br, fdb, RTM_NEWNEIGH);
                        }
                }
                /* else  we lose race and someone else inserts
                 * it first, don't bother updating
                 */
                spin_unlock(&br->hash_lock);
        }
}

c)再看看br_pass_frame_up:进入br_pass_frame_up的skb是打算经由Bridge设备处理,输入到本地Host的。

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;
	}
/*关于skb->dev的更新:数据包从网桥端口设备进入,经过网桥设备,然后再进入协议栈,其实是“两次经过net_device”,一次是端口设备,另一次是网桥设备。现在数据包离开网桥端口进入网桥设备,需要修改skb->dev字段。
skb->dev 起初是网桥端口设备,现在离开网桥端口进入网桥的时候,被替换为网桥设备的net_device。如果设备是TX,或者从一个端口转发的另一个,skb->dev也会相应改变。不论数据的流向如何,skb->dev总是指向目前所在的net_device{},这时经过br_pass_frame_up 函数后在调用回netif_receive_skb,但是skb->dev->rx_handler为空,所以这次的netif_receive_skb将不会再进行网桥处理*/
	indev = skb->dev;
	skb->dev = brdev;
/*出口数据包是否要增加vlan id(tag)在函数br_handle_vlan中进行判断,如果接口的untagged_bitmap位图中含有vid比特位,说明不要加tag,清空数据包(skb)的vlan_tci字段,否则保留vlan_tci字段的值。*/
	skb = br_handle_vlan(br, NULL, vg, skb);
	if (!skb)
		return NET_RX_DROP;
	/* update the multicast stats if the packet is IGMP/MLD */
	br_multicast_count(br, NULL, skb, br_multicast_igmp_type(skb),
			   BR_MCAST_DIR_TX);
/*递交的最后一步是经过NF_BR_LOCAL_IN钩子点,然后是最终调用我们熟悉的netif_receive_skb,只不过这次进入该函数的时候skb->dev已经被换成了Bridge设备。这可以理解为进入了Bridge设备的处理。*/
	return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, NULL, skb,
		       indev, NULL,
		       br_netif_receive_skb);
}


3)br_handle_frame_finish 函数

      br_handle_frame_finish 这个函数对数据包的dmac进行判断,然后走不同的处理函数 。
根据dmac 的不同的,处理方式不同:
A.bridge it,如果dmac是在网桥的别的端口,复制一份帧到dmac所在的端口                                 ---->br_forward 
B.flood it over all the forwarding bridge ports,如果dmac地址是网桥不知道的,就泛洪                      ---->br_flood_forward
C.pass it to the higher protocol code,如果dmac是网桥的,或者网桥其中一个端口的                 ---->br_pass_frame_up
D.ignore it,dmac在进来的端口的这一边的,即dmac能在进来端口的mac地址表中找到              ---->br_forward
总之数据包发送有两个地方,一个是转发出去br_forward或者br_flood_forward,一个是发往本地br_pass_frame_up。

转发过程:
br_forward,通过should_deliver()来进行判断,是否真的需要__br_forward 还是 ignore it,
__br_forward->NF_HOOK(NFPROTO_BRIDGE, NF_BR_FORWARD, ... skb->dev,br_forward_finish) ,
__br_forward 函数改变了skb->dev
br_forward_finish->NF_HOOK(NFPROTO_BRIDGE,NF_BR_POST_ROUTING,skb,NULL,skb->dev,br_dev_queue_push_xmit);
br_dev_queue_push_xmit->dev_queue_xmit

a)具体看看br_handle_frame_finish 的实现:

/* note: already called with rcu_read_lock */
int br_handle_frame_finish(struct sock *sk, struct sk_buff *skb)
{
	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
	enum br_pkt_type pkt_type = BR_PKT_UNICAST;
	struct net_bridge_fdb_entry *dst = NULL;
	struct net_bridge_mdb_entry *mdst;
	bool local_rcv, mcast_hit = false;
	const unsigned char *dest;
	struct net_bridge *br;
	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;

	nbp_switchdev_frame_mark(p, skb);

	/* insert into forwarding database after filtering to avoid spoofing */
	br = p->br;
/*更新CAM表(CAM表就是交换机转发数据帧要查找的表,该表是MAC地址与出接口的对应关系,每一个地址-端口对应的项称为fdb项,内核中使用链表来组织fdb,它是一个struct net_bridge_fdb_entry),过程和br_fdb_insert非常相似,但是br_fdb_insert插入以前如果通过fib_find发现有同样的fdb则将原来的fdb删除,但是br_fdb_update则直接返回保留原来的fdb项*/
	if (p->flags & BR_LEARNING) 
		br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, false);

	local_rcv = !!(br->dev->flags & IFF_PROMISC);//设备为混杂模式
	dest = eth_hdr(skb)->h_dest;
	if (is_multicast_ether_addr(dest)) {//如果目的地址是广播地址
		/* by definition the broadcast is also a multicast address */
		if (is_broadcast_ether_addr(dest)) {
			pkt_type = BR_PKT_BROADCAST;
			local_rcv = true;//广播
		} else {
			pkt_type = BR_PKT_MULTICAST; //多播
			if (br_multicast_rcv(br, p, skb, vid))
				goto drop;
		}
	}
/*桥的端口状态(和上面的flag不冲突,上面的flag表示网桥可以做的事情),state表示网桥端口所处于的状态,如果网桥处于BR_STATE_LEARNING状态,还没有开始转发,所以学习新的fdb以后,直接将数据包drop*/
	if (p->state == BR_STATE_LEARNING)
		goto drop;

	BR_INPUT_SKB_CB(skb)->brdev = br->dev;

	if (IS_ENABLED(CONFIG_INET) && skb->protocol == htons(ETH_P_ARP))
		br_do_proxy_arp(skb, br, vid, p);

	switch (pkt_type) {
	case BR_PKT_MULTICAST:
		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)) {
				local_rcv = true;
				br->dev->stats.multicast++;
			}
			mcast_hit = true;
		} else {
			local_rcv = true;
			br->dev->stats.multicast++;
		}
		break;
	case BR_PKT_UNICAST:
		dst = br_fdb_find_rcu(br, dest, vid);//获取目的地址对应的网桥端口设备的fdb项
	default:
		break;
	}

	if (dst) {/* 目的mac对应的fdb项如果存在,不是广播或者多播的情况下,判断是否本地地址,如果是本地地址,调用br_pass_frame_up发往本地。 否则调用br_forward进行数据包转发*/
		unsigned long now = jiffies;
/*如果网卡虚拟设备处于混杂模式,或者目的地址是多播地址,或者数据包的目的地址是网桥虚拟设备的地址,则数据包需要上传到本地协议栈进行处理。*/
		if (dst->is_local)
			return br_pass_frame_up(skb); //skb发送到本机

		if (now != dst->used)		
			dst->used = now;
		br_forward(dst->dst, skb, local_rcv, false);/*转发表中存在并且不是本地的,即需要转发到其它端口dst->dst*/
	} else {//如果dmac地址是网桥不知道的,就泛洪   
		if (!mcast_hit)
			br_flood(br, skb, pkt_type, local_rcv, false);
		else
			br_multicast_flood(mdst, skb, local_rcv, false);
	}

	if (local_rcv)//如果网卡虚拟设备处于混杂模式,或者目的地址是多播地址,或者数据包的目的地址是网桥虚拟设备的地址,则数据包需要上传到本地协议栈进行处理
		return br_pass_frame_up(skb);

out:
	return 0;
drop:
	kfree_skb(skb);
	goto out;
}

b) br_forward函数

void br_forward(const struct net_bridge_port *to,
                struct sk_buff *skb, bool local_rcv, bool local_orig)
{
//接口检查,确认端口处于BR_STATE_FORWARDING状态,网桥允许转发,并且转发的出口和入口的dev不相等
        if (to && should_deliver(to, skb)) {
                if (local_rcv)//网卡为混杂模式,本地也会被转发
                        deliver_clone(to, skb, local_orig);
                else
                        __br_forward(to, skb, local_orig);
                return;
        }

        if (!local_rcv)
                kfree_skb(skb);
}
EXPORT_SYMBOL_GPL(br_forward);

c) __br_forward函数

static void  __br_forward(const struct net_bridge_port *to,
                         struct sk_buff *skb, bool local_orig)
{
        struct net_bridge_vlan_group *vg;
        struct net_device *indev;
        struct net *net;
        int br_hook;
/*获取vlan组,这个组中有许多的vlanid,br_handle_vlan函数就是要在这个组中查找自己的vid*
        vg = nbp_vlan_group_rcu(to);
/*添加vlan的相关配置*/
        skb = br_handle_vlan(to->br, to, vg, skb);
        if (!skb)
                return;
//将出口的dev指针赋给skb
        indev = skb->dev;
        skb->dev = to->dev;
        if (!local_orig) {
                if (skb_warn_if_lro(skb)) {
                        kfree_skb(skb);
                        return;
                }
                br_hook = NF_BR_FORWARD;
                skb_forward_csum(skb);
                net = dev_net(indev);
        } else {
                if (unlikely(netpoll_tx_running(to->br->dev))) {
                        if (!is_skb_forwardable(skb->dev, skb)) {
                                kfree_skb(skb);
                        } else {
                                skb_push(skb, ETH_HLEN);
                                br_netpoll_send_skb(to, skb);
                        }
		return;
                }
                br_hook = NF_BR_LOCAL_OUT;
                net = dev_net(skb->dev);
                indev = NULL;
        }
//进入NF_BR_FORWARD挂接点并调用br_forward_finish函数
        NF_HOOK(NFPROTO_BRIDGE, br_hook,
                NULL, skb, indev, skb->dev,
                br_forward_finish);
}

最终的调用流程就是:br_forward_finish-->br_dev_queue_push_xmit-->dev_queue_xmit

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值