目錄
目錄
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