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