TLS ラッパーを OpenVPN の手前に置くと、配備には二つ目のリスナーが加わりますが、インターネットに面するのはそのうち片方だけであるべきです。同じマシンからはトンネルが通り、外からは失敗するとき、転送はたいてい二つのうち誤った側に向いています。揃えるべきは連鎖で、公開側の規則がラッパーに届き、ラッパー自身のバックエンド接続が本来の VPN ソケットに届きます。四つの跳びがこの連鎖を運び、そのどれが欠けても調査は早く終わります。

二つのソケット、二つの持ち主、片方は公開

ラッパーは外側の接続のために TLS を終端し、復号したバイト列を素の OpenVPN TCP リスナーへ渡します。これらは 別々の設定ファイルを持つ別々のソケット なので、片方を編集しても他方は手つかずのまま、見た目は健全に保たれます。また、片方だけを変えるたびに二つが食い違う可能性があることも意味します。

同じマシンから作業するだけでは、公開経路についてほとんど何も証明できません。元のポートへの直接接続はラッパーを飛ばし、ラッパーの LAN アドレスへの接続は変換を飛ばしますが、外部の利用者が行うことを実際に試すのはクライアント側の完全な構成だけです。名前も慎重に読む必要があり、localhost はその部品を動かしているホストを指して、たまたま入力しているホストではありません。覆った跳びを書かない成功は別の二つのものを比べる誘いになるので、この区別をメモに残してください。

転送が実際に着く先

連鎖は三つのポート番号としてより四つの跳びとして捉えるほうが楽で、同じ番号でも両端では違う意味になり得ます。表を行ごとに読むと、各行は接続を受け入れてから次を開く一つのソケットだと分かります。ある行が誤った相手を向いているとき、最後の列の症状が利用者の報告するところです。

HopListens onMust reachSymptom when it points elsewhere
Client machineA local plaintext portThe wrapper's public TLS addressPlain text offered to a TLS endpoint
Router forwardPublic TCP 443The wrapper host on its translated portWrapper records no connection at all
Wrapper, server modeIts accepted port, 8443 hereThe native VPN socket on loopbackHandshake completes, then silence
Native VPN listenerLoopback TCP portIts peer, over TCP onlyA UDP socket behind a byte-stream relay

ラッパーではなく元の VPN ポートを狙う規則は TLS 終端を丸ごと飛ばし、クライアントが OpenVPN のフレーミングを期待するリスナーとハンドシェイクを開いて接続が開いたまま止まる、この誤りの最もよくある形になります。翻訳されたポートは、次にそのファイルを編集する人が別の番号を選んでいるかもしれないので、記憶ではなくラッパーの設定ファイルで確認してください。下の二つのコマンドはその問いの両側を同時に示します。

grep -nE '^[[:space:]]*(accept|connect)' /etc/stunnel/stunnel.conf
ss -lntp | grep -E ':443|:8443|:1194'

あなたを拒むバインドアドレス

127.0.0.1 に束縛された accept 行はホスト自身にしか応答しないため、LAN アドレスに届いた転送は接続できず、クライアントにはパケットフィルターの問題のように見える拒否が返ります。ソケットのほうが安上がりな答えなので、ファイアウォールに触る前にソケットを見てください。ラッパーを転送が狙うインターフェースに束縛するか、loopback に置いたまま、転送が本当に機能する何かを経由して loopback に届くようにしてください。これは二つの設計で、それぞれに独自の規則が要ります。

両者を混ぜると、どのファイルも正しく見えるのに、動いているラッパーが外から届かなくなります。下の確認は一つのコマンドで、ファイアウォール規則の見直しでは答えられない問いに答えます。出力が loopback アドレスを示すのに転送が LAN アドレスを狙っているなら、修正は accept 行に属し、パケットフィルターではありません。

ss -lntp '( sport = :8443 )'

ローカルテストが実際に証明したこと

覆った跳びを記さない成功は、他の跳びについての証拠になりません。テストがどのアドレスを使い、どのプロセスが応答したかを書き留めてください。そのうえで、ヘアピンの挙動はルーターごとに違い、LAN を一度も出ていない接続でも転送がまったく関与しないままクライアントを満足させられるので、同じ Wi-Fi 上の携帯電話からではなくネットワークの外から繰り返してください。その二度目の試行だけが、部外者のたどる経路を試します。

試行が失敗したら、ラッパーの accept と connect の行、続いて VPN デーモン自身のセッションログと、一つの接続を両方のログで追ってください。何も記録しない最初の境界が、次の変更の場所です。OpenVPN がピアのオプションを比べ始めたら、そのメッセージはその実装の規則で判断してください。link-mtu の不一致警告は自動的に致命的ではなく、link-mtu を 1500 に固定しても断片化が止まった証明にはならず、暗号の交渉はルーターの転送ではなく内側の OpenVPN 設定に属します。

nc -vz 127.0.0.1 1194
journalctl -u stunnel -n 50 --no-pager

これらの検査はバックエンドのソケットとラッパーのログを覆い、拒否されたバックエンドと拒否されたハンドシェイクを切り分けます。事後にではなく、一回の試行が進行している間に実行してください。同じ試行のタイムスタンプが比較を信頼できるものにします。

二つの識別、二つの失敗順序

外側の証明書はラッパーに属し、内側の識別は OpenVPN に属し、両者は別の機関から来ることがあります。経路を健全に見せるためにどちらかの検査を切るのは、実際の保護を外見上の結果と引き換えにすることです。メッセージの順序が層を名指しする、つまりラッパー証明書の問題はどの VPN オプションの交渉よりも前に現れ、内側の識別の失敗はストリームが確立してから初めて現れるので、両方の検証を残して順番に調べてください。

stunnel のマニュアル は accept、connect、クライアントモードを正確に定義しており、その節は当て推量より速くソケットの問いに答えます。どの層が応答したかまだ分からないときは、下のコマンドで公開ポートが提示する証明書を読んでください。出力にラッパーの証明書があれば転送はラッパーに届いており、沈黙やプロトコルエラーなら届いていません。この一つの区別が、ここでの他のどの確認よりも時間を節約します。

openssl s_client -connect vpn.example:443 -servername vpn.example </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer

関連記事