最近看了一下我服务的访问日志,发现 ip 都是内网的 ip,经过一番排查总算是修好了,心路历程记录一下。
我的服务器有一个公网 ip,绑定到了 pve 宿主机上,里面有一个 openwrt 虚拟机,作为我全部虚拟机的网关。
当前我是在 pve 上,通过 socat 工具进行流量转发的,但在虚拟机的 http server 中,我获取到的 ip 永远是 pve 的内网 ip 100.100.0.1
经过研究,我发现 socat 确实会改变来源的端口 ip,针对我的需求,最简单的办法应该是用 NAT:
DNAT
把目标地址变成 100.100.0.6
SNAT
把源地址变成 公网 ip
那么我就写两条 iptables 吧:
shell# DNAT
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 100.100.0.2:443
# SNAT
iptables -t nat -A POSTROUTING -s 100.100.0.0/24 -o vmbr0 -j MASQUERADE
实际上发现,这样不行,因为我的 openwrt 里面是有 openclash 的,所有的出去的流量会经过 openclash 内核一份(因为我的 http server 虚拟机的网关是 openwrt),那此时的逻辑就变成了:
这时候的问题,就变成了我发送的 http 请求得不到回应了。
经过一番尝试,我发现:
100.100.0.1
:shelliptables -t nat -A POSTROUTING -o vmbr1 -j MASQUERADE
应该是 openwrt 中开启了 openclash 后,对于 clash 内核来说,进来的流量和出去的流量对不上了。
有两种探索思路,用 tcpdump 探索一下数据包,另外可以看看 clash 究竟对数据做了哪些处理。
我先关闭对 vmbr1
的 SNAT 规则,此时客户端是无法获取响应的。
观察一下 vmbr0(外网出口)8081 端口的 tcp 数据:
shelltcpdump -i vmbr0 port 8081 -vv tcpdump: listening on vmbr0, link-type EN10MB (Ethernet), snapshot length 262144 bytes 12:51:20.973650 IP (tos 0x0, ttl 51, id 0, offset 0, flags [DF], proto TCP (6), length 64) 245.8.110.36.static.bjtelecom.net.55398 > PVE8.0.tproxy: Flags [S], cksum 0xac8d (correct), seq 837711648, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 2999108670 ecr 0,sackOK,eol], length 0 12:51:20.973840 IP (tos 0x0, ttl 62, id 0, offset 0, flags [DF], proto TCP (6), length 60) PVE8.0.tproxy > 245.8.110.36.static.bjtelecom.net.55398: Flags [S.], cksum 0x7295 (incorrect -> 0x78be), seq 3073755857, ack 837711649, win 65160, options [mss 1460,sackOK,TS val 3334471025 ecr 2999108670,nop,wscale 7], length 0 12:51:21.005369 IP (tos 0x0, ttl 51, id 0, offset 0, flags [DF], proto TCP (6), length 52) 245.8.110.36.static.bjtelecom.net.55398 > PVE8.0.tproxy: Flags [.], cksum 0x9dea (correct), seq 1, ack 1, win 2058, options [nop,nop,TS val 2999108701 ecr 3334471025], length 0 12:51:21.006410 IP (tos 0x0, ttl 51, id 0, offset 0, flags [DF], proto TCP (6), length 134) 245.8.110.36.static.bjtelecom.net.55398 > PVE8.0.tproxy: Flags [P.], cksum 0x614a (correct), seq 1:83, ack 1, win 2058, options [nop,nop,TS val 2999108701 ecr 3334471025], length 82 12:51:21.169220 IP (tos 0x0, ttl 51, id 0, offset 0, flags [DF], proto TCP (6), length 134) 245.8.110.36.static.bjtelecom.net.55398 > PVE8.0.tproxy: Flags [P.], cksum 0x60a6 (correct), seq 1:83, ack 1, win 2058, options [nop,nop,TS val 2999108865 ecr 3334471025], length 82 12:51:21.169340 IP (tos 0x0, ttl 62, id 0, offset 0, flags [DF], proto TCP (6), length 40) PVE8.0.tproxy > 245.8.110.36.static.bjtelecom.net.55398: Flags [R], cksum 0xc379 (correct), seq 3073755858, win 0, length 0
经过 chatgpt 的分析,客户端其实和服务端握手成功了,但是收到了一个 RST 的包,来关闭连接,但是这个包是哪里来的呢。
我继续观察 vmbr1
的 tcpdump 输出:
shelltcpdump: listening on vmbr1, link-type EN10MB (Ethernet), snapshot length 262144 bytes 12:57:06.867861 IP (tos 0x0, ttl 50, id 0, offset 0, flags [DF], proto TCP (6), length 64) 245.8.110.36.static.bjtelecom.net.58797 > 100.100.0.6.tproxy: Flags [S], cksum 0x6411 (correct), seq 2884348287, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 162847935 ecr 0,sackOK,eol], length 0 12:57:06.867944 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60) 100.100.0.6.tproxy > 245.8.110.36.static.bjtelecom.net.58797: Flags [S.], cksum 0x91fb (incorrect -> 0xe08f), seq 4162753135, ack 2884348288, win 65160, options [mss 1460,sackOK,TS val 3334816919 ecr 162847935,nop,wscale 7], length 0 12:57:06.868023 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto TCP (6), length 60) 100.100.0.6.tproxy > 245.8.110.36.static.bjtelecom.net.58797: Flags [S.], cksum 0x91fb (incorrect -> 0xe08f), seq 4162753135, ack 2884348288, win 65160, options [mss 1460,sackOK,TS val 3334816919 ecr 162847935,nop,wscale 7], length 0 12:57:06.898465 IP (tos 0x0, ttl 50, id 0, offset 0, flags [DF], proto TCP (6), length 52) 245.8.110.36.static.bjtelecom.net.58797 > 100.100.0.6.tproxy: Flags [.], cksum 0x05bc (correct), seq 1, ack 1, win 2058, options [nop,nop,TS val 162847966 ecr 3334816919], length 0 12:57:06.900780 IP (tos 0x0, ttl 50, id 0, offset 0, flags [DF], proto TCP (6), length 134) 245.8.110.36.static.bjtelecom.net.58797 > 100.100.0.6.tproxy: Flags [P.], cksum 0xc919 (correct), seq 1:83, ack 1, win 2058, options [nop,nop,TS val 162847968 ecr 3334816919], length 82 12:57:06.900795 IP (tos 0x0, ttl 64, id 26279, offset 0, flags [DF], proto TCP (6), length 52) 100.100.0.6.tproxy > 245.8.110.36.static.bjtelecom.net.58797: Flags [.], cksum 0x91f3 (incorrect -> 0x0b54), seq 1, ack 83, win 509, options [nop,nop,TS val 3334816952 ecr 162847968], length 0 12:57:06.901126 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 245.8.110.36.static.bjtelecom.net.58797 > 100.100.0.6.tproxy: Flags [R], cksum 0xc316 (correct), seq 2884348370, win 0, length 0 12:57:06.901214 IP (tos 0x0, ttl 64, id 26280, offset 0, flags [DF], proto TCP (6), length 299) 100.100.0.6.tproxy > 245.8.110.36.static.bjtelecom.net.58797: Flags [P.], cksum 0x92ea (incorrect -> 0xea11), seq 1:248, ack 83, win 509, options [nop,nop,TS val 3334816953 ecr 162847968], length 247 12:57:06.901289 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 245.8.110.36.static.bjtelecom.net.58797 > 100.100.0.6.tproxy: Flags [R], cksum 0xc316 (correct), seq 2884348370, win 0, length 0 12:57:07.065430 IP (tos 0x0, ttl 50, id 0, offset 0, flags [DF], proto TCP (6), length 134) 245.8.110.36.static.bjtelecom.net.58797 > 100.100.0.6.tproxy: Flags [P.], cksum 0xc874 (correct), seq 1:83, ack 1, win 2058, options [nop,nop,TS val 162848133 ecr 3334816919], length 82 12:57:07.065494 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 100.100.0.6.tproxy > 245.8.110.36.static.bjtelecom.net.58797: Flags [R], cksum 0x8e45 (correct), seq 4162753136, win 0, length 0 12:57:07.065639 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto TCP (6), length 40) 100.100.0.6.tproxy > 245.8.110.36.static.bjtelecom.net.58797: Flags [R], cksum 0x8e45 (correct), seq 4162753136, win 0, length 0
chatgpt 分析说是因为客户端突然发送了一个 RST 包,但是感觉不太对,继续分析。
后面也没能分析出什么有用的点了,感觉是 openclash 内核给了一个 RST?
其中一个解决办法,就是我让这种流量不经过 clash 内核就好了,openclash 中的访问控制中,正好可以设置不经过内核的源端口流量(内网),我把 80,443 加上,就好了。
现在能正常访问通,也能正确获取源 ip 了。
发现这样也不行,会让内部的服务没办法访问
比如我在 100.100.0.7 上访问这台服务器提供的 http 服务,然后就访问不了。
解决办法是增加 NAT 回环规则:
shelliptables -t nat -A OUTPUT -p tcp -d 公网ip --dport 443 -j DNAT --to-destination 100.100.0.2:443 iptables -t nat -A POSTROUTING -s 100.100.0.0/24 -p tcp -d 100.100.0.2 --dport 443 -j MASQUERADE
发现内部的虚拟机上不了网了,发现在端口转发的 PREROUTING
中,必须得指定目标为特定 ip 才转发,不然虚拟机内打 www.baidu.com 证书都不对。
shelliptables -t nat -A PREROUTING -p tcp -d <公网ip> --dport 443 -j DNAT --to-destination 100.100.0.2:443
使用 socat
进行端口转发,会让 http server 无法正确获取来源 ip。
要想在部署了 openclash
的内网 NAT 环境下(openwrt 作为旁路网关,NAT 为 pve 提供),正确使用 iptables 进行端口映射需要(以映射到 100.100.0.2:443 为例):
shell#DNAT 注意要指定来源端口是 vmbr0 进来的,不然内网机器就上不了网了
iptables -t nat -A PREROUTING -p tcp -d <公网ip> --dport 443 -j DNAT --to-destination 100.100.0.2:443
#SNAT 无需添加,因为本身 pve 做 nat 的时候做了这个规则
iptables -t nat -A POSTROUTING -s 100.100.0.0/24 -o vmbr0 -j MASQUERADE
shell#OUTPUT
iptables -t nat -A OUTPUT -p tcp -d <公网ip> --dport 443 -j DNAT --to-destination 100.100.0.2:443
#SNAT 一下,注意一定要限制来源为内网网段,不然 http 来源 ip 还是获取的都是内网的。
iptables -t nat -A POSTROUTING -s 100.100.0.0/24 -p tcp -d 100.100.0.2 --dport 443 -j MASQUERADE
本文作者:mereith
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!