bigrandall.io

← back to writing

「折腾日记」重组家庭网络拓扑

by Randall · 23 Jun 2026

起因很小:旁路由上的 OpenClash 订阅里有几个 IPv6 的节点,想用,结果发现旁路由自己根本没有 v6。本来以为是个十分钟的小活,最后从 IPv6 一路牵出 mwan3、系统时间、DNS 模式、DHCP 网关、Tailscale,断断续续弄了一下午,中间还一度把全家网搞断、只能开手机热点远程救。

记一下,主要是给以后的自己看,免得再踩一遍。

家里这套是什么样的#

电信入户,光猫后面一台 GL.iNet 主路由负责拨号,下面挂一台树莓派 5 跑 Kwrt 当旁路由,OpenClash 和 Tailscale 都在它身上。

设备系统地址干什么的
主路由OpenWrt 23.05(GL.iNet)192.168.8.1拨号、发 DHCP
旁路由Kwrt 25.12(树莓派5)192.168.8.2OpenClash + Tailscale
客户端一堆192.168.8.x手机、电脑、IoT

需求其实就一句话:让旁路由这一台能上 IPv6,给 OpenClash 连那几个 v6 节点用。客户端不需要 v6——而且更准确说,是不能给客户端 v6,不然它们会绕开代理直连出去。

先卡在第一步:上游压根没给前缀#

想给旁路由配 v6,第一反应是让主路由从光猫那边要一段前缀(PD),再分一个 /64 下来。结果一看 WAN 口,前缀是空的:

ifstatus wan6 | grep -A2 ipv6-prefix
#   "ipv6-prefix": [          ]        空的
ip -6 addr show dev eth0 | grep inet6
#   inet6 240e:306:3489:9700:9683:c4ff:fe2d:5a84/64

电信只给了 WAN 一个 /64,没有 PD。顺便说一句,之前我手贱往 LAN 的 IPv6 地址栏里填过一个 240e:...:5a84——现在才反应过来,那玩意儿就是 WAN 口自己 SLAAC 出来的地址,填到 LAN 上当然不对。

没有 PD,就没法给 LAN 原生分一段公网前缀。但我也不需要给整个 LAN,只要旁路由一台有就行。那就走 NAT6:自己造一段内网 ULA 当中转,主路由对它做 IPv6 masquerade,伪装成 WAN 的 240e 地址出去。

中转段我挑了 fd00:c1a5:6c6c::/64,主路由 ::1、旁路由 ::2

# 主路由:先把对客户端发 v6 的功能全关掉(客户端不能有 v6)
uci set dhcp.lan.ra='disabled'
uci set dhcp.lan.dhcpv6='disabled'
uci set dhcp.lan.ndp='disabled'
# 中转地址 + 把它加进 NAT6 源 + lan 到 wan6 的转发
uci add_list network.lan.ip6addr='fd00:c1a5:6c6c::1/64'
uci add_list firewall.wan6.masq_src='fd00:c1a5:6c6c::/64'
uci set firewall.lan_wan6=forwarding
uci set firewall.lan_wan6.src='lan'
uci set firewall.lan_wan6.dest='wan6'
uci commit

旁路由那边配个静态 ::2,默认 v6 路由指向 ::1,收工。路径长这样:

配完一 ping,网关通,公网……不通。

路由是对的,包就是发不出去#

ping6 到中转网关好好的,但 ping 公网直接 Network unreachable。诡异的是 ip route get 显示路由明明正常,下一跳、出接口都对。这种「路由对、发包失败」八成是 fwmark 在搞鬼,加上 mark 再 get 一次就露馅了:

ip -6 route get 2400:3200::1 mark 0x3e00
#   RTNETLINK answers: Network unreachable      命中了某条规则
ip -6 route get 2400:3200::1 mark 0
#   ... via fd00:c1a5:6c6c::1 ...               不带 mark 就正常

翻策略规则,找到一条 fwmark 0x3e00 -> unreachable,是 mwan3 加的。这台旁路由装了 mwan3 做多 WAN,可它四个接口全是 down,于是 mwan3 认定「没有可用出口」,把本机发起的新连接统统标成 unreachable 丢掉。

问题是这是个单臂旁路由,就一根线,要什么多 WAN。mwan3 在这纯属帮倒忙。我一开始还想着加条更高优先级的 ip 规则绕过它,弄了才发现 mwan3 每次 restart 都会把 1000–2100 这段优先级的规则全清掉,绕不干净。干脆直接停了:

/etc/init.d/mwan3 stop
/etc/init.d/mwan3 disable

那几条 blackhole/unreachable 规则当场消失,v4、v6 直连立刻就好了。

