5340 字
27 分钟
家里全屋科学上网的方法

家里全屋科学上网的方法#

用最简单最直白最不绕弯子的一句话告诉你扎心真相:

  1. 富人
    • 买一台多个网口的软路由或小主机装 OpenWRT+OpenClash
      • 优点:
        • 可以做主路由(2 个网口接在路由器上游 WAN 口和光猫之间负责拨号)
        • 可以做旁路由(1 个网口接在路由器下游 LAN 口)。
        • 系统往往可以让老板帮你装好,最省心,最稳定。
        • 或者 v 我 10U 帮你解决一切。
      • 价格:
        • 取决于网口数量和速度 ¥500-1500
        • 富人的世界没有缺点,只有代价,钱能解决一切
  2. 穷人有 NAS
    1. 虚拟机跑 OpenWRT 里面再跑 OpenClash
      • 优点:免费、图形界面操作、性能稳定
      • 缺点:性能稳定的拉垮、配置繁琐、出了问题懵逼
    2. Docker 跑 Mihomo
      • 优点:免费、图形界面操作、性能稳定
      • 缺点:性能稳定的拉垮、出了问题还是懵逼
    3. NAS 裸核跑 Mihomo
      • 优点:免费、性能稳定
      • 缺点:性能稳定的拉垮、安装配置命令行,出了问题特别懵逼
  3. 穷鬼没 NAS
    • 在某台电脑或者手机上装任意客户端或者裸核,配置中并允许局域网接入,而且这台电脑要固定 IP
      • 优点:免费,把 NAS 的钱也省了,手机电费低。
      • 缺点:手机发烫、性能既不稳定还拉完了。

假设穷人有 NAS,那么虚拟 OpenWRT 做旁路由还是最经济最简单的选择,因为本质上是消耗大量内存(1-2G)和硬盘(1-2G)方案获得易用性的轻奢穷人,虽然一切都可以图形化配置,但是多了个虚拟机、OpenWRT、OpenClash 等 Luci 程序等众多概念、操作和配置,尤其是旁路由 IPv6 就可能劝退一批人。

Docker 和裸核跑比想象的方便多了,系统占用极低,和 OpenWRT 对比,内存只要 50MB,磁盘空间也只要一个核(~40MB )。要配置的东西极少。找一个优秀的模版把 分流规则自动选择或者故障转移等都写好,能够做到常年不用管。

旁路由 开启 tun 或者设置透明代理后才能全屋自动生效,手机平板电脑 TV 等等设备都不需要装梯子翻墙。否则在各个设备上还是要支持 socks/http 代理才行。操作方法就是:

  1. 装好旁路由
  2. 家里的 路由器下发的 DHCP 配置中的网关 IP 改成旁路由的 IP
  3. 如果 NAS 上直接跑裸核或者在容器里直接跑裸核,那就填 NAS 的 IP
  4. 如果用了 MacVLan 给容器指定了别的 IP,那就填 mihomo 所在容器的 IP)
  5. 特别重要:在 NAS/OpenWRT 的网卡设置中手动配置 IP,网关 IP 要填写光猫/交换机/路由器的 IP。(否则就环路了,啥都出不去)
  6. 一分价钱一分货的真理永远存在,金钱和性(幸)能(福)是成正比的

Docker 跑 Mihomo#

在群晖 DSM 的 Container Manager 中添加一个 project,选择新建 compose.yml 填入以下内容

services:
metacubexd:
image: ghcr.io/metacubex/metacubexd-server:latest
container_name: metacubexd
restart: unless-stopped
environment:
CONTROL_TOKEN: '${CONTROL_TOKEN:?Set CONTROL_TOKEN in .env}'
CLASH_SECRET: '${CLASH_SECRET:?Set CLASH_SECRET in .env}'
GITHUB_TOKEN: '${GITHUB_TOKEN:-}'
# Optional: pre-fill the connect form's backend address (#2155).
DEFAULT_BACKEND_URL: '${DEFAULT_BACKEND_URL:-}'
CONTROL_PORT: '8080' #面板端口
CLASH_API_PORT: '9090' #控制端口
MIXED_PORT: '1080' #代理端口
TZ: '${TZ:-UTC}'
ulimits:
nofile:
soft: 65535
hard: 65535
# CPU priority tuning
cpu_shares: 2048
volumes:
- '.:/data'
healthcheck:
test: ['CMD', 'wget', '-qO-', 'http://127.0.0.1:8080/api/control/health']
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
network_mode: host
privileged: true

