透明プロキシと TUN

このページの目次

透明プロキシにより、アプリケーションは HTTP や SOCKS プロキシインターフェースを明示的に使用する必要がなくなり、ホストまたはゲートウェイが選択されたトラフィックを奪取し、ポリシーに基づいて転送します。これは、アプリケーションが差異を完全に認識できないことを意味するものではなく、また透明な復号化を意味するものでもありません。接続のタイミング、アドレス、プロトコルのサポート、および証明書境界は依然として変更される可能性があります。

別々に回答すべき点は、パケットがどのようにプロキシに到達するか、プロキシが元の宛先をどのように取得するか、ドメイン名をどのように推測するか、そしてプロキシ自身のアウトバウンドがどのように再び奪取されるのを回避するかです。リスニングポートを1つ設定したり、デフォルトルートを1つ設定したりするだけでは、通常、この完全な閉ループを完了するには不十分です。

要件主要メカニズム
ローカルアプリケーションのアウトバウンドを受信ローカルのOUTPUTパス、ルーティング、またはTUNによる奪取
ゲートウェイ経由で転送された他のマシンのトラフィックを受信受信側のPREROUTINGおよびルーティングによる配信
パケットの元の宛先アドレスを保持TPROXYなどの透明ソケットメカニズム
アプリケーションリクエストのドメイン名を取得DNSマッピング、可視なハンドシェイク情報、またはアプリケーションメタデータ
プロキシ自体の再帰的な奪取を防ぐアウトバウンドのマーキング、ルーティングルール、または分離境界
IPv4/IPv6を同時にカバー2つのアドレスファミリのルーティング、ルール、およびDNS動作

3つの奪取方式は異なる抽象層にある

トラフィックの奪取とターゲットの識別は異なる作業である REDIRECT:宛先エンドポイントの変更 NAT / conntrack;元の宛先の復元はプロトコルと実装に依存 TPROXY:パケットの宛先NATを保持 透明ソケット + 一致ルール + ポリシールーティングの連携 TUN:IPパケットをユーザー空間プログラムへ送信 プログラムがIPを転送するか、接続を終了してプロキシプロトコルに変換 これら3つはいずれも自動的にドメイン情報を付与せず、HTTPSの復号化もしません。独立したドメインと信頼ポリシーが必要です。

REDIRECT:宛先エンドポイントの変更し、接続追跡を利用して関係を復元

REDIRECTは特殊な宛先NATです。IPv4のローカルOUTPUTシナリオではループバックアドレスへリダイレクトでき、PREROUTINGシナリオでは通常、入力インターフェースのローカルアドレスを選択します。すべての状況を127.0.0.1への変更として記述することはできません。iptables REDIRECT

NATは現在のパケットの宛先アドレスを変更しますが、conntrackは元の方向のメタデータを保持できます。TCPプロキシは、適用可能なSO_ORIGINAL_DSTを介して元の宛先を照会できます。これはREDIRECTターゲットでのみ意味を持つ専用インターフェースではなく、「元のアドレスは永久に失われる」と単純に記述すべきではありません。

Linux 7.2のソースコードでは、net/netfilter/nf_nat_redirect.cがLOCAL_OUTとPRE_ROUTINGのアドレス選択を区別し、nf_conntrack_proto.cのgetorigdstはconntrackの元の宛先を照会し、対応するプロトコルに制限を設けます。UDPはこのTCPインターフェースを汎用的な復元方法として使用できず、データグラム元の宛先の取得は別途設計する必要があります。

REDIRECTは通常の宛先アドレスをローカルアドレスに変更しますが、プロキシは転送されるトラフィックのプロトコルとターゲットを理解する必要があります。TLS SNIを元の宛先の代用とするのも不十分です。SNIには宛先ポートが含まれておらず、すべての接続でドメイン名が可視になるとは限りません。

TPROXY:パケットは宛先NATを行わず、依然としてローカルの透明ソケットへ渡す

