Istio 流量劫持過程
阿新 • • 發佈:2020-05-29
## 開篇
Istio 流量劫持的文章其實目前可以在servicemesher社群找到一篇非常詳細的文章,可查閱:[Istio 中的 Sidecar 注入及透明流量劫持過程詳解](https://www.servicemesher.com/blog/sidecar-injection-iptables-and-traffic-routing/)。特別是博主整理的那張“流量劫持示意圖”,已經可以很清晰的看出來劫持流程。這裡我藉著那張圖片解釋一版該圖片的文字版本。在開始文字版前如果對`iptables`命令如果不是非常瞭解的話建議先重點看下下面的兩篇文章,深入淺出的解釋了該命令的概念及用法:
1. [iptables概念]( http://www.zsythink.net/archives/1199 ) - 以通俗易懂的方式描述iptables的相關概念
2. [iptables指南]( https://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html ) - iptables命令用法指南
這裡引用iptables的一張報文流向圖(版權歸原博主所有)
![iptables-routing](https://img2020.cnblogs.com/blog/1236907/202005/1236907-20200529131948078-1181086693.png)
> 當客戶端訪問伺服器的web服務時,客戶端傳送報文到網絡卡,而tcp/ip協議棧是屬於核心的一部分,所以,客戶端的資訊會通過核心的TCP協議傳輸到使用者空間中的web服務中,而此時,客戶端報文的目標終點為web服務所監聽的套接字(IP:Port)上,當web服務需要響應客戶端請求時,web服務發出的響應報文的目標終點則為客戶端,這個時候,web服務所監聽的IP與埠反而變成了原點。 -- 引用自 zsythink
上面這部分描述相當重要,它是理解sidecar在進行流量劫持的基礎之一。
下面我們分析下昨天`istio-init`啟動時執行的`istio-iptables`命令
```shell
nsenter -t 8533 -n iptables -t nat -S
# 預設
# 為PREROUTING/INPUT/OUTPUT/POSTROUTING鏈設定策略為接收資料包(ACCEPT)
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
# 自定義4個istio的規則鏈
-N ISTIO_INBOUND
-N ISTIO_IN_REDIRECT
-N ISTIO_OUTPUT
-N ISTIO_REDIRECT
# 進入PREROUTING鏈tcp協議請求全部定向至 ISTIO_INBOUND 自定義鏈進行規則匹配
-A PREROUTING -p tcp -j ISTIO_INBOUND
# 進入OUTPUT鏈tcp協議請求全部定向至 ISTIO_OUTPUT 自定義鏈進行規則匹配
-A OUTPUT -p tcp -j ISTIO_OUTPUT
# 入口
# tcp協議請求且請求埠為22/15090/15021/15020的請求停止執行當前鏈中的後續Rules,並執行下一個鏈
-A ISTIO_INBOUND -p tcp -m tcp --dport 22 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN
# tcp協議且埠不是22/15090/15021/15020的請求全部定向至 ISTIO_IN_REDIRECT
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
# 將重定向於此的tcp協議請求流量全部重定向至15006埠(envoy入口流量埠)
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
# 出口
# 源IP地址為localhost且資料包出口為 ”lo“ 的流量都返回到它的呼叫點中的下一條鏈執行(1)
-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN
# 目的地非localhost,資料包出口為 ”lo“,是istio-proxy使用者的流量轉發至 ISTIO_REDIRECT (2)
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
# 資料包出口為 ”lo“,非istio-proxy使用者的流量都返回到它的呼叫點中的下一條鏈執行(1)
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
# istio-proxy 使用者的流量都返回到它的呼叫點中的下一條鏈執行
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
# 目的地非localhost,資料包出口為 ”lo“,是istio-proxy使用者組的流量轉發至 ISTIO_REDIRECT(2)
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
# 資料包出口為 ”lo“ 且非istio-proxy使用者組流量都返回到它的呼叫點中的下一條鏈執行(1)
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
# 到 istio-proxy 使用者組的流量都返回到它的呼叫點中的下一條鏈執行(1)
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
# 所有目的地為localhost的流量都返回到它的呼叫點中的下一條鏈執行(1)
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
# 其他不滿足上述rules的流量全部轉發到 ISTIO_REDIRECT (2)
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
# 將重定向於此的tcp協議請求流量全部重定向至15001埠(envoy出口流量埠)
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
```
> -m = --match. istio-proxy 使用者身份執行, uid-owner 1337 為使用者ID / gid-owner 1337 為使用者組,即 sidecar 所處的使用者空間,這也是 istio- proxy 容器預設使用的使用者。
注意文中打括號的地方
(1) 代表流量會**直接執行下一個攔截鏈**,本文中下一個攔截鏈為`POSTROUTING`鏈
(2) 代表流量會**被重定向至envoy出口流量埠**
根據上面的規則小結一下:
> ISTIO_INBOUND 鏈:所有進入Pod但非指定埠(如22)的流量全部重定向至15006埠(envoy入口流量埠)進行攔截處理。
>
> ISTIO_OUTPUT 鏈:所有流出Pod由 istio-proxy 使用者空間發出且目的地不為localhost的流量全部重定向至15001埠(envoy出口流量埠),其他流量全部直接放行至下一個POSTROUTING鏈,不用被envoy攔截處理。
其實仔細思考下可以看到,流量攔截主要發生在兩個地方:
1. 使用者請求到達Pod時對應流量會被攔截至sidecar進行處理,由sidecar請求業務服務
2. 當業務服務響應使用者請求時該響應再次被攔截至sidecar,由sidecar響應使用者
再看下iptables nat 表的規則
```shell
nsenter -t 8533 -n iptables -t nat -L -v
Chain PREROUTING (policy ACCEPT 3435 packets, 206K bytes)
pkts bytes target prot opt in out source destination
3435 206K ISTIO_INBOUND tcp -- any any anywhere anywhere (1)
Chain INPUT (policy ACCEPT 3435 packets, 206K bytes) (5)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 599 packets, 54757 bytes)
pkts bytes target prot opt in out source destination
22 1320 ISTIO_OUTPUT tcp -- any any anywhere anywhere
Chain POSTROUTING (policy ACCEPT 599 packets, 54757 bytes) (8)
pkts bytes target prot opt in out source destination
Chain ISTIO_INBOUND (1 references) (2)
pkts bytes target prot opt in out source destination
0 0 RETURN tcp -- any any anywhere anywhere tcp dpt:22
1 60 RETURN tcp -- any any anywhere anywhere tcp dpt:15090
3434 206K RETURN tcp -- any any anywhere anywhere tcp dpt:15021
0 0 RETURN tcp -- any any anywhere anywhere tcp dpt:15020
0 0 ISTIO_IN_REDIRECT tcp -- any any anywhere anywhere (3)
Chain ISTIO_IN_REDIRECT (3 references)
pkts bytes target prot opt in out source destination
0 0 REDIRECT tcp -- any any anywhere anywhere redir ports 15006 (4)
Chain ISTIO_OUTPUT (1 references) (6)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- any lo 127.0.0.6 anywhere
0 0 ISTIO_IN_REDIRECT all -- any lo anywhere !localhost owner UID match 1337
0 0 RETURN all -- any lo anywhere anywhere ! owner UID match 1337
22 1320 RETURN all -- any any anywhere anywhere owner UID match 1337
0 0 ISTIO_IN_REDIRECT all -- any lo anywhere !localhost owner GID match 1337
0 0 RETURN all -- any lo anywhere anywhere ! owner GID match 1337
0 0 RETURN all -- any any anywhere anywhere owner GID match 1337
0 0 RETURN all -- any any anywhere localhost
0 0 ISTIO_REDIRECT all -- any any anywhere anywhere
Chain ISTIO_REDIRECT (1 references)
pkts bytes target prot opt in out source destination
0 0 REDIRECT tcp -- any any anywhere anywhere redir ports 15001
```
讓我們一起再來仔細看下面這個圖片,同步觀察上面iptables chain的規則。這裡的分析主要針對紅色的數字一對一解釋:
![envoy-sidecar-traffic-interception](https://img2020.cnblogs.com/blog/1236907/202005/1236907-20200529132011322-503993776.png)
1. `productpage` 服務對`reviews` 服務傳送 TCP 連線請求
2. 請求進入`reviews`服務所在Pod核心空間,被netfilter攔截入口流量,經過`PREROUTING`鏈然後轉發至`ISTIO_INBOUND`鏈
3. 在被`ISTIO_INBOUND`鏈被這個規則`-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT`攔截再次轉發至`ISTIO_IN_REDIRECT`鏈
4. **`ISTIO_IN_REDIRECT`鏈直接重定向至 envoy監聽的 `15006` 入口流量埠**
5. 在 envoy 內部經過一系列入口流量治理動作後,發出TCP連線請求 `reviews` 服務,這一步對envoy來說屬於出口流量,會被netfilter攔截轉發至出口流量`OUTPUT`鏈
6. `OUTPUT`鏈轉發流量至`ISTIO_OUTPUT`鏈
7. 目的地為localhost,不能匹配到轉發規則鏈,直接`RETURN`到下一個鏈,即`POSTROUTING`鏈
8. sidecar發出的請求到達`reviews`服務`9080`埠
9. `reviews`服務處理完業務邏輯後響應sidecar,這一步對`reviews`服務來說屬於出口流量,再次被netfilter攔截轉發至出口流量`OUTPUT`鏈
10. `OUTPUT`鏈轉發流量至`ISTIO_OUTPUT`鏈
11. 傳送非localhost請求且為`istio-proxy`使用者空間的流量被轉發至` ISTIO_REDIRECT`鏈
12. **` ISTIO_REDIRECT`鏈直接重定向至 envoy監聽的 `15001` 出口流量埠**
13. 在 envoy 內部經過一系列出口流量治理動作後繼續傳送響應資料,響應時又會被netfilter攔截轉發至出口流量`OUTPUT`鏈
14. `OUTPUT`鏈轉發流量至`ISTIO_OUTPUT`鏈
15. 流量直接`RETURN`到下一個鏈,即`POSTROUTING`鏈
針對上文其實我還有兩個疑問點,還請大家不吝指教:
- 上面的理解沒有寫第`16`點,博主的圖中的`16`點還會再進` ISTIO_REDIRECT`鏈,我們可以看到` ISTIO_REDIRECT`鏈中只有一個改寫埠轉發的規則,這樣豈不是會進入一個死迴圈?或者是我還沒有理解清楚
- envoy 轉發流量是不是自己新建立的tcp 連線請求還是通過修改請求報文地址來實現的。因為對c++瞭解有限,無法查閱其原始碼去一探究竟
從整個流量攔截流程大家也可以看出,路徑這麼長, 在大併發場景下肯定會損失轉發效能。目前業界有一些框架在試著縮短這個攔截路徑,讓大家拭目以待吧。
## 參考文獻
https://www.servicemesher.com/blog/sidecar-injection-iptables-and-traffic-routing/
https://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html#REDIRECTTARGET
http://www.zsythink.net/archi