1. 程式人生 > >Neutron 理解(14):Neutron ML2 + Linux bridge + VxLAN 組網

Neutron 理解(14):Neutron ML2 + Linux bridge + VxLAN 組網

學習 Neutron 系列文章:

1. 基礎知識

1.1 VXLAN 和 Linux 以及 Linux bridge 的關係

    VXLAN 是一個新興的SDN 標準,它定義了一種新的 overlay 網路,它主要的創造者是 VMware, Cisco 和 Arista。它被設計來消除虛擬化網路世界中的 VLAN 數目的限制。VXLAN 本身是一個多播標準,但是大多數的企業既不情願啟用多播,而且許多網路裝置也不支援多播。因此,許多 VXLAN 的實現,比如 Linux vxlan, 就既支援多播也支援單播。VXLAN 的兩個主要概念是 VTEP 和 VXLAN ID。 其中,VTEP 負責解包和封包,以及包的傳送。它可以由專有硬體來實現,也可以使用純軟體實現。目前比較成熟的軟體實現的 VTEP 包括:

  • 3.9 版本及以後的帶 vxlan 核心模組的 Linux
  • Open vSwitch
  • VMware vSphere

   以 vSphere 為例,下面是它所實現的 VXLAN 虛擬二層網路的一個參考架構:

可以看出來,這和 Open vSwitch 的網路架構非常類似。對於 Linux 作為 VTEP 來說,使用 linux bridge 替代 VMware 的 VDS 或者 Open vSwitch,其它也是一樣的了。

特點:

  • Linux vxlan 建立一個 UDP Socket,預設在 8472 埠監聽
  • Linux vxlan 在 UDP socket 上接收到 vxlan 包後,解包,然後根據其中的 vxlan ID 將它轉給某個 vxlan interface,然後再通過它所連線的linux bridge 轉給虛機
  • Linux vxlan 在收到虛機的多播或者廣播幀後,將其封裝為多播 UDP 包(在使用多播時)或者多個單播包(在使用單播時),在從指定網絡卡發出
  • Linux vxlan 在虛機的單播幀後,如果 fdb 表中包含有目的虛機的 VTEP IP 條目,則將其封裝為單播 UDP 包,否則,通過多播或者多個單播,發到指定的 VTEP
  • Linux vxlan 在配置了 Proxy ARP 時會根據 fdb 表來響應虛機的 ARP 廣播請求

因此:

VXLAN 元件 vSphere Linux Open vSwitch (OVS)
VTEP vSphere Linux OVS
Bridge VDS Linux bridge OVS

1.2 多播和多播組

這是單播、多播和廣播的概念:

  • 單播:使用單播地址來發送一個網路包到一個目的機器。
  • 廣播:使用廣播地址來發送一個網路包到一個網段內的所有機器。
  • 多播:使用多播地址來發送一個網路包到一個已經加入一個多播組內的所有成員,這些成員是可以跨網段的。

一個多播IP網路示例:

多播的一些基礎知識:

  • 多播使用一個特殊的 IP 地址,來將資料從一個傳送至發至多個接收者。IANA 規定的多播地址是 224/4,也就是 224.0.0.0 到 239.255.255.255 區間。
  • 如果網絡卡上啟用了多播,那麼在計算器啟動後,它會預設加入多播組 224.0.0.1,該組內包括該網段內的所有啟用了多播的網絡卡。因此,此時的多播和廣播就是一樣的了。網絡卡使用 Internet Group Management Protocol (IGMP) 協議去通知本地的啟用了多播的路由器它要加入一個多播組。
  • IGMP 是一個 IP 層的協議,非常類似於 ICMP。多播路由器使用該協議來維護多播組及其成員,支援的功能包括加入一個多播組、查詢組成員和傳送組成員報告。多播路由器週期性地傳送 IGMP 查詢給所有的主機,主機通過響應該請求來加入一個或者多個多播組。
  • 多播的路由協議包括 DVMRP (Distance Vector Multicast Routing Protocol) and PIM (Protocol-Independent Multicast),但是,許多的網路並不支援這些協議。
  • 如果 XVLAN 使用多播的話,則需要物理網路包括交換機和路由器支援多播。
  • 多播只支援UDP 作為上層協議,可見多播不是一種可靠的協議

要支援多播,需要物理網路上做一些設定:

  • 往往對虛機使用的資料網路劃分一個單獨的 VALN,從而控制廣播/多播域
  • 配置物理交換機上的 IGMP snooping
  • 配置物理路由器上所在 VLAN 的 IGMP querier,它會響應各主機的多播查詢請求

比如:

1.3 Linux vxlan

在支援 vxlan 的 Linux 主機上,可以建立多個多播或者單播的 vxlan interface,每個interface 是一個 vxlan tunnel 的 endpoint。比如在主機1上:

建立多播 interface 1:ip link add vxlan0 type vxlan id 42 group 239.1.1.1 dev eth1 dstport 4789

建立多播 interface 2:ip link add vxlan2 type vxlan id 43 group 239.1.1.2 dev eth1 dstport 4790

