[All In One] HomeLab 虚拟化 iKuai + Mihomo

预计阅读 11 分钟

本文思路基本遵循 iKuai与Openwrt的有机结合:传统旁路由方案的完美替代,主要差异点如下:

  • 虚拟化平台: unRaid 替换为 Proxmox VE (PVE)
  • 透明网关: OpenWRT 替换为 Debian + Mihomo
  • DNS 服务: paopaodns 替换为 AdGuard Home + Mihomo DNS + SmartDNS 组合

网络拓扑

为方便描述,下文统一将处理出站流量的虚拟机称为透明网关 (gateway)

架构核心思想

本方案采用“主路由 + 透明网关”的模式。iKuai 作为主路由,以其出色的稳定性、多线负载和流控能力负责整体网络的 DHCP、NAT 和流量调度。透明网关则是一个专职处理特定流量(如国际流量)的“处理器”。iKuai 通过策略路由(PBR)将特定流量“扔”给透明网关处理,处理完毕后再由透明网关“扔”回给 iKuai,由 iKuai 统一出口。这种架构实现了功能解耦,稳定性和灵活性兼得。

  • iKuai (主路由):

    • WAN1: 用于 PPPoE 拨号或从光猫 DHCP 连接到外网。
      • 物理网卡:eth3 (硬件直通)
    • LAN: 地址为 192.168.100.1,桥接所有局域网内的网卡,构成统一的二层网络。
      • eth1, eth2: 硬件直通,连接 AP 或其他有线设备。
      • vmbr0: PVE 虚拟网桥,专用于 PVE 的 Web 管理界面访问,桥接到 LAN 使其可从内网访问。
      • vmbr2: PVE 虚拟网桥,作为通用网桥,接入其他虚拟机或容器。
    • WAN2: 逻辑接口,作为 iKuai 与透明网关之间的数据通道。
      • IP 地址: 192.168.200.2
      • 网关指向: 192.168.200.1 (透明网关的 LAN 地址)
      • 网卡:vmbr1 (PVE 虚拟网桥)
  • gateway (透明网关):

    • LAN: 接收来自 iKuai (WAN2) 的流量。
      • IP 地址: 192.168.200.1
      • 网卡:vmbr1 (连接 iKuai WAN2)
    • WAN: 将处理后的流量发回 iKuai 的 LAN。
      • IP 地址: 192.168.100.2
      • 网关指向: 192.168.100.1 (iKuai 的 LAN 地址)
      • 网卡:vmbr2 (连接 iKuai LAN)

网络拓扑图

PVE 网络设置

pve-net

iKuai 安装与初始化

虚拟机配置

在 PVE 中为 iKuai 创建虚拟机时,以下是一些关键配置建议:

  • 机型: q35。提供更现代的硬件特性,是使用 PCIe Passthrough (硬件直通) 的推荐选项。
  • BIOS: 默认 (SeaBIOS)。对 iKuai 兼容性良好。
  • SCSI 控制器: VirtIO SCSI single。使用半虚拟化驱动 VirtIO 可最大化磁盘 I/O 性能。

安装步骤

  1. 从 iKuai 官网下载 32 位 ISO 镜像并上传到 PVE。

  2. 新建虚拟机,可以暂时不添加网络设备,稍后统一配置。

  3. 硬件直通 (PCI Passthrough): 进入 iKuai 虚拟机的“硬件”菜单,将物理网卡(除 PVE 管理口外)直通给虚拟机。

    什么是硬件直通 (PCI Passthrough)?

    它允许虚拟机绕过 Hypervisor (PVE) 直接控制物理硬件设备。对于网卡而言,这意味着虚拟机可以获得接近物理机的网络性能和延迟,并能直接利用网卡的硬件特性,是追求极致性能的软路由方案首选。

  4. 添加虚拟网卡: 添加 vmbr0, vmbr1, vmbr2 这三个 PVE 虚拟网桥作为虚拟网卡。

  5. 启动虚拟机,iKuai 系统将自动安装。完成后关闭虚拟机,在“选项”中调整引导顺序至硬盘,并移除 ISO 镜像。

  6. 重启虚拟机,通过 PVE 控制台初始化 iKuai。根据 PVE 硬件信息中显示的 MAC 地址,将网卡与 iKuai 的接口(如 eth0, eth1)一一对应绑定,并设置 LAN 口 IP。

系统内设置

