このアプリを除外するスイッチはない:プロセスに独自の送信元を持たせる
ルーティングが見るのはアドレスであり、プロセス名ではありません。除外したいプログラムに独自の送信元とテーブルを与えるか、判断をルーターへ移してください。そこでは誠実に語れます。
一つのプログラムだけ通常の接続を使い、ほかはすべてトンネルに残したい。だがクライアントにそのためのスイッチはない。隠しメニューを探しても無駄である。ルートはパケット内部のフィールドから選ばれる。パケットが運ぶのはアドレスとポートであり、それを作ったプロセスの身元ではない。したがって除外は、そのフィールドのどれかで表現するしかない。パケットを一度たどれば、選べる手段は謎ではなくなる。
クライアントが提供できない理由
宛先ルーティングは、宛先アドレスをプレフィックスと比較し、最も長い一致を採用する。その瞬間、どのパケットも同じに見える。何が生成したものであっても関係ない。送信元ポリシールーティングは比較の対象を送信元アドレスまで広げる。これが実用的な回避策すべてが使うフックである。送信元を変えれば、別のテーブルが対象になる。
ポートは助けにならない。ポートによる除外は宛先検索より前に評価しなければならないが、その判断にポートは関与しない。ポートベースの除外として宣伝されるものは、実際には別のフィールドで除外しており、それを曖昧に説明しているにすぎない。
本当のアプリ単位ルーティングは、プラットフォームが仕組みを追加した場所にしかない。Windows のフィルターコールアウトや Android の VPN サービスバインディングがそれである。デスクトップ版 OpenVPN にはどれも搭載されておらず、設定フラグで作れるものでもない。
Linux:プロセスに印を付け、その印でルーティングする
最も清楚な方法は、判断をソケットを所有するプロセスに結び付ける。owner の一致は OUTPUT チェーンでローカルに生成されたトラフィックに有効なので、そこで付けた印を使って別のテーブルを選べる:
iptables -t mangle -A OUTPUT -m owner --uid-owner 1001 -j MARK --set-mark 0x1
ip rule add fwmark 0x1 table 200
ip route add 192.168.1.0/24 dev eth0 src 192.168.1.0 table 200
ip route add default via 192.168.1.1 dev eth0 table 200
ユーザー 1001 のパケットはその後テーブル 200 を参照し、通常のゲートウェイから出ていく。メインテーブルは引き続きほかをすべてトンネルへ送る。owner の一致が効くのはローカルに生成されたパケットだけであり、それはまさにここで問題になるトラフィックである。別のホスト宛てに転送されるパケットは分類できない。意図ではなく道具で確かめる。ss -tnp は確立済み接続のローカルアドレスと所有プロセスを示し、ip route get 1.1.1.1 mark 0x1 は印がどのテーブルを選ぶかを示す。
ネットワーク名前空間は、自己完結したプログラムにとってより堅牢な答えである。その内部で作られたソケットは構造上すべてそこに束縛されるため、ヘルパープロセスや子接続は分類されることなく振る舞いを引き継ぐ。代償は配管作業だ。veth ペア、両端のアドレス、そして名前空間の内部からホストを指すデフォルトルートが要る。
ip netns add novpn
ip link add veth-host type veth peer name veth-ns
ip link set veth-ns netns novpn
ip addr add 10.200.0.1/24 dev veth-host
ip netns exec novpn ip addr add 10.200.0.2/24 dev veth-ns
ip netns exec novpn ip route add default via 10.200.0.1
Windows:宛先が今も決める
Windows は宛先から送信インターフェースを選ぶ。したがって別の経路を望むプログラムは、その選択より前にスタックが参照する何かを変えなければならない。ForceBindIP は前半を担う。指定したローカルアドレスにソケットをあらかじめ束縛した状態でプログラムを起動する。しかしそれだけでは、パケットがどのインターフェースから出るかは変わらない。経路を選ぶのは今も宛先だからだ。デフォルトルートがトンネルを指すノート PC で送信元を束縛しても、パケットはやはりトンネルに入る。
この症状には読める形がある。一度知れば見当がつく。選んだインターフェースが直接届く LAN アドレスでは成立し、経路がトンネルを指すインターネット上のものにはすべて失敗する。プログラムが作業をヘルパーに渡すまでは動いているようにも見える。自分のソケットを開くヘルパーには、束縛のことが一切伝わっていないからだ。
一つのプロセスが誤った単位なら判断を移す
本当の要件がプログラムではなく機器なら、専用の SSID か VLAN に置き、ルーターに決めさせる。その送信元は通常の経路を通り、ほかの送信元はトンネルを使う。専用のゲートウェイマシンもまた誠実な設計だ。VPN を保持し、ルーターが送ってくる宛先だけを受け取る。VLAN では、その機器が開くすべてのフローがポリシーの対象になる。Windows の手法を壊したヘルパープロセスも含まれる。その代償は範囲だ。同じ機器上の二つのプログラムを別々に扱うことはもうできない。
どちらも送信元または宛先のポリシーであり、範囲は一文で言い表せる。どちらもアプリ単位ではなく、アプリ単位を名乗る設計は、プログラムが作業を二つ目のプロセスに委譲した最初の時点で破綻する。