IPv6 の VPN クライアントはワークロードに到達し、ワークロードはトンネル内へ応答を返すが、その応答は決して届かない。ファイアウォールかトンネルを疑いたくなるが、通常どちらも原因ではない。OpenVPN が払い出すアドレスは、トンネルの内部にしか存在しない内部ネットワークであり、Google Cloud は fd42:90::/64 が特定の VM の背後にあるべきだと、あなたが伝えるまで知らない。

この事実を学ぶ必要があるのは二つの別個のシステムであり、そのうち OpenVPN は一方にすぎない。VPC は何かを返送する前にルートを必要とし、VM は届いたものを転送することを許可されなければならない。どちらか半分を誤ると、症状はまったく同じに見える。

プールはサブネットではなく内部ネットワーク

OpenVPN は選択した範囲から IPv6 アドレスを払い出すことができ、その範囲はトンネルの両端が合意した虚構である。Google Cloud のサブネットではなく、VPC のサブネット一覧にも現れず、VM のネットワークインターフェースがそのアドレスを取得することもない。したがって、クライアントに応答するワークロードは、ネットワークが経路不達と見なすアドレスへパケットを送ることになる。

プールは VPC がすでに使っているどの範囲からも外しておく。他のトンネルの背後の範囲からも外しておく。重複は、きれいなルーティング障害を、負荷時にだけ現れる断続的な障害に変えてしまう。

fd42:90::/64 のような一意ローカルアドレスは、この内部ネットワークに妥当な選択だ。このプレフィックスは、設計がそもそもプライベートであることを思い出させてもくれる。ULA プールであるからといって、公衆インターネット上で正当な送信元になるわけではない。

VPC に宛先とネクストホップを与える

応答には、宛先がプールでネクストホップが VPN VM である VPC ルートが必要だ。現在のスタティックルートのドキュメントは IPv6 の宛先範囲を扱い、どのネクストホップ種別が対象かを示しているので、古い IPv4 の例をコピーするのではなく、自分のトポロジでサポートされる形式を確認してほしい。

ネクストホップのアドレスは、実際にインスタンスのクラウドインターフェースに割り当てられているものでなければならない。VM の内部トンネルアドレスはそれではない。それでも使うと、きれいにインストールされ、パケットに一度も一致しないルートができる。ルートを書く前にインターフェース一覧を読んでほしい。

ルートの優先順位は、エントリそのものと同じくらい重要だ。サブネットルートは追加するカスタムルートより具体的なので、既存のサブネット範囲から切り出したプールは、カスタムルートの書き方に関係なくサブネットルートに負ける。別個のプールを選び、そのうえでルートがインスタンスをネクストホップとして、正しい宛先プレフィックスを示していることを確認する:

gcloud compute routes create vpn6-return \
  --network=vpn-vpc \
  --destination-range=fd42:90::/64 \
  --next-hop-instance=openvpn-vm \
  --next-hop-instance-zone=europe-west1-b

フォワーディングは二度有効にする必要がある

ルートはパケットをインスタンスに届ける。しかし、自分宛てでないものを転送してよいことにはならず、その許可は二つの層に存在する。

クラウド側ではインスタンスのフォワーディング機能を有効にする必要がある、つまり、VM が自分が所有しないアドレス宛てのトラフィックを受け取って転送できるようにする設定だ。ゲスト側ではカーネルがパケット到着後に別の判断をするため、独自の IPv6 フォワーディングが必要になる。一つの応答がトンネルを越える前に、両方が有効でなければならない。

sysctl -w net.ipv6.conf.all.forwarding=1

さらに、ファイアウォールルールが内部の送信元範囲を両方向で許可する必要がある。Google Cloud はカプセル解除後のパケットを評価するので、VPC サブネットだけを対象にしたルールでは、トンネルがすでに成功した後にプールのトラフィックが落ちる。プールをイングレスルールの送信元範囲に追加し、カスタムのイグレスポリシーが応答を遮断していないことを確認する。暗黙の許可ルールは既定のケースを覆うが、手書きのイグレスルールは覆わない。

OpenVPN に誰が何を所有するかを伝える

OpenVPN は、そのクライアントが宛先を所有していると分かったときにクライアントへ転送する。プールからのクライアントアドレスは、プールを定義するサーバーディレクティブが扱うが、クライアントの背後のサブネットは別の配置であり、そのクライアントに対する明示的な所有エントリが必要になる。

この二つを混同すると、サーバーへの ping は通るのにその先が何も通らないトンネルになる。どちらのケースを構築しているかを決め、二本目の push 行を足して期待するのではなく、対応するエントリを書いてほしい。

インターネット向けの出口はこの設計に含めない

プライベートアクセスとインターネットアクセスは別々の計画だ。ULA プールは、ルーティングとフォワーディングが一致すればトンネル経由で VPC のワークロードに到達できるが、同じプールが VPC を通って公衆インターネットへ抜ける経路はない、なぜならクラウドゲートウェイは任意のトンネル送信元を自分のものとして受け入れないからだ。

クライアントにインターネット向けの出口も必要な場合は、サポートされるアドレッシングとルーティングの設計を別に計画し、プライベートプールとは独立させ、二つの経路を別々にテストする。片方の経路が動いても他方については何も分からず、両者を混同した設計は、壊れたトンネルのように見える形で失敗する。

関連記事