# Tinkering with Home Network: N100 + PVE + iKuai + sing-box

> Notes on building a home network around an N100 mini PC running PVE 9.0 with iKuai as the main router and Debian 13 as a sing-box side gateway. Covers port assignment tricks for PVE management access, choosing sing-box over dae due to Shadowsocks 2022 support, bringing IPv6 to the LAN via ULA + radvd when the ISP has no v6 uplink, and fixing the Happy Eyeballs stall on domestic apps by forcing the direct outbound to ipv4_only.

Author: Vincent Yang
Published: 2026-04-21
Tags: Home Network, PVE, iKuai, sing-box, IPv6
Source: https://missuo.me/posts/home-network-n100-sing-box

---

> 最近搬了新家，顺手开了一条 2000M 的宽带，家里的网络设备也正好趁这个机会重新折腾一遍。这篇就当是一次记录，也顺便给以后留个备忘。

## 为什么没用 BE7200 当主路由

一开始为了省事，我先买了一台 TP-LINK 的 Wi-Fi 7 路由器 BE7200，四个 2.5G 口加四个 1G 口。单看硬件规格其实挺能打，拨号、测速都没什么问题，2000M 的带宽基本也能跑满。

但系统功能就不太够了。我想要的是一个「能自己折腾」的主路由：能自动分流，能屏蔽一些广告和追踪域名，最好还能把代理直接放到网关上，LAN 里的设备不用单独配置就能自动分流。TP-LINK 的固件明显还是面向普通用户的，这些需求基本没法满足。

## 架构：N100 + PVE + iKuai + Debian

正好手头还有一台几年前买的 N100 小主机，四口 2.5G，拿来跑软路由绰绰有余。最后定下来的架构是这样：

- 底层装 **PVE 9.0** 作为虚拟化平台；
- 虚拟机 1：**iKuai**，负责 PPPoE 拨号、DHCP Server、NAT；
- 虚拟机 2：**Debian 13**，跑 sing-box 作为旁路由网关，负责透明代理和分流。

这样拆开之后，职责会比较清楚：iKuai 只负责「稳定的基础网络」，Debian 负责「很可能被我反复改配置」的代理部分。就算哪天代理被我搞挂了，把 Debian 关掉，家里的基础网络也还能照常用。

## 网口分配与 PVE 管理口的小技巧

N100 的四个 2.5G 网口分别是 ETH0 到 ETH3。分配方案是：

- **ETH0**：留给 PVE 管理；
- **ETH1、ETH2、ETH3**：全部通过 PCI passthrough 直通给 iKuai，其中 ETH1 作 WAN，ETH2/ETH3 作 LAN。

这么做马上会遇到一个问题：iKuai 独占这三个口之后，PVE 的管理口 ETH0 和 iKuai 的 LAN 在物理上就是隔开的。即使两边都配在 `10.10.10.0/24` 这个网段里，也还是互相访问不了。总不能再浪费一个口，专门把 ETH0 接回 LAN 吧。

解决办法其实也简单：在 PVE 里给 iKuai 虚拟机额外挂一张 **虚拟网卡**（virtio net），然后在 iKuai 里把这张虚拟网卡和 LAN 口桥接起来。这样相当于在 iKuai 内部搭了一座桥，PVE 的管理网络就能通过这张虚拟网卡接到 LAN 里。ETH0 这个物理口反而可以完全空着，数据只走虚拟网卡。

配完之后，所有端口协商都在 2.5G，基础网络这一层就算理顺了。

## Debian 侧：从 Surge 到 dae 再到 sing-box

旁路由这边，我花的时间就多一些。

最想用的其实是 **Surge VM Gateway**。规则语法熟，日常 Mac 上也一直在用。但现在手头已经没有闲置的 Mac mini 可以拿来当网关了（一台在日本，另一台在另一个家里），所以只能看 Linux 方案。

