歡迎光臨
每天分享高質量文章

基於 Linux 內核新特性的網關設計實踐

我們在不斷跟蹤 Linux 內核社區的新技術發展,並將之用於下一代外網網關的設計時發現,新特性存在不少缺陷和 Bug,為此我們向 Linux 內核社區回饋了 10 多個補丁,並融入到 Linux 內核 5.0 版本中,幫助完善內核功能並提升穩定性。

— 文旭

 

轉自:UCloud作者:UCloud / 文旭

UCloud 外網網關是為了承載外網IP、負載均衡等產品的外網出入向流量,當前基於 Linux 內核的 OVS/GRE 隧道/netns/iptables 等實現,很好地支撐了現有業務。同時,我們也在不斷跟蹤 Linux 內核社區的新技術發展,並將之用於下一代外網網關的設計。這些新特性可將系統性能和管理能力再提上一檔,滿足未來幾年的需求。在方案設計研發過程中發現,新特性存在不少缺陷和 Bug,為此我們向 Linux 內核社區回饋了 10 多個補丁,並融入到 Linux 內核 5.0 版本中,幫助完善內核功能並提升穩定性。

當前業界的多租戶外網網關很多都是基於 OpenFlow 的 OpenvSwitch(OVS)方案,然而隨著內核路由轉發功能的不斷完善,利用內核原生路由轉發方式進行設計多租戶外網網關係統成為一種可能。在這種方式下能有效的使用傳統 iproute2 路由工具以及 iptables、nftables 等防火牆工具,並且隨著 SwitchDev 技術的興起,未來將網關係統遷移到 Linux Switch 上也成為一種可能。

現有 Linux 內核 3.x 的不足

當前廣泛使用的內核版本為 3.x 系列,例如 CentOS 7 全系列標準支持的內核為 3.10 版本,Fedora/Ubuntu 等 Linux 發行版也有大量使用。在 3.x 系列內核下存在著 IP 隧道管理複雜、租戶隔離性能損耗等問題。

1. IP 隧道管理複雜
Linux 內核創建 IP 隧道設備來建立點對點的隧道連接,創建時需指定隧道標的和隧道密鑰。因為宿主機之間兩兩建立連接,面向宿主機的目的地址眾多,這樣就會導致網關節點上需要創建成千上萬的隧道設備,在大規模業務環境下,隧道的管理將變得及其複雜。
2. 多租戶隔離導致的性能下降
3. 公有雲需要實現多租戶隔離以確保用戶間的安全和隱私
由於 VPC 網絡下不同租戶的內網地址可以重合,導致路由也有重合的可能性,此時需要通過大量的策略路由去隔離租戶的路由規則,由於策略路由的鏈表屬性,性能會隨著鏈表長度的增加而急劇下降。
4. 由於防火牆和 NAT 的實現基於同樣鏈式的 iptables,性能損耗同樣可觀。
5. netns 帶來性能開銷
通過 netns 實現租戶路由和防火牆規則的隔離,但是 netns 會引入虛擬網卡和協議棧重入開銷,使整體性能下降 20% 左右。

三項內核新技術

為瞭解決原有方案存在的困擾,我們調研了大量行業主流方案和內核上游的新動向,發現輕量級隧道Lightweight tunneling(簡稱 lwtunnel)、虛擬路由轉發Virtual Routing Forwarding(簡稱 VRF)以及 nftable & netfilter 流卸載flow offload三項內核新技術的特性,可以幫助規避原方案存在的缺陷。

1、輕量級隧道

Linux 內核在 4.3 版本中引入了輕量級隧道,它提供了通過路由方式設置隧道屬性的方法,這樣可以避免管理大量的隧道設備。
創建隧道設備時指定 external 樣式,利用路由設置的輕量級隧道通過 tun 設備發送報文。

  1. # ip l add dev tun type gretap external
  2. # ifconfig tun 1.1.1.7/24 up
  3. # ip r r 2.2.2.11 via 1.1.1.11 dev tun encap ip id 1000 dst 172.168.0.1 key 

