2つのカーネルの現状と系譜

オリジナル Clash(Dreamacro/clash)は2023年末にアーカイブされ、最後の正式版は v1.18.0 で、以降のコミットはありません。同時期には非公開の Clash Premium カーネルも存在し、TUN、rule-provider、script の機能を提供していましたが、ソースは公開されておらず、オリジナル版とともに更新が停止しました。Clash.Meta はコミュニティがオリジナルカーネルをベースに書き直した派生版で、2024年に mihomo へ改名され、リポジトリは MetaCubeX/mihomo として現在も更新が続いています。

設定ファイルのレベルでは両者は上位互換です。port、socks-port、mixed-port、allow-lan、mode、log-level、external-controller、proxies、proxy-groups、rules といったフィールドはどちらも認識します。違いは4つの領域に集中しています。インバウンドプロトコル、ルール種別、DNS 解決経路、TUN 実装です。以下で順に比較し、移行時に書き換えるフィールドを挙げます。

機能 オリジナル Clash v1.18.0 mihomo
インバウンド方式 port、socks-port、redir-port の3つのトップレベルスイッチ トップレベルスイッチ + listeners セクションで複数のインバウンドを宣言
プロキシプロトコル ss、vmess、trojan、snell、socks5、http 上記すべて + vless、hysteria2、tuic、wireguard、ssh、anytls
ルール種別 ドメイン、IP、ポート、GEOIP、MATCH 上記すべて + GEOSITE、IP-ASN、正規表現、論理演算、SUB-RULE
ルールセット 非対応。ルールは設定ファイルに直接書くしかない rule-providers。mrs バイナリ形式に対応
DNS nameserver + fallback + fallback-filter nameserver-policy、DNS 分流、DoQ、fake-ip ホワイトリストモード
TUN 非対応 system / gvisor / mixed の3スタック
メンテナンス状況 2023年末にアーカイブ 継続的に更新

プロトコル対応:mihomo だけが認識する proxy type

オリジナル Clash の proxies セクションが認識する type は ss、vmess、trojan、snell、socks5、http の6つだけです。リストにない type があると、カーネルは解析段階で unsupported proxy type を出して終了し、そのノードを飛ばして動作を続けることはありません。カーネルを替えたときに最もよく遭遇するエラーの第一類です。

mihomo はこのリストに vless(XTLS Vision と REALITY を含む)、hysteria2、tuic v5、wireguard、ssh、anytls を追加し、Shadowsocks の暗号化方式も 2022 系(2022-blake3-aes-128-gcm、2022-blake3-aes-256-gcm、2022-blake3-chacha20-poly1305)まで拡張しました。以下の3ノードのフィールド構成は、オリジナルカーネルでは1つも解析できません。

proxies:
  - name: vless-vision
    type: vless
    server: edge.example.com
    port: 443
    uuid: 8f2c1d40-3a7e-4b91-9c2d-5e6f7a8b9c0d
    network: tcp
    tls: true
    udp: true
    flow: xtls-rprx-vision
    servername: www.example.com
    client-fingerprint: chrome
    reality-opts:
      public-key: uM7Kd2QpX1sVbN4tRzY8wLcE3aHfJgOiPqSvTnBm5kU
      short-id: 6ba85179e30d4fc2

  - name: hy2-edge
    type: hysteria2
    server: edge.example.com
    port: 8443
    password: 9f2c7d1a4b6e
    sni: www.example.com
    skip-cert-verify: false
    up: "30 Mbps"
    down: "200 Mbps"

  - name: tuic-edge
    type: tuic
    server: edge.example.com
    port: 10443
    uuid: 8f2c1d40-3a7e-4b91-9c2d-5e6f7a8b9c0d
    password: 9f2c7d1a4b6e
    congestion-controller: bbr
    udp-relay-mode: native
    alpn: [h3]

あるサブスクリプションをそのままオリジナルカーネルに渡せるかは、2点だけ見れば判断できます。proxies に現れる type と、ss ノードの cipher です。type が ss、vmess、trojan、snell の範囲内で、cipher も aes-128-gcm のような旧来の値であれば両方で動作します。reality-opts、congestion-controller、あるいは 2022-blake3- で始まる暗号化方式が現れた時点で、mihomo へ切り替える必要があります。

