背景

之前折腾过 OpenWrt + OpenClash,用起来不顺手——OpenWrt 太重,OpenClash 配置繁琐,隔三差五出点小问题。Sing-box 看过一眼,功能确实新,但迭代太快,还没到能放心用的程度。最后决定直接拿 OpenClash 的内核 Mihomo 自己搭,省去中间那层。

触发这次折腾的直接原因是 Vibe Coding。AI 自动跑任务时,npm 装包、Docker 拉镜像、调 LLM API,哪个卡住都得手动去处理,烦死了。手动写代码偶尔挂个代理还好说,AI 自动跑的时候网络一抖就全乱了,得有个稳的底座。

Docker + Macvlan + Mihomo + TUN 这套组合,比在 OpenWrt 上再加一层要干净得多:不需要完整的 OpenWrt 系统,容器启停很快,Mihomo 本身也够成熟。

准备工作

  • 一台能跑 Docker 的 Linux 主机(物理机、虚拟机都行)

  • Linux 内核 3.9+(基本都支持 macvlan)

  • 准备好 config.yaml(下面有模板)

创建 Macvlan 网络

先开 IP 转发,这个不开旁路由就是摆设:

# 临时开启(重启后失效)
sysctl -w net.ipv4.ip_forward=1

# 永久开启
echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/99-ipforward.conf
sysctl -p /etc/sysctl.d/99-ipforward.conf

然后创建 macvlan 网络,按实际情况改 --subnet--gateway-o parent

docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  -o parent=eth0 \
  macnet

有一个坑要注意:macvlan 模式下宿主机和容器默认不能互通。如果宿主机本身也要走代理,得加一个 shim 接口:

ip link add macvlan-shim link eth0 type macvlan mode bridge
ip addr add 192.168.1.10/24 dev macvlan-shim
ip link set macvlan-shim up
ip route add 192.168.1.11/32 dev macvlan-shim

这样宿主机通过 192.168.1.10 就能访问容器的 192.168.1.11。宿主机不需要代理就跳过。

准备配置文件

/root/mihomo/ 下建 config.yaml,下面是一份可以直接用的最小配置,代理信息替换成自己的:

# Mihomo Minimal Transparent Proxy Config
# Suitable for Docker macvlan + TUN model

# -----------------------------
# 代理配置(示例)
# -----------------------------
proxies:
  - name: "SS1"
    type: ss
    server: aaa.mangege.com  # 替换为你的代理服务器地址
    port: 443                # 替换为你的代理端口
    cipher: chacha20-ietf-poly1305
    password: "aaa"          # 替换为你的代理密码
    udp: true
    udp-over-tcp: true
    udp-over-tcp-version: 2
    ip-version: ipv4

# -----------------------------
# 代理提供者(示例)
# -----------------------------
proxy-providers:
  my-provider:
    type: http
    path: ./providers/my-provider.yaml
    url: https://example.com/provider.yaml  # 替换为你的订阅链接
    interval: 3600
    health-check:
      enable: true
      url: http://www.gstatic.com/generate_204
      interval: 300

# -----------------------------
# 基础配置
# -----------------------------
mixed-port: 7890
redir-port: 7892       # TCP 透明代理
tproxy-port: 7893      # UDP 透明代理
ipv6: true
allow-lan: true
unified-delay: false
tcp-concurrent: true

# -----------------------------
# 外部控制
# -----------------------------
external-controller: 127.0.0.1:9090
external-ui: ui
external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip"

# -----------------------------
# Geo 数据
# -----------------------------
geodata-mode: true
geox-url:
  geoip: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.dat"
  geosite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat"
  mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country-lite.mmdb"
  asn: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb"

# -----------------------------
# 其他设置
# -----------------------------
client-fingerprint: chrome
profile:
  store-selected: true
  store-fake-ip: true
sniffer:
  enable: true
  sniff:
    HTTP:
      ports: [80, 8080-8880]
      override-destination: true
    TLS:
      ports: [443, 8443]
    QUIC:
      ports: [443, 8443]
  skip-domain:
    - "Mijia Cloud"
    - "+.push.apple.com"

# -----------------------------
# TUN 设置(关键)
# -----------------------------
tun:
  enable: true
  stack: gvisor
  mtu: 1500
  dns-hijack:
    - "any:53"
    - "tcp://any:53"
  auto-route: true
  auto-redirect: true
  auto-detect-interface: true
  fake-ip-range: 198.18.0.1/16

# -----------------------------
# DNS 设置
# -----------------------------
dns:
  enable: true
  ipv6: true
  enhanced-mode: fake-ip
  fake-ip-filter:
    - "*"
    - "+.lan"
    - "+.local"
    - "+.market.xiaomi.com"
  default-nameserver:
    - tls://223.5.5.5
    - tls://223.6.6.6
  fallback:
    - tls://1.1.1.1
    - tls://8.8.8.8
  nameserver:
    - https://doh.pub/dns-query
    - https://dns.alidns.com/dns-query

