家里全屋科学上网的方法
用最简单最直白最不绕弯子的一句话告诉你扎心真相:
- 富人
- 买一台多个网口的软路由或小主机装 OpenWRT+OpenClash
- 优点:
- 可以做主路由(2 个网口接在路由器上游 WAN 口和光猫之间负责拨号)
- 可以做旁路由(1 个网口接在路由器下游 LAN 口)。
- 系统往往可以让老板帮你装好,最省心,最稳定。
- 或者 v 我 10U 帮你解决一切。
- 价格:
- 取决于网口数量和速度 ¥500-1500
- 富人的世界没有缺点,只有代价,钱能解决一切
- 优点:
- 买一台多个网口的软路由或小主机装 OpenWRT+OpenClash
- 穷人有 NAS
- 虚拟机跑 OpenWRT 里面再跑 OpenClash
- 优点:免费、图形界面操作、性能稳定
- 缺点:性能稳定的拉垮、配置繁琐、出了问题懵逼
- Docker 跑 Mihomo
- 优点:免费、图形界面操作、性能稳定
- 缺点:性能稳定的拉垮、出了问题还是懵逼
- NAS 裸核跑 Mihomo
- 优点:免费、性能稳定
- 缺点:性能稳定的拉垮、安装配置命令行,出了问题特别懵逼
- 虚拟机跑 OpenWRT 里面再跑 OpenClash
- 穷鬼没 NAS
- 在某台电脑或者手机上装任意客户端或者裸核,配置中并允许局域网接入,而且这台电脑要固定 IP
- 优点:免费,把 NAS 的钱也省了,手机电费低。
- 缺点:手机发烫、性能既不稳定还拉完了。
- 在某台电脑或者手机上装任意客户端或者裸核,配置中并允许局域网接入,而且这台电脑要固定 IP
假设穷人有 NAS,那么虚拟 OpenWRT 做旁路由还是最经济最简单的选择,因为本质上是消耗大量内存(1-2G)和硬盘(1-2G)方案获得易用性的轻奢穷人,虽然一切都可以图形化配置,但是多了个虚拟机、OpenWRT、OpenClash 等 Luci 程序等众多概念、操作和配置,尤其是旁路由 IPv6 就可能劝退一批人。
Docker 和裸核跑比想象的方便多了,系统占用极低,和 OpenWRT 对比,内存只要 50MB,磁盘空间也只要一个核(~40MB )。要配置的东西极少。找一个优秀的模版把 分流规则自动选择或者故障转移等都写好,能够做到常年不用管。
旁路由 开启 tun 或者设置透明代理后才能全屋自动生效,手机平板电脑 TV 等等设备都不需要装梯子翻墙。否则在各个设备上还是要支持 socks/http 代理才行。操作方法就是:
- 装好旁路由
- 家里的 路由器下发的 DHCP 配置中的网关 IP 改成旁路由的 IP
- 如果 NAS 上直接跑裸核或者在容器里直接跑裸核,那就填 NAS 的 IP
- 如果用了 MacVLan 给容器指定了别的 IP,那就填 mihomo 所在容器的 IP)
- 特别重要:在 NAS/OpenWRT 的网卡设置中手动配置 IP,网关 IP 要填写光猫/交换机/路由器的 IP。(否则就环路了,啥都出不去)
- 一分价钱一分货的真理永远存在,金钱和性(幸)能(福)是成正比的
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 ServiceAfter=network.target network-online.target nss-lookup.targetWants=network-online.target
[Service]Type=simpleUser=rootExecStart=/volume1/mihomo/bin/mihomo -d /volume1/mihomo/configRestart=on-failureRestartSec=5sLimitNOFILE=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 mihomo3. 服务状态管理与日志查看(可选)
- 查看服务运行状态:
sudo systemctl status mihomo- 查看运行日志(实时监控):
sudo journalctl -u mihomo -f- 手动停止服务:
sudo systemctl stop mihomo- 手动重启服务:
sudo systemctl restart mihomo4. 图形化控制面板
部署后,用浏览器访问 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 其实也可有可无
原因是
- 如果在 NAS 跑 tun 模式会影响 NAS 上其他服务,特别是 fake-ip,几乎很难穷尽所有可能性的去写各种规则,能用,但是非常麻烦不稳定。
- 如果在 MacVLan 上跑 tun,性能差很多——网卡先要开混杂模式降低整体 NAS 性能,然后数据包多转一次,用户态和内核态的切换也多一倍。
- 最终一个普通容器不开 tun,只用 socks5 代理能跑 40 万的节点,用 MacVLan 开 tun 后就只能跑到 3.x 万。所以 MacVLan 的 Tun 模式只适合做最终的 failsafe。
- 既然作为 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 denied 或 RTNETLINK answers: Permission denied 报错。
本文记录了完整的排查过程与最终的生产级配置方案,包含 IPv6 双栈动态路由与全局透明代理的完美结合。
1. 架构与痛点解析
核心痛点:为什么 TUN 会在群晖 DSM 下报 Permission denied?
在 Macvlan 模式下部署容器并启动 TUN,Mihomo 初始化时会按顺序执行以下动作:
-
创建虚拟网卡(例如
Meta/tun0)。 -
向该网卡绑定 IPv4 / IPv6 地址(默认私有段为
198.18.0.1/30和fdfe:dcba:9876::1/126)。 -
拉起网卡并注入
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 || truesudo mkdir -p /dev/netsudo mknod /dev/net/tun c 10 200 2>/dev/null || truesudo chmod 0666 /dev/net/tun2. 设置群晖开机自动加载 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/tunfi3. 项目最终配置文件
基于 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/shset -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 || truesysctl -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 || truefi
# 3. 修正并重置 IPv6 默认网关路由echo "[IPv6] Setting up IPv6 default route..."ip -6 route del default 2>/dev/null || trueip -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.confecho "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:9090secret: passwordmixed-port: 1080allow-lan: truebind-address: '*'ipv6: truemode: rulelog-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-query4. 部署与功能验证
创建 macvlan
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网卡混杂状态
- 先看一眼
ip link show | grep -i promisc- 如果没开,要开启
sudo ip link set ovs_eth0 promisc onsudo ip link set eth0 promisc on- 如果未来卸载时要关闭
sudo ip link set ovs_eth0 promisc offsudo ip link set eth0 promisc off1. 验证 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::1d1d3. 验证局域网客户端接入
在同局域网的电脑/手机上,将网关与 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虚拟网卡上,分配独立 IP192.168.1.200,内部开启 TUN 网卡utun0 -
目标网站:
1.1.1.1
下面以一个标准的 TCP Request(客户端发送请求)和 Response(目标网站返回数据)为例,拆解 Socks5 模式 与 MacVlan + TUN 模式 的完整数据包链路与内核开销。
模式一:直连 Socks5 代理模式(基准路径)
在 Socks5 模式下,客户端显式发起 Socks5 握手,数据包在三层(IP)是直接发给 Mihomo 的。
1. Request(请求数据包)链路
-
客户端 NAS 物理网卡:客户端构建 TCP 包,目标 IP 为
192.168.1.200(Mihomo),目标端口为7890。包通过物理网卡eth0接收。 -
硬中断(Hard IRQ) 软中断(SoftIRQ):网卡收到数据包,触发 CPU 硬中断;内核网口驱动处理完后抛出
NET_RX软中断,Linux 协议栈解析 Ethernet / IP / TCP 头部。 -
Socket 缓冲区 用户态:数据包通过 Linux 协议栈送入 Mihomo 绑定的 Listening Socket 缓冲区。
-
上下文切换(Context Switch):内核唤醒 Mihomo 进程。Mihomo 执行
read()或epoll_wait(),数据从内核空间(Kernel Space)复制到用户空间(User Space)。 -
用户态加密/转发:Mihomo 解析 Socks5 协议,根据 Rule 决定走节点 Proxy,将数据通过 TLS/QUIC 等代理协议封装。
-
用户态 内核空间:Mihomo 调用
write()将加密后的代理数据发往远端代理节点。数据从用户空间复制回内核空间。 -
协议栈发送 物理网卡:内核经过路由表判断,从
eth0发出 TCP 包发往代理节点/目标服务器。
2. Response(响应数据包)链路
-
目标服务器 NAS 物理网卡:远端节点返回加密响应,
eth0接收。 -
硬/软中断 Socket 缓冲区:协议栈解包,交由 Mihomo 与代理节点建立的 TCP 连接 Socket。
-
上下文切换(用户态处理):Mihomo 接收数据,在用户态解密。
-
用户态 内核空间 客户端: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 的二层流量打转与网卡混杂模式
-
客户端 NAS 物理网卡:客户端构建 TCP 包,目标 IP 为
1.1.1.1,但目标 MAC 地址为 Mihomo 的 MacVlan MAC (02:42:c0:…)。数据包到达物理网卡eth0。 -
混杂模式与硬/软中断:由于包的 MAC 不是 NAS 宿主机自身的 MAC,
eth0必须处于混杂模式(Promiscuous Mode)。网卡触发硬中断,驱动抛出 SoftIRQ。 -
MacVlan 驱动处理:Linux 内核接收到包后,MacVlan 模块(
macvlan_handle_frame)在二层拦截该包,匹配 MAC 后将其送入macvlan0虚拟网卡的接收队列。这一过程产生了额外的内核网络子系统二层转发开销。
第二阶段:TUN 虚拟网卡与二次内核/用户态切换
-
内核路由表与 iptables / nftables:
macvlan0拿到包后,容器内网络协议栈处理 IP 层。由于开启了auto-route或 iptables 劫持,内核路由表将目标 IP1.1.1.1路由到utun0网卡。 -
写入 TUN 队列:内核协议栈把完整的 IP 数据报(包含 IP 头部、TCP 头部、Payload)写入
utun0的环形缓冲区(Ring Buffer)。 -
上下文切换 (切换 1):Mihomo 进程阻塞在
utun0的字符设备文件描述符(/dev/net/tun)上。内核触发唤醒,发生上下文切换,数据从内核utun0缓冲区复制到用户态 Mihomo。 -
用户态协议栈重组(lwIP / gVisor):
-
在 Socks5 下,内核替我们处理 TCP 握手和状态机。
-
在 TUN 模式下,收到的是原始 IP 数据包。Mihomo 必须在用户态运行一套内置的 TCP/IP 协议栈(如 gVisor 或 lwIP),在用户态手动解析 TCP 序列号、ACK 确认号、窗口大小,将其还原成标准的 TCP 字节流。这一步会产生剧烈的 CPU 运算开销。
-
第三阶段:代理封装与重新送回内核
-
代理封装:Mihomo 将提取出的 TCP 字节流打入代理协议(如 ShadowSocks / Vless / Trojan)。
-
上下文切换 (切换 2):Mihomo 调用
write(),将加密后的代理数据通过真实 Socket 发往代理节点。数据再次从用户空间复制回内核空间。 -
二层再次出站:数据经过 Linux 协议栈,从
macvlan0出发,再次经过 MacVlan 驱动转发到物理网卡eth0发出。
2. Response(响应数据包)链路:逆向再剥一层皮
远端代理节点返回数据包时的流程是 Request 的完美镜像,开销同样巨大:
-
eth0接收代理响应包 MacVlan 驱动二层识别 扔给macvlan0。 -
软中断(SoftIRQ) 触发,协议栈处理,送到 Mihomo 与代理节点建立的真实 Socket 缓冲区。
-
上下文切换 (切换 3):Mihomo 读取数据,在用户态完成解密。
-
用户态 TCP/IP 协议栈伪造 IP 包:Mihomo 的用户态协议栈根据刚刚的解密数据,手动构建一个源 IP 为
1.1.1.1、目标 IP 为192.168.1.50的原始 TCP/IP 包。 -
上下文切换 (切换 4):Mihomo 将伪造的 IP 包写入
/dev/net/tun(utun0网卡)。 -
TUN 网卡注入内核:内核
utun0接收到这个数据包,以为是从网卡收到了数据,重新压入内核网络协议栈。 -
二层重定向与软中断:内核路由表判断该包目标为
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 次以上 (内核 TUN Mihomo 内核) | 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 模式下,每一个数据包都要经历:
-
网卡混杂模式 + MacVlan 驱动 的二层开销;
-
两次额外的
/dev/net/tun内存复制与上下文切换; -
Go 用户态协议栈(gVisor)伪造/解析 TCP 报文 的 CPU 密集计算;
-
网卡硬件分包(TSO/GRO)失效 导致软中断(SoftIRQ)数量呈指数级上升。
当网络吞吐量加大时,CPU 的单个核心会瞬间被 ksoftirqd(软中断进程) 和 上下文切换 彻底吃满。系统大量的算力不是花在“传输数据”上,而是花在了内核与用户态之间来回搬运和重组数据包上,最终导致极限吞吐量出现数量级的断崖式下跌。