逆方向の移行にも落とし穴 mihomo のノード設定をオリジナルカーネルへ戻すと、client-fingerprint、reality-opts、packet-encoding はいずれも未知のフィールドになります。バージョンによって許容度が異なり、解析段階でエラー終了するものもあれば、無視してデフォルト値で接続するものもあります。後者の場合、ノードは表示されるのに遅延テストがすべてタイムアウトし、回線障害と誤判断しやすくなります。

ルール構文とマッチング順序

マッチングモデルは両者で同じです。rules を上から順に照合し、最初に一致した時点で停止、MATCH が最後の受け皿になります。違いは利用できる種別、マッチングのコスト、そしてルールセットの読み込み方式です。

ルール種別 オリジナル Clash mihomo 説明
DOMAIN / DOMAIN-SUFFIX / DOMAIN-KEYWORD 対応 対応 ドメインインデックスを使うため、1条あたりのコストは非常に低い
DOMAIN-REGEX / DOMAIN-WILDCARD 非対応 対応 1条ずつ正規表現で照合するため、先頭に置くと初回パケットが遅くなる
IP-CIDR / IP-CIDR6 / SRC-IP-CIDR 対応 対応 ドメイン接続で解決が発生するため、no-resolve の付与を推奨
GEOIP 対応 対応 GeoIP データファイルに依存
GEOSITE 非対応 対応 geosite データファイルに依存
IP-ASN / IP-SUFFIX 非対応 対応 ASN または IP サフィックス単位で振り分け
PROCESS-NAME 非公開の Premium のみ 対応 デスクトップではプロセス名で振り分け
PROCESS-PATH / PROCESS-NAME-REGEX 非対応 対応 実行ファイルのパスでマッチング
RULE-SET 非公開の Premium のみ 対応 format: mrs を指定可能
SUB-RULE / AND / OR / NOT 非対応 対応 複数条件の組み合わせ。括弧とカンマに注意
IN-TYPE / IN-USER / IN-PORT / NETWORK 非対応 対応 インバウンド元とトランスポート層プロトコルでマッチング
MATCH 対応 対応 必ず最後の1条に置く

ルールセットの違いはルール種別以上に見落とされやすい部分です。オリジナル Clash は rule-providers に対応しておらず、ルールは config.yaml に直接書くしかありません。mihomo の rule-providers は behavior に domain、ipcidr、classical、format に yaml、text、mrs を指定でき、mrs はバイナリ形式のため domain か ipcidr としか組み合わせられません。mrs は読み込み時にドメイン接頭辞でインデックスを構築するため、YAML 全体をオブジェクトへ展開する必要がなく、ルールが1万条を超えるあたりで差が最も顕著になります。

rule-providers:
  reject-list:
    type: http
    behavior: domain
    format: mrs
    url: "https://rules.example.com/reject.mrs"
    path: ./ruleset/reject.mrs
    interval: 86400

rules:
  - DOMAIN-SUFFIX,example.org,ダイレクト
  - GEOSITE,category-ads-all,REJECT
  - RULE-SET,reject-list,REJECT
  - IP-CIDR,198.18.0.0/16,ダイレクト,no-resolve
  - AND,((NETWORK,udp),(DST-PORT,443)),ノード選択
  - MATCH,ノード選択

rules セクションを書くとき、順序でつまずきやすい点が3つあります。

  1. 具体的なドメインは GEOSITE より前に置きます。GEOSITE は一致範囲が広く、DOMAIN-SUFFIX,example.org より前に書いてしまうと、後ろのルールは永遠に実行されません。
  2. IP-CIDR をドメインルールより前に置くと、ドメイン接続はまず IP を解決しないと照合できません。DNS クエリが1回増えるうえ、解決結果が上流リゾルバに渡ってしまいます。no-resolve を付ければ、ドメイン接続はこのルールをそのまま飛ばせます。
  3. MATCH が指すポリシーグループには、少なくとも1つの有効なノードを残してください。ルールセット更新後に未カバーのドメインが現れると、すべてのトラフィックが MATCH に落ち、グループが空だと端末全体がネットワークから切断された状態になります。

GEOSITE と GEOIP はデータファイルに依存します。オリジナル Clash は初回起動時に Country.mmdb をダウンロードします。mihomo は geodata-mode、geox-url、geo-auto-update、geo-update-interval が追加され、データソースを自前のアドレスに差し替えたり、時間単位で自動更新したりでき、手動でファイルを置き換える必要がありません。

