Android の単一 VPN ルールはホスト型 SSH サーバーを妨げない
一本の VPN という制限が縛るのは VPN サービスであり、ソケットではない。スマートフォン上の SSH サーバーはリスナー、ピアの到達性、戻り経路の理由で失敗する。
この思い込みは事実のように扱われるほど広まっている。Android が許す VPN は一つだけなので、スマートフォン上で動く SSH サーバーはそのトンネル越しには動かない、というものだ。前半は正しいが、結論はそこから導けない。プラットフォームの制限は、何個のアプリが VPN サービスの枠を保持できるかという話であり、いくつのソケットが存在できるか、誰がスマートフォンに接続できるかという話ではない。待ち受けソケットを持つ SSH デーモンは、普通のポートを持つ普通のアプリであり、トンネルはその通信が通りうる経路の一つにすぎない。
単一 VPN ルールが実際に縛るもの
VpnService は一枠だけの資源だ。アプリがトンネルを確立するとそのアプリが端末の VPN になり、別のアプリがトンネルを確立しようとすると、最初のものが消えるまで拒否される。これはトンネルの所有権に関するルールであり、二つの VPN クライアントが同時に動けない理由を説明する。
待ち受けソケットについては何も言っていない。SSH サーバーは VpnService を呼ばず、パケットを横取りせず、その枠を争わない。ポートを束縛して接続を受け入れる。VPN を一切入れなくても同じことをする。つまりこの俗説は、トンネル提供者に対する制限とネットワーク接続に対する制限を混同しており、両者に関係はない。
リスナーは到達先であり、トンネルの一部ではない
トンネルができると、スマートフォンはその上にアドレスを持ち、ピアの端末はそのアドレスへの経路を持つ。何かが応答するかどうかはリスナーが決める。loopback に束縛されたデーモンはスマートフォン自身からしか到達できない。古いトンネルアドレスに束縛されたデーモンは、再接続の後も束縛を保つことがあるが、束縛したアドレスは変わっている。
束縛の規則は普通の SSH 設定だ。待ち受けアドレスとポート、そして誰が認証できるか。Android ではそうした設定はアプリ自身の設定にあり、アプリのプロセスの所有者には特権ポートの使用が許されていないことがあるので、root として 22 番ポートを束縛するデスクトップの習慣はそのままでは移らない。
ListenAddress 10.90.0.4
Port 2222
AllowUsers alice次に経路そのものの許可がある。外向きのウェブ通信を喜んで運ぶコンシューマー向け VPN でも、クライアントへの内向き接続を一切許さないことがある。多くは許さない。一人の顧客を別の顧客にさらすことになるからだ。ピアがどこからもスマートフォンのトンネルアドレスに届かないなら、問題はサービスの方針にあり、スマートフォンにはない。
戻り経路がふつう壊れる場所だ
ピアがスマートフォンのトンネルアドレスに届き、SYN が到着したとしよう。こんどは応答が同じ経路で出ていかなければならず、それはスマートフォン内部の経路決定だ。応答が既定のネットワークだからという理由で Wi-Fi から出ていくなら、ピアには決して完了しない接続が見える。これはファイアウォールとまったく同じに見える。
Android はこれをデスクトップより複雑にする。既定のネットワークはプロセスごとに選ばれ、VPN アプリの方針で変更できるからだ。VPN クライアントは許可したアプリの一覧だけを経路に載せられるので、その一覧の外にあるサーバーアプリは応答に物理ネットワークを使い続け、そのあいだピアはトンネル越しの応答を待っている。
アプリは自分のソケットを特定のネットワークに束縛することもでき、それが既定を上書きする。あるサービスが一方のアドレスからは届き、別のアドレスからは届かないという報告を読んだら、たいていはこの仕組みが背後にある。
VPN を迂回しても内向きサービスは直らない
よく使われる回避策、SSH アプリを VPN から除外することでその通信を転送させないようにするのは、逆の問題への対処だ。外向き SSH クライアントの転送がトンネルのせいで何度も壊れるときには助けになる。ホストしているサーバーでは、ピアが使っているトンネルから応答を外に出してしまい、部分的な失敗を完全な失敗に変える。
同じことはルーターで公開ポートを開ける場合にも当てはまる。要件が、許可された一台の端末が VPN 経由でスマートフォンに届くことなら、SSH をインターネットにさらすのは別の脅威モデルであって修理ではない。
まずサービスのアドレスを決める。スマートフォンの LAN アドレスか、トンネルアドレスか、意図して転送したエンドポイントか。それぞれに到達性と露出の契約があり、それらを混ぜることが、小さなアクセスの問題を恒久的な穴に変える道だ。
一度に一方向だけ診断する
設定を変える前に四つの事実を書き出す。ピアが接続しているアドレス、スマートフォン上のリスナーの状態、それを所有するアプリとユーザーの文脈、そして各方向で観測した経路。VPN が SSH を切断するという報告には、そのどれも書かれていないし、クライアントの転送の問題と到達できないサーバーを区別できない。
adb shell ip -br addr
adb shell ip route get 10.90.0.9
nc -vz 10.90.0.4 2222ポートの確認はピアから実行し、スマートフォンからは実行しない。そして SSH クライアントの詳細出力を読み、TCP ハンドシェイクの前で失敗しているのか後で失敗しているのかを見る。決して接続しないソケットと拒否されるログインは、別の段階であり、責任者も別だ。
デバッグ中もホスト鍵の検証とアカウントの制限はそのままにしておく。リスナーと許可ユーザーを絞ることが、到達可能なサーバーを許容できるものにする。接続を通すためにそれらを緩めるのは、一つの問題をより悪い問題と交換することだ。OpenSSH サーバーのリファレンスは、あなたの Android 実装が同じ設定名に従うなら、待ち受けとアクセスの制御を扱っている。