摘要:

流量劫持;tproxy;

 :Sidecar 模式中基于 IPtables 的流量劫持方式;Ambient 模式中,使用了 tproxy 做透明流量劫持。

——由于 Ambient Mesh 是 4 层透明设计,虽然中间经过了 ztunnel 或是 waypoint proxy 的转发,但无论是 sleep Pod 中的 curl 应用还是 httpbin pod 中的 httpbin 应用都无法感知到中间代理的存在。为了达到这个目的,数据包在很多环节无法直接通过 DNAT 及 SNAT 进行转发,因为 DNAT 和 SNAT 动作会修改数据包的源/目标信息,Ambient Mesh 设计的这些规则很大程度是为了贯彻两端都透明的传输路径

  • 在 Ambient 模式下,由于 Pod 内不再注入 Sidecar,所以 Pod 网络命名空间不再有任何 iptables 规则,所以这一阶段的流量路径与一般 K8s pod 无异;
  • K8s 为服务路由配置了 iptables 规则,以将服务地址 DNAT 为具体的 Endpoint 地址,为了跳过这个逻辑将数据包重定向到 ztunnel,Ambient Mesh CNI 为宿主机在 K8s 的规则之前添加了一些 iptables 规则和路由规则;Node节点的网络命名空间--match-set ztunnel-pods-ips
  • 数据包通过 pistioout 到达 ztunnel Pod 网络命名空间后,由于数据包的目标地址并非 ztunnel Pod 中的任何网络设备,所以如果不进行任何干预,这个数据包将被 ztunnel Pod 的协议栈丢弃。ztunnel 进程的 outbound 代理监听在 15001 端口上,为了将数据包正确地送达该端口,在 ztunnel Pod 内也设置了一些 iptables 规则和路由规则,数据包到达 ztunnel Pod 网络命名空间后(-A PREROUTING -i pistioout -p tcp -j TPROXY --on-port 15001 --on-ip 127.0.0.1 --tproxy-mark 0x400/0xfff)这条规则将从 pistioout 设备进入的数据包的目标地址重定为 127.0.0.1 的 15001 端口上,并为数据包打上 0x400 的标识,需要注意的是,这里虽然重定向了目标地址,但是 tproxy 会保留数据包的原始目标地址,应用程序可以通过 getsockopt 的 SO_ORIGINAL_DST 选项获得数据包的原始目标地址。
  • istioout 是一个 Geneve(Generic Network Virtualization Encapsulation) 类型的虚拟网卡,它的 IP 是 192.168.127.1,远端是 10.4.2.19(节点 A 上的 ztunnel Pod 的 IP),网关是 192.168.127.2(节点 A 上 ztunnel Pod 中 pistioout 网卡的 IP。数据包应用以下这条路由规则后,将通过 istioout 设备离开宿主机,然后从 pistioout 到达 ztunnel Pod 网络命名空间。
default via 192.168.127.2 dev istioout   //宿主机路由表101
  • 数据包到达 ztunnel 后,会进行 4 层负载均衡,经过 4 层负载均衡之后,ztunnel 将目标服务 IP 转换为 Pod IP

1、(根据代理对客户端或服务端是否可见visible来分为透明代理和非透明代理完全透明的流量劫持方式:从功能来看类似于让操作系统充当路由器,或者内置一个代理服务器,并允许部分流量被转移到用户空间处理iptables REDIRECT不同,基于tproxyTransparent proxy其中的 t 代表 transparent,即透明。需要在内核配置中启用 NETFILTER_TPROXY 和策略路由的方案并不会修改源地址和端口

  • tproxy尝试从连接或者监听套接字列表中找到一个符合四元组的套接字,然后如果发现此套接字含有IP_TRANSPARENT就修改此数据包的sock结构,这个行为其实就是转发。
  • IP_TRANSPARENT的作用不仅仅是用于tproxy的转发,还可以使得被设置的套接字绑定非本地的IP地址,但是必须设置路由表,否则数据包会被路由、丢弃,根本无法被转发到绑定IP_TRANSPARENT到套接字。除非如果目标地址与本地地址匹配,则由系统本身接受处理,这就需要手动设置一个单独的路由表,并由mark去指示。
  • 使用--tproxy-mark设置使用透明代理流量的fwmark(firewall mark用于标记数据包的一个标志,它可以用来指定特定的路由表或者策略路由--tproxy-mark 0x1/0x1:fwmark/掩码:利用fwmark配置高级路由功能将非本地流量送到本地lo interface,从而能进入本地协议栈的处理。skb随后通过ip_rcv进入本地协议栈处理后,可以直接利用已关联的socket进行处理,而不需像普通的处理流程那样,使用skb中的tcp 4-tuples来查找listening socket或者established socket。这样可以把去往任意目的IP和目的端口的skb,关联到本地代理程序监听的socket上(Ztunnel(其中的 Envoy)实际上是充当了透明代理,它使用 Envoy Internal Listener 来接收 HTTP CONNECT 请求和传递 TCP 流给上游集群 )。本地代理程序通过accept返回的socket中包含的local address和local port仍然为原始连接的信息。关联到哪个本地监听的socket,可以通过on-port和on-ip option来指定,并且本地监听的socket需要设置IP_TRANSPARENT option,否则TPROXY target会当做找不到指定的本地socket,将skb丢弃。
  • 透明代理实际是在网络数据包从程序实际面对的套接字到物理网卡之间加入了一个处理数据包的过程,而且因为此数据包的处理流程不像是eBPF一样在内核中充满阻碍的处理代码,而是在被转发到用户态的套接字,这意味着我们可以捕获流量实现任意的注入,统计,分析等等。
=== PREROUTING ===
    --- raw ---
    --- mangle ---
        ......
        -A PREROUTING -i pistioout -p tcp -j TPROXY --on-port 15001 --on-ip 127.0.0.1 --tproxy-mark 0x400/0xfff
        ......
    --- nat ---
    --- filter ---

ztunnel Pod 网络命名空间

ztunnel 进程的 outbound 代理监听在 15001 端口上,为了将数据包正确地送达该端口,在 ztunnel Pod 内也设置了一些 iptables 规则和路由规则,数据包到达 ztunnel Pod 网络命名空间后,将命中上表中的规则,这条规则将从 pistioout 设备进入的数据包的目标地址重定为 127.0.0.1 的 15001 端口上,并为数据包打上 0x400 的标识,需要注意的是,这里虽然重定向了目标地址,但是 tproxy 会保留数据包的原始目标地址,应用程序可以通过 getsockopt 的 SO_ORIGINAL_DST 选项获得数据包的原始目标地址。iptables 规则执行完毕后,将执行策略路由

20000:  from all fwmark 0x400/0xfff lookup 100

该策略路由指定使用 100 路由表(由 Istio CNI 创建)进行路由,我们再看一下 100 路由表的内容:

local default dev lo scope host

100 路由表只有一条规则,即通过 lo 设备发往本机协议栈,至此数据包进入 ztunnel Pod 本机协议栈,由于在 iptables 中将数据包重定向到 127.0.0.1:15001,所以数据包最终到达 ztunnel 进程监听的 15001 端口。

2、iptables:

  • -t table这个选项指定命令要操作的匹配包的表。TABLES当前有三个表(哪个表是当前表取决于内核配置选项和当前模块)。如果内核被配置为自动加载模块,这时若模块没有加载,(系统)将尝试(为该表)加载适合的模块。
  • filter:这是默认的表,包含了内建的链INPUT(处理进入的包)、FORWORD(处理通过的包)和OUT‐PUT(处理本地生成的包)。
  • nat:这个表被查询时表示遇到了产生新的连接的包,由三个内建的链构成:PREROUTING(修改到来的包)、OUTPUT(修改路由之前本地的包)、POSTROUTING(修改准备出去的包)。
  • mangle:这个表用来对指定的包进行修改。它有两个内建规则:PREROUTING(修改路由之前进入的包)和OUTPUT(修改路由之前本地的包)。
  • -m, --match match : 显式指明要使用的扩展模块:例如--match-set匹配ipset定义的IP集;(-A ztunnel-PREROUTING -p tcp -m set --match-set ztunnel-pods-ips src -j MARK --set-xmark 0x100/0x100
  • MARK用来设置包的netfilter标记值。只适用于mangle表。 --set-mark mark。mark 这个模块和与netfilter过滤器标记字段匹配(就可以在下面设置为使用MARK标记)。
  • --mark value [/mask]匹配那些无符号标记值的包(如果指定mask,在比较之前会给掩码加上逻辑的标记)。
  • -j --jump target (-j  目标跳转)指定规则的目标;也就是说,如果包匹配应当做什么。目标可以是用户自定义链(不是这条规则所在的),某个会立即决定包的命运的专用内建目标,或者一个扩展(参见下面的EXTENSIONS)。如果规则的这个选项被忽略,那么匹配的过程不会对包产生影响,不过规则的计数器会增加。      
  • EXTENSIONS:对应的扩展,iptables能够使用一些与模块匹配的扩展包,大多数都可以通过在前面加上!来表示相反的意思。
  • CONNMARK和MARK的区别:同样是打标记,但CONNMARK是针对连接的,而MARK是针对单一数据包的,这两种机制一般都要和ip rule中的fwmark联用,实现对满足某一类条件的数据包的策略路由:
  • 对连接打了标记,只是标记了连接,没有标记连接中的每个数据包。标记单个数据包,也不会对整条连接的标记有影响。二者是相对独立的;
  • 路由判定(routing decision)是以单一数据包为单位的。或者说,在netfilter框架之外,并没有连接标记的概念。或者说,ip命令只知道MARK, 而不知道CONNMARK是什么
  • 关键在于:给所有要进行ip rule匹配的单一数据包打上标记。方法一般有二:用MARK直接打,或者用CONNMARK –restore-mark把打在连接上的标记转移到数据包上。
-A ztunnel-PREROUTING ! -i veth51e3b96d -m connmark --mark 0x210/0x210 -j MARK --set-xmark 0x40/0x40
  •  --mark value[/mask] 数据包的nfmark值与value进行匹配,其中mask的值为可选的;
  • --set-mark就是设置了skb->mark而已,并没有改变“报文”内容;(对连接打了标记,只是标记了连接,没有标记连接中的每个数据包,这里就是实现给数据包打标记