2、虛擬路由轉發

Linux 內核在 4.3 版本中引入了 VRF 的初步支持,併在 4.8 版本形成完備版本。虛擬路由轉發可以將一臺 Linux Box 的物理路由器當多台虛擬路由器使用,能很好的解決租戶路由隔離問題,避免直接使用策略路由。因此,可以將不同租戶的網卡加入租戶所屬的虛擬路由器中來實現多租戶的虛擬路由。

  1. # ip link add user1 type vrf table1
  2. # ip link add user1 type vrf table2
  3. # ip l set user1 up
  4. # ip l set user2 up
  5. # ip l set dev eth1 master user1
  6. # ip l set dev eth1 master user2
  7. # ip r a default via 192.168.0.1 dev eth1 table 1 onlink
  8. # ip r a default via 192.168.0.1 dev eth1 table 2 onlink

3、流卸載

nftables 是一種新的資料包分類框架,旨在替代現存的 {ip,ip6,arp,eb}_tables。在 nftables 中,大部分工作是在用戶態完成的,內核只知道一些基本指令(過濾是用偽狀態機實現的)。nftables 的一個高級特性就是映射,可以使用不同型別的資料並映射它們。例如,我們可以映射 iif 設備到專用的規則集合(之前創建的儲存在一個鏈中)。由於是哈希映射的方式,可以完美的避免鏈式規則跳轉的性能開銷。

Linux 內核在版本 4.16 引入了流卸載功能,它為 IP 轉發提供了基於流的卸載功能。當一條新建連接完成首回合原方向和反方向的報文時,完成路由,防火牆和 NAT 工作後,在處理反方向首報文的 forward 鉤子,根據報文路由、NAT 等信息創建可卸載流到接收網卡 ingress 鉤子上。後續的報文可以在接收 ingress 鉤子上直接轉發,不需要再進入 IP 棧處理。此外,將來流卸載還將支持硬體卸載樣式,這將極大提高系統轉發性能。

  1. # nft add table firewall
  2. # nft add flowtable f fb1 { hook ingress priority 0 \; devices = { eth0, eth1 } \; }
  3. # nft add chain f ftb-all {type filter hook forward priority 0 \; policy accept \; }
  4. # nft add rule f ftb-all ct zone 1 ip protocol tcp flow offload @fb1

方案設計與優化實踐

通過對上述三項新技術的研究,我們發現可以嘗試設計一套基於路由的方式,實現多租戶層疊網絡的外網網關。在方案設計過程中,我們也碰到了諸如 lwtunnel 和流卸載功能不足,以及 VRF 和流卸載不能一起有效的工作等問題。最終我們都設法解決了,並針對這些內核的不足提交補丁給 Linux 內核社區。

1、lwtunnel 發送報文 tunnel_key 丟失

問題描述:我們利用 lwtunnel 路由方式發送報文時,創建了一個 external 型別的 gretap 隧道,我們將命令設置了 id 為 1000,但是發送成功報文中沒有 tunnel_key 欄位。

  1. # ip l add dev tun type gretap
  2. # ifconfig tun 1.1.1.7/24 up
  3. # ip r r 2.2.2.11 via 1.1.1.11 dev tun encap ip id 1000 dst 172.168.0.1

問題定位:我們研究 iproute2 代碼,發現由於 TUNNEL_KEY 的標誌並沒有開放給用戶態,所以 iproute2 工具並沒有對 lwtunnel 路由設置 TUNNEL_KEY,導致報文不會創建 tunnel_key 欄位。

提交補丁:我們給內核和用戶態 iproute2 分別提交補丁來解決這一問題:

◈ iptunnel: make TUNNEL_FLAGS available in uapi[1]
◈ iproute: Set ip/ip6 lwtunnel flags[2]