第二选择是 [dae](https://github.com/daeuniverse/dae)。dae 的设计很简洁，基于 eBPF，功能不多但刚好够用，本来挺合我胃口。结果试了一下发现它不支持 **Shadowsocks 2022**，而我用的大部分都是 ss2022 和 snell，没办法，只能放弃。

最后还是落到了 [sing-box](https://github.com/SagerNet/sing-box) 上。sing-box 的协议支持很全，但配置复杂度也确实有名，官方文档又分散在一堆子页面里，手写 JSON 会很费劲。我比较懒，直接把 ssh 交给 Claude Code（Opus 4.7），再把我 Surge 的 conf 文件丢过去，让它参考里面的分流规则，直接生成并部署 sing-box 配置。效率确实高了很多。

## IPv6 踩坑：为什么要造一个 ULA 前缀

配完之后，v4 用起来没问题，但 v6 开始有点意思了。

先说下背景。我的代理节点是有 global v6 的，宿主机走代理 `curl -6 ip.sb` 也能正常返回 node 的 v6 地址。但是 LAN 里的客户端（手机、Mac）拿到的 v6 只有 link-local `fe80::`，基本等于没有可用的 v6 地址，自然也发不出 v6 流量。

原因是 **iKuai 这边没有 v6 上行**，也没有从上游拿到任何可以下发给 LAN 的 v6 前缀。那这种情况下，要怎么让客户端「用上 v6」？

答案是：**自己在 LAN 里造一个 v6 前缀**。具体做法是在 Debian 上用 [radvd](https://github.com/reubenhwk/radvd) 广播一个 **ULA（Unique Local Address）** 前缀 `fd00:dae::/64`，让 LAN 客户端通过 SLAAC 自动配出一个 ULA 地址。

这里的关键是：ULA 在 v6 里有点像 v4 的 `10.0.0.0/8` 这种私有地址段，不能直接出公网。但我并不需要 ULA 真的能跨运营商路由出去。客户端发出的 v6 流量会被 sing-box 的 TUN 截获，最后通过 **proxy node 的 global v6 出境**。外面看到的源地址是 node 的 IP，不会看到 LAN 里的 ULA。对我这套环境来说，ULA 更像是一张 v6 世界里的「临时发车牌照」：先让客户端愿意生成 v6 流量，后面再交给 sing-box 送到真正有 v6 的出口。

除此之外 Debian 侧还要做几件事才能让这套跑起来：

- `net.ipv4.ip_forward=1`、`net.ipv6.conf.all.forwarding=1`、`net.ipv6.conf.ens18.forwarding=1`（radvd 启动时会专门检查接口级别的 forwarding）；
- `send_redirects=0`：不然 kernel 会发 ICMP Redirect，告诉客户端「直接找 10.10.10.1 更快」，客户端就会绕过旁路由；
- `iptables FORWARD` 放行 tun0 的入和出：Docker 会把 FORWARD 默认策略改成 DROP，导致 UDP / ICMP 在 `ens18 → tun0` 这条路径上被丢掉，TCP 反倒没事（因为走的是 REDIRECT 转本地，不走 FORWARD）；
- 给 ens18 挂上 `fd00:dae::1/64` 的静态地址：sing-box 的 `auto_redirect` 是从接口上读 v6 前缀加进 nftables 的 `inet6_local_address_set` 的，没有这个地址，LAN 前缀就不会进 set，v6 转发规则全部失效（参见 sing-box [issue #3698](https://github.com/SagerNet/sing-box/issues/3698)）。

## Happy Eyeballs 的奇怪现象

v6 通了之后，又遇到一个挺微妙的问题：**打开国内一些 App（微信、抖音、小红书之类）的时候，经常要点好几次内容才加载出来**。网页和国际 App 倒是完全没问题。

排查了一圈，原因是这样的：

TUN + radvd 让 LAN 客户端拿到了 v6 地址，所以客户端发起新连接时会做 **Happy Eyeballs**：同时试 v4 和 v6 两条路径，哪条先连上就用哪条。

国内大厂的 API 域名现在基本都有 AAAA 记录（腾讯、阿里、字节都在上 v6），但它们的 v6 CDN 通常只在国内 ISP 的 v6 链路上好用，也就是得从运营商的 v6 线路直连过去。

而我这边的出口情况是：

- **代理走 v6**：proxy node 在海外，有 global v6，能连上国内站点的 v6，但路径绕了一大圈，延迟很高；
- **直连走 v6**：Debian 的 direct outbound 走 ens18，ens18 只有 link-local v6，宿主机的 v6 主路由表里根本没有 default v6（default v6 被 sing-box TUN 接管了，放在 table 2022 给代理用），直连 v6 包发不出去，最后只能超时。

按照我的分流规则，国内 App 的 API 命中 `geosite-cn` → 走 direct → 客户端先试 v6 → 超时（默认 250ms 到 2s）→ Happy Eyeballs 回退 v4 → 成功。于是第一次连接会卡一下，再加上 App 自己的重试 / 缓存逻辑把这个体感放大了，看起来就像是「点一下没反应，再点一下才行」。

国际站不受影响，是因为它们命中 proxy 规则，代理节点有真 v6，直接就通了。

### 解法：direct outbound 强制 ipv4_only

解决也很小，只要给 sing-box 的 direct outbound 加一个 `ipv4_only`：

- 命中 direct 的流量只拿 A 记录，客户端只走 v4，不再先试一条注定会超时的 v6；
- 代理出口（proxy / ai-vless）继续保留 v6 能力，node 有 v6，外部 v6 站照样快；
- 国内 v6 原本就跑不通，这个限制等于**把一个坏选项直接从菜单里删掉**，不用再浪费 2 秒等它失败。

配完之后，国内 App 的点按体感马上就正常了。

## 小结

整套架构跑下来，主路由 iKuai 管稳定，旁路由 Debian 管分流，管理面板用 metacubexd，v4/v6 都能用，家里每台设备不用单独配置就能自动走分流。相比之前一台 BE7200 裸拨号的方案，可玩性和可控性确实高了不少。

唯一比较「耗心力」的是 v6 这一块，踩了好几个坑才理清：「LAN 要有 v6 地址，客户端才会发 v6」「ULA 不是为了出公网，而是为了让客户端愿意生成 v6 流量」「direct 出口没 v6，但客户端有 v6，就会触发 Happy Eyeballs 回退」。这些点想明白之后，以后再配类似环境应该会轻松很多。

接下来还有几件事想慢慢补上：先把分流规则再细一点，尤其是国内 App、AI 服务、流媒体这些容易走错出口的场景；再加上 DDNS，方便在外面稳定访问家里的入口；最后把 Tailscale 也配起来，之后人在外面排查网络或者访问家里服务会省事很多。

AP 也想换掉。现在 BE7200 在这套架构里只是当 AP 用，感觉有点大材小用。也许换成小一点的 Wi-Fi 7 AP 就够用了。