建立單播 interface 3:ip link add vxlan2 type vxlan id 44 dev eth1 port 32768 61000 proxy ageing 300

然後在每一個裝置啟動後,如果使用的是多播地址,vxlan 就會加入相應的多播組,並且建立相應的 UDP socket。

/* Start ageing timer and join group when device is brought up */
static int vxlan_open(struct net_device *dev) #對每個 vxlan dev 使用
{
    ......
    ret = vxlan_sock_add(vxlan); #建立 UDP socket
    if (ret < 0)
        return ret;

    if (vxlan_addr_multicast(&vxlan->default_dst.remote_ip)) {
        ret = vxlan_igmp_join(vxlan); #加入多播組
        ......
        }
    }
    .....
}

在主機2上,使用同樣的 dstport, group 和 vxlan id 建立相應的 vxlan interface,即可建立多個虛擬的 vxlan tunnel。

因此,只要沒有衝突,一個Linux 主機上可以加入多個多播組,建立多個 UDP socket。

2. 為什麼Neutron 要用 Linux bridge agent 而不使用 Open vSwitch agent?

    我們都知道,OpenStack 社群官方的安裝文件的步驟都是以 Open vSwitch 為例子的。而且從OpenStack 使用者調查來看,使用 OVS 的人比使用 linux bridge 多很多。

 

   那還有人使用 Linux bridge 嗎?答案是有的,據我所知,國內廠商比如海雲捷迅就推薦私有云中的兩種配置:Linux bridge + VLAN 以及 Linux bridge + VxLAN。而且,Liberty 版本之前社群官方文件都是使用 neutron-plugin-openvswitch-agent, 但是 Liberty 版本轉為使用 neutron-plugin-linuxbridge-agent, 不知道這背後究竟發生了什麼。國外的 Rackspace 也已經使用Linux bridge 替代 Open vSwitch,請參見下圖:

本段以 Rackspace 為例,說明他們使用 Linux bridge 的理由:

(1)Open vSwitch 目前還存在不少穩定性問題,比如:
  • Kernetl panics 1.10
  • ovs-switched segfaults 1.11
  • 廣播風暴
  • Data corruption 2.01
(2)為什麼可以使用 Linux bridge?
  • 穩定性和可靠性要求:Linux bridge 有十幾年的使用歷史,非常成熟。
  • 易於問題診斷 (troubleshooting)
  • 社群也支援
  • 還是可以使用 Overlay 網路 VxLAN 9需要 Linux 核心 3.9 版本或者以上)
(3)使用 Linux bridge 的侷限性
  • Neutron DVR 還不支援 Linux bridge
  • 不支援 GRE
  • 一些 OVS 提供但是 Neutorn 不支援的功能

(4)長期來看,隨著穩定性的進一步提高,Open vSwitch 會在生產環境中成為主流。

Linux bridge 和 Open vSwitch 的功能對比:

可以看出:

(1)OVS 將各種功能都原生地實現在其中,這在起始階段不可避免地帶來潛在的穩定性和可除錯性問題

(2)Linux bridge 依賴各種其他模組來實現各種功能,而這些模組的釋出時間往往都已經很長,穩定性較高

(3)兩者在核心功能上沒有什麼差距,只是在集中管控和效能優化這一塊OVS有一些新的功能或者優化。但是,從測試結果看,兩者的效能沒有明顯差異:

 

總之,目前,除了 SDN 對集中管控的需求,Linux bridge 是個較好的選擇。

3. 使用 Linux bridge + VXLAN 組網

3.1 配置

在控制、網路和計算節點上修改 /etc/neutron/plugins/ml2/ml2_conf.ini 中的如下配置項:

[ml2]
type_drivers = flat,vlan,gre,vxlan
tenant_network_types = vxlan
mechanism_drivers = linuxbridge

[ml2_type_vxlan]
vni_ranges = 1001:2000

[vxlan]
local_ip = 10.0.0.13
enable_vxlan = true

[securitygroup]
firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver

[agent]
tunnel_types = vxlan

3.2 組網

3.2.1 配置步驟

  1. 建立三個網路,每個網路都啟動DHCP,每個網路建立一個子網
  2. 建立一個 router,連線其中兩個網路的若干子網
  3. 建立多個虛機

3.2.2 租戶網路的構成

 

計算節點1上有三個虛機,分佈在兩個網路上:

(1)兩個 Linux bridge

[email protected]:~# brctl show
bridge name     bridge id               STP enabled     interfaces
brq18fc2ba1-05          8000.9292780d149d       no      tap5d8a021b-64
                                                        tapf64dcdd0-0e
                                                        vxlan-1074
brq243661e7-f7          8000.66239c87f16c       no      tap818682fe-a6
                                                        vxlan-1027

(2)VXLAN 在埠 8472 上建立了一個 UDP Socket

[email protected]:~# netstat -lu
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
udp        0      0 192.168.122.1:domain    *:*
udp        0      0 *:bootps                *:*
udp      768      0 *:8472                  *:*

(3)建立了兩個 vxlan network interface

