为了好好上个网, 我折腾了一整套代理网络
我希望打开电脑的时候, 可以直接去做想做的事.
读一篇文章, 查一份资料, 看看别处的人正在关心什么. 或者把手头卡住的问题拿去和 AI 商量, 话说到一半, 不必停下来研究那个迟迟不动的加载圆圈.
好好上网, 看看世界, 再稳定、安全地用 AI. 最初想要的, 大致就是这些. 后来 AI 渐渐成了每天要用的工具, 我对网络也就多了一份认真: 住宅 IP, 稳定的出口, 还有大家常说的”不降智”, 都在我的考虑里. 一个 IP 无法保证模型每次都聪明, 我能做的, 是先把自己这段连接照料好.
谁知, 为了省下日后打开电脑时的那一点麻烦, 我先花了几个月.
手机里的节点, 家里的软路由, 公网网关, 远端的住宅出口, 一样样接起来, 又一样样检查. 当时做的时候只觉得琐碎, 现在回头看, 才发现自己已经走了这么远.
那些看上去都像网络不好的日子
最初, 我很自然地把问题归给线路. 这条不稳, 再添一条; 那个服务不好用, 给它单独加几条规则. 手机用 Shadowrocket, 电脑用 Clash/Mihomo, 路由器上还有自己的配置.
每次改动都有理由. 日子一长, 却越来越难说清它们合在一起, 究竟怎样工作.
同一个服务, 手机上正常, 电脑上不正常, 得先确认两边是不是同一份规则. 一台设备更新了订阅, 另一台还在用旧配置. 列表里有一个节点, 不代表自动模式真的会选它. 看见一个延迟数字, 也不能说明业务请求走的是自己以为的出口.
这类问题最烦的地方, 是看上去都像网络不好.
有一次, 客户端把线路测成了 timeout, 实际请求却能通. 查下来, 测速使用的探测地址并不能可靠代表那条线路的可用性. 换掉探测地址后, 那个红色的 timeout 才消失.
还有一次, 服务端和订阅都核对过了, 手机仍然表现不对. 最后重启了一下客户端, 恢复了. 具体是哪一层旧状态没有刷新, 当时没有继续拆, 但至少不用再去改服务器.
类似的小事多了, 我才发觉, 真正磨人的还有那份不确定. 一次请求没有回来, 面前却摆着那么多可能. 我需要知道它经过了哪里, 而不是再凭运气换一个节点.
先把去向理清
于是, 我开始把主要的出口选择收回到服务端. 频繁变化的部分, 尽量在一个地方维护.
客户端保留自己该处理的直连和拒绝规则, 比如局域网, 内网互联, 以及明确不需要代理的请求. 需要进入代理的部分, 尽量交给统一网关. 网关再决定普通流量从哪里出去, 哪些请求必须进入指定出口链.
下面把实际机器和线路名称都换掉了, 只保留关系:
手机 / 电脑 / 路由器
├─ 直连规则 → 设备所在网络
├─ 拒绝规则 → 停止连接
└─ 代理规则 → 公网网关
├─ 普通流量 → 网关出口
└─ 指定流量 → 住宅出口候选链
├─ 可用 → 出口 A / B
└─ 全部不可用 → 停止连接这里的住宅出口, 就是流量最终从住宅网络出去. 它只是我选择的一种出口条件, 不代表某个服务一定放行, 也不能替代账号本身的安全措施.
理清这一层以后, 修改服务端的出口规则, 终于不用再往手机, 电脑和路由器里各抄一遍. 客户端仍然各有要做的事, 但最容易变化的那部分, 总算有了确定的归处.
手动选出口的入口我也留了. 自动模式负责平时用, 固定出口负责排查. 出问题时能够钉住某一条路径, 比反复点一个含糊的 Auto 有用得多.
改造没有直接拿原来的服务开刀. 我们先起了独立的试验入口, 验证请求能走通, 再逐步切换. 即便如此, 还是踩到了很朴素的坑: 新入口的防火墙没放行, 客户端当然怎么测都是 timeout. 另一次是配置文件权限收得太紧, 检查时用的用户能读, 实际运行服务的用户读不了.
看过许多协议的说明, 最后仍要回来检查一条防火墙规则, 一个文件权限. 做网络的耐心, 有一部分就是这样练出来的.
接上家里的 Wi-Fi, 就能继续
软路由是这套网络里离日常最近的一环.
公网网关负责后面的出口选择, 家里的这台设备则站在更前面. OpenWrt 上运行 Mihomo, 通过透明代理接住需要处理的流量, 按规则决定直连还是送入代理. 接到这张 Wi-Fi 的设备, 可以把这一部分工作交给它, 不用每台都先打开客户端, 再各自选一遍节点.
我想要的便利很小: 手里从电脑换成平板, 注意力还留在正在读的东西上. 网络在后面接着做它的事, 不必每次都出来打招呼.
但让它不打扰人, 需要先做不少细活.
曾经查到, 平板的云同步被兜底规则送进了代理. 还有一次排查音乐跳歌, 发现相关请求缺少直连规则, 连 DNS 得到的 CDN 去向也与直连路径不协调. 这时只改一个代理开关不够, 得把域名分流与解析策略一起理顺, 再看新连接实际去了哪里.
后来, 国内域名的直连规则也在软路由上补齐, 对应的 DNS 策略一并对齐. AI 的指定出口规则保留在前面, 需要直连的日常服务各走自己的路. 那些看似不起眼的先后顺序, 决定了接入这张 Wi-Fi 以后, 每一次请求怎样离开家里.
改动时, 我们先检查配置, 再热加载, 必要时清理受影响的旧连接, 最后核对新请求命中的规则. 没有动不动就重启整台路由器. 这样处理, 才能分清变化来自刚才的修正, 还是旧连接恰好断了.
软路由替设备省下了操作, 也把更多责任集中到了自己身上. 一条写得太宽的规则, 影响的就不止一个客户端. 所以我对它的要求, 渐渐从能用, 变成了能解释清楚为什么这样用.
两台设备, 四条路
家里这一端理顺了, 请求还得走到远端. 那里的连接, 也有自己的脾气.
我先用了 Tailscale, 后来又加上独立的 WireGuard 隧道, 把主要的数据传输放到 WireGuard 上, 同时保留 Tailscale 这条路. 当时做过同一对机器之间的对照测试, WireGuard 在其中一个单流方向上更有优势. 这个结果足够支持我调整自己的链路, 但也只说明那次测试的情况.
真正让我重新考虑备用路径的, 是一次 WireGuard 握手长时间不再更新.
机器没重启, 没找到内存耗尽, 网卡掉线或者代理进程崩溃的证据. 两端重启隧道后, 连接又恢复了. 当时也不是忘了配 keepalive, 它本来就在.
这很像 UDP 会话或 NAT 状态出了问题, 但没有足够证据把原因钉死在路由器或运营商身上. 我能确定的只有: 主机还在, 服务还在, 这条路径却已经不能正常用了.
那次故障还暴露了另一件事: 我以为已经留好的后路, 没有如期接住连接.
回头查配置, 才重新弄清探测列表, 候选集合与 fallback 的关系. 健康检查观察到一条线路, 并不代表当前选择器会使用它. 我曾经把出现在文件里的名字, 当成了已经备好的退路.
后来整理出来的顺序是这样的:
| 优先级 | 候选路径 |
|---|---|
| 1 | 出口设备 A, 经独立 WireGuard 隧道 |
| 2 | 出口设备 A, 经 Tailscale 网络 |
| 3 | 出口设备 B, 经独立 WireGuard 隧道 |
| 4 | 出口设备 B, 经 Tailscale 网络 |
| 最后 | 全部不可用时停止连接 |
四条路径并不是四条完全独立的宽带. 前两条仍然依赖同一台设备和它所在的网络, 后两条也是. 同一台机器断电, 换一条隧道救不了它; 整个住宅网络断了, 也一样. 我保留两种连接方式, 是想覆盖一部分路径故障, 再用另一台出口设备覆盖另一部分故障.
Tailscale 这条路径也不等于每次都经过中继, 它可能直接连接, 也可能借助 relay. 我关心的是它能不能提供一条可用的替代路径, 以及最后有没有从指定的出口出去.
最后做逐级故障注入时, 我们用的是隔离的 Xray 实例. 依次让前面的候选不可用, 核对 TCP, UDP 的路由标签和响应, 直到最后的阻断. 正式入口还分别验证了各条路径, 自动模式也确认命中了主路径.
测试的最后一步, 是确认所有候选失效以后, 连接会停下来. 对于明确要求住宅出口的请求, 我宁愿看见一次失败, 也不希望它悄悄换个出口, 留给我一张仿佛正常的页面.
做到这一步, 我才对备用路径多了一点底气. 隔离测试当然还不能代替日后真实的断电与断网, 但至少那条写在配置里的顺序, 已经逐级走过了.
也有亲手搭起, 又亲手拆掉的东西
中间我还自建过 Tailscale 的 DERP.
当时觉得这很顺手: 既然已经有自己的服务器, 中继也由自己维护, 整套东西应该更可控. 后来碰到跨 tailnet 分享设备的场景, 才发现这里有一条很具体的限制.
Tailscale 官方文档写明, 自定义 DERP 不支持 device sharing 等跨 tailnet 功能. 我当时又移除了默认的官方 DERP 候选, 相当于在这个场景下把本来能用的选择也拿走了.
最后删除了自定义中继映射, 恢复官方中继候选, 设备重新连通. 自建服务, 证书同步和相关规则也一起清理了.
拆的时候, 难免想起搭它花过的时间. 但留下来, 也不能替当初的投入多挣回什么. 清理完服务与规则, 重新确认连接, 这件事就算过去了. 后来再看, 少维护一处, 也是那段折腾留给我的收获.
页面之外, 还有没有走完的检查
TCP 好测. 打开一个页面, 发一个请求, 看看响应就有了直观感觉. UDP 比较容易被漏掉, 尤其是 WebRTC 用到的 STUN 流量.
有一次检测页上的出口结果不一致, 我们继续查真实连接, 发现不同 STUN 请求没有走同一类出口: 一部分进了住宅路径, 另一部分从网关直接出去了.
看到两个地址的时候, 不能直接认定本地真实地址泄露了. 那次查到的是代理链内部的出口不一致. 对我的规则要求来说, 它依然需要修, 只是不能把两件事混在一起.
后来把相关 STUN/TURN 请求也收进了指定出口策略, 再用实际 UDP 请求确认路由. 那条四级候选链, 因此也必须把 UDP 单独测一遍.
备用设备的 Tailscale 路径还遇到过一个更细的坑: SOCKS 的 UDP 返回地址, 在分享设备的场景里并不适合另一侧直接使用. TCP 能通, 掩盖不了这个问题. 最后给这条路径换了适合当时条件的承载方式, 再把 TCP 和 UDP 各自验过, 才把它算作可用候选.
现在写下来, 不过几句话. 当时却要在不同机器之间来回核对, 把一个请求送出去, 再去另一端找它的踪迹. 许多所谓稳定, 就藏在这些不太好讲得有趣的检查里.
账单记得的事, 日志未必记得
另一类很有记忆点的问题, 是流量突然涨得特别快.
前面提到的平板云同步, 就是在查实时连接时找出来的. 后来还发现, 内网互联工具自己的部分通信也绕进了代理, 平白多走一层. 改好规则, 再观察连接与流量的变化, 这类问题尚有清楚的来回可看.
但不是每次都能查清楚.
有一段流量增长, 等开始调查时, 大流量连接已经结束了. 当时的日志没有留下足够的字节记录. 能看到某台设备开过什么程序, 也能看到某些连接, 但没办法据此把过去那一大段用量准确算到某个下载头上.
这段时间, 我也补上了持续的用量统计, 按使用主体归并, 把不同协议的计数收进来. 同一个人换出口, 不应该在账上凭空变成另一个人. 进程重启导致计数归零, 也不能把之前累计的用量一起抹掉.
这件事让我对那些漂亮的用量图表多了一点耐心. 账要从有可靠记录的那一刻算起. 过去没有留下的字节, 不会因为今天画出一条曲线, 就重新有了去处.
后来, 网络慢慢安静下来
这套东西里面确实用了不少组件: Xray, sing-box, WireGuard, Tailscale, 再加上手机, 电脑和路由器上的客户端. 真正省心的部分, 是后来一点点理清的关系.
哪份配置是源文件, 哪份是发布出来的订阅. 首页节点列表和路由规则分别从哪里来. 自动模式选哪些候选, 手动模式又固定到哪里. 服务端更新之后, 客户端究竟有没有加载到新内容.
早先节点列表和规则文件混在一起, 刷新一下就可能又冒出一组重复节点. 后来把这两部分拆开, 分别维护. 发布时也开始核对本地文件, 服务器文件和实际订阅响应, 而不只看上传命令有没有报错.
把这些关系一点点理清以后, 日常反而没有那么多值得说的事了. 接上 Wi-Fi, 打开文档, 继续和 AI 讨论手里的问题. 那些忙过许久的配置, 终于退到了注意力之外.
我现在依然会看新版本, 也会惦记还能怎么改. 只是不会看到更新就马上替换正在用的核心组件. 真要升级, 得把自己的客户端和 TCP, UDP 路径再跑一遍, 还得确认出口切换和统计没有退回去. 好不容易折腾到能日常用, 不太舍得随手把它弄坏.
偶尔我还是会打开控制台看看. 看请求走在安排好的路径上, 看软路由替家里的设备分清去向. 说实话, 是有一点得意的.
规模不大, 也还有没完全解释清楚的故障. 可从起初对着节点列表挨个尝试, 到现在能够说清一次请求经过哪里, 为什么走这条路, 中断以后又怎样处理, 这段变化是实实在在的.
我把面板关掉, 回到原来想读的文章.
折腾这么久, 想要的无非就是这一刻: 世界在页面那头, 我可以把心思放回它身上.
本文依据这几个月的实际配置, 排障和验证记录整理. 机器名称, 地点, 地址, 端口, 账号及访问入口均已省略或替换, 图中只保留架构关系. 文中的测试结果指当时的记录, 不代表持续在线监测.