Linux 虛擬網絡設備之 bridge

本文將通過實際的例子來一步一步解釋 bridge 是如何工作的。

什麼是 bridge?

首先,bridge 是一個虛擬網絡設備,所以具有網絡設備的特徵,可以配置 IP、MAC 地址等;其次,bridge 是一個虛擬交換機,和物理交換機有類似的功能。

對於普通的網絡設備來說,只有兩端,從一端進來的數據會從另一端出去,如物理網卡從外面網絡中收到的數據會轉發給內核協議棧,而從協議棧過來的數據會轉發到外面的物理網絡中。

而 bridge 不同,bridge 有多個端口,數據可以從任何端口進來,進來之後從哪個口出去和物理交換機的原理差不多,要看 mac 地址。

創建 bridge

我們先用 iproute2 創建一個 bridge:

dev@debian:~$ sudo ip link add name br0 type bridge
dev@debian:~$ sudo ip link set br0 up

當剛創建一個 bridge 時,它是一個獨立的網絡設備,只有一個端口連着協議棧,其它的端口啥都沒連,這樣的 bridge 沒有任何實際功能,如下圖所示:

+----------------------------------------------------------------+
|                                                                |
|       +------------------------------------------------+       |
|       |             Newwork Protocol Stack             |       |
|       +------------------------------------------------+       |
|              ↑                                ↑                |
|..............|................................|................|
|              ↓                                ↓                |
|        +----------+                     +------------+         |
|        |   eth0   |                     |     br0    |         |
|        +----------+                     +------------+         |
| 192.168.3.21 ↑                                                 |
|              |                                                 |
|              |                                                 |
+--------------|-------------------------------------------------+
               ↓
         Physical Network

這裏假設 eth0 是我們的物理網卡,IP 地址是 192.168.3.21,網關是 192.168.3.1

將 bridge 和 veth 設備相連

創建一對 veth 設備,並配置上 IP

dev@debian:~$ sudo ip link add veth0 type veth peer name veth1
dev@debian:~$ sudo ip addr add 192.168.3.101/24 dev veth0
dev@debian:~$ sudo ip addr add 192.168.3.102/24 dev veth1
dev@debian:~$ sudo ip link set veth0 up
dev@debian:~$ sudo ip link set veth1 up

將 veth0 連上 br0

dev@debian:~$ sudo ip link set dev veth0 master br0
#通過bridge link命令可以看到br0上連接了哪些設備
dev@debian:~$ sudo bridge link
6: veth0 state UP : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2

這時候,網絡就變成了這個樣子:

+----------------------------------------------------------------+
|                                                                |
|       +------------------------------------------------+       |
|       |             Newwork Protocol Stack             |       |
|       +------------------------------------------------+       |
|            ↑            ↑              ||
|............|............|..............|............|..........|
|            ↓            ↓              ↓            ↓          |
|        +------+     +--------+     +-------+    +-------+      |
|        | .3.21|     |        |     | .3.101|    | .3.102|      |
|        +------+     +--------+     +-------+    +-------+      |
|        | eth0 |     |   br0  |<--->| veth0 |    | veth1 |      |
|        +------+     +--------+     +-------+    +-------+      |
|            ↑                           ↑            ↑          |
|            |                           |            |          |
|            |                           +------------+          |
|            |                                                   |
+------------|---------------------------------------------------+
             ↓
     Physical Network

這裏爲了畫圖方便,省略了 IP 地址前面的 192.168,比如. 3.21 就表示 192.168.3.21

br0 和 veth0 相連之後,發生了幾個變化:

相當於 bridge 在 veth0 和協議棧之間插了一腳,在 veth0 上面做了點小動作,將 veth0 本來要轉發給協議棧的數據給攔截了,全部轉發給 bridge 了,同時 bridge 也可以向 veth0 發數據。

下面來檢驗一下是不是這樣的:

通過 veth0 ping veth1 失敗:

dev@debian:~$ ping -c 1 -I veth0 192.168.3.102
PING 192.168.2.1 (192.168.2.1) from 192.168.2.11 veth0: 56(84) bytes of data.
From 192.168.2.11 icmp_seq=1 Destination Host Unreachable

--- 192.168.2.1 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

