🌐OpenWrt 代理引擎与分流方案参考

本文保留 2026 年 9 月 23–24 日在小米 AX3000T 上的研究与实测记录,版本、配置和结论以文中记录日期为准。本文不包含真实 IP、密钥或个人设备标识。配置中的地址均为占位符(如 LAN_GATEWAY_IP、DNS_REMOTE_IP),实际使用前请按自己的网络填写。
这份参考记录涵盖 OpenWrt 代理组件选型、资源预算、Tailscale 出口联动、TPROXY 排查和 DNS 架构调整,供后续 OpenWrt 项目复用。

⚠️ 实测修正(官方 OpenWrt 25.12.2 + apk,2026-09-23)

本笔记原为「纸面选型」,以下是小米 AX3000T 实机验证后的修正:
  1. apk 不是 opkg。25.12 起 OpenWrt 改用 apk 包管理,feed 在 /etc/apk/repositories.d/distfeeds.list。
      • 第三方 opkg feed(PassWall2 / OpenClash / kenzok8 等)在 apk 固件上完全不可用。
      • 官方 feed 有 11174 个包;国内建议把 https://downloads.openwrt.org 换成清华镜像 https://mirrors.tuna.tsinghua.edu.cn/openwrt(实测 806 KB/s vs 142 KB/s)。
  1. homeproxy 不在上游 OpenWrt feed 里(已迁到 ImmortalWrt 维护);官方源里能搜到的只有 luci-app-v2raya。
  1. sing-box 有 tiny / 完整两个变体,差别不只是体积:
      • sing-box-tiny(29 MiB)编译标签写死:with_gvisor with_quic with_utls with_clash_api badlinkname tfogo_checklinkname0 —— 没有 tailscale。
      • sing-box(42 MiB)标签来自 Kconfig,其中 SINGBOX_WITH_TAILSCALE 的 default y —— 有 tailscale。
      • 用 tiny 跑 tailscale endpoint 会报:FATAL initialize endpoint[0]: Tailscale is not included in this build, rebuild with -tags with_tailscale。
      • 判断方法:直接 sing-box check -c <配置>,比翻 Makefile 快。

组件候选对比

需求
候选方案
取舍
简单 SOCKS5 转发(无分流)
gost(第三方 ipk feed,如 kenzok8/openwrt-packages)
Go 二进制,功能全(SOCKS5+HTTP CONNECT auto),但没有官方源,RAM 比纯 C 方案高
简单 SOCKS5 转发(无分流)
microsocks(官方 OpenWrt packages 源net/microsocks)
纯 C,SOCKS5-only,极低 RAM(<1MB),无用户名密码,和无分流需求场景天然匹配
反向隧道(注册到云端 frps)
frp(官方 OpenWrt packages 源net/frp)
Go 二进制,RAM 空载约 8-15MB。本机实测 25.12.2 上是 frpc/frps 0.66.0(笔记原先记的 0.71.0 偏高),官方 apk 源已含,无需第三方 feed
自动分流(GeoIP/GeoSite 规则)
sing-box 内核(可直接直写配置文件,不必套 GUI)
必须用这一类才能做"部分流量走代理、部分直连"的自动分流,microsocks/gost 做不到。⚠️ 若要接 tailscale 出口,必须装完整版sing-box-tiny 无此功能
自动分流 GUI
PassWall2
支持 Xray/Clash/sing-box/SS-rust 多内核,设备级 ACL(按 IP/MAC 强制走代理或直连)UI 成熟,依赖更重
自动分流 GUI
homeproxy(官方维护的 sing-box 前端)
更轻量,和 sing-box 上游版本贴得更紧,适合想直接手改 sing-box 配置(比如接 Tailscale exit node outbound)的场景;设备级 ACL 不如 PassWall2 成熟
自动分流 GUI
OpenClash(Clash Meta 内核)
界面简单,但 Clash Meta 内核历史上 RAM 占用偏高,资源紧张场景不优先
远程管理/组网
tailscale(官方 OpenWrt packages 源)
aarch64 支持良好,RAM 约 20-30MB
⚠️ 官方建议:PassWall / PassWall2 / OpenClash / Nikki / homeproxy 这类代理插件不要同时启用多个,选一个当主力。
⚠️ 2026-09-23 实测补充:在官方 OpenWrt 25.12(apk)上,homeproxy(已迁 ImmortalWrt)和 passwall2(第三方 opkg feed)都装不了;luci-app-v2raya 在官方源但体积太大(v2raya 25M + xray-core 28M)。结论:apk 固件下最现实的路子是直接装官方 sing-box 写配置文件。

