Mihomo 踩坑实录:从 systemd 服务到 TUN 模式的全套折腾
2025年3月11日

Mihomo 踩坑实录:从 systemd 服务到 TUN 模式的全套折腾


我的立场:

  • 我对代理工具的态度是「又爱又恨」:没有它工作没法开展,配置错了又让人想砸键盘
  • 我的观点受限于:只在公司内网环境折腾过,家用宽带可能完全是另一回事
  • 如果一年后我发现错了,最可能错在:「所有内网问题都能用 fake-ip-filter 解决」这个假设

我必须承认的偏见: 我对 systemd 有种莫名的信任,觉得「写成服务就稳了」。这可能只是心理安慰,实际上服务崩了我经常后知后觉。


为什么又写这个

去年写过一篇 systemd 服务的教程,当时只懂皮毛,以为 systemctl enable 就是终点。现在回头看,那篇文章太「正确」了,全是教科书式的命令,没有血和泪。

这篇不一样。这是我在公司内网折腾 Mihomo 的真实记录,包括三次完全搞崩网络、两次重装配置文件、以及无数次对着日志骂娘的过程。

如果你正在用 Clash/Mihomo,或者打算把它做成系统服务,这篇可能帮你省几个小时。


先搞清楚你在用什么

很多人(包括三个月前的我)对代理的理解停留在「App 里填个端口」。这没错,但 Mihomo 的 TUN 模式完全是另一回事。

普通代理:App 主动设置代理端口 → Mihomo → 节点 → 目标。只有支持代理的 App 才生效。

TUN 模式:App → 内核网络栈 → 虚拟网卡 Meta(198.18.0.1) → Mihomo → 节点 → 目标。所有流量强制经过,App 无感知。

TUN 模式的好处是「全局生效」,坏处是「全局都可能出问题」。我就是被这个「全局」坑了。


文件结构:别动 runtime.yaml

Mihomo 的配置分三层,这个设计很巧妙,但新手容易懵:

config.yaml # 主配置(节点、规则,从订阅获取)
mixin.yaml # 覆盖配置(TUN、DNS 等个性化设置)
↓ 合并
runtime.yaml # 实际运行配置(每次重启时生成,不要手动改这个)

合并命令我写成 alias 了:

Terminal window
alias mihomo-restart='echo "你的密码" | sudo -S bash -c "/opt/clash/bin/yq eval-all ". as \$item ireduce ({}; . *+ \$item)" /opt/clash/config.yaml /opt/clash/mixin.yaml > /opt/clash/runtime.yaml && systemctl restart mihomo"'

血的教训:我手动改过 runtime.yaml,重启服务后被覆盖,当场愣住。


systemd 服务:不只是开机自启

之前的文章只讲了怎么写 service 文件,但没讲为什么要用 root 跑 Mihomo。

答案很简单:TUN 模式需要创建虚拟网卡,普通用户没权限。

我的 /etc/systemd/system/mihomo.service

[Unit]
Description=Mihomo Daemon
After=network.target
[Service]
Type=simple
User=root
ExecStart=/opt/clash/bin/mihomo -f /opt/clash/runtime.yaml
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

几个细节:

  • Restart=on-failure 很重要,Mihomo 崩溃后自动重启
  • 日志走 journal,用 sudo journalctl -u mihomo -f 看实时输出
  • 不要用 network-online.target,某些系统上它会拖慢启动

大坑:TUN 开启后 GitLab 挂了

这是我折腾最久的问题。

现象:TUN 关闭可以访问 gitlab.mychery.com,TUN 开启后超时。

排查过程

看日志发现 DIRECT 出站超时,IP 是 115.120.50.150。但公司的 GitLab 明明是内网服务啊?

Terminal window
# TUN 开启时,mihomo 用公共 DNS 解析:
nslookup gitlab.mychery.com 114.114.114.114
115.120.50.150 (公网IP,内网不可达)
# TUN 关闭时,系统用公司内网 DNS 解析:
nslookup gitlab.mychery.com
10.145.229.67 (内网IP,可达)

恍然大悟:Mihomo 的 nameserver 只配了公共 DNS,解析内网域名得到公网 IP,而公网 IP 从公司内网根本走不通。