# -----------------------------
# 代理组
# -----------------------------
proxy-groups:
  - name: "DEFAULT"
    type: select
    proxies: [SS1]

# -----------------------------
# 规则
# -----------------------------
rules:
  - GEOIP,lan,DIRECT,no-resolve
  - GEOSITE,CN,DIRECT
  - GEOIP,CN,DIRECT
  - DOMAIN-SUFFIX,ts.net,DIRECT
  - DOMAIN-SUFFIX,tailscale.io,DIRECT
  - DOMAIN-SUFFIX,tailscale.com,DIRECT
  - MATCH,DEFAULT

serverportpassword 换成自己的,用 proxy-providers 的话把 url 改成订阅链接,其余按需调整。更多选项看 Mihomo 官方文档。

启动容器

docker run -d \
  --name mihomo \
  --restart always \
  --network macnet \
  --ip 192.168.1.11 \
  -v /root/mihomo/config.yaml:/root/.config/mihomo/config.yaml \
  metacubex/mihomo:latest

--ip 指定容器在局域网里的地址,确认没被占用。-v 挂载配置文件,路径对就行。

配置说明

Macvlan 让容器拿到一个独立的局域网 IP,对局域网里其他设备来说它就是一台普通机器。TUN 模式在容器内建虚拟网卡,流量进来就被接管,应用层完全无感知。DNS 劫持把所有 53 端口的查询都拦下来,防止 DNS 泄露。

分流规则很直接:局域网直连,国内域名和 IP 直连,Tailscale 相关域名直连,剩下的全走 DEFAULT 代理组。

客户端这边需要手动把网关和 DNS 都改成 192.168.1.11,流量才能经过旁路由:

  • Windows:网络设置 → 更改适配器选项 → 右键以太网/WiFi → 属性 → IPv4 → 手动填 IP 和 DNS

  • macOS:系统偏好设置 → 网络 → 高级 → TCP/IP 手动配置,DNS 填 192.168.1.11

  • Linux:用 nmcli 或直接在 /etc/resolv.confnameserver 192.168.1.11

安全方面几个要注意的:配置文件权限改成 chmod 600,Mihomo 的端口(7890、7892、7893、9090)不要暴露到公网,镜像记得定期更新。

性能方面,MTU 默认 1500,如果走的是 PPPoE 或者有封装开销,试试改到 1400。store-selectedstore-fake-ip 已经开了,对性能有帮助。

验证

容器起来之后,先跑几个基础检查:

# 容器状态
docker ps | grep mihomo

# DNS 解析
nslookup google.com 192.168.1.11

# 连通性
ping 192.168.1.11
curl -x socks5://192.168.1.11:7890 https://httpbin.org/ip

再测分流是否正常:

curl -I https://www.baidu.com   # 应该直连
curl -I https://www.google.com  # 应该走代理

# 看日志确认
docker logs mihomo -f | grep -E "DIRECT|REJECT"

故障排查

容器起不来,先看日志:docker logs mihomo,多半是 config.yaml 语法错了。确认 macvlan 网络存在:docker network ls。容器 IP 冲突的话 ping 192.168.1.11 会有响应但容器没跑起来。

客户端上不了网,先确认网关和 DNS 都改对了,然后 nslookup google.com 192.168.1.11 测一下 DNS 通不通。

速度慢,先排查代理节点本身的问题,再试着调 MTU,用 docker stats mihomo 看看容器资源有没有跑满。

维护

# 重启
docker restart mihomo

# 改配置后重启
vim /root/mihomo/config.yaml
docker restart mihomo

# 通过 API 热重载(不停服)
curl -X PUT http://127.0.0.1:9090/configs -d '{"path":"/root/.config/mihomo/config.yaml"}'

# 更新镜像
docker pull metacubex/mihomo:latest
docker stop mihomo && docker rm mihomo
docker run -d --name mihomo --restart always --network macnet --ip 192.168.1.11 \
  -v /root/mihomo/config.yaml:/root/.config/mihomo/config.yaml \
  metacubex/mihomo:latest

# 备份配置
cp /root/mihomo/config.yaml /root/mihomo/config.yaml.backup

日志查看:

docker logs mihomo -f
docker logs mihomo --tail 100
docker logs mihomo --since 2026-03-25T21:00:00

整体跑下来,这套方案比 OpenWrt 那一套轻多了,折腾成本也低。把客户端网关和 DNS 指向容器 IP,剩下的 Mihomo 自己处理。