返回

Mihomo旁路小型网络最佳实践

探讨在小型网络中实现Mihomo旁路的最佳实践方法
2025-05-29
29010 字 · 约 73 分钟阅读

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

实现目标:

  1. 局域网 192.168.10.0/24 内其他设备将网关和 dns 指向服务器 A,可以获得 fake-ip dns 伪装和流量分流
  2. 服务器 A 借助 iptablesip route 实现针对指定设备进行流量分流和 dns 伪装
  3. 在 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 模式
工作原理使用 iptablesREDIRECT 改变数据包目标地址和端口使用 iptablesTPROXY 保留数据包的原始目标信息
目标地址保留
支持的协议主要支持 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 配置解析:

  1. allow-lan 必须为 true 才会监听 0.0.0.0,且网络模式设置为 host 可以减少很多内核转发的意外情况
  2. https://www.gstatic.com/generate_204 是一个用于检测外网互联网连接状态的 URL
  3. ipv6 没有需求请禁用 ipv6 的 dns 解析,否则需要额外做 ipv6 的分流适配
  4. fake-ip-filter 用于解决 Windows 系统的网络图标无网络问题
  5. 直连域名列表如果没有自己的代理域名,则考虑使用规则提供者仓库里的 cdn 链接
  6. 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 规则,才可以保证网关服务正常运行

方便起见,应该实现效果:

  1. mihomo 启动时,自动添加相关 iptables 和 iproute 规则
  2. 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 "$@"

这个脚本实现:

  1. 执行 tproxy.sh start 自动添加相关 iptables 和 iproute 规则
  2. 执行 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

重点在于 ExecStartPostExecStopPost,这将保证启动 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-lanmixed-porttproxy-port 这些配置属于顶层配置,决定了服务器的监听端口和监听方式
  • proxies / proxy-providers 通常要具备一个,一个是手动输入的单点代理,一个是订阅链接用于一次性导入多个配置
  • proxy-groups 代理组配置,可设置主备、自动选择、手动切换等策略,合理分组,常用如 selectfallbackurl-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

3.2. 参考资料

留言

发表留言