[email protected]:~# ip -d  link show dev vxlan-1074
14: vxlan-1074: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq18fc2ba1-05 state UNKNOWN mode DEFAULT group default
    link/ether 92:92:78:0d:14:9d brd ff:ff:ff:ff:ff:ff promiscuity 1
    vxlan id 1074 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300
[email protected]:~# ip -d  link show dev vxlan-1027
12: vxlan-1027: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq243661e7-f7 state UNKNOWN mode DEFAULT group default
    link/ether 66:23:9c:87:f1:6c brd ff:ff:ff:ff:ff:ff promiscuity 1
    vxlan id 1027 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300

(4)vxlan interface 使用網絡卡 eth1

網路節點上:

(1)一個 qrouter network namespace

(2)三個 dhcp network namespace,每個 enable DHCP 的網路各一個

(3)三個 Linux bridge,每個 network 一個

(4)三個 vxlan network interface(ip link),每個 network 一個 bridge, 每個 linux bridge 上只有一個 vxlan interface

[email protected]:~/s1#  ip -d  link show dev vxlan-1027
11: vxlan-1027: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq243661e7-f7 state UNKNOWN mode DEFAULT group default
    link/ether a6:6b:39:67:60:68 brd ff:ff:ff:ff:ff:ff promiscuity 1
    vxlan id 1027 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300
[email protected]:~/s1# ip -d  link show dev vxlan-1074
5: vxlan-1074: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq18fc2ba1-05 state UNKNOWN mode DEFAULT group default
    link/ether 8a:e2:2f:2d:e7:48 brd ff:ff:ff:ff:ff:ff promiscuity 1
    vxlan id 1074 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300
[email protected]:~/s1# ip -d  link show dev vxlan-1054
8: vxlan-1054: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq1e7052dc-7f state UNKNOWN mode DEFAULT group default
    link/ether 5e:c7:d4:85:23:fc brd ff:ff:ff:ff:ff:ff promiscuity 1
    vxlan id 1054 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300

 (5)vxlan 在 8472 埠上監聽 UDP 請求

 (6)vxlan interface 的廣播網絡卡是 eth1

3.2.3 網路流量走向

注:紅色線條為計算節點1上的vm3訪問 DHCP Agent 來獲取 IP 地址;藍色線條為同一個網段的 VM3 和 VM2 互訪

4. vxlan + linux bridge 組網的實現

4.1 vxlan interface

4.1.1 建立 vxlan interface

Neutron Linux bridge agent 使用 "ip link add type vxlan" 命令來建立 vxlan interface。這是該命令的幫助資訊:

Command: ['ip link add type vxlan help']
Usage: ... vxlan id VNI [ { group | remote } ADDR ] [ local ADDR ]
                 [ ttl TTL ] [ tos TOS ] [ dev PHYS_DEV ]
                 [ port MIN MAX ] [ [no]learning ]
                 [ [no]proxy ] [ [no]rsc ]
                 [ [no]l2miss ] [ [no]l3miss ]

Where: VNI := 0-16777215                         #network 的 segemntation_id
       ADDR := { IP_ADDRESS | any }              #IP 地址
       TOS  := { NUMBER | inherit }              #可以由 tos 配置項指定
       TTL  := { 1..255 | inherit }              #可以由 ttl 配置項指定,不設定時預設為1

以 vxlan id 1027 group 224.0.0.1 dev eth1 port 32768 61000 ageing 300 為例,它使用如下的配置項:

(1)id 1027,這是 vxlan interface id,其值為 neutron network 的 provider:segmentation_id 屬性。

(2)group 224.0.0.1。表示這個是一個多播 VXLAN interface,使用 Neutron 預設的多播組 224.0.0.1,你可以使用 vxlan_group 配置項指定其它的多播組。需要注意的是:

(a)每個 Neutron linux bridge agent 只能配置一個 vxlan_group 地址,這意味著只能加入一個多播組。

(b)在需要廣播時使用多播組地址為目的地址,比如:

17:01:28.524935 IP (tos 0x0, ttl 1, id 45252, offset 0, flags [none], proto UDP (17), length 78)
    10.0.0.10.49332 > 224.0.0.1.8472: [no cksum] OTV, flags [I] (0x08), overlay 0, instance 1074
ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 70.0.0.105 tell 70.0.0.100, length 28

(c)Neutron linux bridge agent 會優先使用單播(ucast),只有在單播不能使用的時候才會嘗試多播(mcast),而該配置項只有在使用多播時候才有效。看程式碼:

檢查是使用單播還是多播:

if self.vxlan_ucast_supported():
    self.vxlan_mode = lconst.VXLAN_UCAST #使用單播
elif self.vxlan_mcast_supported():
    self.vxlan_mode = lconst.VXLAN_MCAST #使用多播
else:
   raise exceptions.VxlanNetworkUnsupported()

而 vxlan_group 配置項只有在 MCAST 時候才生效:

if self.vxlan_mode == lconst.VXLAN_MCAST:
   args['group'] = cfg.CONF.VXLAN.vxlan_group