提交補丁後,可以通過以下方式設置路由:

  1. ip r r 2.2.2.11 via 1.1.1.11 dev tun encap ip id 1000 dst 172.168.0.1 key

2、lwtunnel 對指定密鑰的 IP 隧道無效

問題發現:為了能有效隔離租戶路由,我們給每個租戶創建一個基於 tunnel_key 的 gretap 隧道設備。如下圖,創建一個 tunnel_key 1000 的 gretap 隧道設備,把隧道設備加入租戶所屬 VRF,隧道設備能有效地接收報文,但並不能發送報文。

  1. # ip l add dev tun type gretap key 1000
  2. # ifconfig tun 1.1.1.7/24 up
  3. # ip r r 2.2.2.11 via 1.1.1.11 dev tun encap ip id 1000 dst 172.168.0.1 key

問題定位:研究內核發現,IP 隧道在非外部樣式下即使指定了輕量級隧道路由,發送報文也沒有使用它,導致報文路由錯誤被丟棄。

提交補丁:

◈ ip_tunnel: Make none-tunnel-dst tunnel port work with lwtunnel[3]

提交補丁後,在未指定 tunnel_dst 的非外部樣式 IP 隧道下,能使用輕量級隧道路由進行發送報文。

3、外部 IP 隧道 ARP 無法正常運行

問題描述:鄰居 IP 隧道進行了 ARP 請求,但是本端的 ARP 回應報文的隧道頭中並沒帶 tunnel_key 欄位。

  1. # ip l add dev tun type gretap external
  2. # ifconfig tun 1.1.1.7/24 up
  3. # ip r r 2.2.2.11 via 1.1.1.11 dev tun encap ip id 1000 dst 172.168.0.1 key

問題定位:研究代碼發現,隧道收到了對端的 ARP 請求,在發送報文 ARP 回覆的時候會複製請求報文的隧道信息,但是遺漏了所有 tun_flags。

提交補丁:

◈ iptunnel: Set tun_flags in the iptunnel_metadata_reply from src[4]

4、流卸載不能與 DNAT 有效工作

問題描述:防火牆創建規則從 eth0 收到目的地址 2.2.2.11 的報文,DNAT 為 10.0.0.7, 流卸載無法工作。

問題定位:分析發現,客戶端 1.1.1.7 -> 2.2.2.7 DNAT 到服務器 10.0.0.7,第一個回覆的反向報文(syc+ack)使用了錯的目的地址獲取反向路由。

  1. daddr = ct->tuplehash[!dir].tuple.dst.u3.ip

此時 dir 為反方向,所以 daddr 獲取為原方向的目的地址,這個值是 2.2.2.7,但是由於被 DNAT過,真正的路由不應該通過 2.2.2.7 去獲取,而是應該根據 10.0.0.7 這個值去獲取。

  1. addr = ct->tuplehash[dir].tuple.src.u3.ip

提交補丁:

◈ netfilter: nft_flow_offload: Fix reverse route lookup[5]

5、流卸載不能與 VRF 有效工作

問題描述:將網卡 eth0 和 eth1 加入 VFR 後,流卸載不起作用。

  1. # ip addr add dev eth0 1.1.1.1/24
  2. # ip addr add dev eth1 1.1.1.1/24
  3. # ip link add user1 type vrf table 1
  4. # ip l set user1 up
  5. # ip l set dev eth0 master user1
  6. # ip l set dev eth1 master user1

問題定位:查看代碼發現,原方向和反方向首報文進入協議堆棧後 skb->dev 會設置為 vrf device user1,創建流卸載規則的 iif 就是 user1。但是卸載規則下發在 eth0 和 eth1 的 ingress 鉤子上,所以後續報文在 eth0 和 eth1 的 ingress 鉤子上不能匹配流規則規則。

提交補丁:

◈ netfilter: nft_flow_offload: fix interaction with vrf slave device[6]

