OpenVPNの背後にあるDockerとVMの通信:経路か、再採番か
OpenVPNクライアントを動かすホスト上のコンテナとゲストは、戻り経路を失うかトンネルのプレフィックスと衝突する。三つの問いが対処を選ぶ。
ホストはトンネル経由で閲覧しているのに、隣のコンテナは何にも届かない。自前のVPNクライアントを動かすコンテナからポートを公開するとさらに悪い。要求は届くが、応答は決して出て行かない。どちらもホスト上の経路の判断であり、Dockerはただの伝達者にすぎない。
この二つのどちらなのかは、三つの問いで決まる。所要時間は一分ほどだ。
同じ姿で現れる二つの障害
一つ目は戻り経路の問題だ。公開したポートはコンテナへ変換され、要求は届き、応答はホストのデフォルト経路で送られる。その経路を OpenVPN クライアントが tun0 に向けている。応答は対向側が想定していないアドレスから出て行くため、破棄される。二つ目はプレフィックスの衝突だ。Dockerのデフォルトブリッジが 172.17.0.0/16 を占有し、社内トンネルが同じ範囲を押し込んでくる。
前者は経路テーブルで、後者はブリッジの再採番で直る。どちらなのかを確かめる前に手を入れると、動いていたコンテナを壊すことになる。
三つの問いで切り分ける
問い一:VPNを停止すると障害は消えるか。トンネルを止めて全て動くなら、戻り経路を疑う。問い二:ip route show table all は同じプレフィックスを二度、ブリッジと tun0 の両方で一覧に出すか。それが衝突だ。問い三:影響を受けているアドレスはコンテナか、NATゲストか、ブリッジ接続のゲストか。
三つ目の問いが重要なのは、それぞれ正しい直し方が違うからだ。コンテナ向けの直し方を libvirt ゲストに当てると、一晩を無駄にする。
ip route
ip route show table all | grep -E '172\.1[7-9]|10\.8'
docker network inspect bridge --format '{{.IPAM.Config}}'戻り経路:送信元アドレス一つ分のテーブル
コンテナの通信は物理インターフェースから出しつつ、ホスト自体はトンネルを使う場合、送信元ベースのルーティング が最小の変更だ。コンテナのアドレスに一致するルールを追加し、専用のテーブルへ送る。ホスト上の他の設定は動かない。
ip rule add from 172.22.0.3 lookup 10
ip route add default via 172.22.0.1 table 10送信元がそのアドレスである全パケットは、まずテーブル 10 を参照し、テーブル 10 はトンネルではなくブリッジのゲートウェイへ送る。
確認には ip route get 1.1.1.1 from 172.22.0.3 を使う。応答にはブリッジ側のインターフェースが示され、tun0 ではないはずだ。この方法で追加したルールは再起動で消えるため、コンテナを管理する unit かネットワークフックに書いておく。
同じ形は WireGuard のコンテナにも当てはまり、そこでも戻り経路に明示的なルールが要る。
プレフィックスの衝突:ブリッジを再採番する
経路テーブルは一つのプレフィックスを一つの場所へ送る。VPNが 172.17.0.0/16 を押し込み、Dockerのブリッジがすでにそこにあると、メトリックの小さい方が勝つ。Dockerは接続済み経路をメトリック 0 で入れ、押し込まれた経路は 50 で届く。するとDockerは動き続けるのにトンネルだけ壊れて見える、あるいはその逆になり、設置順によって変わる。メトリックと争うのではなく、ブリッジを再採番する。
{
"bip": "10.200.0.1/24",
"default-address-pools": [{"base": "10.201.0.0/16", "size": 24}]
}変更後はDockerを再起動し、古いプールで作ったネットワークを作り直す。代わりにメトリックを変えても、衝突が次の起動に移るだけだ。
VMにはまた別の答えが要る
デフォルトのNATネットワーク上の libvirt ゲストは、ホストの経路設定を引き継ぐ。ホストのトンネルがデフォルト経路を持つと、外へ出られなくなる。ゲストをブリッジか macvtap のデバイスに移せば、物理ネットワーク上に独自の道ができる。virt-manager ではハードウェアを追加し、Network を選び、ソースを macvtap のホストデバイスにし、モデルは virtio のままにして、最後に古いNATインターフェースを無効にする。
macvtap のゲストは通常、自分のホストに届かない。これは不具合ではなく想定された挙動であり、残すメモに書いておくべきものだ。
ホストのLANだけが必要なゲストなら、ベンダーのクライアントに欠けていたスイッチがあるかもしれない。Mullvad なら mullvad lan set allow が libvirt に触れずにローカル到達性を戻す。そしてコンテナをトンネル経由でのみ到達させたい場合は、公開ポートをトンネルのアドレスに縛る。書き方は -p 10.8.0.5:8080:8080 とし、すべてのアドレスで公開しない。