4 分で読了 #networking #代理契約
このページの目次

透過的プロキシと TUN

クライアント側の設定が不要なプロキシ——ゲートウェイでトラフィックを静かにハイジャックし、ユーザーには完全に透過的。核心的な課題はルーティングの振り分けです:ClientHello の SNI または fake-ip DNS から「この接続はどこへ行くべきか」を判断します。TPROXY と TUN はハイジャックの実装方法としてそれぞれ長所と短所があります。

概要

透過的プロキシにはクライアント側の設定は不要です——ゲートウェイでトラフィックを静かにハイジャックし、ユーザーには完全に透過的になります。実装方法は、nftables REDIRECT から TPROXY、そして TUN 仮想ネットワークインターフェースに至るまで多岐にわたります。核心的な課題はルーティングの振り分けです:プロキシは、その接続が baidu.com へ向かっているのか google.com へ向かっているのかを、どのようにして知ることができるのでしょうか?TLS SNI スニフィングは ClientHello からドメイン名を抽出しますが、fake-ip DNS はより徹底したアプローチを採用しています——ドメイン名を偽の IP にエンコードし、接続時に逆引きテーブルを参照します。

透過的プロキシが解決する課題

従来のプロキシでは、クライアントがプロキシアドレスを明示的に設定する必要があります(ブラウザの設定 / システムのプロキシ設定 / アプリケーション層のプロキシ設定)。透過的プロキシはこの要件を排除します:ゲートウェイで静かにトラフィックをハイジャックするため、クライアントはプロキシの存在に気づきません。

3つの実装方法

REDIRECT (iptables NAT)

iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 7891

原理: DNAT — 宛先 IP:port を 127.0.0.1:7891 に書き換える
問題: プロキシは元の宛先アドレスを知らない(元の dst IP:port が失われる)
→ プロキシ側で必要となる処理:
  1. getsockopt(SO_ORIGINAL_DST) — REDIRECT のみに有効(conntrack が元の宛先を記録しているため)
  2. スニフィング — TLS ClientHello の SNI または HTTP Host ヘッダーからドメイン名を抽出

TPROXY

iptables -t mangle -A PREROUTING -p tcp -j TPROXY --on-port 7891 --tproxy-mark 0x1/0x1
ip rule add fwmark 0x1 table 100
ip route add local default dev lo table 100

TPROXY と REDIRECT の違い:
  REDIRECT: dst IP:port を書き換える → 元の宛先が失われる
  TPROXY: dst IP:port を書き換えない → 元の宛先アドレスを保持
  → プロキシ: 元の dst IP:port がそのまま残っている → ソケットの元の宛先を直接読み取ればよい → SO_ORIGINAL_DST は不要
  → 自機以外のトラフィックもプロキシ可能(真のゲートウェイ透過プロキシとして機能)

  TPROXY マーク: パケットにマークを付与 → ポリシールーティング(table 100)→ ローカルへルーティング(宛先が自機でなくても可)
  原理: "local default dev lo" → ローカルプロトコルスタックへの処理を強制 → プロキシがパケットを受信

TUN デバイス

TUN は iptables を必要としません——仮想 NIC を作成し、IP レベルでトラフィックをインターセプトします:

[TUN インターフェース: utun / tun0] ← mihomo / WireGuard / 任意の VPN
  ↑ ルーティング: 0.0.0.0/0 → tun0(全トラフィックが TUN を通過)

アプリ → ルーティング → tun0 → mihomo プロトコルスタックで処理 → ルーティングの振り分け:
  → DIRECT: 再ルーティング(tun0 をバイパス)→ 物理 NIC から送信
  → PROXY: 暗号化 + エンカプセーション → 物理 NIC → プロキシノード

nftables の補助(tun0 出口付近):
  table inet mihomo {
    chain prerouting {
      type filter hook prerouting priority -150; policy accept;
      ip daddr 192.168.0.0/16 return   ← ローカルネットワークはハイジャックしない
      meta mark 0x1 return              ← マーク済みのパケットは再ハイジャックしない
    }
  }

ドメイン名ベースのルーティング: スニファ + Fake-IP

プロキシは、「この接続が baidu.com へ向かっているのか google.com へ向かっているのか」を知ることで、DIRECT と PROXY のどちらに振り分けるかを判断する必要があります。ドメイン名を取得する方法は以下の2つがあります:

スニファ(パッシブスニフィング)

TLS ClientHello(平文、Encrypted ClientHello が広く普及する前):
  オフセット 5 + 32 バイト(ランダム)+ session_id + cipher_suites
  → SNI 拡張(type=0x0000)→ ホスト名
  → mihomo: ClientHello 受信時に最初の数バイトを peek → SNI を抽出 → ルーティングの振り分けを決定

HTTP:
  GET http://example.com/path HTTP/1.1
  Host: example.com
  → Host ヘッダーを直接読み取る

QUIC:
  QUIC Initial パケットの SNI(TLS ClientHello 内にあるが、QUIC は CRYPTO フレームを使用)
  → TLS と同様だが、QUIC フレームのラップ構造を解析する必要がある

Fake-IP DNS

通常の DNS: クライアントは baidu.com → 123.125.115.110 を取得し、TCP 接続時には IP しか見えない → プロキシは IP の所属(GEOIP)に基づいてドメイン名を推測しなければならない。

Fake-IP: DNS は偽の 198.18.0.0/15 範囲のアドレスを返し、各 IP は一意のドメイン名に対応する:

クライアント: DNS クエリ google.com
mihomo (fake-ip DNS): 198.18.0.42 を返す(実際の IP ではない)
クライアント: TCP connect 198.18.0.42:443
mihomo TUN: SYN を受信 → fake-ip テーブルを参照 → 198.18.0.42 = google.com → ルーティングの振り分けを決定

GEOIP はフォールバックとして機能します。ドメイン名ベースのルーティングは、fake-ip の正確性に依存します。

参考

  • mihomo: wiki.metacubex.one/config/dns
  • TPROXY: kernel Documentation/networking/tproxy.txt
  • nftables: wiki.nftables.org

キーワード: 透過的プロキシ, TPROXY, TUN, スニファ, fake-ip, nftables, ポリシールーティング