这个官方镜像是接合了 MetaCubeXD 这个图形化控制面板和 mihomo 内核的,所以配置文件可以在面板中添加。访问面板默认是 http://IP:8080

然后新建一个 .env 文件,里面填写

CONTROL_TOKEN=写点随机数
CLASH_SECRET=进面板的密码

启动后,局域网内可以用 socks://IP:1080 或 http://IP:1080 就可以翻墙了

如果要开启 tun 模式,详见最后:Docker 中开启 tun

NAS 裸核跑 Mihomo#

以下步骤全部需要通过 SSH 命令行 (不想折腾的也可以使用 shellcrash 脚本)

准备工作#

下载自己机器架构的内核,解压到 NAS。(不知道下哪个的问 AI)

把解压的二进制文件改好名字,和配置文件找一个空目录存放,比如 /volume1/mihomo/bin/volume1/mihomo/config

1. 创建 systemd 服务配置文件#

创建 service 文件:

sudo vim /etc/systemd/system/mihomo.service

在文件中写入以下内容:(vim 不会用的问 AI)

[Unit]
Description=Mihomo Daemon Service
After=network.target network-online.target nss-lookup.target
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=/volume1/mihomo/bin/mihomo -d /volume1/mihomo/config
Restart=on-failure
RestartSec=5s
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
  • Restart=on-failure:当程序异常退出(crash、非 0 状态码退出)时会自动重启;如果是手动正常停止则不会重启。>
  • RestartSec=5s:崩溃后等待 5 秒再自动重启,避免频繁死循环重启。
  • LimitNOFILE=65535:解除文件句柄数限制,防止网络并发较高时报错。

2. 重新加载配置并启动服务#

保存并退出编辑器后,运行以下命令(只需要做一次之后重启关机都不用再做):

# 1. 重新加载 systemd 配置
sudo systemctl daemon-reload
# 2. 设置开机自启
sudo systemctl enable mihomo
# 3. 立即启动服务
sudo systemctl start mihomo

3. 服务状态管理与日志查看(可选)#

  • 查看服务运行状态
sudo systemctl status mihomo
  • 查看运行日志(实时监控)
sudo journalctl -u mihomo -f
  • 手动停止服务
sudo systemctl stop mihomo
  • 手动重启服务
sudo systemctl restart mihomo

4. 图形化控制面板#

部署后,用浏览器访问 metacubexd endpoint url 的填入 NAS 的地址加 mihomo 的 API 控制端口 ,比如http://ip:9090,(端口默认9090) 应该就可以进行查看和控制了。比部署虚拟机搞 OpenWRT 做旁路由占用资源低多了,也便于管理。缺点是没有 OpenClash 之类的客户端自动更新订阅。

但是如果你在 DSM 的计划任务中新建一个自动更新订阅的脚本,定时执行,那就完全可以不用管了 示例:

(curl -fL -s "替换成订阅的链接" -o /volume1/mihomo/config/config.yaml.tmp && mv /volume1/mihomo/config/config.yaml.tmp /volume1/mihomo/config/config.yaml) && echo "Successfully updated config.yaml" || echo "Failed to download config.yaml"

如果是用 docker 的,可以直接重启 docker 或者用面板手动 reload


Docker 中开启 tun#

Docker 容器跑裸核难度随着 tun 模式和 IPv6 逐步上升,如果既要 tun 又要 IPv6 可以直接拉到文章最后面有一个折腾笔记。先说结论——

不建议折腾 tun,ipv6 其实也可有可无

