旁路由透明代理与端口转发排错实战:从网络不通到完美运行
零、最终目标
本文记录了一次完整的网络配置排错过程。目标是实现一个经典的“旁路由”透明代理方案:
- 主路由(小米):作为家庭网络的主网关,负责 DHCP 和基础路由。其核心任务是识别出需要代理的流量,并将其转发给旁路由。
- 旁路由(PVE下的LXC Ubuntu容器):不作为网关,仅作为网络中的一个普通设备。其核心任务是运行
mihomo代理核心,接收并处理主路由转发过来的流量,实现“科学上网”。 - 附加需求:在实现透明代理的同时,保证外部公网能通过端口转发正常访问到旁路由上的服务(如 SSH)。
一、排错之旅:遇到的问题与解决方案
问题一:透明代理正常后,端口转发失效
症状:透明代理可以工作了,但是从公网通过主路由端口转发到旁路由的 SSH 连接失败了。然而,只要停止旁路由上的 mihomo 服务,端口转发就立刻恢复正常。
分析与解决:
- 锁定问题:“停止旁路由服务就能恢复”这一现象,将问题 100% 锁定在旁路由内部,并且是
mihomo进程及其iptables规则导致的。 - 深入分析:问题的根源在于,当旁路由上的 SSH 服务回复数据包时,这个包是从旁路由自己发出的,它会经过
OUTPUT链。mihomo在OUTPUT链的规则不够智能,错误地将这个 SSH 回复包也当成了需要代理的流量,将其拦截并错误地路由,导致它永远无法回到公网的客户端。 - 解决方案:为了解决此问题,需要在
mihomo的systemd服务中,通过ExecStartPost指令来调用一个脚本,该脚本负责在mihomo创建完自己的规则后,再将我们的OUTPUT链豁免规则插入到iptables链的顶端。这个脚本 (apply_iptables_fixes.sh) 的最终版本如下,它包含了智能等待循环以解决竞态条件问题:#!/bin/sh # --- 智能等待循环 --- # 等待,直到在 mangle OUTPUT 链中看到 mihomo_output 规则出现 while ! iptables -t mangle -L OUTPUT -n | grep -q 'mihomo_output'; do sleep 0.5 done # --- 等待结束 --- # 定义豁免规则的内容 RULE_OUTPUT="-p tcp --sport 22 -j RETURN" # 检查并插入 OUTPUT 规则 (确保在最顶端) iptables -t mangle -C OUTPUT $RULE_OUTPUT 2>/dev/null || iptables -t mangle -I OUTPUT 1 $RULE_OUTPUT
问题二:局域网设备与 VPN 设备的体验不一致
症状:局域网设备直接访问被代理的 HTTPS 网站会出现证书错误,而通过 OpenVPN 连接回内网的设备访问却没有问题。
分析与解决:
- 锁定问题:两种客户端都通过了
mihomo代理,但表现不一,这通常与 DNS 解析路径有关。 - 根本原因:
- 局域网设备:使用主路由作为 DNS 服务器,拿到的是网站的真实 IP。
mihomo为了识别域名进行分流,被迫启用 SNI 嗅探,即进行“中间人”解密,导致浏览器因证书不符而报警。 - VPN 设备:很可能被 OpenVPN 服务器强制指派了旁路由的 IP 作为 DNS 服务器。DNS 查询直接发给了
mihomo。mihomo的 DNS 服务工作在fake-ip模式,它为被代理的域名返回一个假的 IP 地址。后续mihomo看到这个假 IP,就知道要去访问哪个域名,无需 SNI 嗅探,自然也就没有证书错误。
- 局域网设备:使用主路由作为 DNS 服务器,拿到的是网站的真实 IP。
- 解决方案:(可选)修改主路由的 DHCP 配置,将所有局域网设备的 DNS 服务器都指向旁路由的 IP,即可获得一致的“无证书错误”体验。
问题三:所有规则在重启后全部失效
症状:在主路由和旁路由上精心调试好的所有 ip 和 iptables 命令,在设备重启后全部消失。
分析与解决:
- 根本原因:消费级路由器和一些 Linux 发行版的文件系统是非持久化的,很多系统目录(如
/etc/firewall.user,/etc/rc.local)都存在于内存中,重启后会被重置。此外,脚本的执行时序也非常重要,过早执行可能会因为依赖的服务(如网络、磁盘挂载)未就绪而失败。 - 最终解决方案:
- 主路由:通过分析
shellcrash的行为,我们发现/data目录是持久化的,并且通过uci将脚本挂载到防火墙是可靠的启动方式。最终方案是:- 将所有主路由的
ip和iptables命令写入一个脚本,存放在持久化的/data/my_proxy_script.sh。 - 在脚本内部,加入智能等待逻辑 (
while ! ip a | grep -q 'br-lan'; do ...),确保网络就绪。 - 通过
uci命令,创建一个独立的防火墙include任务来调用此脚本,完美解决持久化和时序问题。
- 将所有主路由的
- 旁路由:
mihomo的iptables规则是动态生成的,我们的豁免规则需要与它的生命周期绑定。最终方案是:- 将豁免规则写入
/usr/local/bin/apply_iptables_fixes.sh脚本(即本教程第一节中的脚本)。 - 在脚本中加入智能等待循环,等待
mihomo自己的iptables规则出现后,再执行插入。 - 修改
mihomo.service文件,使用ExecStartPost指令,在mihomo主服务启动后,调用我们的豁免脚本。确保了执行顺序的正确性。
- 将豁免规则写入
- 主路由:通过分析
二、总结
这次排错过程曲折但收获颇丰,关键的技术点和心得如下:
- 怀疑硬件加速:在嵌入式路由设备上,当软件网络规则不生效时,首先要怀疑硬件加速。
- 追踪数据包路径:仔细思考数据包的“进”(
PREROUTING,INPUT)和“出”(OUTPUT,POSTROUTING),以及“转发”(FORWARD),能帮助定位问题是在哪个环节被拦截。 - 理解 DNS 与透明代理:
fake-ip和SNI 嗅探是透明代理处理 HTTPS 的两种核心技术,它们的区别直接影响用户体验。问题的根源往往在 DNS 的解析路径上。 - 重视持久化与启动时序:在嵌入式 Linux 系统上,找到正确的持久化配置方法(
uci,cron, 特定脚本)至关重要。同时,必须考虑脚本执行时,依赖的服务和硬件是否已就绪,使用“智能等待”是比“固定sleep” 更可靠的方案。 - 善用日志:在启动脚本中加入详尽的日志输出,是排查“为什么重启后不生效”这类问题的“黑匣子”,能极大地提高排错效率。
通过这次实践,我们不仅实现了一个健壮的旁路由透明代理方案,还深入理解了其背后复杂的网络机制。