资源预算参考(以 256MB RAM / 128MB flash 机型为例)

组件
估算 RAM
实测 / 备注
OpenWrt 系统基线(LuCI+dnsmasq+firewall+驱动)
~30-50MB
实测:开机后 used ≈ 58 MiB(free 总量 239540 KB)
sing-box(含 GeoIP+GeoSite 规则)
~70MB
社区参考值;本机用完整版(42 MiB 安装,含 tailscale)
tailscaled(系统 TUN 模式)
~20-30MB
实测:运行后 available 从 ~139 MiB 降到 ~94 MiB
tailscaled(sing-box 内建 userspace endpoint)
~20-30MB
这是第二个 tailscale 身份,同样吃 RAM
frpc
~10-15MB
Go runtime 基线(本机安装 14 MiB)
合计
~130-165MB
256MB 机型够用但不宽松;⚠️ 注意两个 tailscale 实例会叠加
本机实测(小米 AX3000T / 官方 OpenWrt 25.12.2)
  • RAM:239540 KB ≈ 234 MiB 总量,初始可用 ~139 MiB;装上 tailscale(系统)+ sing-box 后可用 ~94 MiB
  • Flash /overlay:总 85.7M,初始 81.0 MiB;装 tailscale(25)+ 完整版 sing-box(42)+ frpc(14)+ curl 后剩 43.5 MiB
  • ⚠️ 无 USB,flash 不可扩展;sing-box-tiny 省 13 MiB 但不含 tailscale,要用就得吃完整版的体积
调优建议:
  • 规则库只留必要分类(比如"国内直连+默认代理"两条),不要把 GeoSite 所有分类都加载。
  • 不要在同一台设备上叠加 AdGuard Home 全量 / mosdns 全量规则 / 第二个代理内核。
  • Flash 往往比 RAM 更紧:sing-box 二进制(~15-20MB)+ tailscale 二进制(~15-25MB)+ frp(~5MB)+ 规则库文件,合计可能占 40-70MB,装之前务必 df -h /overlay 确认真实剩余空间。

Tailscale Exit Node + 分流引擎联动

需求场景:部分 LAN 设备走常规分流规则,部分设备强制 100% 走 Tailscale exit node。
  1. sing-box 内建 tailscale endpoint —— ✅ 本机实测可用,推荐主路径
      • sing-box 1.12+ 提供 endpoints 类型 tailscale,支持 exit_node 选项,直接以其他 tailnet 节点为出口。
      • 它是 userspace 实现,完全不改系统路由表,天然满足「不影响本地路由」。
      • 实测(1.13.21):出口从本地运营商切换到所选的远端 exit node。
      • 前置条件:必须用完整版 sing-box;需要一个独立的 auth key,会以另一个节点(如 openwrt-singbox)出现在 tailnet 里。
  1. bind_interface: tailscale0 写法 —— 不再推荐
      • 它依赖系统 tailscaled 的全局 exit node 偏好(tailscale set --exit-node=...),而后者会把 default 塞进 table 52,并让规则 from all lookup 52 命中所有转发的包 → 等于整个 LAN 都被带着走出口,无法只给 sing-box 用。
      • 也就是说:它在「只让引擎走出口」这个目标上是方向错误的。
  1. OpenWrt 原生策略路由:ip rule + 独立路由表 + nftables 按源 MAC/IP 打标记
      • 仍然保留,用于「按设备分流」这一步:把指定设备打标记后导入出口路由。
建议:出口节点订阅走路径1(sing-box 内建 tailscale);「按设备分流」的标记部分走路径3。两者天然解耦,不必一次性绑死。

