旁路由透明代理与端口转发排错实战:从网络不通到完美运行

8 阅读6分钟

旁路由透明代理与端口转发排错实战:从网络不通到完美运行

零、最终目标

本文记录了一次完整的网络配置排错过程。目标是实现一个经典的“旁路由”透明代理方案:

  • 主路由(小米):作为家庭网络的主网关,负责 DHCP 和基础路由。其核心任务是识别出需要代理的流量,并将其转发给旁路由。
  • 旁路由(PVE下的LXC Ubuntu容器):不作为网关,仅作为网络中的一个普通设备。其核心任务是运行 mihomo 代理核心,接收并处理主路由转发过来的流量,实现“科学上网”。
  • 附加需求:在实现透明代理的同时,保证外部公网能通过端口转发正常访问到旁路由上的服务(如 SSH)。

一、排错之旅:遇到的问题与解决方案

问题一:透明代理正常后,端口转发失效

症状:透明代理可以工作了,但是从公网通过主路由端口转发到旁路由的 SSH 连接失败了。然而,只要停止旁路由上的 mihomo 服务,端口转发就立刻恢复正常

分析与解决

  1. 锁定问题:“停止旁路由服务就能恢复”这一现象,将问题 100% 锁定在旁路由内部,并且是 mihomo 进程及其 iptables 规则导致的。
  2. 深入分析:问题的根源在于,当旁路由上的 SSH 服务回复数据包时,这个包是从旁路由自己发出的,它会经过 OUTPUT 链。mihomoOUTPUT 链的规则不够智能,错误地将这个 SSH 回复包也当成了需要代理的流量,将其拦截并错误地路由,导致它永远无法回到公网的客户端。
  3. 解决方案:为了解决此问题,需要在 mihomosystemd 服务中,通过 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 连接回内网的设备访问却没有问题。

分析与解决

  1. 锁定问题:两种客户端都通过了 mihomo 代理,但表现不一,这通常与 DNS 解析路径有关。
  2. 根本原因
    • 局域网设备:使用主路由作为 DNS 服务器,拿到的是网站的真实 IPmihomo 为了识别域名进行分流,被迫启用 SNI 嗅探,即进行“中间人”解密,导致浏览器因证书不符而报警。
    • VPN 设备:很可能被 OpenVPN 服务器强制指派了旁路由的 IP 作为 DNS 服务器。DNS 查询直接发给了 mihomomihomo 的 DNS 服务工作在 fake-ip 模式,它为被代理的域名返回一个假的 IP 地址。后续 mihomo 看到这个假 IP,就知道要去访问哪个域名,无需 SNI 嗅探,自然也就没有证书错误。
  3. 解决方案:(可选)修改主路由的 DHCP 配置,将所有局域网设备的 DNS 服务器都指向旁路由的 IP,即可获得一致的“无证书错误”体验。

问题三:所有规则在重启后全部失效

症状:在主路由和旁路由上精心调试好的所有 ipiptables 命令,在设备重启后全部消失。

分析与解决

  1. 根本原因:消费级路由器和一些 Linux 发行版的文件系统是非持久化的,很多系统目录(如 /etc/firewall.user, /etc/rc.local)都存在于内存中,重启后会被重置。此外,脚本的执行时序也非常重要,过早执行可能会因为依赖的服务(如网络、磁盘挂载)未就绪而失败。
  2. 最终解决方案
    • 主路由:通过分析 shellcrash 的行为,我们发现 /data 目录是持久化的,并且通过 uci 将脚本挂载到防火墙是可靠的启动方式。最终方案是:
      1. 将所有主路由的 ipiptables 命令写入一个脚本,存放在持久化的 /data/my_proxy_script.sh
      2. 在脚本内部,加入智能等待逻辑 (while ! ip a | grep -q 'br-lan'; do ...),确保网络就绪。
      3. 通过 uci 命令,创建一个独立的防火墙 include 任务来调用此脚本,完美解决持久化和时序问题。
    • 旁路由mihomoiptables 规则是动态生成的,我们的豁免规则需要与它的生命周期绑定。最终方案是:
      1. 将豁免规则写入 /usr/local/bin/apply_iptables_fixes.sh 脚本(即本教程第一节中的脚本)。
      2. 在脚本中加入智能等待循环,等待 mihomo 自己的 iptables 规则出现后,再执行插入。
      3. 修改 mihomo.service 文件,使用 ExecStartPost 指令,在 mihomo 主服务启动后,调用我们的豁免脚本。确保了执行顺序的正确性。

二、总结

这次排错过程曲折但收获颇丰,关键的技术点和心得如下:

  • 怀疑硬件加速:在嵌入式路由设备上,当软件网络规则不生效时,首先要怀疑硬件加速。
  • 追踪数据包路径:仔细思考数据包的“进”(PREROUTING, INPUT)和“出”(OUTPUT, POSTROUTING),以及“转发”(FORWARD),能帮助定位问题是在哪个环节被拦截。
  • 理解 DNS 与透明代理fake-ipSNI 嗅探 是透明代理处理 HTTPS 的两种核心技术,它们的区别直接影响用户体验。问题的根源往往在 DNS 的解析路径上。
  • 重视持久化与启动时序:在嵌入式 Linux 系统上,找到正确的持久化配置方法(uci, cron, 特定脚本)至关重要。同时,必须考虑脚本执行时,依赖的服务和硬件是否已就绪,使用“智能等待”是比“固定 sleep” 更可靠的方案。
  • 善用日志:在启动脚本中加入详尽的日志输出,是排查“为什么重启后不生效”这类问题的“黑匣子”,能极大地提高排错效率。

通过这次实践,我们不仅实现了一个健壮的旁路由透明代理方案,还深入理解了其背后复杂的网络机制。