这就像是让外地导航给北京人指路,它当然会告诉你走高速,但你要去的其实是胡同里的老院子。


解决方案:fake-ip-filter 和 nameserver-policy

我查到了公司的内网 DNS 地址:

Terminal window
resolvectl status | grep "DNS Server"
# → 10.1.4.141 10.1.4.142

然后在 mixin.yaml 里加了这两段:

dns:
enable: true
enhanced-mode: fake-ip
fake-ip-filter:
- "*.mychery.com" # 内网域名不走 fake-ip,返回真实 IP
- "gitlab.mychery.com"
nameserver-policy:
"*.mychery.com": [10.1.4.141, 10.4.1.142] # 内网域名专用内网 DNS
nameserver:
- 10.1.4.141
- 10.1.4.142
- 114.114.114.114
- 8.8.8.8

fake-ip-filter 的作用:让 mychery.com 返回真实 IP 而不是假 IP,这样 DIRECT 规则才能拿到内网 IP 直连。

nameserver-policy 的作用:mychery.com 的 DNS 查询专门走内网 DNS,保证解析到内网 IP。

规则里还要加

rules:
- DOMAIN-SUFFIX,mychery.com,DIRECT # 内网域名直连
...

三套组合拳打下来,GitLab 终于能访问了。


等等,fake-ip 到底是什么

写到这我突然意识到,很多人可能不知道 fake-ip 是啥。

简单说:TUN 收到的是 IP 数据包,但规则按域名写,需要还原「IP → 域名」的映射。

传统 DNS:域名 → 真实 IP fake-ip:域名 → 分配假 IP(198.18.0.x) + 记录映射表 → 收到包时查表还原域名 → 匹配规则

所以 DNS 配置只在 TUN 开启时有意义,TUN 关闭时系统走 systemd-resolved,Mihomo 的 DNS 配置完全不参与。

dns-hijack: any:53 是用来在内核层面拦截 DNS 查询的。Mihomo 自身监听哪个端口无所谓,我配的是 1053,这样 TUN 关闭时不会占用 53 端口干扰 systemd-resolved。


我的最终 mixin.yaml

external-controller: "0.0.0.0:9090"
external-ui: public
secret:
allow-lan: false
authentication:
tun:
enable: true
stack: system
auto-route: true
auto-redir: true
auto-redirect: true
auto-detect-interface: true
dns-hijack:
- any:53
- tcp://any:53
strict-route: true
dns:
enable: true
enhanced-mode: fake-ip
fake-ip-filter:
- "*.mychery.com"
- "gitlab.mychery.com"
nameserver-policy:
"*.mychery.com": [10.1.4.141, 10.1.4.142]
nameserver:
- 10.1.4.141
- 10.1.4.142
- 114.114.114.114
- 8.8.8.8

这个配置在我电脑上能跑,但不保证在你那也能跑。


我现在还在困惑的事

  1. strict-route 到底开不开? 有人说开了能解决某些泄漏问题,但好像也会出问题,我还没搞懂。

  2. system stack 和 gvisor 哪个好? 我用的是 system,但 gvisor 据说更隔离。没测试过性能差异。

  3. 为什么有时候重启服务后要等 30 秒网络才通? 像是有个缓存没清,但不知道在哪。


如果你要折腾,记得这些命令

Terminal window
# 重新合并配置并重启
mihomo-restart
# 查看实时日志(排查流量命中规则)
sudo journalctl -u mihomo -f
# 查看公司 DNS
resolvectl status | grep "DNS Server"
# 测试域名解析
nslookup gitlab.mychery.com
# 查看路由
ip route get 115.120.50.150

结语

写完这些,我感觉像是把一个混乱的战场整理成了地图。但地图永远不是战场本身。

如果你也遇到「TUN 开启后内网服务挂了」的问题,希望这篇能帮你少走弯路。如果还是没解决,欢迎来骂我(但我不保证能帮你修好)。

一年后我回头看这篇,可能会觉得「怎么连这个都没搞懂」,但那也是好事,说明我又进步了。

你现在卡在哪个坑? 还是已经顺利上岸了?