iptables 概览
在 Linux 中,netfilter 是防火墙真正的安全框架,位于内核空间,有网络地址转换(NAT)、数据包修改、数据包过滤等防火墙功能。
iptables 是 netfilter 的命令行管理工具,在用户空间。
当我们启用防火墙功能,报文需要经过如下“链”:
如果报文需要转发,则报文不会经过 input 链进入用户空间,而是直接在内核空间中经过 forward 链和 postrouting 链转发出去。
我们对每个“链”设置了一堆规则,但是这些规则中有些很相似,那这些相似功能的规则是能够被放到一起的,这就组成了表。
filter表:负责过滤功能,防火墙;内核模块:iptables_filter
nat表:network address translation,网络地址转换功能;内核模块:iptable_nat
mangle表:拆解报文,做出修改,并重新封装 的功能;iptable_mangle
raw表:关闭nat表上启用的连接追踪机制;iptable_raw
istio 流量劫持
网格内的 Pod 启动时,会在 Pod 中启动 istio-init 容器,以下是我截取的该容器的启动命令:
istio-iptables -p 15001 -z 15006 -u 1337 -m REDIRECT -i '*' -x "" -b '*' -d 15090,15020
可以理解为,istio-init 容器是通过 istio-iptables 命令来给主机注入 iptables 规则。
istio-init容器主要是地址转换的功能,所以修改的是 iptables 的 NAT 表。
该容器存在的意义就是让 sidecar 代理可以拦截所有的进出 pod 的流量,15090 端口(Mixer 使用)和 15020 端口(Prometheus)除外的所有入站(inbound)流量重定向到 15006 端口(sidecar),再拦截应用容器的出站(outbound)流量经过 sidecar 处理(通过 15001 端口监听)后再出站。关于 Istio 中端口用途请参考 Istio 官方文档。
到 sidecar 进程的命名空间下查看注入的所有 iptables 规则:
# 查看 NAT 表中规则配置的详细信息。
$ iptables -t nat -L -v
# PREROUTING 链:用于目标地址转换(DNAT),将所有入站 TCP 流量跳转到 ISTIO_INBOUND 链上。
Chain PREROUTING (policy ACCEPT 2701 packets, 162K bytes)
pkts bytes target prot opt in out source destination
2701 162K ISTIO_INBOUND tcp -- any any anywhere anywhere
# ISTIO_INBOUND 链:将所有入站流量重定向到 ISTIO_IN_REDIRECT 链上,目的地为 15090(mixer 使用)和 15020(Ingress gateway 使用,用于 Pilot 健康检查)端口的流量除外,发送到以上两个端口的流量将返回 iptables 规则链的调用点,即 PREROUTING 链的后继 POSTROUTING。
Chain ISTIO_INBOUND (1 references)
pkts bytes target prot opt in out source destination
0 0 RETURN tcp -- any any anywhere anywhere tcp dpt:ssh
2 120 RETURN tcp -- any any anywhere anywhere tcp dpt:15090
2699 162K RETURN tcp -- any any anywhere anywhere tcp dpt:15020
0 0 ISTIO_IN_REDIRECT tcp -- any any anywhere anywhere
# ISTIO_IN_REDIRECT 链:将所有的入站流量跳转到本地的 15006 端口,至此成功的拦截了流量到 Sidecar 中。
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
# 随后 Sidecar 向业务容器建立连接, 之后从业务容器出站的流量会被 netfilter 拦截进 OUTPUT 链
# OUTPUT 链:将所有出站数据包跳转到 ISTIO_OUTPUT 链上。
Chain OUTPUT (policy ACCEPT 79 packets, 6761 bytes)
pkts bytes target prot opt in out source destination
15 900 ISTIO_OUTPUT tcp -- any any anywhere anywhere
# ISTIO_OUTPUT 链:选择需要重定向到 Envoy(即本地) 的出站流量,所有非 localhost 的流量全部转发到 ISTIO_REDIRECT。为了避免流量在该 Pod 中无限循环,所有到 istio-proxy 用户空间的流量都返回到它的调用点中的下一条规则,本例中即 OUTPUT 链,因为跳出 ISTIO_OUTPUT 规则之后就进入下一条链 POSTROUTING。如果目的地非 localhost 就跳转到 ISTIO_REDIRECT;如果流量是来自 istio-proxy 用户空间的,那么就跳出该链,返回它的调用链继续执行下一条规则(OUTPUT 的下一条规则,无需对流量进行处理);所有的非 istio-proxy 用户空间的目的地是 localhost 的流量就跳转到 ISTIO_REDIRECT。
Chain ISTIO_OUTPUT (1 references)
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
15 900 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
# ISTIO_REDIRECT 链:将所有流量重定向到 Sidecar(即本地) 的 15001 端口。
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
## 从 Sidecar 出站的流量会被 netfilter 拦截到 OUTPUT 链,OUTPUT 链会将流量重定向到 ISTIO_OUTPUT 链,再之后流量直接 RETURN 便到了 POSTROUTING 链
# POSTROUTING 链:所有数据包流出网卡时都要先进入 POSTROUTING 链,内核根据数据包目的地判断是否需要转发出去,我们看到此处未做任何处理。
Chain POSTROUTING (policy ACCEPT 79 packets, 6761 bytes)
pkts bytes target prot opt in out source destination
通过 istio-iptables 执行这条命令的意义就是拦截所有进出 Pod 的流量,15090 端口和 15020 端口除外的所有入站(inbound)流量重定向到 15006 端口(sidecar),再拦截应用容器的出站流量(outbound)到 sidecar,通过 15001 端口监听 outbound。
以下内容参考 ServiceMesh 社区中的文档。以 bookinfo 服务为例,观察 iptables 中“链”的详细调用过程:
红色数字:
productpage服务向reviews服务发送 TCP 连接请求- 请求进入
reviews服务所在Pod内核空间,被 netfilter 拦截入口流量,经过PREROUTING链然后转发至ISTIO_INBOUND链 - 在
ISTIO_INBOUND链中被规则-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT拦截再次转发至ISTIO_IN_REDIRECT链 ISTIO_IN_REDIRECT链直接重定向至 sidecar 监听的15006入口流量端口- 在 sidecar 内部经过一系列入口流量治理动作后,发出TCP连接请求
reviews服务,这一步对 sidecar 来说是出口流量,会被 netfilter 拦截转发至出口流量OUTPUT链 OUTPUT链转发流量至ISTIO_OUTPUT链- 目的地为 localhost,不能匹配到转发规则链,直接 RETURN 到下一个链,即
POSTROUTING链 - sidecar 发出的请求到达
reviews服务 9080 端口 reviews服务处理完业务逻辑后响应 sidecar,这一步对reviews服务来说属于出口流量,再次被 netfilter 拦截转发至出口流量OUTPUT链OUTPUT链转发流量至ISTIO_OUTPUT链- 发送非 localhost 请求且为
istio-proxy用户空间的流量被转发至ISTIO_REDIRECT链 ISTIO_REDIRECT链直接重定向至 sidecar 监听的15001出口流量端口- 在 sidecar 内部经过一系列出口流量治理动作后继续发送响应数据,响应时又会被netfilter 拦截转发至出口流量
OUTPUT链 OUTPUT链转发流量至ISTIO_OUTPUT链- 流量直接
RETURN到下一个链,即POSTROUTING链
至此,istio 对流量的整个“劫持”过程便完成了。