爲什麼 veth0 加入了 bridge 之後,就 ping 不通 veth2 了呢?先抓包看看:

#由於veth0的arp緩存裏面沒有veth1的mac地址,所以ping之前先發arp請求
#從veth1上抓包來看,veth1收到了arp請求,並且返回了應答
dev@debian:~$ sudo tcpdump -n -i veth1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth1, link-type EN10MB (Ethernet), capture size 262144 bytes
21:43:48.353509 ARP, Request who-has 192.168.3.102 tell 192.168.3.101, length 28
21:43:48.353518 ARP, Reply 192.168.3.102 is-at 26:58:a2:57:37:e9, length 28

#從veth0上抓包來看,數據包也發出去了,並且也收到了返回
dev@debian:~$ sudo tcpdump -n -i veth0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
21:44:09.775392 ARP, Request who-has 192.168.3.102 tell 192.168.3.101, length 28
21:44:09.775400 ARP, Reply 192.168.3.102 is-at 26:58:a2:57:37:e9, length 28

#再看br0上的數據包,發現只有應答
dev@debian:~$ sudo tcpdump -n -i br0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on br0, link-type EN10MB (Ethernet), capture size 262144 bytes
21:45:48.225459 ARP, Reply 192.168.3.102 is-at 26:58:a2:57:37:e9, length 28

從上面的抓包可以看出,去和回來的流程都沒有問題,問題就出在 veth0 收到應答包後沒有給協議棧,而是給了 br0,於是協議棧得不到 veth1 的 mac 地址,從而通信失敗。

給 bridge 配上 IP

通過上面的分析可以看出,給 veth0 配置 IP 沒有意義,因爲就算協議棧傳數據包給 veth0,應答包也回不來。這裏我們就將 veth0 的 IP 讓給 bridge。

dev@debian:~$ sudo ip addr del 192.168.3.101/24 dev veth0
dev@debian:~$ sudo ip addr add 192.168.3.101/24 dev br0

於是網絡變成了這樣子:

+----------------------------------------------------------------+
|                                                                |
|       +------------------------------------------------+       |
|       |             Newwork Protocol Stack             |       |
|       +------------------------------------------------+       |
|            ↑            ↑                           ↑          |
|............|............|...........................|..........|
|            ↓            ↓                           ↓          |
|        +------+     +--------+     +-------+    +-------+      |
|        | .3.21|     | .3.101 |     |       |    | .3.102|      |
|        +------+     +--------+     +-------+    +-------+      |
|        | eth0 |     |   br0  |<--->| veth0 |    | veth1 |      |
|        +------+     +--------+     +-------+    +-------+      |
|            ↑                           ↑            ↑          |
|            |                           |            |          |
|            |                           +------------+          |
|            |                                                   |
+------------|---------------------------------------------------+
             ↓
     Physical Network

其實 veth0 和協議棧之間還是有聯繫的,但由於 veth0 沒有配置 IP,所以協議棧在路由的時候不會將數據包發給 veth0,就算強制要求數據包通過 veth0 發送出去,但由於 veth0 從另一端收到的數據包只會給 br0,所以協議棧還是沒法收到相應的 arp 應答包,導致通信失敗。這裏爲了表達更直觀,將協議棧和 veth0 之間的聯繫去掉了,veth0 相當於一根網線。

再通過 br0 ping 一下 veth1,結果成功

dev@debian:~$ ping -c 1 -I br0 192.168.3.102
PING 192.168.3.102 (192.168.3.102) from 192.168.3.101 br0: 56(84) bytes of data.
64 bytes from 192.168.3.102: icmp_seq=ttl=64 time=0.121 ms

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

但 ping 網關還是失敗,因爲這個 bridge 上只有兩個網絡設備,分別是 192.168.3.101 和 192.168.3.102,br0 不知道 192.168.3.1 在哪。

dev@debian:~$ ping -c 1 -I br0 192.168.3.1
PING 192.168.3.1 (192.168.3.1) from 192.168.3.101 br0: 56(84) bytes of data.
From 192.168.3.101 icmp_seq=1 Destination Host Unreachable

--- 192.168.3.1 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

將物理網卡添加到 bridge

將 eth0 添加到 br0 上:

dev@debian:~$ sudo ip link set dev eth0 master br0
dev@debian:~$ sudo 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

br0 根本不區分接入進來的是物理設備還是虛擬設備,對它來說都一樣的,都是網絡設備,所以當 eth0 加入 br0 之後,落得和上面 veth0 一樣的下場,從外面網絡收到的數據包將無條件的轉發給 br0,自己變成了一根網線。

這時通過 eth0 來 ping 網關失敗,但由於 br0 通過 eth0 這根網線連上了外面的物理交換機,所以連在 br0 上的設備都能 ping 通網關,這裏連上的設備就是 veth1 和 br0 自己,veth1 是通過 veth0 這根網線連上去的,而 br0 可以理解爲自己有一塊自帶的網卡。

#通過eth0來ping網關失敗
dev@debian:~$ ping -c 1 -I eth0 192.168.3.1
PING 192.168.3.1 (192.168.3.1) from 192.168.3.21 eth0: 56(84) bytes of data.
From 192.168.3.21 icmp_seq=1 Destination Host Unreachable

--- 192.168.3.1 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

#通過br0來ping網關成功
dev@debian:~$ ping -c 1 -I br0 192.168.3.1
PING 192.168.3.1 (192.168.3.1) from 192.168.3.101 br0: 56(84) bytes of data.
64 bytes from 192.168.3.1: icmp_seq=ttl=64 time=27.5 ms

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

#通過veth1來ping網關成功
dev@debian:~$ ping -c 1 -I veth1 192.168.3.1
PING 192.168.3.1 (192.168.3.1) from 192.168.3.102 veth1: 56(84) bytes of data.
64 bytes from 192.168.3.1: icmp_seq=ttl=64 time=68.8 ms

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

由於 eth0 已經變成了和網線差不多的功能,所以在 eth0 上配置 IP 已經沒有什麼意義了,並且還會影響協議棧的路由選擇,比如如果上面 ping 的時候不指定網卡的話,協議棧有可能優先選擇 eth0,導致 ping 不通,所以這裏需要將 eth0 上的 IP 去掉。

#在本人的測試機器上,由於eth0上有IP,
#訪問192.168.3.0/24網段時,會優先選擇eth0
dev@debian:~$ sudo route -v
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         192.168.3.1     0.0.0.0         UG    0      0        0 eth0
link-local      *               255.255.0.0     U     1000   0        0 eth0
192.168.3.0     *               255.255.255.0   U     0      0        0 eth0
192.168.3.0     *               255.255.255.0   U     0      0        0 veth1
192.168.3.0     *               255.255.255.0   U     0      0        0 br0

#由於eth0已結接入了br0,所有它收到的數據包都會轉發給br0,
#於是協議棧收不到arp應答包,導致ping失敗
dev@debian:~$ ping -c 1 192.168.3.1
PING 192.168.3.1 (192.168.3.1) 56(84) bytes of data.
From 192.168.3.21 icmp_seq=1 Destination Host Unreachable

--- 192.168.3.1 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

#將eth0上的IP刪除掉
dev@debian:~$ sudo ip addr del 192.168.3.21/24 dev eth0

#再ping一次,成功
dev@debian:~$ ping -c 1 192.168.3.1
PING 192.168.3.1 (192.168.3.1) 56(84) bytes of data.
64 bytes from 192.168.3.1: icmp_seq=ttl=64 time=3.91 ms

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

#這是因爲eth0沒有IP之後,路由表裏面就沒有它了,於是數據包會從veth1出去
dev@debian:~$ sudo route -v
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.3.0     *               255.255.255.0   U     0      0        0 veth1
192.168.3.0     *               255.255.255.0   U     0      0        0 br0
#從這裏也可以看出,由於原來的默認路由走的是eth0,所以當eth0的IP被刪除之後,
#默認路由不見了,想要連接192.168.3.0/24以外的網段的話,需要手動將默認網關加回來

#添加默認網關,然後再ping外網成功
dev@debian:~$ sudo ip route add default via 192.168.3.1
dev@debian:~$ ping -c 1 baidu.com
PING baidu.com (111.13.101.208) 56(84) bytes of data.
64 bytes from 111.13.101.208: icmp_seq=ttl=51 time=30.6 ms

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