而使用單播的條件比較苛刻(可以看程式碼中的詳細條件),基本上(1)如果沒有配置 l2population,則肯定不使用單播 (2)如果配置了l2population,支援 iproute2,vxlan 支援 proxy 等等一系列檢查,都合格是才使用單播。而使用單播時,vxlan interface 沒有 group 屬性,比如:

vxlan id 1074 dev eth1 port 32768 61000 proxy ageing 300

需要注意的是,目前的程式碼只檢查 vxlan interface 的名稱(name)而不檢查具體的屬性包括 group 等,如果你變換 l2population 的配置,導致需要切換使用單播和多播,則需要手工刪除已有的 vxlan interface,然後重啟 neutron linux bridge agent 讓它重建 vxlan interface,否認, 你的租戶網路可能會不通。

(3)dev eth1 是 UDP 出去的網絡卡,由配置項 local_ip 指定。

(4)port 32768 61000 是 linux vxlan 實現的 UDP 源埠號範圍,這個不可以配置。

/* The below used as the min/max for the UDP port range */
>> +#define VXLAN_SRC_PORT_MIN      32768
>> +#define VXLAN_SRC_PORT_MAX      61000

在計算該埠的時候,可以考慮每個虛機的特定屬性,來實現底層轉發網路上多條轉發路徑上的負載均衡。

+/* Compute source port for outgoing packet.
>> + * Currently we use the flow hash.
>> + */
>> +static u16 get_src_port(struct sk_buff *skb)
>> +{
>> +       unsigned int range = (VXLAN_SRC_PORT_MAX - VXLAN_SRC_PORT_MIN) + 1;
>> +       u32 hash = OVS_CB(skb)->flow->hash;
>> +
>> +       return (__force u16)(((u64) hash * range) >> 32) + VXLAN_SRC_PORT_MIN;
>> +}

(5)ageing 300,這是 unreachable-vtep-aging-timer,單位是秒。其含義是,經過學習得到的遠端 VTEP 後面的虛機的 MAC 地址過期時長。

(6)目前 Neutron linux bridge agent 無法配置 VXLAN UDP 埠,只能使用linux的預設埠,具體見這個 ticket:[Juno]: Cannot set the VXLAN UDP destination port to 4789 using Linux Bridge。到目前為止其狀態依然是 “In progress”。一般來講,linux 預設使用的埠號是 8472。修改配置檔案 /etc/modprobe.d/vxlan-port.conf 來向 vxlan 核心模組傳遞埠引數可以修改該埠號,比如如下的配置會使得系統改為使用 4789 埠:

cat /etc/modprobe.d/vxlan-port.conf
options vxlan udp_port=4789 

或者在建立 vxlan interface 時指定 dstport 引數 (這是 Neutron linux bridge fix 的做法)。建立好以後,需要設定其 IP 地址或者加入一個bridge。Neutron 的方案是將它加入到一個 linux bridge 上。然後,將其設定為 up (使用 ip link set up 命令)。

4.1.2 VXLAN interface 的功能

該 interface 會:

(1)建立並連線到一個 UDP 埠 8472 的 socket。IANA 標準化組織規定的埠是 4789,而 Linux 核心為了後向相容需要而使用的埠是 8472.

/* UDP port for VXLAN traffic.
 62  * The IANA assigned port is 4789, but the Linux default is 8472
 63  * for compatibility with early adopters.
 64  */
err = sock_create(AF_INET, SOCK_DGRAM, 0, &vxlan_port->vxlan_rcv_socket);
kernel_bind(vxlan_port->vxlan_rcv_socket, (struct sockaddr *)&sin, sizeof(struct sockaddr_in));
udp_sk(vxlan_port->vxlan_rcv_socket->sk)->encap_rcv = vxlan_rcv;

(2)在支援多播的情況下,加入一個多播組。可使用 netstat -g 命令檢視多播組成員。

(3)虛機經過 linux bridge 由 vxlan 出去的流量,由 vxlan interface 封裝 VXLAN 頭,然後使用 UDP 由指定網絡卡發出

    src_port = udp_flow_src_port(net, skb, 0, 0, true); #計算源 UDP 埠
    md.vni = htonl(be64_to_cpu(tun_key->tun_id) << 8);
    md.gbp = vxlan_ext_gbp(skb);
    vxflags = vxlan_port->exts |(tun_key->tun_flags & TUNNEL_CSUM ? VXLAN_F_UDP_CSUM : 0);

    err = vxlan_xmit_skb(rt, sk, skb, fl.saddr, tun_key->ipv4_dst, #經過 UDP socket 發出
                 tun_key->ipv4_tos, tun_key->ipv4_ttl, df, src_port, dst_port, &md, false, vxflags);

(4)進來的 vxlan 流量,首先到達 UDP 埠,再交給 vxlan 鉤子函式,由 vxlan 做解包處理後,經過 vxlan interface 通過 linux bridge 轉發給虛機

/* Called with rcu_read_lock and BH disabled. */
>> +static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)

注:

(1)以上示例程式碼都是 OVS 對 vxlan 的實現,linux 核心的實現原理其實也差不多。

(2)其實更準確地說以上這些都是Linux vxlan 核心模組的功能,只不過都是通過 vxlan interface 來體現給使用者的。