TPROXYは一致するトラフィックを透明な受信をサポートするソケットへ配信し、IP/トランスポート層の宛先エンドポイントを保持します。その有効な動作には、透明なリスニング、ルール選択、マーキング、およびポリシールーティングの連携が必要です。アプリケーションは、IP_TRANSPARENTなどのオプションを要件に従って設定する必要があります。

例えば、ルールが選択されたパケットにマーキング1を付け、ポリシールールがテーブル100を参照し、テーブル100にlocalタイプのリルートが含まれている場合、ローカルではない宛先アドレスをローカルプロトコルスタックへ渡すことができます。local ... dev loはここでローカル配信を表現しており、元の宛先IPを127.0.0.1へ書き換えるものではありません。

TPROXYアクションのみで対応するルーティングがない場合、またはルーティングのみを作成してプロキシが透明ソケットをサポートしない場合、完全なパスを構成することはできません。ローカルで生成されたトラフィックとゲートウェイのインバウンドは異なるフックを経由するため、1つのPREROUTINGルールがすべてのローカルアウトバウンドを同時に奪取できるとは限りません。Linux TPROXYドキュメント

TCP接続のターゲットは、適用可能なソケットセマンティクスから取得できますが、UDPは通常、各データグラムに対して宛先メタ情報を処理する必要があります。実際のプログラムは、アドレスファミリ、ポート、および返信の送信元を正しく処理する必要があり、1つのUDPリスニングソケットに1つの恒久的なターゲットしかないと仮定してはいけません。

TUN:ユーザー空間でIPパケットを送受信するデバイスインターフェース

TUNはIPパケットを対象とし、TAPはイーサネットフレームを対象とします。Linuxプログラムは/dev/net/tunなどのインターフェースを介してデバイスを作成またはアタッチし、ファイル記述子からカーネルがそのデバイスへ送信したデータを読み取り、返されたデータをカーネルの受信パスへ書き込みます。4バイトのパケット情報プレフィックスの有無はIFF_NO_PIなどの設定に依存し、すべての実装で同じ長さを固定してスキップすることはできません。

TUN自体は暗号化を行わず、ドメイン名を解析せず、SOCKSエグジットを自動的に提供しません。ユーザー空間プログラムはIPパケットをVPNにカプセル化することも、プロトコルスタックを実行してローカルTCPを終了し、プロキシへ別の接続を確立することもできます。後者は、元のTCPセグメントを1つずつCONNECTへそのまま送り込むこととは異なります。Linux TUN/TAP

仮想ネットワークインターフェースがすべて/dev/net/tunであるわけではありません。例えば、LinuxカーネルのWireGuardは独自のネットワークデバイス実装を使用しており、ユーザー空間バージョンでのみTUNを介してIPパケットを配信する可能性があります。すべてのWireGuardとTUNプロキシを完全に同一のデータパスとして描画することはできません。

アウトバウンドの迂回:DIRECT でも循環に遭遇する可能性がある

プロキシ自身のアウトバウンド接続には、奪取パスを迂回する出口が必要である アプリケーションのパケット送信 ルーティング選択 TUN ユーザー空間プロキシ IPパケットを読み取り、転送ポリシーを選択 プロキシのアウトバウンドソケット マーキング / インターフェース / ルーティングによる分離 物理エグジット ターゲットへの直接接続またはプロキシノードへの接続 アウトバウンドが再びデフォルトのTUNを経由すると、再帰的な奪取が発生します。DIRECT でも TUN を本当に迂回する必要があります。

アプリケーションのターゲットパケットがTUNへルーティングされ、プロキシがDIRECTを選択した場合でも、実際のアウトバウンド接続を作成する必要があります。新しいソケットが同じデフォルトルートに引き続き一致する場合、接続は再びTUNへ戻り、循環が発生します。遠端のプロキシノードへの接続も同様で、DIRECTターゲットのみを除外してノードアドレスを忘れることはできません。

ポリシーマーキング、専用ルーティングテーブル、インターフェースのバインド、またはネットワークネームスペースなどの方法を採用できます。具体的な優先順位とアドレスのオーバーライドは、対応する設定と組み合わせて行う必要があります。プロセスUIDによる迂回も有効ですが、スコープと共有ユーザーの問題があるため、任意のプラットフォームで同じルールセマンティクスが利用できるとは限りません。