DNS と fake-ip の実装差

オリジナル Clash の dns セクションには nameserver、fallback、fallback-filter、enhanced-mode、fake-ip-range、fake-ip-filter、hosts といったスイッチしかなく、すべてのドメインが同じ解決経路を通り、fallback-filter の geoip と ipcidr で結果の信頼性を判断します。以下は古い設定で最もよく見られる書き方です。

# オリジナル Clash の書き方。mihomo でも解析できるが、そのまま使い続けるのは非推奨
dns:
  enable: true
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  nameserver:
    - 223.5.5.5
  fallback:
    - https://1.1.1.1/dns-query
  fallback-filter:
    geoip: true
    ipcidr:
      - 240.0.0.0/4

mihomo は解決を複数の経路に分割します。default-nameserver は DNS サーバー自身のドメイン解決にのみ使い、IP の記入が必須です。proxy-server-nameserver はプロキシサーバーのアドレス解決を担当します。direct-nameserver は直接接続のトラフィックを処理します。nameserver-policy はドメインやルールセット単位で上流を指定します。この分割が解決するのは同じ1つの問題です。プロキシサーバーのドメインをどの経路で解決するかが、ノードに接続できるかどうかを直接左右します。

dns:
  enable: true
  listen: 0.0.0.0:1053
  ipv6: false
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  fake-ip-filter-mode: blacklist
  fake-ip-filter:
    - "*.lan"
    - "+.stun.*.*"
  default-nameserver:
    - 223.5.5.5
    - 1.1.1.1
  proxy-server-nameserver:
    - https://223.5.5.5/dns-query
  nameserver:
    - https://1.1.1.1/dns-query
    - quic://dns.adguard-dns.com:784
  nameserver-policy:
    "geosite:cn":
      - 223.5.5.5
    "rule-set:reject-list":
      - rcode://refused
  respect-rules: true
  cache-algorithm: arc

単独で覚えておく価値のある挙動差が2つあります。fake-ip-filter はオリジナル Clash ではブラックリストの意味しか持ちませんが、mihomo は fake-ip-filter-mode: whitelist が追加され、逆にリスト内のドメインにだけ偽 IP を割り当てる書き方ができます。nameserver-policy のキーは geosite: と rule-set: の接頭辞に対応し、長い fallback-filter.domain を維持する必要がなくなります。また respect-rules: true を指定すると DNS クエリ自体も rules の分流を通るため、有効にする前に proxy-server-nameserver を設定しておかないと、解決リクエストが自分自身へ戻って無限ループになる可能性があります。

移行時にもっとも見落としやすい項目 fallback と fallback-filter は mihomo でも解析でき、起動失敗にはなりません。そのためプロトコル部分と TUN だけを直し、proxy-server-nameserver の追加を忘れる人が多くいます。典型的な症状は、カーネルは正常に起動しログにもエラーが出ないのに、すべてのノードが一斉にタイムアウトするというものです。プロキシサーバーのドメインが通らない解決経路を使っていることが原因です。

TUN の実装と3つのネットワークスタック

オリジナルのオープンソース Clash には TUN がまったくなく、TUN は非公開の Premium カーネルにのみ存在し、調整できるパラメータもわずかでした。mihomo は TUN を設定ファイル内の一級機能として扱い、デバイス名や MTU から Android のパッケージ名単位の許可まで網羅しています。

tun:
  enable: true
  stack: mixed
  device: mihomo
  mtu: 9000
  auto-route: true
  auto-detect-interface: true
  strict-route: false
  dns-hijack:
    - any:53
  udp-timeout: 300
  endpoint-independent-nat: false
  gso: true
  gso-max-size: 65536

stack の3つの値の使い分け:

  • system:パケットをシステムのプロトコルスタックに転送する方式で、スループットが最も高い反面、システムのルーティングとファイアウォールの状態に依存し、複数 NIC や IPv6 環境ではループバックや取りこぼしが起きやすくなります。
  • gvisor:完全にユーザー空間で実装され、プラットフォームを問わず挙動が一定で、システムの転送に依存しません。単一接続のスループットは system より低く、ルーティングテーブルが複雑な環境や権限が制限された環境に適しています。
  • mixed:TCP はシステムスタック、UDP は gvisor スタックに任せる方式で、多くのクライアントのデフォルト値です。gvisor から system へ切り替える前に一度試す中間的な選択肢でもあります。