禁 mwan3 前禁 mwan3 后
v4 ping 223.5.5.5Network unreachable
v6 curl taobao000200
解析节点域名couldn't find ip出 IP 了

旁路由能上 v6 了,开 OpenClash。然后翻车更精彩的来了。

DNS 全挂,根子在系统时间#

OpenClash 一起来,日志哗哗刷 dns resolve failed: couldn't find ip,连节点的域名都解析不出来。这东西排起来特别绕,因为它是个闭环:

树莓派没有 RTC,断电后时间不走,开机停在上次的 06-10,实际已经 06-23 了,差了十几天。时间一错,clash 用的 DoH(走 HTTPS)证书直接验不过,DNS 就废了。更要命的是 OpenClash 开了「本机代理」,把路由器自己的 NTP 流量也劫持进了这个已经坏掉的 clash,于是 NTP 永远校不准,时间永远是错的——自己锁死自己。

破局点是先从外面把时间塞对。主路由时间是准的,直接抄过来:

MAIN=$(ssh root@192.168.8.1 "date '+%Y-%m-%d %H:%M:%S'")
date -s "$MAIN"

光这样治标,重启又会犯。所以顺手让主路由开 NTP 服务端,旁路由的时间源首选指它——本地直连,不经过 clash,开机几百毫秒就校准了:

ssh root@192.168.8.1 "uci set system.ntp.enable_server='1'; uci commit; /etc/init.d/sysntpd restart"
uci -q delete system.ntp.server
uci add_list system.ntp.server='192.168.8.1'
uci add_list system.ntp.server='ntp.aliyun.com'
uci commit system && /etc/init.d/sysntpd restart

验证它确实在跟主路由对时:

ntpd: reply from 192.168.8.1: offset:+0.019767 delay:0.000981 strat:3

delay 0.9 毫秒,纯本地。这下重启也不怕了。

排这一段的时候还顺手踩了个小坑:我图省事用 restart & 后台重启 OpenClash,结果跟它自带的进程守护打架,俩 clash 实例同时起来抢端口,DNS 服务直接没绑上。后来老老实实 stop 等进程清零、再 start,就没事了。记住别图快。

google 还是打不开,这次是 DNS 模式#

时间修好、节点全连上了,连 v6 节点都能测速,wikipedia 走代理也 200。就 google、youtube 死活 000。

单独拎出来测解析,发现规律:

测的东西结果
clash 解析 www.google.com(配置里走 cloudflare DoH)context deadline exceeded
clash 解析 www.wikipedia.org(走 doh.pub)正常
经代理访问 wikipedia200

订阅配置里把 google 这些域名指定用 cloudflare 的 DoH 去解析,可 clash 直连 cloudflare DoH 在国内是被墙的,于是这批域名全卡在解析这一步。

不去跟它较劲改 DNS,直接把模式从 redir-host 换成 fake-ip——clash 不在本地解析这些被墙域名了,给个假 IP、按域名直接甩给节点,让节点那头去解析:

uci set openclash.config.en_mode='fake-ip'
uci commit openclash
/etc/init.d/openclash stop && /etc/init.d/openclash start

google、youtube 就都开了。

设备根本没走代理#

弄到这儿旁路由自己一切正常,但拿手机一试,流量压根没进 OpenClash,直接从主路由出去了。

道理也简单:DHCP 是主路由发的,它默认把网关写成自己(.1),设备自然不会绕到旁路由去。把主路由 DHCP 下发的网关和 DNS 都改成旁路由就行:

uci add_list dhcp.lan.dhcp_option='3,192.168.8.2'   # 3 是网关
uci add_list dhcp.lan.dhcp_option='6,192.168.8.2'   # 6 是 DNS
uci commit dhcp && /etc/init.d/dnsmasq restart

设备重连一下、续到新租约,clash 的连接列表里立马就能看到它们走节点了:

192.168.8.195 -> [Hy2]US-Breeze-0-v6   (一上来 66 条活跃连接)
192.168.8.158 -> [Hy2]...

代价是从此全家走旁路由,旁路由一挂全家断网——这是旁路由方案天生的,认了。哪台不想走代理,把它网关单独改回 .1 就完事。

最后一个:Tailscale 连不上#

旁路由还兼着 Tailscale 子网路由。设备能上网之后发现连 tailnet 里的机器(比如 100.90.62.64)连不上。这块绕了点,其实是两件事叠在一起。

先排除 OpenClash——它的 localnetwork 放行集合里本来就有 100.64.0.0/10,没拦。真正的毛病有两层。

一是少了 SNAT。 旁路由对外通告了 192.168.8.0/24 这条子网路由,但客户端拿着 192.168.8.x 的源地址出 tailscale0,对端不认这个源、回不来。补一条 masquerade:

iptables -t nat -A POSTROUTING -s 192.168.8.0/24 -o tailscale0 -j MASQUERADE