以下は、単一のIPv4教育用ポリシーをシミュレートしたものです。プロキシのアウトバウンドマーキングによる優先迂回、明確なローカルネットワークセグメントの迂回、それ以外はTUNへ委ねるというものです。これは実際のルーティングを変更するものではなく、すべての特殊アドレスやデプロイメントルールをカバーするものでもありません。

from ipaddress import ip_address, ip_network

BYPASS_MARK = 0x80
local_ranges = [ip_network('127.0.0.0/8'), ip_network('192.168.0.0/16')]

def route(destination, mark=0):
    addr = ip_address(destination)
    if mark & BYPASS_MARK:
        return 'physical'
    if any(addr in net for net in local_ranges):
        return 'local'
    return 'tun'

assert route('203.0.113.10') == 'tun'
assert route('203.0.113.10', BYPASS_MARK) == 'physical'
assert route('192.168.1.10') == 'local'
assert route('198.18.0.42') == 'tun'
# ターゲットを遠端のプロキシノードに変更しても、TUNを自然に迂回しない。出口ポリシーが必要。
assert route('192.0.2.20') == 'tun'
assert route('192.0.2.20', BYPASS_MARK) == 'physical'
print('ローカル迂回、プロキシエグジット、再帰リスクモデルが通過')

「TUNの作成にiptablesは不要である」ことは、デプロイメントにファイアウォールやポリシールーティングが不要であることを意味しません。Mihomoのauto-route、auto-redirect、strict-routeなどのオプションにはプラットフォームと実装の制限があるため、名前をクロスプラットフォームの固定動作として解釈するのではなく、現在のバージョンのドキュメントを読むべきです。Mihomo TUN設定

元の宛先IPはドメイン名ではない

同じIPが多くのドメイン名でサービスを提供することがあり、同じドメイン名が複数のアドレスに対応することもあります。IPの所有権や地理データベースは、アプリケーションがアクセスしたい名前を信頼できる形で復元できません。「IPが某国に属する」ことは、ドメイン分類の同等の代替手段ではありません。

情報ソース提供できるもの欠落または信頼できない状況
元のIP/ポートネットワークターゲットサービスドメイン名を含まない
平文HTTP Hostアプリケーションが宣言したHTTPターゲットHTTPS下では直接読み取れず、接続ターゲットと一致しない場合もある
TLS ClientHello SNI可視なサービス名のヒントSNIなし、ECH、または解析材料が不完全
DNS関連テーブル某回の解決と後続の接続の関連付け共有アドレス、キャッシュ、バイパスDNS、およびライフサイクルの差異
Fake-IPマッピングローカル合成アドレスが表す名前マッピングの消失、競合、またはクライアントが該DNSをバイパス

TLS ClientHelloにおいて、SNIは固定の「5+32」オフセットで見つかるものではありません。レコード、ハンドシェイクの長さ、セッションID、スイート、および拡張機能は構造に従って解析する必要があり、TCPの読み取り境界をまたぐ可能性もあります。QUIC Initialでは、対応する保護を除去してCRYPTOデータを再構築する必要があり、UDPペイロードに対して固定オフセット検索を行うことはできません。

ECHは内部のClientHelloを隠蔽し、外部の公開名がアプリケーションの元のターゲットであるとは限りません。スニファリングは限定的なルーティングのヒントとして使用できますが、信頼できる認証手段として見なすべきではありません。RFC 9849:ECH

Fake-IP:ローカルインデックスの割り当てであり、ドメイン名をIPにエンコードするものではない

プロキシが制御するDNSは、api.example.comに198.18.0.42を割り当てることができ、クライアントはその後この合成アドレスへ接続します。奪取プログラムはローカルマッピングを照会し、ドメイン名を復元して実際のエグジットを決定します。アドレス自体からは通常、ドメイン名を単独でデコードすることはできず、重要な状態はマッピングテーブルにあります。