4.1.3 不使用 l2population 情況下的 Linux vxlan interface 的 FDB 表(forwarding table)

   在不使用 l2population 的情況下,VXLAN 通過多播學習來得到 fdb 表項即(VM-MAC, VTEP-IP)。這個成本是蠻高的。你可以使用 bridge 命令來檢視 FDB,該命令由 iproute2 包提供。我們通過下面的實驗來觀察該過程:

(1)vxlan interface 被建立後,fdb 只有一個表項,就是所有的流量都發往多播組

[email protected]:~# bridge fdb show dev vxlan-1074
c6:a5:bf:5b:67:a3 vlan 0 permanent
00:00:00:00:00:00 dst 224.0.0.1 via eth1 self permanent

(2)連線該 vxlan interface 的 vm1 先獲得ip 地址,然後 ping 另一個網段上的vm2

[email protected]:~# bridge fdb show dev vxlan-1074
fa:16:3e:19:15:fe vlan 0
fa:16:3e:32:35:ef vlan 0
c6:a5:bf:5b:67:a3 vlan 0 permanent
00:00:00:00:00:00 dst 224.0.0.1 via eth1 self permanent
fa:16:3e:32:35:ef dst 10.0.0.10 self
fa:16:3e:19:15:fe dst 10.0.0.10 self

可見,這兩個過程中,vxlan VTETP 學習到了兩個地址:qdhcp 的一個埠和 qrouter 的一個埠

(3)vm1 ping vm3, vm3 在 vm1 同網段,但是在不同的機器上

[email protected]:~# bridge fdb show dev vxlan-1074
fa:16:3e:c4:c8:58 vlan 0
fa:16:3e:19:15:fe vlan 0
fa:16:3e:32:35:ef vlan 0
c6:a5:bf:5b:67:a3 vlan 0 permanent
00:00:00:00:00:00 dst 224.0.0.1 via eth1 self permanent
fa:16:3e:32:35:ef dst 10.0.0.10 self
fa:16:3e:c4:c8:58 dst 10.0.0.14 self
fa:16:3e:19:15:fe dst 10.0.0.10 self

可見它學習到了另一個主機上的VTEP 資訊。

(5)我們也可以看到,這些表項都不是 permanent 的,因此,一定時間後,它們就會因過期被刪除。vxlan interface 的 aging 屬性值決定,預設為5分鐘。因此,五分鐘後,這些學到的表項都會過期了,重新回到狀態(1),開始新的一輪學習過程。

c6:a5:bf:5b:67:a3 vlan 0 permanent
00:00:00:00:00:00 dst 224.0.0.1 via eth1 self permanent

(6)需要注意的是,ip n 表在整個過程中都沒有變化,因為這時候,vxlan 不會承擔 ARP Proxy 的任務。

4.2 使用 Neutorn l2population - fdb 更新

fdb 表通過幫助幫助解決兩個問題來使得 Linux VTEP 可以使用單播而不是需要使用多播:

(1)獲得遠端 VM IP 和 MAC 地址的對映關係,從而本地虛機不需要使用 ARP 廣播來獲取該地址

(2)獲得遠端 VM MAC 和 它的 VTEP IP 的 對映關係,從而使得本地 VTEP 不需要通過多播來獲取該地址

普通情況下,這些對映關係都是 VTEP 使用多播通過地址學習獲得的。

4.2.1 Linux IP neigh 表

    Linux bridge agent 在使用 l2population 的情況下,使用該表來儲存遠端 VM IP 和 MAC 地址的對映關係。要使用 l2population,需要在配置檔案 /etc/neutron/plugins/ml2/ml2_conf.ini 中做如下配置:

[ml2]

mechanism_drivers = linuxbridge,l2population

[vxlan]

l2_population = true

在l2pop 生效後,並且啟用了 arp_reponder 之後,新建的 vxlan interface 上就會增加 “proxy” 功能:

[email protected]:~# ip -d link show dev vxlan-1027
20: vxlan-1027: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master brq243661e7-f7 state UNKNOWN mode DEFAULT group default
    link/ether d6:40:1e:47:38:59 brd ff:ff:ff:ff:ff:ff promiscuity 1
    vxlan id 1027 dev eth1 port 32768 61000 proxy ageing 300

這使得 vxlan interface 能夠通過查詢 ip neighbour table 來響應本地虛機對遠端虛機的 ARP 請求,從而作為這些虛機的 ARP Proxy。

VXLAN Interface 實現 Proxy ARP 的原理:

linux bridge agent 的實現採用的是直接呼叫 ip neighbour 命令列來操作 fdb 表。該命令同樣是 iproute2 包提供的命令之一。

    def add_fdb_ip_entry(self, mac, ip, interface):
        ip_lib.IPDevice(interface).neigh.add(ip, mac)

    def remove_fdb_ip_entry(self, mac, ip, interface):
        ip_lib.IPDevice(interface).neigh.delete(ip, mac)

