透明代理(Transparent Proxy)是一种网络代理,用于转发网络请求和响应,同时对用户和目标服务器保持透明。TProxy(Transparent Proxy)是Linux内核的一个模块,用于实现透明代理的功能。
要实现TProxy,你可以按照以下步骤进行操作:
- 首先,确保你的系统内核支持TProxy模块。你可以通过检查
/boot/config-*
文件或者运行zcat /proc/config.gz | grep CONFIG_NETFILTER_TPROXY
命令来确认内核是否支持。 - 激活TProxy模块。你可以使用以下命令加载模块并启用相应的网络规则:
modprobe xt_TPROXY
- 配置iptables规则。iptables是一个用于配置Linux内核防火墙的工具。你可以通过以下命令添加TProxy规则:
iptables -t mangle -A PREROUTING -p tcp --dport 目标端口 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 透明代理端口
iptables -t mangle -A POSTROUTING -j ACCEPT
其中,目标端口
是要代理的目标服务器的端口号,透明代理端口
是你自己设置的用于监听的端口号。
- 配置路由。为了将流量重定向到透明代理,你需要配置路由规则:
ip rule add fwmark 0x1/0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
通过以上步骤,你应该能够设置一个基本的TProxy透明代理。请注意,具体实现可能因系统和网络环境而异,你可能需要根据自己的需求和情况进行相应的调整。
还请注意,作为一名开发者,你可能需要进一步了解网络编程和Linux内核的相关知识,以便更好地理解和实现TProxy透明代理。
摘要:
实际建立的 TCP 连接有 3 条:
- sleep -> NodeA ztunnel
- NodeA ztunnel -> NodeB ztunnel
- NodeB ztunnel -> httpbin
数据包在iptables规则中匹配的方式是,一条链的所有规则会从头到尾,一条一条的与数据包去匹配,直到某条规则匹配到了数据包,或者,链中的所有规则都不与数据包匹配,则将会返回该链的上一级链继续匹配,或者如果该链是主链,则数据包才会被采用默认策略。RETURN目标的作用是告诉系统,该类型的包不必继续和该链后面的规则继续匹配了,这样可以起到提高效率的作用。
----NodeA首次上行-------sleep pod 协议栈发出的三层数据包的源地址10.244.2.8和目标地址httpbin ClusterIP
-A PREROUTING -j ztunnel-PREROUTING
-A ztunnel-PREROUTING -p tcp -m set --match-set ztunnel-pods-ips src -j MARK --set-xmark 0x100/0x100
打上 0x100 标记的数据包将在随后的 nat 表阶段直接被接受,这就使得数据包跳过了 K8s iptables 规则的处理
--- nat ---
-A ztunnel-PREROUTING -m mark --mark 0x100/0x100 -j ACCEPT
from all fwmark 0x100/0x100 lookup 101
101 路由表:default via 192.168.127.2 dev istioout
----ztunnelA首次上行-------
tproxy 会保留数据包的原始目标地址,应用程序可以通过 getsockopt 的 SO_ORIGINAL_DST 选项获得数据包的原始目标地址。
-A PREROUTING -i pistioout -p tcp -j TPROXY --on-port 15001 --on-ip 127.0.0.1 --tproxy-mark 0x400/0xfff
from all fwmark 0x400/0xfff lookup 100
100 路由表local default dev lo scope host
----NodeA回程-------
数据包不是来自 ztunnel Pod IP,但是却是从 ztunnel Pod 的 veth 进入宿主机的,就打上 0x210 标记,为什么是这样的条件呢?因为 ztunnel 事实上是一个透明代理,也就是说 ztunnel 是在扮演对端的目标服务回复数据包,所以数据包中的源地址是对端服务地址,而不是 ztunnel Pod 的地址。
-A ztunnel-PREROUTING ! -s 10.244.2.3/32 -i veth51e3b96d -j MARK --set-xmark 0x210/0x210
-j MARK --set-mark mark:用来设置包的netfilter标记值。只适用于mangle表;-j 目标跳转指定规则的目标,也就是说,如果包匹配应当做什么。目标可以是用户自定义链(不是这条规则所在的),某个会立即决定包的命运的专用内建目标。或者一个扩展(参见下面的EXTENSIONS)。如果规则的这个选项被忽略,那么匹配的过程不会对包产生影响,不过规则的计数器会增加。
from all fwmark 0x200/0x200 goto 32766
32766: from all lookup main
main 路由表10.244.2.8 dev vethbda3de4b scope host
由于该数据包的目标地址并非本机,所以将再执行 iptables FORWARD 链
-A FORWARD -j ztunnel-FORWARD
这条连接上同样保存了 0x210 标记,属于该连接的数据包经过宿主机时,通过连接标记 0x210 将可以匹配到这条连接上的后续所有数据包。
-A ztunnel-FORWARD -m mark --mark 0x210/0x210 -j CONNMARK --save-mark --nfmask 0x210 --ctmask 0x210
--save-mark 是把数据包中的 mark 设置到连接中;(带 CONN 的是连接的标记,不带的是标记数据包的)
----NodeA后续上行-------
通过 iptables 的 connmark 模块匹配连接标记,然后为该数据包再打上 0x40 标记,
-A ztunnel-PREROUTING ! -i veth51e3b96d -m connmark --mark 0x210/0x210 -j MARK --set-xmark 0x40/0x40
打上 0x40 标记的数据包将在随后的路由阶段命中如下规则
from all fwmark 0x40/0x40 lookup 102
102 路由表default via 10.244.2.3 dev veth51e3b96d onlink
----ztunnelA后续上行-------
如果数据包的目标地址不是 ztunnel Pod 地址,且从 eth0 设备进入,且协议为 TCP,则为数据包打上 0x4d3 标识
-A PREROUTING ! -d 10.244.2.3/32 -i eth0 -p tcp -j MARK --set-xmark 0x4d3/0xfff
打上该标识后,数据包将在路由阶段命中如下规则
from all fwmark 0x4d3/0xfff lookup 100
100 路由表local default dev lo scope host
数据包到达 ztunnel 后,会进行 4 层负载均衡,经过 4 层负载均衡之后,ztunnel 将目标服务 IP 转换为 Pod IP(10.244.1.7).ztunnel 将向对端 Pod 的 15008 端口发起连接,之所以使用 15008 端口(而不是服务端口或者后端 Pod 端口)的原因是在对端 ztunnel 的 iptables 规则匹配时这个端口被用作了匹配条件.
----NodeA到 NodeB-------
main 路由表10.244.1.0/24 via 172.18.0.4 dev eth0
---- NodeB首个上行-------
from all lookup 100
100 路由表10.244.1.7 via 192.168.126.2 dev istioin src 10.244.1.1
----ztunnel-B首个上行-------
由于数据包的目标地址为 15008,所以该数据包将命中如下规则,并将其通过 TPROXY 重定向到 127.0.0.1 的 15008 端口,同时为数据包打上 0x400 标记
A PREROUTING -i pistioin -p tcp -m tcp --dport 15008 -j TPROXY --on-port 15008 --on-ip 127.0.0.1 --tproxy-mark 0x400/0xfff
数据包被打上标记后,将在策略路由阶段命中如下规则
from all fwmark 0x400/0xfff lookup 100
100 路由表local default dev lo scope host
---- NodeB下行-------
如果数据包的源地址不是 ztunnel Pod IP,但是来自 ztunnel Pod veth 设备,则打上 0x210 标记
-A ztunnel-PREROUTING ! -s 10.244.2.3/32 -i veth51e3b96d -j MARK --set-xmark 0x210/0x210
from all fwmark 0x200/0x200 goto 32766
32766: from all lookup main
10.244.2.0/24 via 172.18.0.3 dev eth0
-A ztunnel-FORWARD -m mark --mark 0x210/0x210 -j CONNMARK --save-mark --nfmask 0x210 --ctmask 0x210
----ztunnel-B后续上行-------
由于连接打上了 0x210 标识,所以后续数据包进入 Node-B 后将命中如下 iptables 规则,如果数据包不是来自 ztunnel veth,且带有 0x210 连接标记,则为数据包打上 0x40,目的是让数据包在接下来的路由阶段使用 102 路由表进行路由
A ztunnel-PREROUTING ! -i vethbcda8cd4 -m connmark --mark 0x210/0x210 -j MARK --set-xmark 0x40/0x40
from all fwmark 0x40/0x40 lookup 102
default via 10.244.1.3 dev vethbcda8cd4 onlink
ztunnel-B 收到来自对端 ztunnel 的连接后,将立即建立一条通往目标 Pod 的 TCP 连接,以便将解密的数据通过这个连接发往目标 Pod,ztunnel-B 通过调用 getsockopt 使用 SO_ORIGINAL_DST 选项从连接获得到原始目标地址,并通过 ztunnel-A 发来的 HTTP CONNECT 握手消息获知真实目标端口,同时,为了让 httpbin Pod 认为数据包来自 sleep Pod,ztunnel 需要在用于连接到 httpbin 的 sockt 上通过 IP_TRANSPARENT 选项将连接的源地址强制设置为 sleep Pod 的地址(ztunnel 可以通过连接的源地址来得知 sleep Pod 的地址)。这样一来,从 ztunnelB 发出的数据包的源地址是 sleep Pod 地址,目标地址则是 httpbin Pod 地址,就像数据包真的是从 sleep Pod 发来的一样。
ztunnel-B 到 httpbin
----ztunnel-B—node-B-------
from all lookup main
main 路由表default via 10.244.1.1 dev eth0
---- node-B-------
这条规则匹配的正是来自 ztunnel Pod veth 的数据包,命中这条规则的数据包将打上 0x210 的标识
-A ztunnel-PREROUTING ! -s 10.244.1.3/32 -i vethbcda8cd4 -j MARK --set-xmark 0x210/0x210
from all fwmark 0x200/0x200 goto 32766
from all lookup main
10.244.1.7 dev vethfc1b555e scope host