编辑
2024-04-23
魔法网络
00
请注意,本文编写于 393 天前,最后修改于 390 天前,其中某些信息可能已经过时。

目录

网络架构
socat 的问题?
尝试使用 iptables 解决
尝试调整配置
思考原因
观察数据包情况
尝试解决
后续
后续的后续
总结

最近看了一下我服务的访问日志,发现 ip 都是内网的 ip,经过一番排查总算是修好了,心路历程记录一下。

网络架构

我的服务器有一个公网 ip,绑定到了 pve 宿主机上,里面有一个 openwrt 虚拟机,作为我全部虚拟机的网关。

image.png

当前我是在 pve 上,通过 socat 工具进行流量转发的,但在虚拟机的 http server 中,我获取到的 ip 永远是 pve 的内网 ip 100.100.0.1

socat 的问题?

经过研究,我发现 socat 确实会改变来源的端口 ip,针对我的需求,最简单的办法应该是用 NAT:

  • 请求过来用 DNAT 把目标地址变成 100.100.0.6
  • 回复的时候 SNAT 把源地址变成 公网 ip

尝试使用 iptables 解决

那么我就写两条 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),那此时的逻辑就变成了:

image.png

这时候的问题,就变成了我发送的 http 请求得不到回应了。

尝试调整配置

经过一番尝试,我发现:

  • 关闭 openclash 后,一切正常,获取的源 ip 也是真实 ip 了。
  • 不关闭 openclash,增加下面这条 iptables 规则,可以正常响应 http,但是获取的来源 ip 就都变成了宿主机的内网 ip 100.100.0.1
shell
iptables -t nat -A POSTROUTING -o vmbr1 -j MASQUERADE

思考原因

应该是 openwrt 中开启了 openclash 后,对于 clash 内核来说,进来的流量和出去的流量对不上了。

有两种探索思路,用 tcpdump 探索一下数据包,另外可以看看 clash 究竟对数据做了哪些处理。

观察数据包情况

我先关闭对 vmbr1 的 SNAT 规则,此时客户端是无法获取响应的。

image.png

观察一下 vmbr0(外网出口)8081 端口的 tcp 数据:

shell
tcpdump -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 输出:

shell
tcpdump: 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 加上,就好了。

image.png

现在能正常访问通,也能正确获取源 ip 了。

后续

发现这样也不行,会让内部的服务没办法访问

比如我在 100.100.0.7 上访问这台服务器提供的 http 服务,然后就访问不了。

解决办法是增加 NAT 回环规则:

shell
iptables -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 证书都不对。

shell
iptables -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 为例):

  1. 在 openclash 设置中配置好不经过内核的源端口流量
  2. 在宿主机增加端口转发规则
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
  1. 在宿主机增加 nat 回环规则
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
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:mereith

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!