プラットフォームごとに依存するものは異なります。Windows では wintun ドライバとシステムサービスの権限が必要で、仮想 NIC の作成に失敗するとログに configure tun interface が現れます。macOS は utun デバイスを使い、初回有効化時にネットワーク権限を要求します。Linux では auto-route がルーティングテーブルを書き込み、auto-redirect が nftables でトラフィックを TUN へリダイレクトするため、iptables ルールを手書きする必要はありません。Android クライアントは include-package と exclude-package でどのアプリを TUN に通すかを制御します。

TUN モードでの解決の流れ TUN を有効にすると、dns-hijack がポート53宛のクエリをカーネル DNS へ引き取るため、どの経路で解決するかは dns セクションが決め、システムの DNS 設定とは関係ありません。デバッグ段階では dns-hijack を any:53 と書くと、IPv4 のみのハイジャックによる解決漏れを避けられます。

性能のトレードオフと移行手順

ルールマッチングのコストは主に種別と条数で決まります。ドメイン系ルールはインデックスを使うため1条あたりのコストはごく低く、DOMAIN-REGEX と PROCESS-NAME-REGEX は1条ずつ正規表現で照合するため、rules の先頭に置くと新しい接続ごとの初回パケットが遅くなります。接続側には必要に応じて有効にしたいスイッチがいくつかあります。

  • tcp-concurrent: true —— 同じドメインの複数の解決結果へ並行してハンドシェイクし、初回パケットの待ち時間を短縮します。代わりに同時接続数が増えます。
  • unified-delay: true —— 遅延テストを完全なハンドシェイク所要時間で統一して計算し、プロトコル間で数値が比較できない事態を避けます。
  • sniffer セクション —— IP 直結のトラフィックからドメインを復元し、ドメイン系ルールにヒットさせます。override-destination は宛先アドレスを復元したドメインに書き換えるため、一部の社内ネットワークでは無効化が必要です。
  • find-process-mode: strict —— プロセスマッチングは新しい接続の確立時に一度だけ照会するため、always より CPU を節約できます。
mixed-port: 7890
allow-lan: false
mode: rule
log-level: warning
ipv6: false
unified-delay: true
tcp-concurrent: true
find-process-mode: strict
external-controller: 127.0.0.1:9090
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]

オリジナル Clash から mihomo への移行は、6つの手順に固定すると、各段階で結果を個別に検証できます。

  1. 旧設定をバックアップ

    config.yaml と ruleset ディレクトリをまるごとコピーし、旧カーネルのバージョン文字列を控えておくと、ロールバック時の照合が楽になります。

  2. まず構文チェック

    mihomo -t -f config.yaml -d /etc/mihomo で起動せずに解析を1回実行します。unsupported proxy type はこの段階で露見するため、起動後のログを待つ必要はありません。

  3. プロトコル部分を処理

    hysteria2、tuic、vless ノードは残し、フィールド名を1つずつ確認します。ss ノードの 2022-blake3- 暗号化はカーネル側の対応が必要で、古いバージョンのカーネルはそのまま拒否します。

  4. DNS を書き換え

    default-nameserver と proxy-server-nameserver を追加し、fallback のリストを nameserver-policy へ移します。fake-ip-range は 198.18.0.1/16 のままで問題ありません。

  5. 最後に TUN を有効化

    まずドライバとサービスが準備できていることを確認し、stack は mixed から始め、dns-hijack は any:53 を使います。ルーティングと解決がどちらも正常だと確認できてから system への変更を検討します。

  6. 接続とルールのヒット状況を確認

    external-controller: 127.0.0.1:9090 を有効にし、ダッシュボードで接続リストとルールのヒット状況を見て、すべてのトラフィックが MATCH に落ちていないことを確認します。

オリジナル Clash に留まるべきケースも存在します。設定ファイルに ss、vmess、trojan しかなく、ルールはすべて rules セクションに書き、TUN も不要であれば、カーネルを替えるメリットは限定的で、変更によるリスクのほうが大きくなります。移行すべきかの判断基準はシンプルです。上記4つの領域のうち、mihomo 固有の書き方を使っているものがあるかどうか。あれば移行し、なければそのままにしておきます。