Mihomo 是一个开源的代理工具,其主要功能是帮助用户在不同的网络环境中实现流量转发和分流管理,支持 Windows、macOS、Linux 等
本文将基于 Debian12 实践,若系统有出入,请自行查阅相关文档
本文实践的网络结构参考如下
小型网关服务器 A
:
- IP:192.168.10.99
- 网关:192.168.10.1
- 网卡名称:enp1s0
用于验证分流效果的 Windows 10 电脑客户端 B
:
- IP:192.168.10.50
- 网关:192.168.10.99
实现目标:
- 局域网
192.168.10.0/24
内其他设备将网关和 dns 指向服务器A
,可以获得 fake-ip dns 伪装和流量分流 - 服务器
A
借助iptables
和ip route
实现针对指定设备进行流量分流和dns
伪装 - 在 mihomo 服务关闭后,不影响其服务的局域网内设备,这对于多人的家庭网络非常重要
1. 网关
实现一个普通的网关,用于代理日常国内环境的上网需求
网关由 DNS 查询和流量转发组成,在 Debian12 实现这两点非常简单
1.1. 流量转发
服务器 A
配置内核转发:
# 编辑 /etc/sysctl.conf
net.ipv4.ip_forward=1
让内核转发的配置生效:
sysctl -p
添加 iptables 规则对出口的数据包进行为伪装,enp1s0
是网络出口:
iptables -t nat -A POSTROUTING -o enp1s0 -j MASQUERADE
确保开机时自动执行数据包伪装:
crontab -e
# 添加开机执行数据包伪装
@reboot /usr/sbin/iptables -t nat -A POSTROUTING -o enp1s0 -j MASQUERADE
1.2. DNS 服务
为服务器 A
添加 dns 服务,安装 dnsmasq
作为 DNS 转发器和 DHCP 服务器:
sudo apt update
sudo apt install dnsmasq
修改 dnsmasq 的配置如下:
# 编辑 /etc/dnsmasq.conf
server=114.114.114.114
server=223.5.5.5
启动 dns 服务
sudo systemctl enable dnsmasq --now
1.3. 测试
从客户端 B
上验证 dns 请求正常
>nslookup baidu.com
服务器: gateway
Address: 192.168.10.99
非权威应答:
名称: baidu.com
Addresses: 39.156.66.10
110.242.68.66
使用客户端 B
访问国内网站,如果成功则说明网关服务器 A
正常工作,A
已经具备作为网关服务的能力
这样就完成了一个普通的网关服务,接下来实现 Mihomo 旁路分流,在日常使用时,关闭 Mihomo 也不会影响正常的网关服务
2. Mihomo
对于旁路由来说,有两种常见的模式:
tproxy
适合复杂网络环境,需要高级流量管理和UDP
支持的场景,配置复杂容错低redirect
适合简单的透明代理设置,不需要处理UDP
流量,适用于基础网络环境,配置简单容错高
特性 | Redirect 模式 | TProxy 模式 |
---|---|---|
工作原理 | 使用 iptables 的 REDIRECT 改变数据包目标地址和端口 | 使用 iptables 的 TPROXY 保留数据包的原始目标信息 |
目标地址保留 | 否 | 是 |
支持的协议 | 主要支持 TCP,UDP 无法原始路由回包 | 支持 TCP 和 UDP |
透明性 | 目标地址被修改,代理不可见 | 保留原始目标地址,代理透明 |
复杂流量管理 | 限制较多,不易实现复杂流量控制 | 支持复杂流量管理,适合高级路由策略 |
应用场景 | 简单的 HTTP/HTTPS 透明代理 | 需要保留目标信息的高级代理应用,支持复杂网络环境 |
配置难度 | 较低,适合简单应用 | 较高,需要更复杂的配置 |
性能 | 一般 | 可能更高,但取决于具体实现和网络环境 |
根据需要选择对应的模式即可,下面会实践两种模式
redirect 采用直接安装配置,tproxy 模式采用 docker 运行,反过来也是可行的
2.1. redirect
2.1.1. 安装
安装 Mihomo 非常简单,从官方下载最新版即可:
mkdir /opt/mihomo && cd /opt/mihomo
wget https://github.com/MetaCubeX/mihomo/releases/download/v1.19.10/mihomo-linux-amd64-v1.19.10.gz
gzip -d mihomo-linux-amd64-v1.19.10.gz
创建配置文件夹:
mkdir -p /opt/mihomo/config
编辑 /opt/mihomo/config/config.yaml
文件:
allow-lan: true
mixed-port: 7890
tproxy-port: 789
# Web UI 配置
external-controller: 0.0.0.0:9090
external-ui: ui
external-ui-url: https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip
# 代理节点
proxies:
- {
name: example,
type: ss,
server: 1.1.1.1,
port: 8080,
cipher: chacha20-ietf-poly1305,
password: password,
udp: true,
}
# 订阅节点
proxy-providers:
your-subscribe:
type: http
url: https://example.com/your-subscribe.yaml
interval: 3600
path: ./subscribes/your-subscribe.yaml
# 代理组
proxy-groups:
- name: default
type: fallback
url: https://www.gstatic.com/generate_204
interval: 300
proxies:
- example
- name: specific
type: select
url: https://www.gstatic.com/generate_204
interval: 300
use:
- example
# DNS 配置
dns:
enable: true
ipv6: false # 禁用 IPv6 DNS 查询
listen: 0.0.0.0:5553
nameserver: [8.8.8.8, 1.1.1.1]
fallback: [223.5.5.5, 223.6.6.6]
fake-ip-range: 198.18.0.1/16 # fake-ip 模式下分配的虚拟 IP 段
enhanced-mode: fake-ip # 启用 fake-ip 模式,防止 DNS 污染和透明代理
# 按域名分流 DNS 查询
nameserver-policy:
# 命中以下规则集的域名,DNS 查询会走国内 DNS(请添加当地 dns 服务商获得 cdn 加速)
RULE-SET:direct,apple,icloud,applications: [223.5.5.5, 114.114.114.114]
fake-ip-filter:
# 直连域名列表(解决 Windows 10 连接后显示无网络图标的问题)
- +.msftconnecttest.com
- +.msftncsi.com
# 规则提供者
# https://github.com/Loyalsoldier/clash-rules
rule-providers:
# 直连域名列表
direct:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/direct.txt
path: ./ruleset/direct.yaml
interval: 86400
# 代理域名列表
proxy:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/proxy.txt
path: ./ruleset/proxy.yaml
interval: 86400
# 广告域名列表
reject:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/reject.txt
path: ./ruleset/reject.yaml
interval: 86400
# 苹果域名列表
apple:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/apple.txt
path: ./ruleset/apple.yaml
interval: 86400
# iCloud 域名列表
icloud:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/icloud.txt
path: ./ruleset/icloud.yaml
interval: 86400
# GFW 域名列表
gfw:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/gfw.txt
path: ./ruleset/gfw.yaml
interval: 86400
# 非中国大陆使用的顶级域名列表
tld-not-cn:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/tld-not-cn.txt
path: ./ruleset/tld-not-cn.yaml
interval: 86400
# Telegram 使用的 IP 地址列表
telegramcidr:
type: http
behavior: ipcidr
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/telegramcidr.txt
path: ./ruleset/telegramcidr.yaml
interval: 86400
# 局域网 IP 地址列表
lancidr:
type: http
behavior: ipcidr
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/lancidr.txt
path: ./ruleset/lancidr.yaml
interval: 86400
# 中国大陆 IP 地址列表
cncidr:
type: http
behavior: ipcidr
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/cncidr.txt
path: ./ruleset/cncidr.yaml
interval: 86400
# 需要直连的常见软件列表
applications:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/applications.txt
path: ./ruleset/applications.yaml
interval: 86400
# 规则
rules:
# 关键域名优先分流,写法参考如下:
# DOMAIN-SUFFIX,example.com:匹配所有以 example.com 结尾的域名(如 www.example.com、a.b.example.com)
# DOMAIN,api.example.com:只匹配 api.example.com 这个域名
# DOMAIN-KEYWORD,abc:匹配域名中包含 abc 的所有域名
# IP-CIDR,1.2.3.0/24:匹配 IP 段
# SRC-IP-CIDR,192.168.1.0/24:匹配源地址(较少用)
- RULE-SET,cncidr,DIRECT
- RULE-SET,apple,DIRECT
- RULE-SET,lancidr,DIRECT
- RULE-SET,applications,DIRECT
- RULE-SET,gfw,default
- RULE-SET,proxy,default
- RULE-SET,telegramcidr,default
- RULE-SET,tld-not-cn,default
- RULE-SET,direct,DIRECT
- RULE-SET,icloud,DIRECT
- RULE-SET,reject,REJECT
# 漏网之鱼
- MATCH,DIRECT
mihomo 配置解析:
allow-lan
必须为 true 才会监听0.0.0.0
,且网络模式设置为host
可以减少很多内核转发的意外情况https://www.gstatic.com/generate_204
是一个用于检测外网互联网连接状态的 URL- 对
ipv6
没有需求请禁用ipv6
的 dns 解析,否则需要额外做ipv6
的分流适配 fake-ip-filter
用于解决 Windows 系统的网络图标无网络问题- 直连域名列表如果没有自己的代理域名,则考虑使用规则提供者仓库里的 cdn 链接
- rules 用于控制不同的分流策略,请根据个人需求调整策略
启动 Mihomo:
chmod +x mihomo-linux-amd64-v1.19.10
./mihomo-linux-amd64-v1.19.10 -c /opt/mihomo/config/config.yaml
这样就完成了 Mihomo 的安装和配置,接下来需要配置 iptables 规则来实现流量分流
2.1.2. 分流
在 Mihomo 启动后,执行下面的 iptables 操作将流量劫持到 Mihomo 中处理
# 劫持 53 端口到 mihomo 的 5553 端口实现 DNS 伪装
iptables -t nat -A PREROUTING -p tcp -s 182.168.10.0/24 --dport 53 -j REDIRECT --to-port 5553
iptables -t nat -A PREROUTING -p udp -s 182.168.10.0/24 --dport 53 -j REDIRECT --to-port 5553
# 放行 SSH 流量(22 端口)
iptables -t nat -A PREROUTING -p tcp --dport 22 -j RETURN
# 放行 67 68 udp 流量(DHCP 流量)
iptables -t nat -A PREROUTING -p udp -s 182.168.10.0/24 --dport 67 -j RETURN
iptables -t nat -A PREROUTING -p udp -s 182.168.10.0/24 --dport 68 -j RETURN
# 劫持所有剩余流量到 mihomo 里
iptables -t nat -A PREROUTING -p tcp -s 182.168.10.0/24 -j REDIRECT --to-port 7893
iptables -t nat -A PREROUTING -p udp -s 182.168.10.0/24 -j REDIRECT --to-port 7893
执行后,观察 Mihomo 的日志输出,应该会看到有流量进入,此时证明 Mihomo 已经成功接收到这部分流量并处理
或者观察 iptables 的 nat 表计数:
iptables -L -nv -t nat
2.1.3. 运行
到上一步为止,已经成功实现了 Mihomo 分流效果,但如果关闭 Mihomo 还需要手动删除 iptables 规则,才可以保证网关服务正常运行
方便起见,实现当 mihomo 关闭时自动删除 iptables 规则,启动时,自动添加 iptables 规则
借助 systemd 来实现这个效果,创建文件 /etc/systemd/system/mihomo.service
,内容如下:
[Unit]
Description=Mihomo Transparent Proxy Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/mihomo
ExecStartPre=/opt/mihomo/clash_redirect.sh start
ExecStart=/opt/mihomo/mihomo-linux-amd64-v1.19.10 -d /opt/mihomo/config
ExecStopPost=/opt/mihomo/clash_redirect.sh stop
Restart=on-failure
[Install]
WantedBy=multi-user.target
创建 /opt/mihomo/clash_redirect.sh
脚本,内容如下:
#!/bin/bash
# 用法: bash clash_redirect.sh start|stop|restart
set -euo pipefail
IPTABLES=/usr/sbin/iptables
add_rules() {
# 先放 DNS 重定向(5553),插到最前面
$IPTABLES -t nat -C PREROUTING -p tcp --dport 53 -j REDIRECT --to-port 5553 2>/dev/null || \
$IPTABLES -t nat -I PREROUTING 1 -p tcp --dport 53 -j REDIRECT --to-port 5553
$IPTABLES -t nat -C PREROUTING -p udp --dport 53 -j REDIRECT --to-port 5553 2>/dev/null || \
$IPTABLES -t nat -I PREROUTING 2 -p udp --dport 53 -j REDIRECT --to-port 5553
# 放行 SSH 端口流量
$IPTABLES -t nat -C PREROUTING -p tcp --dport 22 -j RETURN 2>/dev/null || \
$IPTABLES -t nat -I PREROUTING 3 -p tcp --dport 22 -j RETURN
# 放行 udp 67 68 端口流量(DHCP)
$IPTABLES -t nat -C PREROUTING -p udp --dport 67 -j RETURN 2>/dev/null || \
$IPTABLES -t nat -I PREROUTING 4 -p udp --dport 67 -j RETURN
$IPTABLES -t nat -C PREROUTING -p udp --dport 68 -j RETURN 2>/dev/null || \
$IPTABLES -t nat -I PREROUTING 5 -p udp --dport 68 -j RETURN
# 其它流量重定向到 7893
$IPTABLES -t nat -C PREROUTING -p tcp -j REDIRECT --to-port 7893 2>/dev/null || \
$IPTABLES -t nat -I PREROUTING 6 -p tcp -j REDIRECT --to-port 7893
$IPTABLES -t nat -C PREROUTING -p udp -j REDIRECT --to-port 7893 2>/dev/null || \
$IPTABLES -t nat -I PREROUTING 7 -p udp -j REDIRECT --to-port 7893
}
del_rules() {
$IPTABLES -t nat -D PREROUTING -p tcp --dport 53 -j REDIRECT --to-port 5553 2>/dev/null || true
$IPTABLES -t nat -D PREROUTING -p udp --dport 53 -j REDIRECT --to-port 5553 2>/dev/null || true
$IPTABLES -t nat -D PREROUTING -p tcp --dport 22 -j RETURN 2>/dev/null || true
$IPTABLES -t nat -D PREROUTING -p udp --dport 67 -j RETURN 2>/dev/null || true
$IPTABLES -t nat -D PREROUTING -p udp --dport 68 -j RETURN 2>/dev/null || true
$IPTABLES -t nat -D PREROUTING -p tcp -j REDIRECT --to-port 7893 2>/dev/null || true
$IPTABLES -t nat -D PREROUTING -p udp -j REDIRECT --to-port 7893 2>/dev/null || true
}
case "${1:-}" in
start)
add_rules
;;
stop)
del_rules
;;
restart)
del_rules
add_rules
;;
*)
echo "用法: $0 start|stop|restart"
exit 1
;;
esac
先删掉之前的 iptables 规则
# 劫持 53 端口到 mihomo 的 5553 端口
iptables -t nat -D PREROUTING -p tcp -s 182.168.10.0/24 --dport 53 -j REDIRECT --to-port 5553
iptables -t nat -D PREROUTING -p udp -s 182.168.10.0/24 --dport 53 -j REDIRECT --to-port 5553
# 放行 SSH 流量(22 端口)
iptables -t nat -D PREROUTING -p tcp --dport 22 -j RETURN
# 放行 67 68 udp 流量(DHCP 流量)
iptables -t nat -D PREROUTING -p udp -s 182.168.10.0/24 --dport 67 -j RETURN
iptables -t nat -D PREROUTING -p udp -s 182.168.10.0/24 --dport 68 -j RETURN
# 劫持所有剩余流量到 mihomo 里
iptables -t nat -D PREROUTING -p tcp -s 182.168.10.0/24 -j REDIRECT --to-port 7893
iptables -t nat -D PREROUTING -p udp -s 182.168.10.0/24 -j REDIRECT --to-port 7893
查看规则,此时应该都清除干净:
iptables -L -nv -t nat
启动 mihomo 服务:
systemctl daemon-reload
systemctl enable mihomo --now
观察 Mihomo 的日志输出,应该会看到有流量进入,此时证明 Mihomo 已经成功接收到这部分流量并处理
或者观察 iptables 的 nat 表计数:
iptables -L -nv -t nat
如果需要停止 Mihomo 服务,可以执行:
systemctl stop mihomo
到这里,实现了 Mihomo 的旁路分流,局域网内其他设备可以通过服务器 A
的 IP 地址和端口访问 Mihomo 提供的服务
当关闭 Mihomo 时,局域网内设备仍然可以通过服务器 A
的 DNS 服务正常访问互联网,而不受 Mihomo 的影响
2.2. tproxy
2.2.1. docker
mihomo 上一节演示了直接安装,这里采用 Docker
来运行 mihomo 容器,debian12 安装 docker 如下:
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
2.2.2. mihomo 容器
安装完成后,创建用于运行 mihomo 的 docker
目录
mkdir -p /docker/mihomo/config
编辑 /docker/mihomo/compose.yaml
如下:
services:
mihomo:
image: metacubex/mihomo:latest # 或其他官方/支持的镜像
container_name: mihomo
restart: no
volumes:
- ./config:/root/.config/mihomo # 配置文件目录
network_mode: host
environment:
- TZ=Asia/Shanghai # 设置时区
# 如果需要访问主机网络,可以添加host模式
# network_mode: "host" # 注意:如果使用host模式,上面的macvlan网络将不会生效
编辑 /docker/mihomo/config/config.yaml
文件:
allow-lan: true
mixed-port: 7890
tproxy-port: 789
# Web UI 配置
external-controller: 0.0.0.0:9090
external-ui: ui
external-ui-url: https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip
# 代理节点
proxies:
- {
name: example,
type: ss,
server: 1.1.1.1,
port: 8080,
cipher: chacha20-ietf-poly1305,
password: password,
udp: true,
}
# 订阅节点
proxy-providers:
your-subscribe:
type: http
url: https://example.com/your-subscribe.yaml
interval: 3600
path: ./subscribes/your-subscribe.yaml
# 代理组
proxy-groups:
- name: default
type: fallback
url: https://www.gstatic.com/generate_204
interval: 300
proxies:
- example
- name: specific
type: select
url: https://www.gstatic.com/generate_204
interval: 300
use:
- example
# DNS 配置
dns:
enable: true
ipv6: false # 禁用 IPv6 DNS 查询
listen: 0.0.0.0:5553
nameserver: [8.8.8.8, 1.1.1.1]
fallback: [223.5.5.5, 223.6.6.6]
fake-ip-range: 198.18.0.1/16 # fake-ip 模式下分配的虚拟 IP 段
enhanced-mode: fake-ip # 启用 fake-ip 模式,防止 DNS 污染和透明代理
# 按域名分流 DNS 查询
nameserver-policy:
# 命中以下规则集的域名,DNS 查询会走国内 DNS(请添加当地 dns 服务商获得 cdn 加速)
RULE-SET:direct,apple,icloud,applications: [223.5.5.5, 114.114.114.114]
fake-ip-filter:
# 直连域名列表(解决 Windows 10 连接后显示无网络图标的问题)
- +.msftconnecttest.com
- +.msftncsi.com
# 规则提供者
# https://github.com/Loyalsoldier/clash-rules
rule-providers:
# 直连域名列表
direct:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/direct.txt
path: ./ruleset/direct.yaml
interval: 86400
# 代理域名列表
proxy:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/proxy.txt
path: ./ruleset/proxy.yaml
interval: 86400
# 广告域名列表
reject:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/reject.txt
path: ./ruleset/reject.yaml
interval: 86400
# 苹果域名列表
apple:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/apple.txt
path: ./ruleset/apple.yaml
interval: 86400
# iCloud 域名列表
icloud:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/icloud.txt
path: ./ruleset/icloud.yaml
interval: 86400
# GFW 域名列表
gfw:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/gfw.txt
path: ./ruleset/gfw.yaml
interval: 86400
# 非中国大陆使用的顶级域名列表
tld-not-cn:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/tld-not-cn.txt
path: ./ruleset/tld-not-cn.yaml
interval: 86400
# Telegram 使用的 IP 地址列表
telegramcidr:
type: http
behavior: ipcidr
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/telegramcidr.txt
path: ./ruleset/telegramcidr.yaml
interval: 86400
# 局域网 IP 地址列表
lancidr:
type: http
behavior: ipcidr
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/lancidr.txt
path: ./ruleset/lancidr.yaml
interval: 86400
# 中国大陆 IP 地址列表
cncidr:
type: http
behavior: ipcidr
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/cncidr.txt
path: ./ruleset/cncidr.yaml
interval: 86400
# 需要直连的常见软件列表
applications:
type: http
behavior: domain
url: https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/applications.txt
path: ./ruleset/applications.yaml
interval: 86400
# 规则
rules:
# 关键域名优先分流,写法参考如下:
# DOMAIN-SUFFIX,example.com:匹配所有以 example.com 结尾的域名(如 www.example.com、a.b.example.com)
# DOMAIN,api.example.com:只匹配 api.example.com 这个域名
# DOMAIN-KEYWORD,abc:匹配域名中包含 abc 的所有域名
# IP-CIDR,1.2.3.0/24:匹配 IP 段
# SRC-IP-CIDR,192.168.1.0/24:匹配源地址(较少用)
- RULE-SET,cncidr,DIRECT
- RULE-SET,apple,DIRECT
- RULE-SET,lancidr,DIRECT
- RULE-SET,applications,DIRECT
- RULE-SET,gfw,default
- RULE-SET,proxy,default
- RULE-SET,telegramcidr,default
- RULE-SET,tld-not-cn,default
- RULE-SET,direct,DIRECT
- RULE-SET,icloud,DIRECT
- RULE-SET,reject,REJECT
# 漏网之鱼
- MATCH,DIRECT
值得一提的是,dns 端口设置为 5553
,因为宿主机已经使用了 dnsmasq
占用了正常的 53
端口
在 mihomo 启动时,由 iptables 劫持 53
端口流量到 5553
,在 mihomo 关闭后,53
端口由原来的 dnsmasq
处理 dns 请求
这样实现保证了 mihomo 下线时,这台网关服务依旧能稳定提供国内线路的服务
使用 docker compose up
唤起 mihomo 服务,并检查其日志输出是否有问题
2.2.3. 分流
在 mihomo 唤起后,执行下面的 iptables 操作将 192.168.10.50/32 的 ip 全部定向导入到 mihomo 中处理
# 1. 新建 chain
iptables -t mangle -N CLASH
# 2. 设备 B 的流量进 CLASH chain 处理
iptables -t mangle -A PREROUTING -s 192.168.10.50 -p tcp -j CLASH
iptables -t mangle -A PREROUTING -s 192.168.10.50 -p udp -j CLASH
# 3. TPROXY 到宿主机 7892(已映射容器)
iptables -t mangle -A CLASH -p tcp -j TPROXY --on-port 7892 --tproxy-mark 1
iptables -t mangle -A CLASH -p udp -j TPROXY --on-port 7892 --tproxy-mark 1
# 4. DNS 流量转发至 5553
iptables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-port 5553
iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-port 5553
# 5. 策略路由
ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
在客户端 B
上检测效果,使用 dig 解析结果
>nslookup baidu.com
服务器: UnKnown
Address: 192.168.10.99
名称: baidu.com
Address: 198.18.2.3
可以看到返回了一个虚假的 fake-ip,这说明 mihomo 已经成功接收到这部分流量并处理
mihomo 如果订阅和代理已经填写完毕,此时应该正常访问外网
2.2.4. 运行
到上一步为止,已经成功实现了 mihomo 分流效果,但如果关闭 mihomo 还需要手动删除 iptables 规则,才可以保证网关服务正常运行
方便起见,应该实现效果:
- mihomo 启动时,自动添加相关 iptables 和 iproute 规则
- mihomo 关闭时,自动删除相关 iptables 和 iproute 规则
为了实现这个效果,创建文件 /docker/mihomo/tproxy.sh
文件,内容如下:
#!/usr/bin/env bash
# ============================================================================
# Mihomo TProxy 管理脚本,支持日志、绝对路径变量、健壮性与最佳实践
# ============================================================================
set -euo pipefail
# 配置区
IPTABLES="${IPTABLES:-/sbin/iptables}" # iptables 命令绝对路径,可按需修改
IP="${IP:-/sbin/ip}" # ip 命令绝对路径,可按需修改
LOG_FILE="${LOG_FILE:-/var/log/mihomo-tproxy.log}"
TPROXY_PORT=7892
TPROXY_MARK=1
CLASH_CHAIN="CLASH"
ROUTE_TABLE=100
# 从同级目录读取 proxy_devices.txt,忽略注释和空行
PROXY_DEVICES=()
while IFS= read -r line; do
ip=$(echo "$line" | sed 's/#.*//;s/^[ \t]*//;s/[ \t]*$//')
[[ -z "$ip" ]] && continue
PROXY_DEVICES+=("$ip")
done < "$(dirname "$0")/proxy_devices.txt"
log() {
local level="$1"
shift
local msg="$*"
local now
now=$(date "+%Y-%m-%d %H:%M:%S")
echo "[$now][$level] $msg" | tee -a "$LOG_FILE"
}
check_root() {
if [[ $EUID -ne 0 ]]; then
log "ERROR" "必须以 root 权限运行本脚本"
exit 1
fi
}
check_cmds() {
if [[ ! -x "$IPTABLES" ]]; then
log "ERROR" "找不到 iptables 命令: $IPTABLES"
exit 1
fi
if [[ ! -x "$IP" ]]; then
log "ERROR" "找不到 ip 命令: $IP"
exit 1
fi
}
add_rules() {
log "INFO" "开始添加 TPROXY 透明代理规则"
# 检查并添加全局 DNS 劫持策略
for proto in udp tcp; do
if ! $IPTABLES -t nat -C PREROUTING -p $proto --dport 53 -j REDIRECT --to-port 5553 2>/dev/null; then
$IPTABLES -t nat -A PREROUTING -p $proto --dport 53 -j REDIRECT --to-port 5553
log "INFO" "添加全局 DNS 劫持: $proto 53 -> 5553"
else
log "INFO" "全局 DNS 劫持已存在: $proto 53 -> 5553"
fi
done
# 1. 新建 CLASH 链(已存在则跳过)
if ! $IPTABLES -t mangle -L "$CLASH_CHAIN" &>/dev/null; then
$IPTABLES -t mangle -N "$CLASH_CHAIN"
log "INFO" "创建链 $CLASH_CHAIN"
else
log "INFO" "链 $CLASH_CHAIN 已存在"
fi
# 2. 设备流量进 chain(若已存在则跳过)
for ip in "${PROXY_DEVICES[@]}"; do
for proto in tcp udp; do
if ! $IPTABLES -t mangle -C PREROUTING -s "$ip" -p "$proto" -j "$CLASH_CHAIN" 2>/dev/null; then
$IPTABLES -t mangle -A PREROUTING -s "$ip" -p "$proto" -j "$CLASH_CHAIN"
log "INFO" "添加 PREROUTING -s $ip -p $proto 到 $CLASH_CHAIN"
else
log "INFO" "PREROUTING -s $ip -p $proto 已存在,跳过"
fi
done
done
# 3. CLASH 链 TPROXY 规则(若已存在则跳过)
for proto in tcp udp; do
if ! $IPTABLES -t mangle -C "$CLASH_CHAIN" -p "$proto" -j TPROXY --on-port "$TPROXY_PORT" --tproxy-mark "$TPROXY_MARK" 2>/dev/null; then
$IPTABLES -t mangle -A "$CLASH_CHAIN" -p "$proto" -j TPROXY --on-port "$TPROXY_PORT" --tproxy-mark "$TPROXY_MARK"
log "INFO" "$CLASH_CHAIN 链添加 $proto TPROXY 规则"
else
log "INFO" "$CLASH_CHAIN 链 $proto TPROXY 规则已存在"
fi
done
# 4. 策略路由(避免重复和退出,遇到已存在时不报错)
if ! $IP rule list | grep -q "fwmark $TPROXY_MARK .*lookup $ROUTE_TABLE"; then
if $IP rule add fwmark "$TPROXY_MARK" lookup "$ROUTE_TABLE" 2>/dev/null; then
log "INFO" "添加策略路由 fwmark $TPROXY_MARK -> table $ROUTE_TABLE"
else
log "WARN" "策略路由已存在或添加失败,但不影响继续"
fi
else
log "INFO" "策略路由已存在 fwmark $TPROXY_MARK -> table $ROUTE_TABLE"
fi
if ! $IP route show table "$ROUTE_TABLE" | grep -q "^local 0.0.0.0/0 dev lo"; then
if $IP route add local 0.0.0.0/0 dev lo table "$ROUTE_TABLE" 2>/dev/null; then
log "INFO" "添加本地路由 local 0.0.0.0/0 dev lo table $ROUTE_TABLE"
else
log "WARN" "本地路由已存在或添加失败,但不影响继续"
fi
else
log "INFO" "本地路由已存在 table $ROUTE_TABLE"
fi
log "SUCCESS" "TPROXY 透明代理规则已添加"
}
del_rules() {
log "INFO" "开始清理 TPROXY 透明代理规则"
# 检查并删除全局 DNS 劫持策略
for proto in udp tcp; do
while $IPTABLES -t nat -C PREROUTING -p $proto --dport 53 -j REDIRECT --to-port 5553 2>/dev/null; do
$IPTABLES -t nat -D PREROUTING -p $proto --dport 53 -j REDIRECT --to-port 5553
log "INFO" "删除全局 DNS 劫持: $proto 53 -> 5553"
done
done
# 删除 PREROUTING 规则(全部删除,不留冗余)
for ip in "${PROXY_DEVICES[@]}"; do
for proto in tcp udp; do
while $IPTABLES -t mangle -C PREROUTING -s "$ip" -p "$proto" -j "$CLASH_CHAIN" 2>/dev/null; do
$IPTABLES -t mangle -D PREROUTING -s "$ip" -p "$proto" -j "$CLASH_CHAIN"
log "INFO" "删除 PREROUTING -s $ip -p $proto 到 $CLASH_CHAIN"
done
done
done
# 删除 CLASH 链 TPROXY 规则
for proto in tcp udp; do
while $IPTABLES -t mangle -C "$CLASH_CHAIN" -p "$proto" -j TPROXY --on-port "$TPROXY_PORT" --tproxy-mark "$TPROXY_MARK" 2>/dev/null; do
$IPTABLES -t mangle -D "$CLASH_CHAIN" -p "$proto" -j TPROXY --on-port "$TPROXY_PORT" --tproxy-mark "$TPROXY_MARK"
log "INFO" "删除 $CLASH_CHAIN 链 $proto TPROXY 规则"
done
done
# 清空并删除 CLASH 链
if $IPTABLES -t mangle -L "$CLASH_CHAIN" &>/dev/null; then
$IPTABLES -t mangle -F "$CLASH_CHAIN"
$IPTABLES -t mangle -X "$CLASH_CHAIN"
log "INFO" "清空并删除链 $CLASH_CHAIN"
fi
# 删除策略路由
while $IP rule list | grep -q "fwmark $TPROXY_MARK .*lookup $ROUTE_TABLE"; do
$IP rule del fwmark "$TPROXY_MARK" lookup "$ROUTE_TABLE"
log "INFO" "删除策略路由 fwmark $TPROXY_MARK -> table $ROUTE_TABLE"
done
# 删除本地路由
while $IP route show table "$ROUTE_TABLE" | grep -q "^local 0.0.0.0/0 dev lo"; do
$IP route del local 0.0.0.0/0 dev lo table "$ROUTE_TABLE"
log "INFO" "删除本地路由 table $ROUTE_TABLE"
done
log "SUCCESS" "TPROXY 透明代理规则已清除"
}
show_help() {
echo "用法: $0 {start|stop}"
}
main() {
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE" 2>/dev/null || LOG_FILE="/dev/stdout"
check_root
check_cmds
case "${1:-}" in
start)
add_rules
;;
stop)
del_rules
;;
*)
show_help
exit 1
;;
esac
}
if [[ $# -eq 0 ]]; then
show_help
exit 1
fi
main "$@"
这个脚本实现:
- 执行
tproxy.sh start
自动添加相关 iptables 和 iproute 规则 - 执行
tproxy stop
自动删除相关 iptables 和 iproute 规则
这个脚本会读取当前目录下的 proxy_devices.txt
作为设备列表
该文件里应记录需要分流的设备:
192.168.10.50 # Windows B
192.168.10.51 # Windows C
...
验证这个脚本效果,先删除之前添加的 iptables 规则:
# 删除 CLASH chain
iptables -t mangle -F CLASH
iptables -t mangle -X CLASH
# 删除 PREROUTING 规则
iptables -t mangle -D PREROUTING -s 192.168.10.50 -p tcp -j CLASH
iptables -t mangle -D PREROUTING -s 192.168.10.50 -p udp -j CLASH
iptables -t nat -D PREROUTING -p tcp --dport 53 -j REDIRECT --to-port 5553
iptables -t nat -D PREROUTING -p udp --dport 53 -j REDIRECT --to-port 5553
# 删除策略路由规则
ip rule del fwmark 1 lookup 100
# 删除路由表中的条目
ip route del local 0.0.0.0/0 dev lo table 100
确保 proxy_devices.txt
文件格式正确后执行脚本
$ bash /docker/mihomo/tproxy.sh start
检查 mangle
链的规则:
$ iptables -L -nv -t mangle
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
82489 12M CLASH 6 -- * * 192.168.10.50 0.0.0.0/0
7158 6896K CLASH 17 -- * * 192.168.10.50 0.0.0.0/0
2846 1007K CLASH 6 -- * * 192.168.10.51 0.0.0.0/0
155 24415 CLASH 17 -- * * 192.168.10.51 0.0.0.0/0
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain CLASH (4 references)
pkts bytes target prot opt in out source destination
620K 652M TPROXY 6 -- * * 0.0.0.0/0 0.0.0.0/0 TPROXY redirect 0.0.0.0:7892 mark 0x1/0xffffffff
9812 7289K TPROXY 17 -- * * 0.0.0.0/0 0.0.0.0/0 TPROXY redirect 0.0.0.0:7892 mark 0x1/0xffffffff
2.2.5. systemd
有上面的脚本后,可以在启动关闭 mihomo 时快捷地添加和删除规则,但仍需手动执行,我们需要将脚本和 mihomo 的开启关闭关联起来
借助 systemd 可以实现在启动 mihomo 时执行脚本添加规则,在关闭 mihomo 时删除规则
编辑 /etc/systemd/system/mihomo.service
如下:
[Unit]
Description=Mihomo docker-compose service
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/docker/mihomo
ExecStartPost=/docker/mihomo/tproxy.sh start
ExecStart=/usr/bin/docker compose up -d
ExecStopPost=/docker/mihomo/tproxy.sh stop
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
重点在于 ExecStartPost
和 ExecStopPost
,这将保证启动 mihomo 时执行脚本添加规则,在关闭 mihomo 时删除规则
关闭 mihomo 并删除相关规则后,加载这份配置
systemctl daemon-reload
启动 mihomo,然后检查规则:
$ systemctrl start mihomo
$ iptables -L -nv -t mangle
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
82489 12M CLASH 6 -- * * 192.168.10.50 0.0.0.0/0
7158 6896K CLASH 17 -- * * 192.168.10.50 0.0.0.0/0
2846 1007K CLASH 6 -- * * 192.168.10.51 0.0.0.0/0
155 24415 CLASH 17 -- * * 192.168.10.51 0.0.0.0/0
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain CLASH (4 references)
pkts bytes target prot opt in out source destination
620K 652M TPROXY 6 -- * * 0.0.0.0/0 0.0.0.0/0 TPROXY redirect 0.0.0.0:7892 mark 0x1/0xffffffff
9812 7289K TPROXY 17 -- * * 0.0.0.0/0 0.0.0.0/0 TPROXY redirect 0.0.0.0:7892 mark 0x1/0xffffffff
关闭 mihomo 然后检查规则
$ systemctrl stop mihomo
$ iptables -L -nv -t mangle
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain CLASH (0 references)
3. 结尾
相较于 openclash,mihomo 对于拥有网络基础的人群来说更好控制和自定义化
拆分分流规则给 iptables 和路由策略,以保证即使 mihomo 下线也不影响为局域网内提供国内线路的解析,提高了稳定性
3.1. Clash 配置说明
通常来说,Clash 的配置组成部分主要有:
- 顶层配置,如
allow-lan
、mixed-port
、tproxy-port
这些配置属于顶层配置,决定了服务器的监听端口和监听方式 proxies
/proxy-providers
通常要具备一个,一个是手动输入的单点代理,一个是订阅链接用于一次性导入多个配置proxy-groups
代理组配置,可设置主备、自动选择、手动切换等策略,合理分组,常用如select
、fallback
、url-test
,提升连接稳定性dns
控制 DNS 查询方式,支持 fake-ip、分流等,rule-providers
规则订阅,自动获取常用分流规则(如直连、代理、广告等)rules
具体分流规则,决定不同流量的处理方式,规则顺序很重要,优先级高的规则应放在前面,可根据实际需求自定义
一份简单的示例如下:
allow-lan: true
mixed-port: 7890
tproxy-port: 7892
external-controller: 0.0.0.0:9090
proxies:
- name: "节点1"
type: ss
server: 1.2.3.4
port: 8388
cipher: aes-128-gcm
password: "your_password"
udp: true
proxy-groups:
- name: "自动选择"
type: url-test
proxies:
- "节点1"
url: "http://www.gstatic.com/generate_204"
interval: 300
dns:
enable: true
listen: 0.0.0.0:53
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
nameserver:
- 223.5.5.5
- 114.114.114.114
fake-ip-filter:
- +.msftconnecttest.com
- +.msftncsi.com
rules:
- DOMAIN-SUFFIX,cn,DIRECT
- DOMAIN-SUFFIX,baidu.com,DIRECT
- DOMAIN-KEYWORD,google,自动选择
- DOMAIN-KEYWORD,youtube,自动选择
- GEOIP,CN,DIRECT
- MATCH,自动选择
rules 是日常修改比较多的部分,官方文档截取如下:
rules:
- DOMAIN,ad.com,REJECT
- DOMAIN-SUFFIX,google.com,auto
- DOMAIN-KEYWORD,google,auto
- DOMAIN-REGEX,^abc.*com,PROXY
- GEOSITE,youtube,PROXY
- IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
- IP-CIDR6,2620:0:2d0:200::7/32,auto
- IP-SUFFIX,8.8.8.8/24,PROXY
- IP-ASN,13335,DIRECT
- GEOIP,CN,DIRECT
- SRC-GEOIP,cn,DIRECT
- SRC-IP-ASN,9808,DIRECT
- SRC-IP-CIDR,192.168.1.201/32,DIRECT
- SRC-IP-SUFFIX,192.168.1.201/8,DIRECT
- DST-PORT,80,DIRECT
- SRC-PORT,7777,DIRECT
- IN-PORT,7890,PROXY
- IN-TYPE,SOCKS/HTTP,PROXY
- IN-USER,mihomo,PROXY
- IN-NAME,ss,PROXY
- PROCESS-PATH,/usr/bin/wget,PROXY
- PROCESS-PATH,C:\Program Files\Google\Chrome\Application\chrome.exe,PROXY
- PROCESS-PATH-REGEX,.*bin/wget,PROXY
- PROCESS-PATH-REGEX,(?i).*Application\\chrome.*,PROXY
- PROCESS-NAME,curl,PROXY
- PROCESS-NAME,chrome.exe,PROXY
- PROCESS-NAME,com.termux,PROXY
- PROCESS-NAME-REGEX,curl$,PROXY
- PROCESS-NAME-REGEX,(?i)Telegram,PROXY
- PROCESS-NAME-REGEX,.*telegram.*,PROXY
- UID,1001,DIRECT
- NETWORK,udp,DIRECT
- DSCP,4,DIRECT
- RULE-SET,providername,proxy
- AND,((DOMAIN,baidu.com),(NETWORK,UDP)),DIRECT
- OR,((NETWORK,UDP),(DOMAIN,baidu.com)),REJECT
- NOT,((DOMAIN,baidu.com)),PROXY
- SUB-RULE,(NETWORK,tcp),sub-rule
- MATCH,auto