fwmark で一つの Linux ホストに二つの OpenVPN クライアントを連鎖させる
二つのクライアントがどちらも既定ルートを要求する。内側のサーバーをホストルートで固定し、二つ目のクライアントによるテーブル書き換えを止め、fwmark でフローを振り分ける。
一つの Linux ホストに二つ目の OpenVPN クライアントを重ねると、二つの箇所のどちらかで失敗します。外側のトンネルがその転送を奪っているために内側のクライアントが自分のサーバーへ到達できないか、二つ目のクライアントがルートテーブルを書き換えて一つ目のトンネルが動かなくなるかです。どちらの問題も暗号化ではなくルーティングにあり、新しいトンネルがアプリケーションのトラフィックを運ぶ前に解決します。ここでのアドレスは例示です。tun0 は外側のトンネル、203.0.113.9 は内側のサーバー、TCP 9999 は内側の経路を使うべきフローです。
二つの失敗モード、一つのルートテーブル
内側のクライアントは自分のサーバーへ UDP ソケットを開き、そのソケットは他のものと同じく現在のルートテーブルに従います。外側のトンネルがすでに既定ルートを握っていれば、ハンドシェイクのパケットは外側のトンネルに入ります。それが機能するのは、外側のサーバーが内側のサーバーへ到達でき、向こう側がそのフローを運ぶことに同意している場合だけです。そうでなければハンドシェイクは止まり、クライアントは再試行します。
二つ目の失敗はサーバーの push から来ます。通常の push は redirect-gateway def1 で、既定ルートを置き換えるのではなく 0.0.0.0/1 と 128.0.0.0/1 を設定します。したがって、それぞれが自分の /1 の組を設定する二つのクライアントは衝突し、ほぼすべての宛先で新しい組が勝ちます。二つ目が接続した後にトンネルが落ちたら、テーブルでこの二つのプレフィックスを探してください。既定ルート自体は一度も変わっていない可能性があります。
フローに印を付け、テーブルを与える
選択したトラフィックを mangle テーブルでマークし、ポリシールールでそのマークに一致させることで誘導します。ルールは既定ルートが内側のデバイスである専用テーブルを指すため、マークされたパケットだけが内側の経路を通ります。
iptables -t mangle -A PREROUTING -p tcp -m multiport --dport 9999 -j MARK --set-mark 0x9999
ip rule add fwmark 0x9999 table 200 pref 1000
ip route add default dev tun1 table 200このルールセットの失敗のほとんどは二つの間違いから来ます。ファイアウォールが設定するマーク値とルールが一致させる値は同一でなければなりません。食い違うとエラーも効果も出ないからです。また PREROUTING は外から届くパケットしか見ないため、このホストで開始される接続は、そもそもマークされる前に同じ一致条件の OUTPUT ルールを必要とします。
二つ目のクライアントからルートの所有権を取り上げる
二つのオプションが二つ目のクライアントによるテーブル編集を防ぎ、それぞれ別の地点で働きます。route-nopull は push されたルートオプションを無視するので、何も設定されません。route-noexec は push されたオプションをクライアントの環境に受け入れますが、テーブルの変更はフックに任せます。サーバーが何を提示したかを見たいときはこちらが適切です。
フックは push された値を持つ環境で実行され、承認した項目だけを追加すべきです。冪等にしてください。自分が管理するテーブルを再構築の前にフラッシュし、前回の実行がきれいな状態を残したと決めつけないことです。再接続でフックは再び呼ばれ、消さずに追加するフックは重複したルートを蓄積します。
所有者を一つ選び、書き留める。両方のオプションを設定するプロファイルや、手動スクリプトと競合するフックは、後から誰にも説明できないテーブルを生みます。
両方のサーバーアドレスを固定する
内側のクライアントが接続する前に、内側のサーバーには外側のデバイス経由のホストルートが必要です。その項目が、内側の転送を既定ルートに従わせず外側のトンネルの中に保ちます。
ip route add 203.0.113.9/32 dev tun0
ip route get 203.0.113.9外側のサーバーにも一致するホストルートをメインテーブルに追加してください。さもないと再接続で外側のクライアント自身の転送が自分のトンネルに入ることがあります。順序は任意ではありません。これらの項目は該当するインターフェースが存在した後に追加します。つまり接続後に実行されるフックかスクリプトです。落ちているデバイスを指すルートは削除されるか無視されるため、起動時に書いた項目は想定した順序を生き延びません。
テーブル全体を読む代わりに検索を確認してください。ルート検索の出力はカーネルが使うデバイスと送信元アドレスを示し、それはクライアントがソケットを開くときに得るものとまったく同じです。
連鎖を試し、失敗を可視化する
内側のクライアントが接続する間、外側のインターフェースでキャプチャします。内側のハンドシェイクは外側のトンネルの中の暗号化ペイロードとして現れるはずです。203.0.113.9 への平文 UDP として現れるなら、ホストルートが欠けており、連鎖は一度も構築されていません。
物理的なアップリンクへ静かに戻る連鎖が最悪の結果です。インターフェースの状態は何も知らせず、内側のトラフィックは誤った送信元アドレスから出ていきます。外側のトンネルが落ちたときに二つ目のクライアントが直接再接続できるなら、その経路を無効にするか外側のインターフェースに縛り付け、失敗が見えるようにしてください。
二つのフローと一つのルールダンプで終えます。マークされた接続とされていない接続は別の経路を通るはずで、ip rule show は意図した優先度を並べるはずです。両方のフローが同じ経路を通るなら、マークは思っている場所に適用されていません。