格式:ip neighbor add REMOTE_VM_IP lladdr REMOTE_VM_MAC dev vx-NET_ID nud permanent。注意這些entry 是 PERMANENT 即永久的,它們不受 vxlan aging 時長的限制。

以 vxlan-1074 interface示例:

[email protected]:~# ip n | grep vxlan-1074
70.0.0.129 dev vxlan-1074 lladdr fa:16:3e:c4:c8:58 PERMANENT #在計算節點2上的虛機
70.0.0.100 dev vxlan-1074 lladdr fa:16:3e:32:35:ef PERMANENT #qdhcp 的 ns-63ee3080-94 interface
70.0.0.1 dev vxlan-1074 lladdr fa:16:3e:19:15:fe PERMANENT   #qrouter 的 qr-cd430335-dd interface

這樣的話,vxlan interface 就可以直接向虛機的 ARP 廣播請求提供 ARP 響應了。但是,這裡只有遠端虛機和 qroute/qdhcp Interface 的條目。對於本機上的同網路虛機,看來 Neutron 是放任 linux bridge 去做廣播了,反正範圍也很小。可見,這裡的目的只是為了減少對外的多播。

再看看 VTEP 的 MAC 地址是如何儲存的:

[email protected]:~# ip n | grep 10.0
10.0.0.10 dev eth1 lladdr 52:54:00:7c:c0:79 STALE
10.0.0.14 dev eth1 lladdr 52:54:00:c6:4d:42 STALE
10.0.0.100 dev eth1 lladdr fa:80:13:21:6b:56 STALE

它們的來源還待查。

4.2.2 vxlan interface 所連線的 linux bridge 的 fdb 表

linux 實現的這個表和 vSphere 中的 fdb 表是對應的(除了 VTEP MAC 儲存在主機的 ip neigh 表中):

這些表儲存 遠端 VM MAC (mac)- 遠端 VTEP IP(dst) - VXLAN ID (dev) 的關係。linux-linuxbridge-agent 實現了通過 “bridge” 命令來操作 fdb 表項的各個方法(程式碼在這裡):

    def add_fdb_bridge_entry(self, mac, agent_ip, interface, operation="add"):
        utils.execute(['bridge', 'fdb', operation, mac, 'dev', interface, 'dst', agent_ip],
                      run_as_root=True, check_exit_code=False)

    def remove_fdb_bridge_entry(self, mac, agent_ip, interface):
        utils.execute(['bridge', 'fdb', 'del', mac, 'dev', interface, 'dst', agent_ip],
                      run_as_root=True, check_exit_code=False)

    def add_fdb_entries(self, agent_ip, ports, interface):
        for mac, ip in ports:
            if mac != constants.FLOODING_ENTRY[0]:
                self.add_fdb_ip_entry(mac, ip, interface)
                self.add_fdb_bridge_entry(mac, agent_ip, interface,
                                          operation="replace")
            elif self.vxlan_mode == lconst.VXLAN_UCAST:
                if self.fdb_bridge_entry_exists(mac, interface):
                    self.add_fdb_bridge_entry(mac, agent_ip, interface,
                                              "append")
                else:
                    self.add_fdb_bridge_entry(mac, agent_ip, interface)

    def remove_fdb_entries(self, agent_ip, ports, interface):
        for mac, ip in ports:
            if mac != constants.FLOODING_ENTRY[0]:
                self.remove_fdb_ip_entry(mac, ip, interface)
                self.remove_fdb_bridge_entry(mac, agent_ip, interface)
            elif self.vxlan_mode == lconst.VXLAN_UCAST:
                self.remove_fdb_bridge_entry(mac, agent_ip, interface)

fdb 條目格式:bridge fdb add REMOTE_VM_MAC dev vx-NET_ID dst REMOTE_HOST_IP 

比如:

[email protected]:~# bridge fdb show dev vxlan-1074
fa:16:3e:19:15:fe vlan 0
b6:dc:2f:dd:8b:81 vlan 0 permanent
00:00:00:00:00:00 dst 10.0.0.10 self permanent #需要多播或者廣播時,比如一個虛機的MAC地址在 fdb 表不存在的時候,需要使用多次單播來模擬多播
00:00:00:00:00:00 dst 10.0.0.14 self permanent #多播或者廣播目標之二。在不使用多播的情況下,如果需要多播的功能,則通過多次傳送單播的方式來模擬多播
fa:16:3e:32:35:ef dst 10.0.0.10 self permanent # qdhcp 的 ns-63ee3080-94 interface
fa:16:3e:c4:c8:58 dst 10.0.0.14 self permanent # 在另一個計算節點上的同網路的虛機
fa:16:3e:19:15:fe dst 10.0.0.10 self permanent # qrouter 的 qr-cd430335-dd interface

這樣的話,VTEP 就不需要通過多播來獲取目的虛機的 VTEP 的 IP 地址了。

4.2.3 問題除錯一例:虛機無法獲取固定IP

問題定位步驟如下:

(1)在 tap 裝置上做 tcpdump,能看到BOOTP 請求發出,但是沒用響應

(2)在 vxlan interface 裝置上做 tcpdump,看不到包發出