最終,我們根據兩個方向查找路由的結果,設置流卸載規則的 iif 和 oif 信息來解決此問題。

6、VRF PREROUTING 鉤子重入問題

問題描述:配置網卡加入 VRF,防火牆 ingress 方向規則為接收目的地址 2.2.2.11 、TCP 目的端口 22 的報文,egress 方向規則為丟棄 TCP 目的端口 22 的報文。出現異常結果: 收到目的地址 2.2.2.11 TCP 22 目的端口的報文卻被丟棄。

問題定位:研究發現網卡加入 VRF 後收到的報文會兩次進入 PREROUTING 鉤子,因為在進入 IP 棧時會進第一次PREROUTING 鉤子,然後被 VRF 設備接管後會再次進入 PREROUTING 鉤子。上述規則第一次在 rule-1000-ingress chain中 dst nat 為 10.0.0.7,第二次由於報文被 DNAT 後會錯誤的進入 rule-1000-egress,導致報文被丟棄。

提交補丁:我們給內核加了一個支持判斷網卡型別的 match 專案,讓用戶態避免可知的第二次無效重入,內核態和用戶態 nftables 分別提交瞭如下的補丁:

◈ netfilter: nft_meta: Add NFT_META_I/OIFKIND meta type[7]
◈ meta: add iifkind and oifkind support[8]

使用方法:

  1. nft add rule firewall rules-all meta iifkind "vrf" counter accept

原型驗證

最終,我們成功地利用 lwtunnel、VRF 和流卸載實現多租戶外網網關的原型驗證。驗證過程如下:

1、首先創建原型環境

a. netns cl 模擬外網客戶端,地址為 1.1.1.7,隧道源地址 172.168.0.7,配置發送路由;
b. netns ns1 模擬租戶 1,內網地址為 10.0.0.7,外網地址為 2.2.2.11,隧道源地址 172.168.0.11 tunnel_key 1000,配置發送路由;
c. netns ns2 模擬租戶 2,內網地址為 10.0.0.7,外網地址為 2.2.2.12,隧道源地址 172.168.0.12 tunnel_key 2000,配置發送路由;
d. Host 模擬外網網關,隧道源地址 172.168.0.1,創建租戶 VRF user1 和 use2,創建租戶 IP 隧道 tun1 和 tun2,配置轉發路由。

原型環境圖如下:

2、創建防火牆規則

a. 租戶 1 入向允許 TCP 目的端口 22 和 ICMP 訪問,出向禁止訪問外部 TCP 22 目的端口;
b. 租戶 2 入向允許 TCP 端口 23 和 ICMP 訪問,出向禁止訪問外部 TCP 23 目的端口;
c. 在租戶 tun1 和 tun2 設備上支持流卸載。

最終,客戶端可以通過 2.2.2.11 成功訪問 user1 tcp 22 端口服務,user1 不能訪問客戶端 tcp 22 端口服務;客戶端可以通過 2.2.2.12 成功訪問 user2 tcp 23 端口服務,user1 不能訪問客戶端 tcp 23 端口服務。

在後續硬體功能完善以及網卡廠商支持後,我們還會做進一步的開發驗證。

寫在最後

以上是本專案涉及的部分核心問題,這些補丁特性都可以在 Linux 內核 5.0 版本里獲取。我們把這期間為 Linux 內核社區貢獻的補丁整理成了一份串列[9],希望能為開發者提供幫助,讀者可以點擊閱覽。

Linux 作為成熟的開源套件,一直是雲廠商使用的主流操作系統,但在技術的更新迭代過程中,一些新特性在實際應用上也會存在穩定性、兼容性等方面的問題。我們在研究使用上游技術的同時,也一直積極探索、豐富開源技術功能,幫助提高開源技術穩定性。並將產出持續回饋給社區,與社區共同構建一個繁榮的開源生態。

赞(0)

分享創造快樂