原因是

  1. 如果在 NAS 跑 tun 模式会影响 NAS 上其他服务,特别是 fake-ip,几乎很难穷尽所有可能性的去写各种规则,能用,但是非常麻烦不稳定。
  2. 如果在 MacVLan 上跑 tun,性能差很多——网卡先要开混杂模式降低整体 NAS 性能,然后数据包多转一次,用户态和内核态的切换也多一倍。
  3. 最终一个普通容器不开 tun,只用 socks5 代理能跑 40 万的节点,用 MacVLan 开 tun 后就只能跑到 3.x 万。所以 MacVLan 的 Tun 模式只适合做最终的 failsafe。
  4. 既然作为 failsafe 用了,是否有 ipv6 的区别就不大了,所以也是可有可无。

如果这还没有劝退,那么就开始折腾:

要开启 TUN 模式,又不影响 NAS 本身的服务(比如 PT、Emby 等),用 macvlan 把容器换一个 IP。但是要解决动态 IPv6 的问题。解决 IPv6 后又会发现无论如何 tun 无法启用,折腾一整天,最后 请 Gemini 帮我总结最终方案:

在 Synology DSM 7.x 系统上通过 Docker 部署 Mihomo (Clash Meta) 旁路由时,Macvlan 网络架构 结合 TUN 模式 是兼顾网络性能与全协议(包含 ICMP / UDP)代理的最佳实践。

然而,由于群晖 Linux 内核(5.10+)对动态创建虚拟网卡的默认安全限制,直接开启 TUN 模式经常会遇到 Start TUN listening error: configure tun interface: permission deniedRTNETLINK answers: Permission denied 报错。

本文记录了完整的排查过程与最终的生产级配置方案,包含 IPv6 双栈动态路由与全局透明代理的完美结合。

1. 架构与痛点解析#

核心痛点:为什么 TUN 会在群晖 DSM 下报 Permission denied#

在 Macvlan 模式下部署容器并启动 TUN,Mihomo 初始化时会按顺序执行以下动作:

  1. 创建虚拟网卡(例如 Meta / tun0)。

  2. 向该网卡绑定 IPv4 / IPv6 地址(默认私有段为 198.18.0.1/30fdfe:dcba:9876::1/126)。

  3. 拉起网卡并注入 auto-route 路由表。

根本病灶:群晖 DSM 内核在动态创建新的网络接口时,默认会开启 disable_ipv6=1。当 Mihomo 试图为新生成的 Meta 网卡赋予私有 IPv6 地址时,系统内核会直接拦截并抛出 RTNETLINK answers: Permission denied,最终导致 Mihomo 启动崩溃。

解决思路#

通过 Docker sysctls 与启动脚本联合解锁内核对动态接口的 IPv6 限制,同时解除 Macvlan 环境下的路由环路与 DNS 绑定端口冲突。

2. 宿主机前期准备 (Synology DSM)#

必须确保群晖宿主机加载了 tun 内核模块并挂载了设备节点。

1. 检查 tun 模块状态#

通过 SSH 登录群晖宿主机执行:

Bash

ls -l /dev/net/tun

若提示文件不存在或未加载,手动建立节点与赋予权限:

Bash

sudo insmod /lib/modules/tun.ko 2>/dev/null || true
sudo mkdir -p /dev/net
sudo mknod /dev/net/tun c 10 200 2>/dev/null || true
sudo chmod 0666 /dev/net/tun
2. 设置群晖开机自动加载 TUN#

在群晖 控制面板 -> 任务计划 中新增一个 触发的任务 (用户定义的脚本)

  • 用户root

  • 事件:开机 (Boot-up)

  • 任务步骤脚本

Bash

if [ ! -c /dev/net/tun ]; then
insmod /lib/modules/tun.ko
mkdir -p /dev/net
mknod /dev/net/tun c 10 200
chmod 0666 /dev/net/tun
fi

3. 项目最终配置文件#

基于 Mihomo 官方 Alpine 镜像,无需任何自定义 Dockerfile,全部由 Compose 与启动脚本托管。