使用 192.168.100.1 访问 iKuai Web 管理界面,完成配置。

  • WAN1: 配置为 PPPoE 拨号或 DHCP,作为主外网出口。
  • WAN2: 配置为静态 IP,绑定 vmbr1 对应的虚拟网卡。
    • IP 地址:192.168.200.2
    • 网关:192.168.200.1
    • 关键配置: 将此线路设置为 默认线路。此举的目的是让所有流量在默认情况下都经过透明网关,再通过后续的分流规则将国内流量“豁免”。
    • 开启 掉线切换,并设置线路检测,确保透明网关异常时网络可用性。
  • LAN1:
    • 在“高级设置”中,启用 链路桥接,将所有内网接口(物理和虚拟)桥接在一起,创建一个统一的广播域,确保所有设备都在同一局域网内。

wan2

配置分流

  1. 进入 流控分流 > 多线负载,添加自定义运营商。将 china_ip_list 的 IP 段复制进来。由于 iKuai UI 存在输入长度限制,需要将 IP 列表分批添加。
  2. 添加负载均衡规则,将目标地址为国内 IP 的流量,强制走 WAN1 出口,实现国内流量直连。

分流

添加端口分流规则防止回环

什么是路由回环 (Routing Loop)?

在本架构中,一个数据包的路径可能是:客户端 -> iKuai -> 透明网关 -> iKuai -> 互联网。当数据包从透明网关返回 iKuai 时,如果 iKuai 依然按照默认路由(即 WAN2)处理,就会把这个包再次发给透明网关,形成 iKuai -> 网关 -> iKuai 的死循环,导致网络中断。

为防止回环,需添加一条规则:将源地址为透明网关 IP (192.168.100.2) 的所有流量,强制从 WAN1 出口发出。

防止回环

透明网关

推荐使用 LXC 容器而非完整虚拟机,因为它更轻量,资源开销小,网络性能接近宿主机。我选择 PVE 官方 CT 模板的 debian 12 作为 mihomo 和 dns 服务的运行时。

新建 LXC 容器

LXC 配置文件 (/etc/pve/lxc/111.conf) 示例:

arch: amd64
cores: 2
features: nesting=1
hostname: gateway
memory: 2048
net0: name=wan0,bridge=vmbr2,gw=192.168.100.1,hwaddr=BC:24:11:B8:D5:64,ip=192.168.100.2/24,type=veth
net1: name=lan0,bridge=vmbr1,hwaddr=BC:24:11:AF:9E:03,ip=192.168.200.1/24,type=veth
ostype: debian
rootfs: local:111/vm-111-disk-0.raw,size=8G
swap: 2048
unprivileged: 1
nameserver: 223.5.5.5

网络配置

编辑 LXC 容器内的 /etc/network/interfaces,为 lan0(接收 iKuai 流量)和 wan0(发回 iKuai)配置静态 IP。

auto lo
iface lo inet loopback

auto lan0
iface lan0 inet static
        address 192.168.200.1/24
        gateway 192.168.200.2

auto wan0
iface wan0 inet static
        address 192.168.100.2/24

Mihomo

安装与配置

  1. 从 Github Releases 下载二进制文件并放置到 /usr/local/bin/
  2. 创建配置目录 /etc/mihomo
  3. 编写核心配置文件 /etc/mihomo/config.yaml。与本方案相关的关键配置如下:
# ... (其他配置)
tproxy-port: 7894 # TProxy 监听端口,需与 nftables 脚本一致
routing-mark: 6666 # Mihomo 自身发出流量的标记,用于在 nftables 中豁免,防止代理自身

dns:
  enable: true
  listen: 127.0.0.1:1053 # Mihomo 仅作为本机 DNS 分流后端,由 AdGuard Home 转发进入
  ipv6: true
  enhanced-mode: redir-host # realip 方案,客户端拿到真实 IP
  respect-rules: true # DNS 上游连接也遵循代理规则,避免 DoH 直连被污染
  default-nameserver:
    - 223.5.5.5
    - 119.29.29.29
  proxy-server-nameserver:
    - 223.5.5.5
    - 119.29.29.29
  nameserver-policy:
    geosite:cn:
      - 127.0.0.1:5354 # 国内域名交给 SmartDNS 做就近解析
  nameserver:
    - https://cloudflare-dns.com/dns-query#节点选择
    - https://dns.google/dns-query#节点选择
# ... (其他配置)

服务化 (Systemd)

创建 /etc/systemd/system/mihomo.service 文件。

Systemd Capabilities (权能) 说明

