0%

Linux Network Bridge

Bridge 是一个虚拟网络设备,所以具有网络设备的特征,可以配置 IP、MAC 地址等;其次,Bridge 是一个虚拟交换机,和物理交换机有类似的功能。对于普通的网络设备来说,只有两端,从一端进来的数据会从另一端出去,如物理网卡从外面网络中收到的数据会转发给内核协议栈,而从协议栈过来的数据会转发到外面的物理网络中。而 Bridge 不同,Bridge 有多个端口,数据可以从任何端口进来,进来之后从哪个口出去和物理交换机的原理差不多,要看 MAC 地址。

深入理解 Bridge

基本信息简介

本机 IP:172.30.234.97/20(物理网卡 eth0)
网关 IP:172.30.224.1/20

Bridge IP:172.30.224.13/20
Veth0 IP:172.30.224.11/20
Veth1 IP:172.30.224.12/20

注意 Bridge 要和网关保持在同一子网

创建 Bridge

当刚创建一个 bridge 时,它是一个独立的网络设备,只有一个端口连着协议栈,其它的端口啥都没连,这样的 bridge 没有任何实际功能。

$ ip link add name br0 type bridge
$ ip link set br0 up

创建 Veth Peer

创建一对 veth 设备,并配置 IP

$ ip link add veth0 type veth peer name veth1
$ ip addr add 172.30.224.11/20 dev veth0
$ ip addr add 172.30.224.12/20 dev veth1
$ ip link set veth0 up
$ ip link set veth1 up

在 Ubuntu 中,处于同一 Network Namespace 的 Veth Peer 无法被 Ping 通,可以通过以下方式解决(或将两个 Veth 置于不同 NS 下)

echo 1 > /proc/sys/net/ipv4/conf/veth1/accept_local
echo 1 > /proc/sys/net/ipv4/conf/veth0/accept_local
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/veth0/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/veth1/rp_filter

将 Bridge 和 Veth 设备相连

将 veth0 连上 br0

$ ip link set dev veth0 master br0

查看 bridge 连接情况

$ bridge link
7: veth0@veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2
# 可以看到 veth0 连接着 br0,且 veth1 是 veth0 的对端

这时候,网络就变成了这个样子:

br0 和 veth0 相连之后,发生了几个变化:

  • br0 和 veth0 之间连接起来了,并且是双向的通道
  • 协议栈和 veth0 之间变成了单通道,协议栈能发数据给 veth0,但 veth0 从外面收到的数据不会转发给协议栈
  • br0 的 mac 地址变成了 veth0 的 mac 地址

相当于 bridge 将 veth0 本来要转发给协议栈的数据给拦截,然后从 bridge 转发。

验证 Bridge 作用

veth0 ping veth1

通过 veth0 ping veth1 失败:

$ ping -c 1 -I veth0 192.168.3.102
ping: Warning: source address might be selected on device other than: veth0
PING 192.168.3.102 (192.168.3.102) from 172.30.50.129 veth0: 56(84) bytes of data.

--- 192.168.3.102 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

为什么 veth0 加入了 bridge 之后,就 ping 不通 veth2 了呢? 先抓包看看:

# 由于veth0的arp缓存里面没有veth1的mac地址,所以ping之前先发arp请求

# 从veth1上抓包来看,veth1收到了arp请求(request),并且返回了应答(reply)
$ tcpdump -eni veth1
listening on veth1, link-type EN10MB (Ethernet), capture size 262144 bytes
16:09:02.306393 be:f4:3d:23:38:ee > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.3.102 tell 172.30.50.129, length 28
16:09:02.306434 06:e0:82:e4:49:ea > be:f4:3d:23:38:ee, ethertype ARP (0x0806), length 42: Reply 192.168.3.102 is-at 06:e0:82:e4:49:ea, length 28

# 从veth0上抓包来看,数据包也发出去了,并且也收到了返回
$ tcpdump -eni veth0
listening on veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:20:30.926201 be:f4:3d:23:38:ee > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.168.3.102 tell 172.30.50.129, length 28
16:20:30.926222 06:e0:82:e4:49:ea > be:f4:3d:23:38:ee, ethertype ARP (0x0806), length 42: Reply 192.168.3.102 is-at 06:e0:82:e4:49:ea, length 28

#再看br0上的数据包,发现只有应答
$ tcpdump -eni br0
listening on br0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:25:00.590601 06:e0:82:e4:49:ea > be:f4:3d:23:38:ee, ethertype ARP (0x0806), length 42: Reply 192.168.3.102 is-at 06:e0:82:e4:49:ea, length 28
16:25:01.595581 06:e0:82:e4:49:ea > be:f4:3d:23:38:ee, ethertype ARP (0x0806), length 42: Reply 192.168.3.102 is-at 06:e0:82:e4:49:ea, length 28

从上面的抓包可以看出,去和回来的流程都没有问题,问题就出在 veth0 收到应答包后没有给协议栈,而是给了 br0,于是协议栈得不到 veth1 的 mac 地址,从而通信失败。通过分析可以看出,给 veth0 配置IP没有意义,因为就算协议栈传数据包给 veth0,应答包也回不来。

# 可以删除veth0的IP,其结果可造成eth0与网络协议栈的链接断裂
$ ip addr del 192.168.3.101/24 dev veth0