本机同时启用时的两个坑

  • 坑1:tailscale0 必须加进 fw4 zone。 tailscale 的放行规则写在独立的 table ip filter,而 fw4 的 input 链是 policy drop 且不含 tailscale0;nftables 里「A 表 accept」抵不过「B 表 drop policy」,于是 tailnet 入站全被拒。修法:
    • 坑2:透明代理不要用 TUN auto_route。 当本机同时「对外提供 exit node」+「sing-box 去用别人的 exit node」时,TUN auto_route 会把远端设备借道本机的转发流量也抓进 sing-box,造成绕圈 + 出口错乱。正确做法:nftables TPROXY + fwmark,只标记 br-lan 的流量,明确排除 tailscale0。

    TPROXY 实战踩坑(2026-09-23 · ✅ 已解决,见下方「根因」)

    在 OpenWrt 25.12 + sing-box 1.13.21 上做 TPROXY 透明代理时踩到的坑:
    1. 必须装 kmod-nft-tproxy(会带入 kmod-nf-tproxy)。没装时 nft 对 tproxy 语句的报错极具误导性——报在 table 那一行:
      1. (用「空壳表 + 逐条加规则」的空表对比法可快速排除语法问题,定位到是 tproxy 语句。)
    1. 除 fw4 外还有 iptables-nft 兼容表在覆盖 packet mark:
      1. 它与自定义链同处 priority mangle。若 TPROXY 靠 meta mark set + ip rule fwmark 选路,mark 会被清掉 → 路由不进 local 表。规避:把自定义链优先级改成 mangle + 10,让它在其后执行。
    1. 但核心问题仍未解决:优先级修好后 mark 保住了、nft 计数器也在涨,sing-box 却收不到任何 inbound/tproxy 连接。后续排查建议:
        • 先把 sing-box log.level 开到 debug 再复现(注意:路由器上没有 python3,改配置别用 python 脚本)
        • 或改成「TCP 用 redirect inbound + UDP 用 tproxy」的组合
        • 或对照 homeproxy 在同类固件上的 nft 实现
    1. 调试前务必先 disable 自启,否则重启后会带着坏规则启动,直接断掉整个 LAN。

    TPROXY 排查续(2026-09-23 晚 · 结论有误,已被下节推翻)

    追加的实测结论(全部已排除):
    检查项
    结果
    markcheck 链统计 meta mark 0x1
    255 包 —— mark 确实被设置且在路由前保留 ✅
    ip rule 优先级改成 100(早于 tailscale 的 5210)
    无变化
    语句顺序改成 meta mark set 0x1 tproxy ip to :2081(mark 在 tproxy 之前)
    无变化
    nft check 语法 / kmod-nft-tproxy
    正常
    flow offload(flowtable / flow_offloading)
    未启用,排除
    命中计数
    635 包确实进入 PREROUTING 并执行了 tproxy
    但 sing-box 始终 0 条 inbound/tproxy,conntrack 里也没有 dport=2081。
    → 结论:包被 tproxy 判定后没有投递到 sing-box 的透明套接字(掉了),具体原因未定位。
    下一步建议(按优先级):
    1. 对照 homeproxy 在同一固件上的 nft 规则逐条 diff(最有希望)
    1. 改用 redirect(TCP)+ tproxy(UDP) 混合方案;redirect 走 DNAT 不需要 mark/ip rule,机制完全不同
    1. 直接换 TUN + auto_route,但要单独处理「远端设备借道本机做 exit node」的绕行(给 tailscale0 加 ip rule 绕过 TUN)
    1. 路由器上没有 python3,改 JSON 配置请用 sed/jq 或本地改好再 scp
    临时可用方案(已落地):sing-box 增加 mixed inbound 监听 LAN_GATEWAY_IP:2080,手机/电脑把 WiFi 代理手动设为 LAN_GATEWAY_IP:2080 即可用上分流(实测国外→US、国内直连都正常)。

    ✅ TPROXY 根因与修复(2026-09-23 · 已解决)

    上一节「包没投递到透明套接字」的结论是错的:套接字投递实测正常(从 WAN / lo 进来的 tproxy 连接 sing-box 都能收到)。
    根因:net.ipv4.conf.all.src_valid_mark = 1(配置文件里没有,运行时被设置,疑似 tailscaled)。
    开启后,内核做反向路径校验时带着 fwmark=1 反查客户端源 IP → 命中 table 100 的 local DEFAULT_IPV4_ROUTE → 源 IP 被当成本机地址 → 按 martian source 丢弃。
    实测证据:
    • 手机流量 1398 包打上 mark 并执行 tproxy,但 INPUT / FORWARD hook 计数都是 0 → 丢在路由决策
    • ip route get <外网IP> from LAN_CLIENT_IP iif br-lan mark 1 → Invalid argument
    • 设 br-lan 的 accept_local=1(或 src_valid_mark=0)后 → 返回 local ... table 100,sing-box 立刻收到连接
    修复(已落地到 /etc/init.d/singbox-tproxy,已启用开机自启):
    不动 src_valid_mark,不影响 Tailscale。
    第二个坑:DNS 污染。 透明代理通了之后,国外站点仍有超时:LAN 设备用 dnsmasq(上游小米/运营商)解析拿到的是污染 IP(如 youtube → POLLUTED_DNS_IP,Facebook 的 IP),这种 IP 走代理也连不上。
    ⚠️ 已废弃(2026-09-24):下面这种「DNS 走 TPROXY」的劫持方式会造成 UDP 套接字堆积与内存上涨,已改为 dnsmasq 缓存 + sing-box dns-in,见文末「DNS 架构调整」。
    修复:/etc/sing-box/tproxy.nft 增加 LAN DNS 劫持(只针对 br-lan,路由器自身仍走 dnsmasq):
    交给 sing-box 的 hijack-dns → 国外域名走 dns-remote(DNS_REMOTE_IP DoT 经 ts-exit)。实测解析得到真实 IP。
    已知小问题:DoT 偶发 read response: EOF(约 10%),可观察后考虑换 DoH 或加备用服务器。
    备份:/etc/sing-box/tproxy.nft.bak、/etc/sing-box/singbox-tproxy.initd.bak
    Apple Intelligence 规则(2026-09-23):以下域名强制走 ts-exit,且 DNS 强制用 dns-remote(否则 geosite-cn / sr-direct 会让它们直连、解析到国内 CDN,Apple 判定为国区而无法开启)。规则插在 sr-direct 之前:
    gateway.icloud.com apple-relay.apple.com apple-relay.fastly-edge.com apple-relay.cloudflare.com guzzoni.apple.com cp4.cloudflare.com gspe1-ssl.ls.apple.com smoot.apple.com apple-relay.akamaized.net apple-relay.mask.apple-dns.net aapps.mzstatic.com
    备份:/etc/sing-box/config.json.bak-apple。路由器无 base64、scp 不可用,传文件用 ssh + 带引号 heredoc。
    DNS 调整(2026-09-23):
    • dns-remote 由 tls://DNS_REMOTE_IP(DoT,偶发 read response: EOF/853 超时,约 10% 失败)改为 UDP DNS_REMOTE_IP 经 ts-exit——已在 WireGuard 隧道内,不会被污染,无需再套 TLS。复测 3 轮 13 个域名:32 次解析 0 失败。
    • DNS 规则补上 sr-proxy → dns-remote(位于 sr-direct 之后、geosite-cn 之前,与路由规则顺序一致),避免「流量走代理、DNS 却用 DNS_DOMESTIC_IP 拿到国内 IP」。
    • 国内域名仍用 DNS_DOMESTIC_IP(拿就近国内 CDN,正确做法)。
    • 注意:sing-box 1.13 DNS 无内建多服务器故障转移,不要以为填两个就是互备。
    • 备份:/etc/sing-box/config.json.bak-dns

    性能与无线优化(2026-09-23)

    现场扫描发现:5G 原在 ch36/HE80,上游路由器 在 ch40 且信号 -6 dBm(几乎贴脸同频);2.4G ch1 周围 59 个网络、空口繁忙 ~79%;国家码 00;flow offload 未开;所有外网流量(含国内直连)都进 sing-box 用户态。
    优化
    做法
    结果
    国家码
    wireless.radio*.country=CN
    5G 149 段发射功率 20 → 28 dBm
    5G 信道
    ch149 HE80(非 DFS,邻居均 < -95 dBm);不开 160MHz(需覆盖 36–64,含 DFS 且与小米重叠)
    客户端 RX 960 → 1080 Mbit/s(MCS9 → MCS10)
    2.4G 信道
    ch6(1/6/11 中最空)
    —
    国内 IP 旁路 sing-box
    sing-box rule-set decompile geoip-cn.srs → 5790 条 CIDR → /etc/sing-box/cn4.nft(set cn4,interval + auto-merge);tproxy.nft 首行 include,在私网 return 之后加 ip daddr @cn4 return
    国内流量走内核转发,不再受 sing-box CPU 限制
    Flow offload
    firewall.@defaults[0].flow_offloading=1、flow_offloading_hw=1
    flowtable 含 lan/wan/phy0-ap0/phy1-ap0;国内连接出现 [OFFLOAD]。tproxy 流量属本机投递、不经 forward,不受影响
    WED(WiFi 硬件转发)
    /etc/modules.conf 加 options mt7915e wed_enable=Y,重启
    ✅ 已启用(dmesg attaching wed device 0 version 2)。代价:可用内存 ~58 → ~32 MB(tailscaled 46M、sing-box 39M RSS),需留意 OOM;若 WiFi 断流或内存吃紧,删掉该行重启即可回退
    重启后全量验证通过(2026-09-23):sing-box / singbox-tproxy / tailscale / frpc / nlbwmon / ttyd / miniupnpd 均自启;tproxy 表 + cn4 集合 + fwmark 规则 + accept_local=1 均恢复;代理出口与国内直连符合预期,既有远程服务均可访问。
    注意:geoip-cn.srs 更新后需重新生成 cn4.nft。备份:wireless.bak-opt、firewall.bak-opt、tproxy.nft.bak-opt。

    直连光猫前的加固(2026-09-23)

    • WAN 防火墙:原为 input/forward = ACCEPT(为从小米侧管理而开),直连光猫后若拿到公网 IPv6 会把 SSH/LuCI/DNS 暴露到公网。已改为 REJECT/REJECT,并加:
      • Allow-Mgmt-From-Upstream-LAN:仅 UPSTREAM_LAN_CIDR → tcp 22/80/443(小米撤掉后自动失效,可删)
      • Allow-Tailscale-UDP:udp 41641(否则 fw4 的 drop 会压过 tailscale 自己表里的 accept,只能走 DERP)
    • IPv6:TPROXY 只处理 IPv4,LAN 拿到公网 v6 会绕过分流。已 dhcp.lan.ra=disabled、dhcpv6=disabled;sing-box dns.strategy 改 ipv4_only。路由器自身 wan6 仍可用
    • 本地 SSH 通过 LAN 地址访问路由器,远程管理通过 Tailscale 访问
    • 备份:firewall.bak-ont、dhcp.bak-ont、network.bak-ont、sing-box/config.json.bak-ont
    ⚠️ 事故记录:清理自动回滚脚本时,孤儿 sleep 已被 procd 收养,脚本按「父进程」kill 误杀了 PID 1(procd)——OpenWrt 上 procd 收到 SIGTERM 会直接重启。断网约 30 秒,重启后全部服务与新配置均正常恢复。教训:kill 前检查目标 PID ≠ 1;路由器无 pkill,用 kill $(pidof …) 或记录真实子进程 PID。

    直连光猫后:WAN 钩子与重启恢复实测(2026-09-23)

    已切换为 光猫(UPSTREAM_GATEWAY_IP, DHCP) → OpenWrt WAN WAN_IP。
    WAN 钩子 /etc/hotplug.d/iface/95-singbox-wan:
    • ifup → 无条件 restart sing-box;ifupdate(DHCP 续约)仅在 WAN IP 变化时重启(状态存 /tmp/singbox-wan-ip)
    • 原因:开机时 sing-box 常抢在 WAN 拿到 IP 前启动(日志 network: missing default interface),内置 tailscale(ts-exit)随后退避重试,国外恢复被拖慢
    • 踩坑:第一版加了「sing-box 启动 <20s 就跳过」的去重判断,恰好把开机竞态这个最需要重启的场景跳过了,已删除
    日志:sing-box log.level 改为 warn(info 会逐连接刷屏,128KB 缓冲只能留 ~3 分钟);system.log_size=512。
    kill 1 重启实测(路由器本机探测,开机 uptime 计;日志挂钟在开机时被 NTP 校时 ~20s,不能直接相减):
    事件
    uptime
    WiFi 两频段 AP-ENABLED
    13–14s
    WAN 拿到 IP
    ~20s
    钩子重启 sing-box
    ~22s
    国内可达
    23s
    国外(ts-exit)可达
    49s
    → 国外比国内晚 ~26s(tailscale endpoint 重新登录控制面、与远端 exit node 重新握手)。单纯 restart sing-box(非开机)时国外 8s 恢复。
    测量注意:不要用笔记本经 WiFi 测——重启后客户端重新入网可能耗时 1–2 分钟,会淹没路由器侧的真实恢复时间。

    DNS 架构调整:从 TPROXY 劫持改为 dnsmasq 缓存(2026-09-24)

    现象:代理「时好时坏」。远端 DNS 每 10 分钟 8–113 次 context deadline exceeded,客户端实测 10% DNS 超时、首查 ~500ms。
    排除:隧道直连、160ms、0 丢包(从远端 exit node 侧 tailscale ping 两个路由器节点均 direct);出口节点直查 DNS_REMOTE_IP / DNS_BACKUP_IP 各 30 次 0 超时、~20ms。→ 问题在路由器 sing-box 内部。
    根因:DNS 用 TPROXY 劫持时,sing-box 为每个 DNS 查询建一个透明 UDP 回包套接字(绑定伪造源 LAN_GATEWAY_IP:53 / DNS_DOMESTIC_IP:53 等),默认 5 分钟 udp_timeout 才释放 → 53 端口堆积 415 个套接字、481 fds,sing-box RSS 持续上涨,可用内存降到 15 MB。
    新架构:
    • sing-box:新增 inbound direct dns-in(LOOPBACK_IP:5353,tcp+udp),路由规则首条 inbound dns-in → hijack-dns
    • tproxy.nft:prerouting 中 th dport 53 return(DNS 永不 TPROXY);新增 chain dns_redirect { type nat hook prerouting priority dstnat - 5; iifname "br-lan" th dport 53 redirect to :53 }
    • dnsmasq:noresolv=1server='LOOPBACK_IP#5353' '/tailscale.com/DNS_DOMESTIC_IP' '/tailscale.io/DNS_DOMESTIC_IP'cachesize=4096
    • 备份:config.json.bak-dns2、tproxy.nft.bak-dns2、dhcp.bak-dns2
    ⚠️ 切换坑:sing-box 重启与 nft 替换之间的几秒内,旧规则仍 TPROXY DNS,sing-box 会新建绑定 LAN_GATEWAY_IP:53 的透明套接字抢走发给 dnsmasq 的包(Recv-Q 堆积、不读)→ 所有发往路由器的 DNS 超时。先换 nft,再重启 sing-box;已踩则再重启一次 sing-box 即可。
    效果:53 端口 sing-box 套接字 415 → 0;DNS 超时 10% → ~0;缓存命中近乎瞬时;真实首查 ~200ms(= 隧道 RTT)。
    30 分钟连续监控(2026-09-24 01:30–02:00,每 30s:3 个随机国外域名 DNS + 国外 HTTPS + 国内 HTTPS,LAN 客户端视角 + 路由器指标):
    指标
    结果
    样本
    54
    DNS
    162 成功 / 0 失败
    国外 HTTPS(经 ts-exit)
    54 / 0 失败,~640ms
    国内 HTTPS
    54 / 0 失败
    可用内存
    23.7–30.7 MB(无下降趋势)
    sing-box RSS
    54–59 MB(会 GC 回落)
    fds / UDP 套接字峰值
    146 / 79 —— 均为 QUIC 长连接(Apple iCloud 相关网段、PCC 中继等),空闲后自动回落到 ~60 / ~16,非泄漏
    sing-box 错误
    +2(冷门广告/CDN 域名上游超时,可忽略)
    监控注意:用笔记本测要 caffeinate,否则 Mac 睡眠会造成假的超时与采样空洞(第一轮就因此丢了 16 分钟)。
    若 UDP 套接字将来持续超过 ~150:可在 sing-box 加 {"network":"udp","port":443,"action":"reject"}(仅对代理流量)屏蔽 QUIC,让应用回落 TCP。

    参考链接

    好奇心驱动的人生2024 年终总结
    Loading...