CapabilityBoundingSetAmbientCapabilities 是 Linux 的安全特性,用于授予进程精确的权限,而非给予完整的 root 权限。

  • CAP_NET_ADMIN: 允许程序修改网络接口、路由表、防火墙规则等。TProxy 设置需要此权限。
  • CAP_NET_RAW: 允许程序使用 RAW 和 PACKET 套接字。
  • CAP_NET_BIND_SERVICE: 允许程序绑定到 1024 以下的特权端口。

这种配置方式比直接使用 User=root 更安全。

[Unit]
Description=Mihomo Daemon, a Clash API compatible core.
After=network.target

[Service]
Type=simple
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
Restart=always
ExecStartPre=/usr/bin/sleep 1s
ExecStart=/usr/local/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

TProxy 透明代理

什么是 TProxy?

TProxy (Transparent Proxy) 是 Linux 内核的一项功能,它允许一个应用程序(如 Mihomo)在不修改数据包目的 IP 地址和端口的情况下,接收并处理本应发往其他地址的数据包。相比传统的 REDIRECT (DNAT) 模式,TProxy 能让服务端程序看到真实的客户端源 IP 地址,且对 UDP 的支持更完善,性能也更优。

步骤一:开启 IP 转发

tee /etc/sysctl.conf <<-'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF

步骤二:配置防火墙与策略路由

启动脚本: /etc/mihomo/ip-route-poststart.sh

#!/bin/sh
# 1. 在策略路由表 100 中,添加或替换一条本地路由
ip route replace local default dev lan0 table 100
# 2. 清理旧的重复规则,再添加一条规则:为防火墙标记(fwmark)为 1 的数据包,应用策略路由表 100
while ip rule del fwmark 1 lookup 100 2>/dev/null; do :; done
ip rule add fwmark 1 lookup 100
# 3. 先删除旧 nftables table,再加载新规则,避免重复追加规则
nft delete table ip clash 2>/dev/null || true
nft -f /etc/mihomo/nftrules.nft

停止脚本: /etc/mihomo/ip-route-stop.sh

#!/bin/sh
# 按相反顺序清理规则
nft delete table ip clash || true
ip route del local default dev lan0 table 100 2>/dev/null || true
while ip rule del fwmark 1 lookup 100 2>/dev/null; do :; done

nftables 规则集: /etc/mihomo/nftrules.nft

#!/usr/sbin/nft -f

## 只处理指定网卡的流量,要和ip规则中的接口操持一致
define interface = lan0

## clash的透明代理端口
define tproxy_port = 7894

## clash打的标记(routing-mark)
define clash_mark = 6666

## 常规流量标记,ip rule中加的标记,要和ip规则中保持一致,对应 "ip rule add fwmark 1 lookup 100" 中的 "1"
define default_mark = 1

## 本机运行了服务并且需要在公网上访问的tcp端口(本机开放在公网上的端口),仅本地局域网访问的服务端口可不用在此变量中,以半角逗号分隔
define local_tcp_port = {
    22,      # ssh,按需设置
    8000-9999  # http
}

## 要绕过的局域网内tcp流量经由本机访问的目标端口,也就是允许局域网内其他主机主动设置DNS服务器为其他服务器,而非旁路由
define lan_2_dport_tcp = {
    53     # dns查询
}

## 要绕过的局域网内udp流量经由本机访问的目标端口,也就是允许局域网内其他主机访问透明网关的DNS服务;另外也允许局域网内其他主机访问远程的NTP服务器
define lan_2_dport_udp = {
    53,    # dns查询
    123    # ntp端口
}

## 保留ip地址
define private_address = {
    127.0.0.0/8,
    100.64.0.0/10,
    169.254.0.0/16,
    224.0.0.0/4,
    240.0.0.0/4,
    10.0.0.0/8,
    172.16.0.0/12,
    192.168.0.0/16
}