二是转发被默认丢了,这条更隐蔽。我从旁路由本机 ping -I 192.168.8.2 是通的,但客户端就是不通。区别在于本机发起走的是 OUTPUT 链,客户端是转发、走 FORWARD 链——而 fw4 的 forward 链默认 policy 是 drop,又没有 tailscale 的防火墙区域,转发包直接被丢。放行一下:

nft insert rule inet fw4 forward iifname "br-lan" oifname "tailscale0" accept
nft insert rule inet fw4 forward iifname "tailscale0" oifname "br-lan" accept

「本机通、客户端不通」这个特征以后看到就该条件反射想到 OUTPUT 和 FORWARD 的区别。

两条补完,客户端访问 tailnet 的机器都好了。但还剩一个:子网路由 10.128.1.102 还是不通。这个连旁路由自己拿 Tailscale IP 做源都 ping 不通,说明问题在对端,不在我家:

ip route show table 52 | grep 10.0.0.0
#   10.0.0.0/8 dev tailscale0     对端 qiyin 通告了整个 10/8,而且在线

对端是台北京的 Ubuntu(qiyin),它通告了 10.0.0.0/8,但只能 ping 通它自己,转不到同网段别的机器——典型的子网路由器没开 ip_forward,或者转发/SNAT 没配。这台得登上去自己弄,给它甩了个诊断脚本,后来在那台机器上把转发打开就好了,跟旁路由没关系。

收尾:顺手把 SD 卡撑满#

弄完上面这些,瞄了一眼旁路由的磁盘,发现一件离谱的事——树莓派插的是块快 1TB 的卡,但根目录只认 5.9G:

/dev/root   5.9G   1.5G used   4.4G avail   25%   /

一开始以为是分区没占满、要合并分区。看了下其实分区早就占满整盘了(mmcblk0p2 一直铺到最后一个扇区),是刷机时文件系统没跟着撑大而已。本来一条 resize2fs 在线扩容就完事,结果撞墙:

resize2fs: Invalid argument While trying to add group #49
old_desc_blocks = 1, new_desc_blocks = 60

镜像做的时候只留了 1 个 GDT 描述块,要扩到 1TB 得 60 个,在线状态加不了——这得把文件系统转成 meta_bg 布局,而这个转换只能在文件系统没挂载的时候做。可它是正在跑的根分区,卸不掉。死路。

远程硬来是能写脚本 pivot_root 到内存盘再卸载根分区操作,但这台是全家网关,真把根分区搞坏就得物理重刷、全家断网,不值当冒这险。最稳的还是关机拔卡,插到电脑上离线扩——离线状态 resize2fs 自己会处理 meta_bg,一把到位:

sudo e2fsck -f /dev/sdX2      # 先检查
sudo resize2fs /dev/sdX2      # 撑满,离线没有那个限制

拔卡前先 poweroff 干净关机,别热拔。弄完插回去开机,结果对了:

/dev/root   938.4G   1.5G used   936.9G avail   0%   /

5.9G 变 938G。顺带,这回开机也等于把前面那一堆持久化实战验了一遍——OpenClash、Tailscale、时间同步全自动起来了,配置一个没丢。

现在长这样#

改的东西都落到配置里了,重启不丢:

在哪改了什么怎么持久化的
旁路由Tailscale 的 SNAT 和 fw4 转发放行塞进 OpenClash 自定义防火墙钩子,开机随 S99 重加
旁路由停 mwan3init.d disable
旁路由fake-ip、v6 中转、NTP、关 RAuci
主路由DHCP 网关指 .2、NAT6、NTP 服务端、关 RAuci

几条以后能用上的#

  • 上游不给 PD 就别死等原生 v6,单台设备要 v6 直接 NAT6 最省事。
  • 看到「路由明明对、却 Network unreachable」,先怀疑 fwmark + 策略路由。mwan3、clash、Tailscale 都爱玩这套,ip route get 目标 mark 0xXXXX 一测就知道是谁干的。
  • 软路由没 RTC,时间错了能把 DoH 连带 DNS 一起搞挂,NTP 一定留个本地源兜底,别全指望外网。
  • 「本机通、客户端不通」基本就是 OUTPUT 和 FORWARD 的区别,去看转发链默认策略。
  • 改完 OpenClash 想重启,老实 stop 等干净再 start,别后台 restart & 跟进程守护抢端口。

说到底这一下午就干了一件事:把 IPv4、IPv6、代理、Tailscale 这四套各管各的路由逻辑,在同一台单臂旁路由上理清楚各自谁拦谁、谁 NAT 谁。理顺了就一直好用,理不顺就处处是 unreachable。

no comments yet

quick anti-bot check — nothing personal.