經過上面一系列的操作後,網絡變成了這個樣子:

+----------------------------------------------------------------+
|                                                                |
|       +------------------------------------------------+       |
|       |             Newwork Protocol Stack             |       |
|       +------------------------------------------------+       |
|                         ↑                           ↑          |
|.........................|...........................|..........|
|                         ↓                           ↓          |
|        +------+     +--------+     +-------+    +-------+      |
|        |      |     | .3.101 |     |       |    | .3.102|      |
|        +------+     +--------+     +-------+    +-------+      |
|        | eth0 |<--->|   br0  |<--->| veth0 |    | veth1 |      |
|        +------+     +--------+     +-------+    +-------+      |
|            ↑                           ↑            ↑          |
|            |                           |            |          |
|            |                           +------------+          |
|            |                                                   |
+------------|---------------------------------------------------+
             ↓
     Physical Network

上面的操作中有幾點需要注意:

bridge 必須要配置 IP 嗎?

在我們常見的物理交換機中,有可以配置 IP 和不能配置 IP 兩種,不能配置 IP 的交換機一般通過 com 口連上去做配置(更簡單的交換機連 com 口的沒有,不支持任何配置),而能配置 IP 的交換機可以在配置好 IP 之後,通過該 IP 遠程連接上去做配置,從而更方便。

bridge 就屬於後一種交換機,自帶虛擬網卡,可以配置 IP,該虛擬網卡一端連在 bridge 上,另一端跟協議棧相連。和物理交換機一樣,bridge 的工作不依賴於該虛擬網卡,但 bridge 工作不代表機器能連上網,要看組網方式。

刪除 br0 上的 IP:

dev@debian:~$ sudo ip addr del 192.168.3.101/24 dev br0

於是網絡變成了這樣子,相當於 br0 的一個端口通過 eth0 連着交換機,另一個端口通過 veth0 連着 veth1:

+----------------------------------------------------------------+
|                                                                |
|       +------------------------------------------------+       |
|       |             Newwork Protocol Stack             |       |
|       +------------------------------------------------+       |
||
|.....................................................|..........|
||
|        +------+     +--------+     +-------+    +-------+      |
|        |      |     |        |     |       |    | .3.102|      |
|        +------+     +--------+     +-------+    +-------+      |
|        | eth0 |<--->|   br0  |<--->| veth0 |    | veth1 |      |
|        +------+     +--------+     +-------+    +-------+      |
|            ↑                           ↑            ↑          |
|            |                           |            |          |
|            |                           +------------+          |
|            |                                                   |
+------------|---------------------------------------------------+
             ↓
     Physical Network

ping 網關成功,說明這種情況下 br0 不配置 IP 對通信沒有影響,數據包還能從 veth1 出去:

dev@debian:~$ ping -c 1 192.168.3.1
PING 192.168.3.1 (192.168.3.1) 56(84) bytes of data.
64 bytes from 192.168.3.1: icmp_seq=ttl=64 time=1.24 ms

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

上面如果沒有 veth0 和 veth1 的話,刪除 br0 上的 IP 後,網絡將會不通,因爲沒有設備和協議棧完全相連

bridge 常用場景

上面通過例子展示了 bridge 的功能,但例子中的那種部署方式沒有什麼實際用途,還不如在一個網卡上配置多個 IP 地址來的直接。這裏來介紹兩種常見的部署方式。

虛擬機

虛擬機通過 tun/tap 或者其它類似的虛擬網絡設備,將虛擬機內的網卡同 br0 連接起來,這樣就達到和真實交換機一樣的效果,虛擬機發出去的數據包先到達 br0,然後由 br0 交給 eth0 發送出去,數據包都不需要經過 host 機器的協議棧,效率高。

