網路收報流程-網橋的處理流程(br網橋)(四)

目錄

目錄

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函數

 a)__br_handle_local_finish

         b)br_fdb_update函數

c)再看看br_pass_frame_up

3)br_handle_frame_finish 函數

a)br_handle_frame_finish 的實現:

b) br_forward函數

c) __br_forward函數

 


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;
}

      上面函數主要調用__br_handle_local_finish和br_pass_frame_up,先看看__br_handle_local_finish,它調用br_fdb_update根據報文的源MAC來更新FDB表項:

      1)__br_handle_local_finish

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

發佈了85 篇原創文章 · 獲贊 22 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章