文件结构#

Plaintext

/volume2/docker/vlan-mihomo/
├── compose.yaml
├── entrypoint-v6.sh
└── config.yaml
配置一:compose.yaml#

YAML

services:
metacubexd:
image: docker.1ms.run/metacubex/mihomo:latest
container_name: vlan_metacubexd
restart: unless-stopped
environment:
TZ: '${TZ:-Asia/Shanghai}'
ulimits:
nofile:
soft: 65535
hard: 65535
cpu_shares: 2048
volumes:
- '.:/root/.config/mihomo'
- './entrypoint-v6.sh:/entrypoint-v6.sh:ro'
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:9090"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
# 关键:通过 sysctls 允许容器内核为新创接口开启 IPv6 并放行路由转发
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.default.disable_ipv6=0
- net.ipv4.ip_forward=1
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.all.accept_ra=2
- net.ipv6.conf.eth0.accept_ra=2
- net.ipv6.conf.eth0.accept_ra_pinfo=2
- net.ipv6.conf.eth0.accept_ra_defrtr=0
- net.ipv6.conf.eth0.autoconf=1
cap_add:
- NET_ADMIN
- NET_RAW
- SYS_ADMIN
security_opt:
- seccomp:unconfined
devices:
- /dev/net/tun:/dev/net/tun
networks:
macvlan_net:
ipv4_address: 192.168.0.2
privileged: true
entrypoint: ["/bin/sh", "/entrypoint-v6.sh"]
networks:
macvlan_net:
external: true
配置二:entrypoint-v6.sh#

Bash

#!/bin/sh
set -e
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"
# 1. 强制解锁内核对新建接口(如 Meta/tun0)的 IPv6 限制
sysctl -w net.ipv6.conf.all.disable_ipv6=0 2>/dev/null || true
sysctl -w net.ipv6.conf.default.disable_ipv6=0 2>/dev/null || true
# 2. 检查并动态分配 eth0 的 IPv6 前缀地址(如未绑定则静态追加)
DYNAMIC_ADDR=$(ip -6 addr show eth0 scope global | grep 'inet6 240' | head -n1 | awk '{print $2}')
if [ -z "$DYNAMIC_ADDR" ]; then
echo "[IPv6] Adding static IPv6 address to eth0..."
ip -6 addr add 2409:8a28:12eb:6e10::2/64 dev eth0 2>/dev/null || true
fi
# 3. 修正并重置 IPv6 默认网关路由
echo "[IPv6] Setting up IPv6 default route..."
ip -6 route del default 2>/dev/null || true
ip -6 route add default via fe80::f66d:2fff:fee6:fab5 dev eth0 2>/dev/null || true
# 4. 覆写容器内部 DNS 解析,防止启动初期依赖宿主机 DNS 超时
echo "nameserver 119.29.29.29" > /etc/resolv.conf
echo "nameserver 223.5.5.5" >> /etc/resolv.conf
# 5. 启动 Mihomo 主程序
echo "[Mihomo] Starting Mihomo daemon..."
exec /mihomo -d /root/.config/mihomo

权限说明:创建脚本后请运行 chmod +x entrypoint-v6.sh

配置三:config.yaml 核心段落#

YAML

# 指定物理出网接口,防止 Macvlan 模式下 TUN 出口流量回流造成无限环路
interface-name: eth0
external-controller: 0.0.0.0:9090
secret: password
mixed-port: 1080
allow-lan: true
bind-address: '*'
ipv6: true
mode: rule
log-level: error
# TUN 模式核心参数
tun:
enable: true
stack: gvisor # 使用用户态网络栈,避开群晖内核高级参数修改限制
auto-route: true
strict-route: false # 必须为 false,防止强制修改宿主机主表
auto-detect-interface: false # 在 Macvlan 下必须关闭自动识别,防止错误绑定
inet4-address: 198.18.0.1/30
inet6-address: fdfe:dcba:9876::1/126
dns-hijack:
- 'any:53'
- 'tcp://any:53'
# 内置 DNS 服务参数(为局域网提供 DNS 劫持与 Fake-IP 解析)
dns:
enable: true
listen: 0.0.0.0:53 # 监听 53 端口,支持作为局域网主 DNS 使用
ipv6: true
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
respect-rules: false
use-hosts: true
nameserver:
- 119.29.29.29
- 223.5.5.5
- https://doh.pub/dns-query
4. 部署与功能验证#