一般的なプールでは198.18.0.0/15が使用されますが、このセグメントのIANAでの用途はネットワークベンチマークテストであり、プロキシ専用に世界中で予約されたアドレスではありません。デプロイメントでは実際のネットワーク使用との競合を回避する必要があり、合成アドレスがマッピングなしでパブリックネットワークへ流れることを許可してはいけません。IANA特殊アドレス登録

例えば、クライアントが合成アドレスを10分間キャッシュし、プロキシが1分後に再起動して同じアドレスを別のドメイン名へ再割り当てした場合、誤ったルーティングが発生する可能性があります。マッピングの永続化、アドレスの回収、DNS TTL、およびアクティブな接続のライフサイクルを調整する必要があります。現在のテーブルで「1IP-1ドメイン」を保証するだけでは不十分です。

クライアントが独自のDoHを介して実際のIPを取得した場合、接続はTUNによって奪取される可能性がありますが、Fake-IPマッピングが存在しない可能性があります。これは「トラフィックは既に奪取されているが、ドメイン情報が不足している」ことを意味し、「トラフィックが完全にプロキシをバイパスしている」わけではありません。逆に、UDP53のみを奪取しても、TCP DNS、DoT、またはDoHはカバーされません。AとAAAA、直接接続ドメイン、およびFake-IPフィルタリングに対しては、一貫したポリシーが必要です。Mihomo DNS設定

マッピングをクリアした後、キャッシュ内のIPは依然として元のドメイン名を表すか?

まずマッピングをクリアしてから接続すると、アドレス自体は名前を復元できません。その後、そのアドレスが誤って再利用されると、クライアントは依然としてapi.exampleにアクセスしたいと考えていますが、プロキシはother.exampleを照会します。修正には双方の状態を調整する必要があり、現在のマッピングテーブルが一対一対応に見えるようにするだけでは不十分です。

図を準備しています
マッピングをクリアした後、キャッシュ内のIPは依然として元のドメイン名を表すか?

合成IPはローカルマッピングのインデックスです。クライアントのコピーとプロキシのマッピングのライフサイクルは異なるため、アドレスを早期に再利用すると、古い意図が新しいオブジェクトへ向いてしまいます。

MTU、アドレスファミリ、および障害の特定

奪取後にカプセル化が追加されると、有効なMTUが低下する可能性があります。小さなリクエストは成功しますが、大きな転送でタイムアウトが発生する場合、パスMTU、ICMPエラーの伝播、およびMSSなどの証拠を確認する必要があります。ユーザー空間TCPスタック、UDPリレー、およびIPカプセル化パスの処理方法は異なるため、「MTUを固定値に変更する」という1つの方法ですべての問題を解決することはできません。

IPv4のデフォルトルート0.0.0.0/0はIPv6をカバーしません。AAAAが利用可能でIPv6パスが奪取されていない場合、アプリケーションはIPv6エグジットを選択する可能性があります。IPv6を一律でブロックすると、到達可能性とアドレス選択が変更される可能性があります。期待されるカバー範囲に基づいて明示的に設定する必要があり、特定のIPv4サイトにアクセスできることをもって完全な検収とするべきではありません。

読み取り専用チェックでは、現在のルールとルーティングを順序立てて確認し、プロキシログと照合できます。

ip rule show
ip -6 rule show
ip route show table all
ip -6 route show table all
ip -d link show
観察結果次のステップの特定
プロキシがターゲットトラフィックを受信していないアドレスファミリ、ルーティング、フック、および一致条件
トラフィックを受信したが、元の宛先が正しくないNAT復元、透明ソケット、UDPメタ情報
ターゲットを識別できるが、同じアウトバウンドが繰り返し発生するアウトバウンドの迂回とmark/ruleの優先順位
特定のドメインのみが誤って振り分けられるDNSパス、マッピング、ECH、およびスニファリングの制限
UDPのみが失敗するプロキシエグジットの能力、関連付け、およびMTU
大きなレスポンスまたは長期間の接続のみが異常断片化/PMTUD、バックプレッシャー、状態タイムアウト

これらのチェックは既存のデプロイメントを理解するために使用されます。設定時には、リスニング、奪取、迂回、および戻りパスを完全なソリューションとして確認する必要があります。

さらに参照