「折腾日记」重组家庭网络拓扑
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.2 | OpenClash + 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.5 | Network unreachable | 通 |
| v6 curl taobao | 000 | 200 |
| 解析节点域名 | 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) | 正常 |
| 经代理访问 wikipedia | 200 |
订阅配置里把 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 重加 |
| 旁路由 | 停 mwan3 | init.d disable |
| 旁路由 | fake-ip、v6 中转、NTP、关 RA | uci |
| 主路由 | DHCP 网关指 .2、NAT6、NTP 服务端、关 RA | uci |
几条以后能用上的#
- 上游不给 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。