创建 macvlan

Terminal window
sudo docker network create -d macvlan \
--subnet=192.168.0.0/24 \
--gateway=192.168.0.1 \
--ipv6 \
--subnet=fe80::/64 \
-o parent=ovs_eth0 \
macvlan_net

在项目目录下执行容器一键启动:

docker compose up -d --force-recreate

网卡混杂状态

  • 先看一眼
Terminal window
ip link show | grep -i promisc
  • 如果没开,要开启
Terminal window
sudo ip link set ovs_eth0 promisc on
sudo ip link set eth0 promisc on
  • 如果未来卸载时要关闭
Terminal window
sudo ip link set ovs_eth0 promisc off
sudo ip link set eth0 promisc off
1. 验证 TUN 接口与网络状态#

进入容器查看网络设备:

docker exec -it vlan_metacubexd sh -c "ip a"

成功标志

  • 出现 Meta 虚拟接口且状态为 UP,LOWER_UP

  • Meta 接口成功绑定了 IPv4 (198.18.0.1/30) 与 IPv6 (fdfe:dcba:9876::1/126)。

  • eth0 正常拿到公网 IPv6 地址 (2409:…/64)。

2. 验证容器内双栈连通性与 DNS 状态#
# 验证 IPv4 连通性
docker exec -it vlan_metacubexd ping -c 2 baidu.com
# 验证 IPv6 连通性
docker exec -it vlan_metacubexd ping6 -c 2 2a0e:97c0:3f0:1::1d1d
3. 验证局域网客户端接入#

在同局域网的电脑/手机上,将网关DNS 服务器 手动指定为 192.168.0.2

# 在客户端命令行验证 DNS 解析
nslookup sony.com 192.168.0.2

此时解析应瞬间返回 Fake-IP(198.18.x.x)或正确 IP,全网 TCP / UDP / ICMP 流量将无感通过 Mihomo TUN 模式进行透明代理转发。

4. 写在最后:性能影响#

为了清晰对比,我们假设场景如下:

  • 局域网客户端:IP 为 192.168.1.50

  • NAS(宿主机):IP 为 192.168.1.100,物理网卡为 eth0

  • Mihomo 容器:挂载在 macvlan0 虚拟网卡上,分配独立 IP 192.168.1.200,内部开启 TUN 网卡 utun0

  • 目标网站1.1.1.1

下面以一个标准的 TCP Request(客户端发送请求)和 Response(目标网站返回数据)为例,拆解 Socks5 模式MacVlan + TUN 模式 的完整数据包链路与内核开销。

模式一:直连 Socks5 代理模式(基准路径)#

在 Socks5 模式下,客户端显式发起 Socks5 握手,数据包在三层(IP)是直接发给 Mihomo 的

1. Request(请求数据包)链路#
  1. 客户端 \rightarrow NAS 物理网卡:客户端构建 TCP 包,目标 IP 为 192.168.1.200(Mihomo),目标端口为 7890。包通过物理网卡 eth0 接收。

  2. 硬中断(Hard IRQ)\rightarrow 软中断(SoftIRQ):网卡收到数据包,触发 CPU 硬中断;内核网口驱动处理完后抛出 NET_RX 软中断,Linux 协议栈解析 Ethernet / IP / TCP 头部。

  3. Socket 缓冲区 \rightarrow 用户态:数据包通过 Linux 协议栈送入 Mihomo 绑定的 Listening Socket 缓冲区。

  4. 上下文切换(Context Switch):内核唤醒 Mihomo 进程。Mihomo 执行 read()epoll_wait(),数据从内核空间(Kernel Space)复制到用户空间(User Space)

  5. 用户态加密/转发:Mihomo 解析 Socks5 协议,根据 Rule 决定走节点 Proxy,将数据通过 TLS/QUIC 等代理协议封装。

  6. 用户态 \rightarrow 内核空间:Mihomo 调用 write() 将加密后的代理数据发往远端代理节点。数据从用户空间复制回内核空间。

  7. 协议栈发送 \rightarrow 物理网卡:内核经过路由表判断,从 eth0 发出 TCP 包发往代理节点/目标服务器。