+----------------------------------------------------------------+-----------------------------------------+-----------------------------------------+
|                          Host                                  |              VirtualMachine1            |              VirtualMachine2            |
|                                                                |                                         |                                         |
|       +------------------------------------------------+       |       +-------------------------+       |       +-------------------------+       |
|       |             Newwork Protocol Stack             |       |       |  Newwork Protocol Stack |       |       |  Newwork Protocol Stack |       |
|       +------------------------------------------------+       |       +-------------------------+       |       +-------------------------+       |
||||
|..........................|.....................................|...................|.....................|....................|....................|
||||
|                     +--------+                                 |               +-------+                 |                +-------+                |
|                     | .3.101 |                                 |               | .3.102|                 |                | .3.103|                |
|        +------+     +--------+     +-------+                   |               +-------+                 |                +-------+                |
|        | eth0 |<--->|   br0  |<--->|tun/tap|                   |               | eth0  |                 |                | eth0  |                |
|        +------+     +--------+     +-------+                   |               +-------+                 |                +-------+                |
|            ↑             ↑             ↑                       |||
|            |             |             +-------------------------------------------+                     |                    |                    |
|            ||                                         |                    |                    |
|            |         +-------+                                 |                                         |                    |                    |
|            |         |tun/tap|                                 |                                         |                    |                    |
|            |         +-------+                                 |                                         |                    |                    |
|            ||                                         |                    |                    |
|            |             +-------------------------------------------------------------------------------|--------------------+                    |
|            |                                                   |                                         |                                         |
|            |                                                   |                                         |                                         |
|            |                                                   |                                         |                                         |
+------------|---------------------------------------------------+-----------------------------------------+-----------------------------------------+
             ↓
     Physical Network  (192.168.3.0/24)

docker

由於容器運行在自己單獨的 network namespace 裏面,所以都有自己單獨的協議棧,情況和上面的虛擬機差不多,但它採用了另一種方式來和外界通信:

+----------------------------------------------------------------+-----------------------------------------+-----------------------------------------+
|                          Host                                  |              Container 1                |              Container 2                |
|                                                                |                                         |                                         |
|       +------------------------------------------------+       |       +-------------------------+       |       +-------------------------+       |
|       |             Newwork Protocol Stack             |       |       |  Newwork Protocol Stack |       |       |  Newwork Protocol Stack |       |
|       +------------------------------------------------+       |       +-------------------------+       |       +-------------------------+       |
|            ↑             ↑                                     |||
|............|.............|.....................................|...................|.....................|....................|....................|
|            ↓             ↓                                     |||
|        +------+     +--------+                                 |               +-------+                 |                +-------+                |
|        |.3.101|     |  .9.1  |                                 |               |  .9.2 |                 |                |  .9.3 |                |
|        +------+     +--------+     +-------+                   |               +-------+                 |                +-------+                |
|        | eth0 |     |   br0  |<--->|  veth |                   |               | eth0  |                 |                | eth0  |                |
|        +------+     +--------+     +-------+                   |               +-------+                 |                +-------+                |
|            ↑             ↑             ↑                       |||
|            |             |             +-------------------------------------------+                     |                    |                    |
|            ||                                         |                    |                    |
|            |         +-------+                                 |                                         |                    |                    |
|            |         |  veth |                                 |                                         |                    |                    |
|            |         +-------+                                 |                                         |                    |                    |
|            ||                                         |                    |                    |
|            |             +-------------------------------------------------------------------------------|--------------------+                    |
|            |                                                   |                                         |                                         |
|            |                                                   |                                         |                                         |
|            |                                                   |                                         |                                         |
+------------|---------------------------------------------------+-----------------------------------------+-----------------------------------------+
             ↓
     Physical Network  (192.168.3.0/24)

容器中配置網關爲. 9.1,發出去的數據包先到達 br0,然後交給 host 機器的協議棧,由於目的 IP 是外網 IP,且 host 機器開啓了 IP forward 功能,於是數據包會通過 eth0 發送出去,由於. 9.1 是內網 IP,所以一般發出去之前會先做 NAT 轉換(NAT 轉換和 IP forward 功能都需要自己配置)。由於要經過 host 機器的協議棧,並且還要做 NAT 轉換,所以性能沒有上面虛擬機那種方案好,優點是容器處於內網中,安全性相對要高點。(由於數據包統一由 IP 層從 eth0 轉發出去,所以不存在 mac 地址的問題,在無線網絡環境下也工作良好)

上面兩種部署方案中,同一網段的每個網卡都有自己單獨的協議棧,所以不存在上面說的多個 ARP 的問題

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/BMZpRK0ft6bMw1tMtYBSxg