table ip clash {
    ## 保留ipv4集合
    set private_address_set {
        type ipv4_addr
        flags interval
        elements = $private_address
    }

    ## prerouting链
    chain prerouting {
        type filter hook prerouting priority filter; policy accept;
        ip protocol { tcp, udp } socket transparent 1 meta mark set $default_mark accept # 绕过已经建立的连接
        meta mark $default_mark goto clash_tproxy                                        # 已经打上default_mark标记的属于本机流量转过来的,直接进入透明代理
        fib daddr type { local, broadcast, anycast, multicast } accept                   # 绕过本地、单播、组播、多播地址
        tcp dport $lan_2_dport_tcp accept                                                # 绕过经由本机到目标端口的tcp流量
        udp dport $lan_2_dport_udp accept                                                # 绕过经由本地到目标端口的udp流量
        ip daddr @private_address_set accept                                             # 绕过目标地址为保留ip的地址
        goto clash_tproxy                                                                # 其他流量透明代理到clash
    }

    ## 透明代理
    chain clash_tproxy {
        ip protocol { tcp, udp } tproxy to :$tproxy_port meta mark set $default_mark
    }

    ## output链
    chain output {
        type route hook output priority filter; policy accept;
        oifname != $interface accept                                   # 绕过本机内部通信的流量(接口lo)
        meta mark $clash_mark accept                                   # 绕过本机clash发出的流量
        fib daddr type { local, broadcast, anycast, multicast } accept # 绕过本地、单播、组播、多播地址
        udp dport { 53, 1053, 5354, 123 } accept                       # 绕过本机dns查询、NTP流量
        tcp sport $local_tcp_port accept                               # 绕过本地运行了服务的tcp端口,如果并不需要从公网访问这些端口,可以注释掉本行
        ip daddr @private_address_set accept                           # 绕过目标地址为保留ip的地址
        ip protocol { tcp, udp } meta mark set $default_mark           # 其他流量重路由到prerouting
    }
}

步骤三:集成到 systemd 服务

/etc/systemd/system/mihomo.service 加入声明周期脚本,令 systemd 维护 mihomo 的时候自动执行:

ExecStartPost=/etc/mihomo/ip-route-poststart.sh
ExecStop=/etc/mihomo/ip-route-stop.sh

DNS

DNS 查询链

本方案构建了一个“责任链模式”的 DNS 解析流程:客户端 -> AdGuard Home :53 -> Mihomo DNS :1053 -> SmartDNS :5354 / 代理 DoH

三个组件分工不同:AdGuard Home 负责去广告、规则管理和查询日志;Mihomo DNS 负责 realip、DNS 分流、污染规避和让国外 DoH 跟随代理规则;SmartDNS 只负责国内域名的就近解析、缓存和双栈优选。不要让 SmartDNS 直接承担全局解析,否则国外域名在污染环境下可能返回错误 IP。

  1. 安装 SmartDNS: apt install smartdns。SmartDNS 只监听本机回环地址,并作为国内域名的解析后端。
bind-tcp 127.0.0.1:5354
bind 127.0.0.1:5354
tcp-idle-time 3

cache-size 128k
cache-persist yes
cache-file /etc/smartdns/smartdns.cache

prefetch-domain yes
serve-expired yes
serve-expired-ttl 259200
serve-expired-prefetch-time 21600
speed-check-mode none
rr-ttl-min 60
rr-ttl-max 86400
log-level warn

dualstack-ip-selection yes
dualstack-ip-selection-threshold 15

server 208.67.222.222 -bootstrap-dns
server 223.5.5.5 -bootstrap-dns

# 国内上游,用于解析 geosite:cn 命中的域名
server-tls 1.12.12.12:853 -group domestic -exclude-default-group
server-tls 120.53.53.53:853 -group domestic -exclude-default-group
server-tls 223.5.5.5:853 -group domestic -exclude-default-group
server-tls 223.6.6.6:853 -group domestic -exclude-default-group

conf-file /etc/smartdns/accelerated-domains.china.domain.smartdns.conf
conf-file /etc/smartdns/apple.china.domain.smartdns.conf
conf-file /etc/smartdns/chinalist.domain.smartdns.conf
  1. 配置 Mihomo DNS: Mihomo 不直接暴露给客户端,只监听 127.0.0.1:1053。国内域名通过 nameserver-policy 转给 SmartDNS;国外域名通过 DoH 解析,并且 DoH 连接使用 #节点选择 让其走代理规则。
dns:
  enable: true
  listen: 127.0.0.1:1053
  ipv6: true
  cache-algorithm: arc
  prefer-h3: false
  enhanced-mode: redir-host
  respect-rules: true
  default-nameserver:
    - 223.5.5.5
    - 119.29.29.29
  proxy-server-nameserver:
    - 223.5.5.5
    - 119.29.29.29
  nameserver-policy:
    geosite:cn:
      - 127.0.0.1:5354
  nameserver:
    - https://cloudflare-dns.com/dns-query#节点选择
    - https://dns.google/dns-query#节点选择
  1. 配置 AdGuard Home: AdGuard Home 监听 0.0.0.0:53,作为所有客户端 DNS 的唯一入口。上游只填 Mihomo DNS:127.0.0.1:1053。关闭 AdGuard Home 查询缓存,避免它在 Mihomo 重启后直接用旧缓存回答,导致 realip 映射缺失。