2. Response(响应数据包)链路#
  1. 目标服务器 \rightarrow NAS 物理网卡:远端节点返回加密响应,eth0 接收。

  2. 硬/软中断 \rightarrow Socket 缓冲区:协议栈解包,交由 Mihomo 与代理节点建立的 TCP 连接 Socket。

  3. 上下文切换(用户态处理):Mihomo 接收数据,在用户态解密。

  4. 用户态 \rightarrow 内核空间 \rightarrow 客户端:Mihomo 将解密后的 Raw TCP 载荷写入与客户端建立的 Socks5 Socket,内核协议栈封装 TCP/IP 报文后直接从 eth0 发回客户端 192.168.1.50

Socks5 路径开销总结

  • 内核/用户态切换:仅 1 次(入站)+ 1 次(出站)。

  • 协议栈解析:标准的标准 Socket 读写,Linux 内核路径短,支持 GSO/GRO(网卡硬件分包/合并),CPU 效率极高。

模式二:MacVlan + TUN 模式(全透明劫持路径)#

在 MacVlan + TUN 模式下,客户端未设置 Socks5 代理,直接把网关指向 Mihomo(192.168.1.200),试图访问 1.1.1.1。此时数据包经历了 二层 MacVlan 软重定向 + 三层 Linux 路由劫持 + TUN 虚拟网卡拆包/重组

1. Request(请求数据包)链路:步步皆是开销#
第一阶段:MacVlan 的二层流量打转与网卡混杂模式#
  1. 客户端 \rightarrow NAS 物理网卡:客户端构建 TCP 包,目标 IP 为 1.1.1.1,但目标 MAC 地址为 Mihomo 的 MacVlan MAC (02:42:c0:…)。数据包到达物理网卡 eth0

  2. 混杂模式与硬/软中断:由于包的 MAC 不是 NAS 宿主机自身的 MAC,eth0 必须处于混杂模式(Promiscuous Mode)。网卡触发硬中断,驱动抛出 SoftIRQ。

  3. MacVlan 驱动处理:Linux 内核接收到包后,MacVlan 模块(macvlan_handle_frame)在二层拦截该包,匹配 MAC 后将其送入 macvlan0 虚拟网卡的接收队列。这一过程产生了额外的内核网络子系统二层转发开销

第二阶段:TUN 虚拟网卡与二次内核/用户态切换#
  1. 内核路由表与 iptables / nftablesmacvlan0 拿到包后,容器内网络协议栈处理 IP 层。由于开启了 auto-route 或 iptables 劫持,内核路由表将目标 IP 1.1.1.1 路由到 utun0 网卡。

  2. 写入 TUN 队列:内核协议栈把完整的 IP 数据报(包含 IP 头部、TCP 头部、Payload)写入 utun0 的环形缓冲区(Ring Buffer)。

  3. 上下文切换 (切换 1):Mihomo 进程阻塞在 utun0 的字符设备文件描述符(/dev/net/tun)上。内核触发唤醒,发生上下文切换,数据从内核 utun0 缓冲区复制到用户态 Mihomo

  4. 用户态协议栈重组(lwIP / gVisor)

    • 在 Socks5 下,内核替我们处理 TCP 握手和状态机。

    • 在 TUN 模式下,收到的是原始 IP 数据包。Mihomo 必须在用户态运行一套内置的 TCP/IP 协议栈(如 gVisor 或 lwIP),在用户态手动解析 TCP 序列号、ACK 确认号、窗口大小,将其还原成标准的 TCP 字节流。这一步会产生剧烈的 CPU 运算开销。

