网桥是在内核中虚拟出来的,可以将主机上真实的物理网卡(如eth0,eth1),或虚拟的网卡(tap0,tap1,vnet0,vnet1)桥接上来。桥接上来的网卡就相当于网桥上的端口。 端口收到的数据包都提交给这个虚拟的”网桥“,让其进行转发。
给网桥配置ip:
网桥其实是不用配置ip的,给网桥配ip地址,是为了方便网桥所在的主机和网桥所桥接的网卡(包括虚拟网卡)进行通信。一般配同一个网段的ip。
桥接在网桥上的网卡(包括物理网卡和虚拟网卡)不能配制ip地址。但是其对应的虚拟机(容器)可以配制ip地址,如果和网桥的ip是同一个网段的话,网桥所在的物理主机和这个网桥上桥接的网卡所对应的虚拟主机就可以进行通信了(能够ping通)。在给linux创建虚拟机时,为了方便主机与其虚拟机进行通信,常用到网桥,并且常给网桥配制ip地址。
注意:
必须给虚拟机桥接的网桥配制IP地址,且保持和桥上tap设备所对应的虚拟机网卡上配制的ip地址在同一网段,这样才能从主机中直接访问到虚拟机
缓存:
网桥首先会对收到的数据帧进行缓存并处理;
学习:
当帧经过网桥,网桥首先在网桥表中查找帧的源MAC地址,如果该地址不在网桥表中,则将有该MAC地址及其所对应的网桥端口信息加入(逆向学习法);
过滤:
判断入帧的和发帧的是否是同一端口中,如果是,网桥就不把帧转发到网桥的其他端口;
转发:
如果在表中找到目标地址,则直接转发给该目的MAC地址对应的端口;
如果在表中找不到目标地址,则按扩散的办法将该数据发送给与该网桥连接的除发送该数据的网段外的所有网段。
每个桥维护了一个基于MAC地址的过滤数据库,网桥根据这个数据库,把收到的帧往相应的局域网(端口)进行转发。
在过滤数据库中,列出了每个可能的目的地(目的MAC地址),以及它属于哪一条输出线路(一个端口号,即表示转发给哪个LAN),每个表项还有一个超时设置。可以及时学习改变了的地址;
老化:
过滤数据库表项的TTL每秒都增加,超过某个值则从数据库中清除,一般缺省的TTL设置为300秒。老化主要是考虑到网桥的内存有限、节点移动的情况。
假设桥在端口x上接收到一个MAC帧,有如下规则
1、查询网桥表中包的源MAC;如果没有,将该MAC地址及其所对应的网桥
端口信息加入;如果有,继续下一步
2、查询过滤数据库,确定该目的MAC地址是否在除[端口x]外的其它端口中;
如果目的MAC地址在端口x内,不进行转发;
3、在转发时,如果目的MAC地址在过滤数据库中的某个端口y中,
确定端口y是否处在阻塞或转发状态(生成树协议)。
【在生成树算法中我们可以看到,一个端口可能有时候是阻塞的,
以防止它接收或发送帧】
如果端口y是非阻塞的,把该帧通过端口y转发到它所连接的LAN中。
4、在转发时,如果目的MAC地址没有找到,把该帧往除了它所到来
的端口外的所有端口发送,即进行转发(扩散)。
数据被直接发到 Bridge 上,而不是从一个端口接收。这种情况可以看做 Bridge 自己有一个 MAC可以主动发送报文,或者说 Bridge 自带了一个隐藏端口和寄主 Linux 系统自动连接,Linux 上的程序可以直接从这个端口向 Bridge 上的其他端口发数据。所以当一个 Bridge 拥有一个网络设备时,如 bridge0 加入了 eth0 时,实际上 bridge0 拥有两个有效 MAC 地址,一个是 bridge0 的,一个是 eth0 的,他们之间可以通讯。
通常来说 IP 地址是三层协议的内容,不应该出现在二层设备 Bridge 上。但是 Linux 里 Bridge 是通用网络设备抽象的一种,只要是网络设备就能够设定 IP 地址。当一个 bridge0 拥有 IP 后,Linux 便可以通过路由表或者 IP 表规则在三层定位 bridge0,此时相当于 Linux 拥有了另外一个隐藏的虚拟网卡和 Bridge 的隐藏端口相连,这个网卡就是名为 bridge0 的通用网络设备,IP 可以看成是这个网卡的。当有符合此 IP 的数据到达 bridge0 时,内核协议栈认为收到了一包目标为本机的数据,此时应用程序可以通过 Socket 接收到它。
当一个设备被 attach 到 Bridge 上时,那个设备的 IP 会变的无效,Linux 不再使用那个 IP 在三层接受数据。
举例如下:
如果 eth0 本来的 IP 是 192.168.1.2,此时如果收到一个目标地址是 192.168.1.2 的数据,Linux 的应用程序能通过 Socket 操作接受到它。而当 eth0 被 attach 到一个 bridge0 时,尽管 eth0 的 IP 还在,但应用程序是无法接受到上述数据的。此时应该把 IP 192.168.1.2 赋予 bridge0。
另外需要注意的是数据流的方向:
对于一个被 attach 到 Bridge 上的设备来说,只有它收到数据时,此包数据才会被转发到 Bridge 上,进而完成查表广播等后续操作。当请求是发送类型时,数据是不会被转发到 Bridge上的,它会寻找下一个发送出口。用户在配置网络时经常忽略这一点从而造成网络故障。
一个设备attach到网桥上之后,这个设备的流量如何被网桥接管
分析之前首先要介绍一个重要函数:netdev_rx_handler_register
/*
* dev: 要注册接收函数的dev
* rx_handler: 要注册的接收函数
* rx_handler_data: 指向rx_handler_data使用的数据
*/
int netdev_rx_handler_register(struct net_device *dev,
rx_handler_func_t *rx_handler,
void *rx_handler_data)
{
ASSERT_RTNL();
if (dev->rx_handler)
return -EBUSY;
/* Note: rx_handler_data must be set before rx_handler */
rcu_assign_pointer(dev->rx_handler_data, rx_handler_data);
rcu_assign_pointer(dev->rx_handler, rx_handler);
return 0;
}
这个函数可以给设备(net_device)注册接收函数,然后在__netif_receive_skb函数中根据接收skb的设备接口,再调用这个被注册的接收函数。比如为网桥下的接口注册br_handle_frame函数,为bonding接口注册bond_handle_frame函数。
这相对于老式的网桥处理更灵活,有了这个机制也可以在模块中自行注册处理函数。比如3.10中的openvswitch(OpenvSwitch在3.10已经合入了内核)创建netdev vport的函数netdev_create。
ovs中设备attach到ovs网桥时也用了一样的做法:
static struct vport *netdev_create(const struct vport_parms *parms)
{
struct vport *vport;
/....../
err = netdev_rx_handler_register(netdev_vport->dev, netdev_frame_hook,vport);
/....../
}
这个函数在创建netdev vport时将设备的接收函数设置为netdev_frame_hook函数
往网桥添加设备时:
如果一个dev被添加到一个bridge(做为bridge的一个接口),的这个接口设备的rx_handler被设置为br_handle_frame函数,这是在br_add_if函数中设置的,而br_add_if (net/bridge/br_if.c)是在向网桥设备上添加接口时设置的。进入br_handle_frame也就进入了bridge的逻辑代码。
int br_add_if(struct net_bridge *br, struct net_device *dev)
{
/*......*/
err = netdev_rx_handler_register(dev, br_handle_frame, p);
/*......*/
}
netif_receive_skb是进入协议栈的入口:
int netif_receive_skb(struct sk_buff *skb)
{
int ret;
if (skb_defer_rx_timestamp(skb))
return NET_RX_SUCCESS;
rcu_read_lock();
...
...
ret = __netif_receive_skb(skb);
}
__netif_receive_skb并没有其他多余的处理逻辑,主要调用 __netif_receive_skb_core
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
struct packet_type *ptype, *pt_prev;
rx_handler_func_t *rx_handler;
struct net_device *orig_dev;
struct net_device *null_or_dev;
bool deliver_exact = false;
int ret = NET_RX_DROP;
__be16 type;
/*......*/
orig_dev = skb->dev;
skb_reset_network_header(skb);
pt_prev = NULL;
skb->skb_iif = skb->dev->ifindex;
// 调用接收设备的rx_handler
// 当一个设备被attach到桥上后,其收到的包的dev是他自己,他的x_handler在加入网桥的时候
// 被br_add_if设置成br_handle_frame,下面执行的实际是br_handle_frame这个函数,
// 也就是进入了网桥的处理逻辑了
rx_handler = rcu_dereference(skb->dev->rx_handler);
if (rx_handler) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
// 调用设备的处理回调函数,对于网桥上的设备就是调用br_handle_frame
switch (rx_handler(&skb)) {
case RX_HANDLER_CONSUMED:
ret = NET_RX_SUCCESS;
goto out;
case RX_HANDLER_ANOTHER:
goto another_round;
case RX_HANDLER_EXACT:
deliver_exact = true;
case RX_HANDLER_PASS:
break;
default:
BUG();
}
}
/*根据 skb->protocol传递给上层协议*/
type = skb->protocol;
list_for_each_entry_rcu(ptype,&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
if (ptype->type == type && (ptype->dev == null_or_dev || ptype->dev == skb->dev ||ptype->dev == orig_dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
if (pt_prev) {
if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
goto drop;
else
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
} else {
drop:
atomic_long_inc(&skb->dev->rx_dropped);
kfree_skb(skb);
ret = NET_RX_DROP;
}
out:
return ret;
}
网桥逻辑处理回调函数:skb->dev 关联 port 关联 bridge
即:skb->dev->p->br
rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
{
// 桥的port对象
struct net_bridge_port *p;
struct sk_buff *skb = *pskb;
const unsigned char *dest = eth_hdr(skb)->h_dest;
br_should_route_hook_t *rhook;
if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
return RX_HANDLER_PASS;
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
goto drop;
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb)
return RX_HANDLER_CONSUMED;
// 根据dev获取对应的bridge port
p = br_port_get_rcu(skb->dev);
/*特殊目的mac地址的处理*/
if (unlikely(is_link_local_ether_addr(dest))) {
switch (dest[5]) {
case 0x00: /* Bridge Group Address */
// If STP is turned off,then must forward to keep loop detection
// 这里根据port来获取br
if (p->br->stp_enabled == BR_NO_STP)
goto forward;
break;
case 0x01: /* IEEE MAC (Pause) */
goto drop;
default:
// Allow selective forwarding for most other protocols
// 这里根据port来获取br
if (p->br->group_fwd_mask & (1u << dest[5]))
goto forward;
}
if (NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
NULL, br_handle_local_finish)) {
return RX_HANDLER_CONSUMED; /* consumed by filter */
} else {
*pskb = skb;
return RX_HANDLER_PASS; /* continue processing */
}
}
/*转发逻辑*/
forward:
switch (p->state) {
case BR_STATE_FORWARDING:
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:
// skb的目的mac和bridge的mac一样,则将skb发往本机协议栈
// 这里根据port来获取br
if (ether_addr_equal(p->br->dev->dev_addr, dest))
skb->pkt_type = PACKET_HOST;
/*NF_BR_PRE_ROUTING hook点*/
NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,br_handle_frame_finish);
break;
default:
drop:
kfree_skb(skb);
}
return RX_HANDLER_CONSUMED;
}