(3)檢視該裝置的 fdb 表,只有一條記錄

[email protected]:~# bridge fdb show dev vxlan-1074
3e:c0:e5:74:f7:49 vlan 0 permanent

  可見,這時候 vxlan 是無法通過 UDP 將包發出去的,因為沒用單播或者多播 fdb 表表項。

(4)重啟 Neutron linux bridge agent,在檢視 fdb 表

[email protected]:~# bridge fdb show dev vxlan-1074
3e:c0:e5:74:f7:49 vlan 0 permanent
00:00:00:00:00:00 dst 10.0.0.14 self permanent
00:00:00:00:00:00 dst 10.0.0.10 self permanent
fa:16:3e:32:35:ef dst 10.0.0.10 self permanent
fa:16:3e:c4:c8:58 dst 10.0.0.14 self permanent
fa:16:3e:19:15:fe dst 10.0.0.10 self permanent

   可見此時表項都正常了

(5)重新在虛機內執行 ifup eth0,正常獲取固定IP

   該問題同時也說明,如果 l2population 功能不正常的話,虛機的網路可能會斷;如果使用多播,這種情況應該會避免。

更深入地看一下到底是什麼原因:

(1)對 linux bridge 來說,它自身有個 fdb。在轉發之前,它會查這個表。如果有查到一條記錄,那麼就將二層幀轉發到該記錄對應的 bridge port;如果找不到,它就會泛洪。也就是說,linux bridge 是無論如何都會將它收到的幀轉發出去的。

(2) vxlan interface 也有自己的 fdb,它由 vxlan driver 來維護。當 linux bridge 將幀發到 vxlan interface 之後,vxlan driver 在將封包發給 udp 協議棧之前,它需要查 fdb 表,從中找出目的 MAC 地址對應的 fdb 條目中的對方 VTEP 的 IP 地址。vxlan.c 中的相關程式碼如下:

    f = vxlan_find_mac(vxlan, eth->h_dest);
    did_rsc = false;
    ...if (f == NULL) {
        f = vxlan_find_mac(vxlan, all_zeros_mac);
        if (f == NULL) {
            if ((vxlan->flags & VXLAN_F_L2MISS) &&
                !is_multicast_ether_addr(eth->h_dest))
                vxlan_fdb_miss(vxlan, eth->h_dest);

            dev->stats.tx_dropped++;
            kfree_skb(skb);
            return NETDEV_TX_OK;
        }
    }

這程式碼說明:(1)首先根據 目的 MAC 地址查表 (2)如果查不到,則根據全0的 MAC查表 (3)再查不到,則丟棄該幀。

4.3 使用多播的一些考量 

   在不使用單播的情況下,Linux vxlan 需要使用多播。而在選擇多播地址上,還是有一些講究:

  • 儘量不要使用 224.0.0.1 到 224.0.0.255 區間內的地址,這些地址往往有一些約定俗成的用途,比如用於多播路由
  • 公司內網使用的話,儘量選擇 239.0.0.0 到 239.255.255.255 區間內的地址,這些地址就像 10.0.0.0/8 地址一樣,都是常用的公司內部地址

    Neutron Linux bridge agent的實現中,預設情況下,所有的計算和網路節點上的 VTEP 都必須在同一個多播組中,這個組的地址可以配置,不配置的話使用預設的組 224.0.0.1。需要注意的是,224.0.0.1 是 “The All Hosts multicast group addresses all hosts on the same network segment.”,因此,它的成員只能在一個物理網段內,也就是說,如果各節點跨網段的話,需要修改多播組的IP。另外,還不清楚 Neutron 是否支援配置多個多播組,如果支援的話,結合 Nova 的 AZ 概念,可以將特定 AZ 內的計算節點加入到一個特定的多播組,而網路節點使用多個AZ的多個多播組,這樣將會有利於控制多播組內成員的數量。

一個示例:

加入多播組:

    • 主機1 上的 VTEP 傳送 IGMP 加入訊息 (join message)去加入多播組
    • 主機4 上的 VTEP 傳送 IGMP 加入訊息去加入同一個多播組

多播過程:

    1. 虛機 VM1 傳送一個廣播幀
    2. 主機1 上的VM1所在網路對應的 VTEP 將該廣播幀封裝成 UDP 多播包,設定其目的 IP 地址為 VTEP interface 多播組的地址。
    3. 物理網路將其發到該多播組內的所有主機
    4. 主機4 上的 VTEP,收到該 IP 包後,檢查其 VXLAN ID,發現其上有同樣 VXLAN ID 的 vxlan interface,則將該包解包後由它通過 linux bridge 轉發給該虛機。
    5. 主機2 和 3 同樣收到該包,因為它們也在在多播組內,然而,它們發現沒有帶 VXLAN ID 的 vxlan interface,因此將包丟棄。

以上是理論部分,具體還要進一步的實踐。TBD。

4.4 Neutron 基於 Linux bridge 的防火牆 IptablesFirewallDriver

github 上的原始碼在這裡。支援 ipset。使用 iptables,作用在各個 linux bridge 上。

在 /etc/neutron/plugins/ml2/ml2_conf.ini 中的配置:

[securitygroup]
# enable_ipset = True
enable_security_group = True
enable_ipset = True
firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver

從實現上看,OVSHybridIptablesFirewallDriver 類是從IptablesFirewallDriver 繼承過來的,只有小幅的改動。因此,可以參考 Neutron 理解 (8): Neutron 是如何實現虛機防火牆的 [How Neutron Implements Security Group],不同的部分的細節待將來再進一步的分析。

5. Linux bridge agent 原始碼分析 

github 上的原始碼在這裡。其程式碼邏輯並不複雜,主要實現瞭如下的邏輯:

(1)main()函式。在啟動 neutron-plugin-linuxbridge-agent 服務時被呼叫,它初始化一個 LinuxBridgeNeutronAgentRPC 例項,並呼叫其 start 方法啟動它。

(2)LinuxBridgeNeutronAgentRPC 類是主類,它初始化SecurityGroupAgentRpc 類的例項用來處理安全組;設定並啟動該Rpc (setup_rpc);啟動一個迴圈來不斷地 scan tap devices (scan_devices)來獲取待處理的 tap 裝置,並在裝置列表有變化時進行相應的處理(包括 add 或者 remove interface)

(3)對於 Rpc,LinuxBridgeNeutronAgentRPC  會啟動一個 LinuxBridgeRpcCallbacks 例項,預設情況下,會處理 PORT_UPDATE,NETWORK_DELETE 和 SG_UPDATE RPC 訊息;在設定了 l2pop 的情況下,會增加處理 L2POP_UPDATE 訊息;並且還會啟動一個迴圈來不斷呼叫 _report_state 來想 neutron server 報告其狀態。

consumers = [[topics.PORT, topics.UPDATE],[topics.NETWORK, topics.DELETE],[topics.SECURITY_GROUP, topics.UPDATE]]
if cfg.CONF.VXLAN.l2_population:
      consumers.append([topics.L2POPULATION, topics.UPDATE])

(4)RPC 處理

  • network_delete:在 network 被刪除後,將相應的 linux bridge 刪除
  • port_update:將 port 的 tap 名稱放到 updated_devices 中,待進一步處理
  • fdb_add:增加 fdb 表項
  • fdb_remove:刪除 fdb 表項
  • _fdb_chg_ip:修改 ip neighbor fdb 表項
  • 直接使用 sg_rpc.SecurityGroupAgentRpcCallbackMixin 的方法來處理安全組通知訊息

畫了一個大致的流程圖:

6. 與 OVS 的對比

  • OVS 通過發往多個隧道埠的方式來模擬多播/廣播
  • OVS 2.1 及以後版本自帶 ARP Responder 功能
  • 沒有 l2pop 時,計算節點之間以及計算節點與網路節點之間都會建立隧道
  • 有 l2pop 時,只有有虛機的計算節點和網路節點之間,以及有虛機在同一個網路的計算節點之間建立隧道

7. VXLAN Hardware Offload 

  現在,越來越多的網絡卡裝置支援 offload 特性,來提升網路收/發性能。offload 是將本來該作業系統進行的一些資料包處理(如分片、重組等)放到網絡卡硬體中去做,降低系統 CPU 消耗的同時,提高處理的效能。包括 LSO/LRO、GSO/GRO、TSO/UFO 等。

  諸於 VXLAN 的網路虛擬化技術給伺服器的CPU帶來了額外的負擔,比如封包、解包和校驗等。因此,一些中高階網絡卡已經添加了 Hardware Offload 的能力,將原本需要伺服器CPU處理的事情交給網絡卡自己來處理。

7.1 常見的 offload 技術

LSO/LRO:Large Segment Offload 和 Large Receive Offload

分別對應到傳送和接收兩個方向

首先來看 LSO。我們知道計算機網路上傳輸的資料基本單位是離散的網包,既然是網包,就有大小限制,這個限制就是 MTU(Maximum Transmission Unit)的大小,一般是1518位元組。比如我們想傳送很多資料出去,經過os協議棧的時候,會自動幫你拆分成幾個不超過MTU的網包。然而,這個拆分是比較費計算資源的(比如很多時候還要計算分別的checksum),由 CPU 來做的話,往往會造成使用率過高。那可不可以把這些簡單重複的操作 offload 到網絡卡上呢?

於是就有了 LSO,在傳送資料超過 MTU 限制的時候(太容易發生了),OS 只需要提交一次傳輸請求給網絡卡,網絡卡會自動的把資料拿過來,然後進行切,並封包發出,發出的網包不超過 MTU 限制。

接下來看 LRO,當網絡卡收到很多碎片包的時候,LRO 可以輔助自動組合成一段較大的資料,一次性提交給 OS處理。

一般的,LSO 和 LRO 主要面向 TCP 報文。

GSO/GRO: Generic Segmentation Offload 和 Generic Receive Offload

分別比 LSO 和 LRO 更通用,自動檢測網絡卡支援特性,支援分包則直接發給網絡卡,否則先分包後發給網絡卡。新的驅動一般用 GSO/GRO。