第三阶段:代理封装与重新送回内核#
  1. 代理封装:Mihomo 将提取出的 TCP 字节流打入代理协议(如 ShadowSocks / Vless / Trojan)。

  2. 上下文切换 (切换 2):Mihomo 调用 write(),将加密后的代理数据通过真实 Socket 发往代理节点。数据再次从用户空间复制回内核空间

  3. 二层再次出站:数据经过 Linux 协议栈,从 macvlan0 出发,再次经过 MacVlan 驱动转发到物理网卡 eth0 发出。

2. Response(响应数据包)链路:逆向再剥一层皮#

远端代理节点返回数据包时的流程是 Request 的完美镜像,开销同样巨大:

  1. eth0 接收代理响应包 \rightarrow MacVlan 驱动二层识别 \rightarrow 扔给 macvlan0

  2. 软中断(SoftIRQ) 触发,协议栈处理,送到 Mihomo 与代理节点建立的真实 Socket 缓冲区。

  3. 上下文切换 (切换 3):Mihomo 读取数据,在用户态完成解密。

  4. 用户态 TCP/IP 协议栈伪造 IP 包:Mihomo 的用户态协议栈根据刚刚的解密数据,手动构建一个源 IP 为 1.1.1.1、目标 IP 为 192.168.1.50 的原始 TCP/IP 包。

  5. 上下文切换 (切换 4):Mihomo 将伪造的 IP 包写入 /dev/net/tunutun0 网卡)。

  6. TUN 网卡注入内核:内核 utun0 接收到这个数据包,以为是从网卡收到了数据,重新压入内核网络协议栈。

  7. 二层重定向与软中断:内核路由表判断该包目标为 192.168.1.50,交由 macvlan0 发送,MacVlan 驱动通过 eth0 最终将数据包送回局域网客户端。

三、 两种模式链路对比表#
比较维度Socks5 显式代理MacVlan + TUN 透明代理性能影响
二层 (L2) 处理物理网卡硬件直收直发物理网卡开启混杂模式 + MacVlan 模块内部转发增加中断频次,CPU 无法利用网卡 Hardware Offload
内核/用户态切换 (单向包)2 次 (Read Socket / Write Socket)4 次 (Read Socket / Write TUN / Read TUN / Write Socket)上下文切换(Context Switch)开销翻倍
数据内存拷贝 (Memory Copy)2 次4 次以上 (内核 \leftrightarrow TUN \leftrightarrow Mihomo \leftrightarrow 内核)L3 Cache 频繁失效,内存带宽开销大
TCP/IP 协议栈处理纯 Linux 内核 C 语言内核栈处理双重处理:Linux 内核栈处理外层,Mihomo(gVisor/lwIP)在 Go 用户态处理内层极其消耗单核 CPU 性能,Go 语言 GC 带来额外开销
网卡硬件加速 (TSO/GRO)完全支持TUN 虚拟网卡通常不支持或加速效果差,导致小包碎片化产生暴风式的 NET_RX 软中断
总结:性能为什么会从 40 万暴跌到 3.7 万?#

对于 NAS 常见的 CPU(如 Intel N100, J4125),单核性能本就有限。

MacVlan + TUN 模式下,每一个数据包都要经历:

  1. 网卡混杂模式 + MacVlan 驱动 的二层开销;

  2. 两次额外的 /dev/net/tun 内存复制与上下文切换

  3. Go 用户态协议栈(gVisor)伪造/解析 TCP 报文 的 CPU 密集计算;

  4. 网卡硬件分包(TSO/GRO)失效 导致软中断(SoftIRQ)数量呈指数级上升。

当网络吞吐量加大时,CPU 的单个核心会瞬间被 ksoftirqd(软中断进程)上下文切换 彻底吃满。系统大量的算力不是花在“传输数据”上,而是花在了内核与用户态之间来回搬运和重组数据包上,最终导致极限吞吐量出现数量级的断崖式下跌。