其实 veth0 和协议栈之间还是有联系的,但由于 veth0 没有配置IP,所以协议栈在路由的时候不会将数据包发给 veth0,就算强制要求数据包通过 veth0 发送出去,但由于 veth0 从另一端收到的数据包只会给 br0,所以协议栈还是没法收到相应的 arp 应答包,导致通信失败。此时 veth0 其实就相当于一根网线。

br0 ping veth1

由于 br0 需要发送和接收数据,因此为其配置 IP

$ ip addr add 172.30.224.13/20 dev br0

于是网络变成了这样子:

通过 br0 ping 一下 veth1,结果成功

$ ping -c 1 -I br0 192.168.3.102
PING 192.168.3.102 (192.168.3.102) from 192.168.3.100 br0: 56(84) bytes of data.
64 bytes from 192.168.3.102: icmp_seq=1 ttl=64 time=0.037 ms

--- 192.168.3.102 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.037/0.037/0.037/0.000 ms

将物理网卡连接至 Bridge

虽然连接 veth1 没有问题,但此时连接外网仍旧是不通的,因为 Bridge 上只有 veth0 这一个设备,与外网根本不连通。

$ bridge link
7: veth0@veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2

即通过 eth0(也就是连接外网的物理网卡)可访问到 www.baidu.com,但通过 br0 是不可以的。

# 通过br0访问外网,不能连通
$ ping -c 1 -I br0 www.baidu.com
PING www.a.shifen.com (39.156.66.18) from 192.168.3.100 br0: 56(84) bytes of data.

--- www.a.shifen.com ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

# 通过eth0访问外网,可以连通
$ ping -c 1 -I eth0 www.baidu.com
PING www.a.shifen.com (39.156.66.18) from 172.30.50.129 eth0: 56(84) bytes of data.
64 bytes from 39.156.66.18 (39.156.66.18): icmp_seq=1 ttl=54 time=17.8 ms

--- www.a.shifen.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 17.784/17.784/17.784/0.000 ms

因此,我们需要将物理网卡 eth0 添加到 br0 上

$ ip link set dev eth0 master br0
$ bridge link
2: eth0 state UP : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 4
6: veth0 state UP : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2

这时通过 eth0 来 ping 外网失败,但虽然 br0 通过 eth0 这根网线连上了外网的物理交换机,但依然 ping 不通外网。

这是此时的路由表

$ netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         172.30.224.1    0.0.0.0         UG        0 0          0 eth0
172.30.224.0    0.0.0.0         255.255.240.0   U         0 0          0 eth0
172.30.224.0    0.0.0.0         255.255.240.0   U         0 0          0 veth0
172.30.224.0    0.0.0.0         255.255.240.0   U         0 0          0 veth1
172.30.224.0    0.0.0.0         255.255.240.0   U         0 0          0 br0

由于与上面讲的通过 veth peer 接入,veth0 无法 ping 通 veth1,原因是 br0 截留了网络包。这个也是同样的道理。

# 删除eth0的ip(其实删除其对应的默认路由也可,且eth0的ip无用)
$ ip addr del 172.30.234.97/20 dev eth0
# 添加默认网关,并将br0设置为出口设备
$ route add default gw 172.30.224.1 dev br0

如果出现 ping: www.baidu.com: Temporary failure in name resolution,可以查看一下 DNS 配置。

# 直接替换(该文件有其他配置慎用)
$ echo 'nameserver 8.8.8.8' > /etc/resolv.conf

下面测试下网络连通性

# br0可连接外网
$ ping -c 1 -I br0 www.baidu.com
PING www.wshifen.com (103.235.46.39) from 172.30.224.13 br0: 56(84) bytes of data.
64 bytes from 103.235.46.39 (103.235.46.39): icmp_seq=1 ttl=44 time=66.5 ms

--- www.wshifen.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 66.504/66.504/66.504/0.000 ms

# eth0不可连接外网
$ ping -c 1 -I eth0 www.baidu.com
ping: Warning: source address might be selected on device other than: eth0
PING www.wshifen.com (103.235.46.39) from 172.30.224.13 eth0: 56(84) bytes of data.
^C
--- www.wshifen.com ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

# 默认走br0的网关
$ ping -c 1 www.baidu.com
PING www.wshifen.com (103.235.46.39) 56(84) bytes of data.
64 bytes from 103.235.46.39 (103.235.46.39): icmp_seq=1 ttl=44 time=49.6 ms

--- www.wshifen.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 49.574/49.574/49.574/0.000 ms

Bridge 常用场景

虚拟机

虚拟机通过 tun/tap 或者其它类似的虚拟网络设备,将虚拟机内的网卡同 br0 连接起来,这样就达到和真实交换机一样的效果,虚拟机发出去的数据包先到达 br0,然后由 br0 交给 eth0 发送出去,数据包都不需要经过 Host 机器的协议栈,效率高。

docker

由于容器运行在自己单独的 network namespace 里面,所以都有自己单独的协议栈,情况和上面的虚拟机差不多,但它采用了另一种方式来和外界通信:

容器发送的数据包先到达 br0,然后交给 Host 机器的协议栈,由于目的 IP 是外网 IP,需要 Host 机器开启 IP forward功能,且配置好 NAT 转换。