Neutron 理解 (8):如何實現虛機防火牆的

Neutron 理解 (1): Neutron 所實現的虛擬化網絡
Neutron 理解 (2): 使用 Open vSwitch + VLAN 組網
Neutron 理解 (3): Open vSwitch + GRE/VxLAN 組網
Neutron 理解 (4): Neutron OVS OpenFlow 流表 和 L2 Population
Neutron 理解 (5):Neutron 是如何向 Nova 虛機分配固定IP地址的
Neutron 理解 (6): 如何實現虛擬三層網絡
Neutron 理解 (7): 負載均衡器虛擬化

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。

Linux vxlan 模塊的代碼在這裏

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

root@compute1:~# 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

root@compute1:~# 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

root@compute1:~# 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
root@compute1:~# 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

網絡節點上:

  • 一個 qrouter network namespace

  • 三個 dhcp network namespace,每個 enable DHCP 的網絡各一個

  • 三個 Linux bridge,每個 network 一個

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

root@controller:~/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
root@controller:~/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
root@controller:~/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
  • vxlan 在 8472 端口上監聽 UDP 請求

  • 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 只有一個表項,就是所有的流量都發往多播組

root@compute1:~# 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

root@compute1:~# 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 同網段,但是在不同的機器上

root@compute1:~# 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 的任務。

詳細情況,可以參考 Neutron 理解 (3): Open vSwitch + GRE/VxLAN 組網 中的 VTEP 學習部分。

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 生效後,創建的 vxlan interface 上多了 “proxy” 功能:

root@controller:~# 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。

關於l2pop的實現原理,可參考>Neutron 理解 (4): Neutron OVS OpenFlow 流表 和 L2 Population

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示例:

root@compute1:~# 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 地址是如何保存的:

root@compute1:~# 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

比如:

root@compute1:~# 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 表,只有一條記錄

root@compute1:~# bridge fdb show dev vxlan-1074
3e:c0:e5:74:f7:49 vlan 0 permanent

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

(4)重啓 Neutron linux bridge agent,在查看 fdb 表

root@compute1:~# 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

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。

GRO 是在內核 2.6.29 之後合並進去的,作者是一個華裔Herbert Xu ,GRO的簡介可以看這裏:http://lwn.net/Articles/358910/

GSO 是在內核 Linux 2.6.18 之後合並進去的。

先來描述一下GRO的作用,GRO 是針對網絡接受包的處理的,並且只是針對 NAPI 類型的驅動,因此如果要支持 GRO,不僅要內核支持,而且驅動也必須調用相應的接口,用 ethtool -K gro on 來設置,如果報錯就說明網卡驅動本身就不支持GRO。

GRO 類似 TSO,可是 TSO 只支持發送數據包,這樣你 tcp 層大的段會在網卡被切包,然後再傳遞給對端,而如果沒有gro,則小的段會被一個個送到協議棧,有了gro之後,就會在接收端做一個反向的操作(想對於tso).也就是將tso切好的數據包組合成大包再傳遞給協議棧。

詳細信息及代碼分析,請閱讀 linux kernel 網絡協議棧之GRO(Generic receive offload)

TSO/UFO:TCP Segmentation Offload 和 UDP fragmentation offload

分別對應 TCP 報文和 UDP 報文。

TSO 將 TCP 協議的一些處理下放到網卡完成以減輕協議棧處理佔用 CPU 的負載。通常以太網的 MTU 是1500Bytes,除去 IP 頭(標準情況下20Bytes)、TCP頭(標準情況下20Bytes),TCP的MSS (Max Segment Size)大小是1460Bytes。當應用層下發的數據超過 MSS 時,協議棧會對這樣的 payload 進行分片,保證生成的報文長度不超過MTU的大小。但是對於支持 TSO/GSO 的網卡而言,就沒這個必要了,可以把最多 64K 大小的 payload 直接往下傳給協議棧,此時 IP 層也不會進行分片,一直會傳給網卡驅動,支持TSO/GSO的網卡會自己生成TCP/IP包頭和幀頭,這樣可以offload很多協議棧上的內存操作,checksum計算等原本靠CPU來做的工作都移給了網卡。

VMware 產品中實現了 TSO 和 LRO 來提高網絡性能。

RSS:Receive Side Scaling

多核服務器中的網卡還推薦考慮 RSS,將網流分配到多個 RSS 隊列上,多個隊列綁定到不同的核心上,分散負載。

發送端 接收端 註釋
LSO/TSO/UFO LRO LRO 是 linux 2.6.24 中引入的。
GSO GRO

GRO 是 linux 2.6.29 中引入的。

GSO 和 GRO 更加通用,G*O 是對 T*O 的增強,新的驅動都是用G*O。

注意兩個可以同時使用,所以要禁止offload的話需要檢查是否都被禁止了

7.2 網卡offload 能力檢測、打開和關閉

檢查你的網卡是否具備Offload 能力:

root@hkg02kvm001ccz023:~# ethtool -k eth0 | grep tx-udp
tx-udp_tnl-segmentation: off [fixed] (悲劇的是我的網卡不具備)

你可以打開或者關閉該功能:

ethtool -K ethX tx-udp_tnl-segmentation [off|on]

發佈了88 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章