二つのサービスが競合せずに同じ 443 番ポートへ応答できるのは、リスナーがトランスポート、アドレス、ポートの三つで決まるのであって、番号だけで決まるのではないからです。同じマシンに Web サーバーと OpenVPN を置いたら、最初に答える価値があるのは、各プロセスがカーネルにどのソケットを要求するかという問いだけです。設定を編集する前にその一覧を書き出しておけば、「アドレスは既に使用中」というエラーの大半は自ずと説明がつきます。一覧の後は、UDP リスナー、共有 TCP ソケット、プロトコルで振り分けるフロントエンドのいずれかを選ぶ話になります。

各プロセスが要求するソケットから始める

bind はその三つ組で成否が決まるため、二つのデーモンが衝突するのは三要素がすべて一致したときだけです。だから TCP 443 の Web サイトと UDP 443 の OpenVPN リスナーは無期限に共存でき、同じホストの二番目のローカルアドレスへ片方を移すだけでも議論は終わります。古い include や忘れられた unit ファイルは覚えている編集より長く生き残るので、作ったはずのファイルではなく実際に動いているリスナーを読みます。

Line in the listener listWhat it establishesWhat to do next
One udp row and one tcp row, each on 443Separate transport sockets, nothing contestedPoint the VPN profile at udp and stop editing
A single tcp address and port listed by two processesA real bind collision between two daemonsMove one service, or put a dispatcher in front of both
A proxy process owns the socket, no vpn processThe proxy accepts and has to hand off correctlyCheck that its backend speaks the protocol it forwards

root で ss -lntup を実行し、IPv6 の項目も含めて 443 に触れるすべての行を読みます。ワイルドカードの v6 リスナーは通常 v4 の通信も受け付けるので、責任があると思っていたファイルを変えた後も、忘れていたリスナーがポートを占有し続けるのです。プロセス名に驚いたら、何も触る前に ss -lntup 'sport = :443' で出力を絞り込みます。

ネットワークが許すならトンネルを UDP に切り替える

UDP リスナーは衝突を完全に避けられ、ソケットを共有せず経路に余計な製品も入らないので、ふつう最短の修理です。ただし無料ではありません。一部のクライアント網は reset を返さずに UDP 443 を落とすため、トンネルは bind に失敗するのではなく確立しなくなり、利用者は読めるエラーもなく VPN が落ちたと報告します。UDP リスナーは Web 層の再起動にも耐えるので、サイトを頻繁に再配備するマシンでは効きます。サーバー上のシェルはサーバーが自分に届くことしか証明しないので、利用者がいるネットワークから試してください。

サーバー設定とクライアントプロファイルの両方でトランスポートを同時に変えます。TCP を要求したままのプロファイルは UDP だけのリスナーに対して再接続を繰り返し、二つのファイルを見比べるまでネットワーク障害のように見えます。

VPN プロセスに TCP 443 を持たせ、残りを転送する

Community OpenVPN の TCP サーバーモードは、受信接続の先頭バイトを読み、自分のプロトコルでないものを HTTPS バックエンドへ転送します。この転送が port-share ディレクティブの役割で、バックエンドは TLS を有効にした普通の Web サーバーであり、そのディレクティブでは URL ではなくホストとポートで指定します。

port-share 10.0.0.5 8080

サイトが二番目のプライベートアドレスを持つマシンでは、その行が VPN 以外の通信を 10.0.0.5:8080 へ送るので、HTTPS のバーチャルホストはそこに bind し、ファイアウォールはローカルのホップを許可する必要があります。Web サーバーは、届くバイト列がブラウザーの元のハンドシェイクで間で何も復号していないため、TLS を自分で終端しなければなりません。ディレクティブを素の HTTP リスナーに向けると、ブラウザーは無効なメソッドのエラーを受け取り、これはバックエンドの置き場所ミスではなく壊れたサイトのように読めます。ディレクティブは TCP サーバーモードにしか存在せず、UDP リスナーには調べるストリームがありません。

VPN プロセスがすべての接続を受け入れるため、Web アプリケーションには転送された要求が訪問者ではなくホスト自身のアドレスのどれかから届いて見えます。本当のクライアントアドレスが必要なものは、バックエンドの PROXY プロトコル対応か、それを付けるフロントエンドに依存します。バックエンドを別のローカルアドレスへ移しても訪問者のアドレスは戻らず、サイトが記録するホストのアドレスが変わるだけです。分析レポートが疑問を出す前に、その制約を配備メモに書いておきます。

リバースプロキシは間違った道具で、トランスポート層のルーターはそうではない

HTTP リバースプロキシは要求を読むために TLS を終端するので、OpenVPN のストリームをそのまま通せません。SNI や最初のパケットの検査で転送するトランスポート層のルーターは、VPN が依存するセッションを終わらせない限り、両方の前に立てます。二つの機器は図では似て見え、ソケットの前ではまったく違う振る舞いをします。

商用の Access Server は独自のポート共有設定を持ち、それはコミュニティのディレクティブと一対一では対応しません。もう一方のために書かれた手順を流用する前に、製品とバージョンを特定してください。黙って無視されるコピー設定は、その後もリスナー一覧がもっともらしく見えるので、設定がないより悪いものです。

Web サイトとトンネルを二つの別経路として試す

サイトを読み込み、トンネルを接続し、そのうえでリスナー一覧をもう一度読んで、今 443 を持っているのはどのプロセスかを見ます。読み込めたページは VPN について何も証明せず、同じ番号を使っていても接続できたトンネルはサイトについて何も証明しません。下の三つのコマンドは、共有ソケット、Web 経路、VPN サービスをこの順に確認します。

ss -lntup 'sport = :443'
curl -sI https://vpn.example.com/ | head -n 1
journalctl -u openvpn-server@server -n 20

同じ瞬間について、バックエンドのアクセスログのクライアントアドレスと VPN ログのそれを比べます。サイトがすべての訪問者をホスト自身として記録していれば、転送は機能し、アドレスの扱いは機能していません。両方のログが沈黙していれば、フロントエンドは接続を渡していません。いずれにせよ、ソケットを受け入れるのは誰かを決めるのはリスナー一覧であり、別のファイルを変える前に確かめるべき事実はそれです。

そのリスナー対応表を設定のそばに残しておけば、次に 443 に触る人が、どのプロセスが受け入れ、もう一方のプロトコルがどこへ行ったかを知れます。編集が何をするはずだったかを信じるのではなく、変更後に所有を確認してください。

関連記事