トンネルは上がり、サービスは待ち受けているのに、オフィスのLANには何も戻ってこない。VPNが壊れたわけではない。LAN側から見れば、ノートPCのトンネルアドレスは、LANに到達方法を誰かが教えるまで存在しない。応答は、要求が出て行ったのと同じ道を戻らなければならない。私は、誰かが戻りの経路を見る前に、完全に健全なトンネルを作り直す人を三度見てきた。

壊れたのはトンネルではない

両端はプライベート範囲に座っており、これは正常なケースであって設定ミスではない。10.8.0.6 のようなアドレスはトンネルの中では実在し、他のどこにも存在しない。だからLANがそこへ何かを送る理由はない。ルーティングに触る前に、サービスがそもそもトンネル内から到達できるか確認する。クライアントでは ss -ltn が、待ち受けが 127.0.0.1、LANインターフェース、トンネルアドレスのどれに束縛されているかを示す。loopback にしか束縛されていないデーモンは自分以外に応答せず、どんなルート、DNAT、ポート転送もそれを変えない。

ポート80や443のプロキシは、別の可視性を持つ別の道具だ。これはパケットではなく要求を動かすので、生のデータベース接続には間違った層であり、それに手を伸ばすことが、誰かが何かを測る前にこの問題を三つ道具の深みへ運ぶ道になる。

ルートの形:プールの所在をLANに伝える

LANゲートウェイ上の静的ルートは、LANとトンネルプールが重ならない場合に最もきれいな答えだ。プールをトンネルを終端するマシンへ向ける:

ip route add 10.8.0.0/24 via 192.168.1.12
route add -p 10.8.0.0 mask 255.255.255.0 192.168.1.12   # Windows, persistent

これは片方向しか直さないので、この変更の半端な版は進歩に見える。トンネル側にも対応する半分が要り、さもなければ応答はクライアントを自身のデフォルトルートで離れて途中で死ぬ:

push "route 192.168.1.0 255.255.255.0"

サーバーの背後ではなく一台のクライアントの背後にあるサブネットには、サーバー側にシステムルートと、そのクライアントのファイルへの iroute エントリの両方が要り、そのファイルは client-config-dir 配下にある。iroute がなければ、OpenVPN はプレフィックスが存在することは知っていても、どのクライアントが所有するかを知らない。パケットは届き、デーモンには照合する相手がない。

ここに名付ける価値のある神話がある。ルートはアドレスを到達可能にするが、相手側を応答する気にさせるわけではない。サイト間ピアはさらに層を重ねる。遠端のゲートウェイは自分のポリシーに列挙されたプレフィックス内の送信元しか受け付けず、家庭用プールから来たノートPCはその中にない。

NATの形:内側へのDNATと、誰もが飛ばすSNATの行

LANゲートウェイに触れられないとき、あるいは正確に一つのポートだけを公開したいときは、VPNホスト上の変換が、LANにルートを一切置かずに機能する。ポートを公開し、転送し、トンネルへ入る道で送信元を変換する:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 10.8.0.6:3000
iptables -A FORWARD -i eth0 -o tun0 -p tcp -d 10.8.0.6 --dport 3000 -j ACCEPT
iptables -t nat -A POSTROUTING -o tun0 -d 10.8.0.6 -p tcp --dport 3000 -j SNAT --to-source 10.8.0.1

三行目が消える行だ。飛ばすとクライアントは元のLANアドレスを送信元として見て、自分のデフォルトルートで応答し、その応答はLANへ戻る代わりにトンネルへ入る。接続が生き残るかは、計画していなかったルーティング次第になる。SNATがあればクライアントはサーバーのトンネルアドレスを見るので、応答はトンネルを戻り、変換を打ち消せる唯一のホストへ届く。

コンシューマー向けファームウェアは独自のチェーン名を使い、その変換を勝手に作ってはくれない。Tomatoでは、インバウンド規則は WANPREROUTING に入り、次に wanin のようなフィルターチェーンに入る。完全に見える規則セットでも、手書きのPOSTROUTING行がまだ要る。これはファームウェアの不具合ではなく、インバウンド通信のためだけに書かれた規則セットだ。

正しく見える規則がパケットを落とす理由

こうした失敗のほとんどは、見つめている規則の中にはない。次の順で四つを確認し、成り立たない最初のところで止まる:

  • チェーンの順序。 iptables -I は先頭に挿入し、-A は末尾に追加する。だから以前の許可あるいは遮断の規則が先に決める。
  • 間違ったチェーン。 ルーター自身宛ての通信はINPUTを通り、転送される通信は決してそこに触れない。一つのサービスを公開するのに両方の規則が要ることがある。
  • 方向のフラグ。 -i tun0 はクライアントから来る通信に一致し、-o tun0 はクライアントへ出る通信に一致する。フラグを誤ったREJECTは、意図しない何かを遮断する。
  • 送信元の制限。 一つのクライアントアドレスを受け付ける転送は、そのクライアントからは機能し、他のあらゆる送信元を一言もなく落とす。

パケットカウンターがゼロの規則は経路にすら入っておらず、カウンターを読むことが、間違ったチェーンの問題と間違った規則の問題を分ける最速の方法だ。

ローカル転送、LAN側で何も変えられないとき

SSHのローカル転送は問い全体を迂回し、LANにルートも、クライアントのファイアウォールの協力も、どこかに公開したポートも要らない。

ssh -N -f -L 3000:10.8.0.6:3000 user@vpn-host

するとサービスはトンネルを保持するマシンの localhost:3000 に現れ、他のどこにも現れない。これが境界だ。一台のワークステーション、一つのポート、そして何かが再起動しない限り転送はセッションとともに死ぬ。ステージングのデータベースを確認する管理者には正しい道具だ。他の人が到達する必要のあるカメラやスキャナーには違う。利用者ごとに自分の転送が必要になるからだ。

別の規則を足す前に両方向を確かめる

変換は痕跡を残し、それを読む方が推測に勝る。VPNホストでは、conntrack -L が変換がそもそも起きたかを示し、iptables -t nat -L -n -v がカウンターが動いたかを示し、tcpdump -ni any port 3000 が実際にどの送信元アドレスが届いたかを示す。クライアントでは、ss -ltn が待ち受けがトンネルから到達できるアドレスにあることを確認する。

次に、残す形を選ぶ。公開した一つのポート、プール全体へのルート、一人の管理者への転送は三つの別の約束であり、それらを混ぜることが、動いていた設定を半年後に誰も説明できない設定に変える道だ。どのアドレスがどこから到達できるかを書き留めれば、次にこれをデバッグする人はトンネルではなく答えから始められる。

関連記事