我們已經知道 DPVS 是一個基于 DPDK 的高性能負載均衡器,它能夠支持 DR、FNAT、NAT 等工作模式。另外支持還能夠支持 SNAT 功能,實現原理和 FNAT 很相似,因此也具備高性能的特點。
在實際生產環境中,有些服務器需要通過 SNAT 功能來訪問外網服務,可能的需求原因如下:
- 只提供內網服務,比如負載均衡反向代理后端的服務器,只配置內網地址
- 機房環境的原因,相關服務器臨近的交換機不能配置公網 IP 資源
- 有些是出于公司商務成本考慮,為了節省開支,沒有提供出公網的能力
- 有些是系統架構的設計或系統安全性的考慮
出于以上的某種原因(可能還有其他別的原因),服務器沒有配置公網 IP 地址,默認是不能夠出公網訪問的。但實際有時會有訪問外網的需求,比如,通過 yum 倉庫安裝軟件包,或是調用外部第三方的某個接口來獲取數據等等的需求場景,這時 SNAT 系統能夠很方便的解決這些問題。據了解,很多公司都會提供 SNAT 服務,可能沒有負載均衡服務那么重要的地位,但也屬于公司內部底層基礎架構的組成部分,大多時候也是必不可少的。
最常見的 SNAT 實現方式
SNAT 最常見也是最經典的實現方式就是通過 linux 提供的 iptables 功能,操作簡單,大概操作方式如下:
# 開啟 IPv4 的轉發功能
$ echo 1 > /proc/sys/net/ipv4/ip_forward
$ iptables -t nat -A POSTROUTING -d 192.168.1.160/32 -p tcp --dport 9200 -j SNAT --to-source 192.168.1.7;
# 客戶端設置默認路由,網關指向 SNAT 設備 IP 地址即可
$ ip route add default via 192.168.1.1 dev eth0
# 注:只描述大概操作步驟和方式。
在實際生產環境中,為了節省大部分服務器出公網時只設置了少量的公網 IP 地址。生產環境中的 SNAT 服務器一般會做成主備模式或 BGP 集群。
- 主備模式 。一般是 2 臺服務器,都配置相同的 iptables 規則,另外需要運行 keepalived 服務來負責 網關IP 和 WAN IP 管理,本質上也是 VIP 的形式存在,如果 A 服務器宕機,B 服務器 keepalived 服務感知到后就會在本機上生成相應的 WAN IP 和 網關IP 地址,繼續提供 SNAT 服務。
- BGP 集群方式 。主備方式比較簡單,但是設備利用效率較低,同一時間只有一臺服務器提供服務器。BGP 集群方式,VIP 不是由 keepalived 來管理,而是由交換機負責將訪問的 VIP 以等價路由的方式,均衡輪詢的分配到多臺 SNAT 服務器上,這樣內網服務器通過網關訪問外網時,流量就會分攤到多臺的 SNAT 設備上了。
這兩種方式用來實現 SNAT 功能也比較成熟和穩定,不過由于 iptables 自身的一些問題,如 nf_conntrack 表的限制,以及大量的 iptables 規則導致的查找性能下降,整體來看,iptables 實現方式存在明顯的性能瓶頸。在生產環境中,經常會遇到某個業務流量跑高導致的 cpu 告警等問題。
DPVS 關于 SNAT 功能
DPVS 本身是一個基于 DPDK 的高性能負載均衡器,比較常用的工作模式是 FNAT,這種方式從運維和管理上比較友好一些,而且它能夠充分的利用多核 CPU 的處理能力,同時也做了很多無鎖的優化,它的整體性能也是非常強勁。SNAT 功能在實現原理和邏輯流程上,與 FNAT 非常相似,因此也具備高性能的特點。
可以對比 FNAT 與 SNAT 來進行理解:
- 對于 FNAT 功能,使外網用戶客戶端訪問流量到達 FNAT 時,DPVS 將數據包的源目的 IP 和端口都進行了轉換,分發給后端的某個真實服務器。
- 而對于 SNAT 功能,是內網服務器作為客戶端訪問流量到達 FNAT 時,DPVS 同樣是將源 IP 和端口轉換為公網 IP,轉發出去到達外網真實服務器
- 如果將 FNAT 流程反過來理解的話,就能理解 FNAT 與 SNAT 的流程非常相似。
(一)基本原理說明
先通過下圖簡單說明 snat 實現的大致原理:

- 內網服務器 s1,沒有配置公網 IP,需要訪問 baidu.com 的某個接口,s1 要將默認網關設置為 SNAT 的 gw 192.168.20.1,那么訪問 baidu 的數據包就會轉發給下一跳(SNAT)。
- 數據包到達 SNAT 設備后,SNAT 會根據源 IP 和協議類型來判斷是否為需要處理的報文,如果查找到對應的 service 配置,則會根據查到的服務信息修改數據包,將源 IP 修改為配置的公網 IP(假設為 wip1),然后通過外網網卡轉發出去。
- 外網 baidu 服務器收到請求后,正常處理并恢復響應數據包,響應數據包到達 SNAT 網關后,這時的目標 IP 是 wip1,SNAT 會根據源 IP 等信息查找連接表,查到連接表后,根據連接表信息將數據表的目標 IP 修改為內網服務器的 IP 地址,然后通過內網網卡轉發出去。
- 內網服務器 s1 收到響應數據包時,都已經復原為正常的連接信息,到此為止,s1 就正常的接收到了響應的數據包了。
(二)DPVS SNAT 的基礎使用操作
這里只羅列在 DPVS 上如何配置 SNAT 服務。
假設設備上有兩張網卡,一個外網,一個內網,測試 SNAT 雙臂模式:
- 內網網卡:dpdk0
- 外網網卡:dpdk1
- wan IP 配置時必須攜帶 sapool 選項
- SNAT 使用 match 類型的 service ,而不是 vip:port
- SNAT 設備上 wan 接口上需要配置默認路由(出公網)
SNAT 設置如下:
WAN_IP=123.1.2.3 # WAN IP can access Internet.
WAN_PREF=24 # WAN side network prefix length.
GATEWAY=123.1.2.1 # WAN side gateway
LAN_IP=192.168.100.1
LAN_PREF=24
# add WAN-side IP with sapool
./dpip addr add $WAN_IP/$WAN_PREF dev dpdk1 sapool # must add sapool for WAN-side IP
# add LAN-side IP as well as LAN route (generated)
./dpip addr add $LAN_IP/$LAN_PREF dev dpdk0
# add default route for WAN interface
./dpip route add default via $GATEWAY dev dpdk1
MATCH0='proto=tcp,src-range=192.168.100.0-192.168.100.254,oif=dpdk1'
MATCH1='proto=icmp,src-range=192.168.100.0-192.168.100.254,oif=dpdk1'
./ipvsadm -A -s rr -H $MATCH0
./ipvsadm -a -H $MATCH0 -r $WAN_IP:0 -w 100 -J
./ipvsadm -A -s rr -H $MATCH1
./ipvsadm -a -H $MATCH1 -r $WAN_IP:0 -w 100 -J
通過上述配置,就配置了兩個 service,分別是 MATCH1 和 MATCH2,這時如果通過 ipvsadm -ln 就能看到 svc 已經生效,這里就不貼展示的內容了。
同樣上述命令行實現的配置效果,用 keepalived 的配置方式也同樣能夠實現:
virtual_server match SNAT1 {
protocol TCP
lb_algo rr
lb_kind SNAT
src-range 192.168.100.0-192.168.100.254
oif dpdk1
real_server 123.1.2.1 0 {
weight 4
}
}
virtual_server match SNAT2 {
protocol ICMP
lb_algo rr
lb_kind SNAT
src-range 192.168.100.1-192.168.100.254
oif dpdk1
iif dpdk0
real_server 123.1.2.1 0 {
weight 4
}
}
通過上述配置,我們可以簡單總結到如下的內容:
- 每個 match service 是有協議類型的,也就是說如果我們需要服務器同時支持訪問 TCP、UDP、ICMP 的話,對同一個配置需要設置至少 3 個 match 服務。
- 配置中的 RS 指的是 wan ip 列表,如果指定多個 RS,那么就會輪詢從 RS 列表中選擇 wan ip。不過需要注意,RS 中的 Port 必須設置為 0
- 另外一個問題,DPVS SNAT 設置 WAN IP 地址時必須使用 dpip 命令行攜帶 sapool 選項才行,keepalived 配置 IP 地址時,默認沒有不支持這個選項。這就意味著現有的方式必須通過 dpip 命令行來操作。
- 如果單機需要支持 一對一 SNAT 功能,只需要指定 src-range 為固定 IP,另外 real_server 只指定一個 RS 就能實現一對一的功能。
(三)SNAT 使用時需要考慮的一些問題
SNAT 是愛奇藝團隊開發的功能,如果我們要使用的話,需要仔細分析是否與自己的需求一致,能否滿足我們的使用方式。
- 實現基本的 SNAT 功能,這點應該沒問題
- 確定 SNAT 各方面:配置方式、配置管理、使用方式
- 業務特殊需求:綁定固定的公網 IP 地址,如需要固定白名單場景,EIP 等需求
- 管理需求:運維查看每個業務的流量、限制每個業務的流量速率
- 服務管理:內網流量不支持使用 SNAT,是否需要支持 IPv6(貌似是支持 v6)
- 考慮特殊的情況:公網 IP 被封后如何快速方便的摘除掉
DPVS 的 SNAT 實現原理詳解
通過上述內容,可以大概了解 SNAT 的基本實現原理,下邊通過代碼層面來分析其實現細節。主要根據數據包的流向為思路,來分析 inbond 和 outbond 方向的數據包流程。
(一)outbond 方向數據包處理流程
outbond 方向是指數據包從內網服務器 -> DPVS -> 外網服務器(如 baidu.com )
假設內網服務器需要訪問外網某個功能接口,請求數據包到達了 DPVS 服務器。數據包從網卡某個隊列 queuex 進入后,被 cpux 接收并開始相關的邏輯處理。收包路徑大概包括如下:
- netif_loop -> lcore_job_recv_fwd -> lcore_process_packets -> netif_deliver_mbuf
- netif_deliver_mbuf 函數中根據 packet_type 分別調用相應的 func 函數,func 類型主要包括:ipv4、ipv6、arp 三種類型。這里主要以 IPv4 分析 SNAT 整個過程。
- IPv4 類型的包處理函數是 ipv4_rcv ,經過 IP 層相關檢查和校驗處理后,調用 INET_HOOK 來執行相關的鉤子函數,這里調用的是 INET_HOOK_PRE_ROUTING 位置鉤子,包括: dp_vs_pre_routing & dp_vs_in 函數,核心代碼入口在 dp_vs_in 里邊。
在 dp_vs_in 中其實調用的是 __dp_vs_in 函數,主邏輯入口在這個函數中體現:
- 首先調用協議查找函數 dp_vs_proto_lookup ,確認此數據包是哪種協議類型,如 TCP 或 UDP 等,每種協議的相關處理邏輯不同,后續的相關操作會根據對應協議注冊的回調函數來執行。
- 假設本次是請求是首包,需要調用 proto 的 conn_sched 函數進行連接的初始化和調度,TCP 的連接調度函數是 tcp_conn_sched 。
- 調度時,只有 TCP syn 包才能觸發正常調度流程。首先,根據數據包的相關數據確認該包是否屬于我們配置的服務,調用 dp_vs_service_lookup 函數進行查找,不過這里查找到的 service 類型是 match 方式。如果找不到響應的 svc 就跳出 SNAT 主流程。
- 找到 svc 后,調用 dp_vs_schedule 進行主調度流程,并創建初始化新的連接 Entry。調用 svc->scheduler->schedule 函數,從 RS 列表中挑選出一個 RS 作為 dest對于 SNAT 模式,調用 dp_vs_snat_schedule() 函數此時已確認 dest 作為源 IP 地址,調用 sa_fetch 根據源地址選擇相對應的源 port調用 dp_vs_conn_fill_param 將相關數據賦值到 param 變量中去。
- 確定相關參數后,調用 dp_vs_conn_new 建立初始化新的連接條目。dp_vs_conn_alloc dp_vs_conn_hash
- 連接調度成功后,根據模式,SNAT 初始化方向為 DPVS_CONN_DIR_OUTBOUND 不同于其他模式。
- 調用 proto 的 state_trans 函數,進行相關協議狀態機的轉換。
- 調用 xmit_outbound 將數據包發送出去。neigh_output
到此從 SNAT 數據包的接收、處理、轉發等過程,完成 SNAT outbond 方向的整個流程。
(二)inbond 方向數據包處理流程
inbound 方向是指數據包從外網服務器(如 baidu.com) -> DPVS -> 內網服務器
內網服務器發送請求數據包后,外網服務器執行相關操作,發出響應數據包,此數據包到達了 DPVS 服務器。此時 DPDK 驅動程序會根據關鍵字段進行 FDIR 規則匹配,數據包從同樣的網卡度列 queuex 進入后,被 cpux 接收并開始相關的邏輯處理,這樣整個連接都是被同一個 cpux 來處理的。收包路徑大概包括如下:
在 dp_vs_in 前的收包處理流程和 outbond 基本一致,這里就不再羅列。
dp_vs_proto_lookup
xmit_inbound
不斷的重復 outbond 和 inbond 流程,就能夠通過多次數據包的交互完成連接過程中業務的傳輸,從而實現 SNAT 功能。
(三)SNAT 其他問題
- ICMP 處理問題 。內網服務器 ping 外網是一個很常見的需求,從實驗測試和代碼分析來看,響應 ICMP 包可能命中到其它網卡隊列,導致連接表 miss 的問題,這個問題可以通過 FDIR 功能將 ICMP 包都定位到固定的網卡 queue 上,這樣可以簡單的方式來解決這個問題。
- 配置方式 。同一個業務配置信息,需要至少配置 3 個 service,其實也不算是大問題,如果用程序自動管理配置的話也就沒什么了。
- sapool配置只能用 dpip 。wan ip 配置時需要攜帶 sapool 選項,目前發現貌似只能用 dpip 命令行工具來進行配置,keepalived 配置不支持 sapool 選項。
總結先到此為止,在使用過程中遇到的問題還會再來補充更新。