dns:
  bind_hosts:
    - 0.0.0.0
  port: 53
  upstream_dns:
    - 127.0.0.1:1053
  bootstrap_dns:
    - 127.0.0.1:1053
  cache_enabled: false
  ratelimit: 0
  serve_plain_dns: true
  1. 整合: 在 iKuai 的 DHCP 服务 -> LAN 口设置 中,将主 DNS 服务器地址修改为透明网关的地址:192.168.200.1。这样所有内网设备获取到的 DNS 服务器就是 AdGuard Home。

这个链路保留了 realip:客户端拿到真实 IP,Mihomo 通过 DNS 映射和 sniffer 还原域名并命中规则。国内域名的“就近解析”以家庭宽带出口为准,交给 SmartDNS;国外域名的“就近解析”以代理出口为准,让 DoH 请求跟随代理节点出去,避免本地网络的 DNS 污染。

                              DNS 查询链路

客户端 / 内网设备
  DNS Server: 192.168.200.1
        |
        v
+-----------------------------+
| AdGuard Home :53            |
| - 唯一对客户端暴露的 DNS 入口 |
| - 去广告 / 规则管理 / 查询日志 |
| - 关闭缓存,避免 realip 映射丢失 |
+-----------------------------+
        |
        | upstream_dns = 127.0.0.1:1053
        v
+-----------------------------+
| Mihomo DNS :1053            |
| - enhanced-mode: redir-host |
| - realip / DNS 分流 / 防污染 |
| - respect-rules: true       |
+-----------------------------+
        |
        +------------------------------+
        |                              |
        | geosite:cn                   | 非国内域名 / 代理域名
        v                              v
+-----------------------------+    +-----------------------------+
| SmartDNS :5354              |    | DoH 上游                    |
| - 仅监听 127.0.0.1          |    | - cloudflare-dns.com        |
| - 国内域名就近解析          |    | - dns.google                |
| - 缓存 / 双栈优选           |    | - 连接跟随 Mihomo 代理规则   |
+-----------------------------+    +-----------------------------+
        |                              |
        | 国内 DoT / DoH / UDP DNS      | #节点选择 / 代理出口
        v                              v
  国内 CDN / 国内站点 IP          国外站点真实 IP

最终结果:
  - 客户端拿到真实 IP,不使用 fake-ip。
  - 国内域名按家庭宽带出口就近解析。
  - 国外域名的 DoH 请求跟随代理节点出去,避免本地 DNS 污染。
  - SmartDNS 不承担全局解析,只作为国内域名解析后端。

自动更新配置

为了实现低维护运行,各类配置应尽可能自动化更新。

  • iKuai 大陆 IP 分流: 手动更新即可,IP 列表变动不频繁。
  • Mihomo 订阅配置: 推荐部署私有 sub-store 用于聚合。在透明网关中设置 cron 定时任务,定期 wget 新的配置文件并 systemctl restart mihomo
  • SmartDNS 国内域名列表: 定时任务从 Olixn/china_list_for_smartdns 等项目下载更新列表并重启 SmartDNS。
  • AdGuard Home 过滤规则: 直接在 AdGuard Home 的 Web UI 中订阅优秀的过滤列表即可自动更新。
  • AdGuard Home 过滤规则自定义规则同步: TODO。

参考资料

更新

2026-06-05

更新 DNS 方案:

  • DNS 链路从 客户端 -> AdGuard Home -> SmartDNS -> 上游 DNS 调整为 客户端 -> AdGuard Home :53 -> Mihomo DNS :1053 -> SmartDNS :5354 / 代理 DoH
  • AdGuard Home 作为客户端唯一 DNS 入口,继续负责去广告、规则管理和查询日志。
  • Mihomo DNS 改为本机后端,负责 realip、DNS 分流和污染规避;国内域名通过 nameserver-policy 交给 SmartDNS,国外域名使用跟随代理规则的 DoH。
  • SmartDNS 改为只监听 127.0.0.1:5354,仅承担国内域名就近解析、缓存和双栈优选,避免客户端误连 SmartDNS 后绕过 Mihomo 分流。
  • TProxy 启停脚本改为幂等写法,避免 Mihomo 多次重启后重复堆叠 ip rule add fwmark 1 lookup 100
  • 实测修复前 Google / YouTube 曾被直连 DNS 污染到异常 IP;调整后 192.168.200.1:53 返回正常 Google IP 段,Google generate_204 延迟降到约 1 秒内。