VPN 経由で IPv6 link-local が機能しない: fe80 がリンクで止まる理由
fe80:: アドレスはひとつのリンク上でのみ意味を持ちます。ルーテッド VPN がなぜそれを運べないのか、障害がどう見えるのか、代わりにどのアドレスを使うべきかを解説します。
トンネルを起動し、トンネルを送信元インターフェースとして fe80::1 に ping しても、何も返ってきません。これは忘れたファイアウォールルールではありません。リンクローカルアドレスは、それが形成された単一のリンク上でのみ意味を持ち、ルーテッド VPN はそのリンクではありません。リモートルーターの fe80::1 はリモート LAN セグメント上にあり、あなたは別のセグメントで 一ホップ離れた場所にいて、IPv6 には、ルーターがリンクローカルパケットを転送する仕組みがそもそもありません。
fe80 プレフィックスは範囲ではなくスコープを表す
リンクローカルユニキャストは fe80::/10 で、定義する性質は番号ではなくスコープです。アドレスはひとつのリンク上でのみ有効で、各インターフェースは自身の識別子からひとつを形成するため、無関係な 二つのネットワーク上の無関係な 二台のマシンが、まったく競合せずにどちらも fe80::1 に応答できます。プレフィックスは要するに「どこかのリンク上」と言っているだけで、リンク自体はコンテキストから決まります。
ホストでは、そのコンテキストはパーセント記号の後に付けるゾーン、すなわちスコープ ID で、Linux では fe80::1%eth0、Windows やインターフェースインデックスを公開するツールでは fe80::1%3 や fe80::1%12 です。カーネルはアドレスをインターフェースインデックスとともに解決してから送信します。ゾーンを外すと宛先が曖昧になり、スタックにはセグメントを選ぶ手段がありません。
ルーテッドトンネルが届けられない理由
ルーテッド VPN は独自のアドレスを持つポイントツーポイントインターフェースで、通常はトンネル内のユニークローカルまたはプライベートな IPv6 空間の /64 です。リモート LAN は別のリンクであり、トンネルエンドポイントがあなたに代わってルーティングと転送を行うことで到達します。これは 二つの異なるセグメントなので、一方のアドレスはもう一方では有効な宛先ではありません。
転送ルールはクライアントの癖ではなく標準に明記されており、ルーターはリンクローカルな送信元または宛先を持つパケットを転送しません。だからルートテーブルが正しく見えてもパケットは死ぬのです。fe80::/64 をトンネルに向けるルートを追加すると、宛先のスコープは「このリンクのみ」と言い、出力インターフェースはそのリンクではないため、準拠したスタックはそれを拒否するか、受け入れて該当するすべてのパケットを破棄します。
近隣探索も同じ境界を補強します。ホストはリンクローカルのネクストホップを、ホップ制限 255 のリンクスコープマルチキャストに送る近隣要請で解決するため、ルーターは転送されたものを拒否できます。これをトンネル越しに機能させるには、リモートセグメントの探索をプロキシすることになり、それは別の帽子をかぶった Layer 2 ブリッジングです。
もっと静かな 二つ目の失敗があり、それでもルートを強制しようとする人を捕まえます——それはゾーンです。ローカルで入力するゾーンはトンネルインターフェースを指しますが、欲しいアドレスはリモート LAN インターフェースに結び付いています。両者は決して一致しないため、パケットを遠端の正しいインターフェースへ誘導できません。
実際の障害はどう見えるか
スコープの誤りのツール出力はルーティングやファイアウォールのエラーのように読めるので、だからこそ人々は間違った層で半日を費やすのです。
| What you run | What you see | What it means |
|---|---|---|
| ping fe80::1 with no zone | Invalid argument on Linux, or a transmit failure on Windows | The stack cannot pick an interface for an ambiguous destination |
| ping -6 -I tun0 fe80::1 | Destination unreachable, or a silent timeout | The address is not on the tunnel link, so there is nothing on-link to answer |
| ssh user@fe80::1 after the tunnel is up | Hangs, then no route to host | The SYN never left the machine in a deliverable form; the server logs nothing |
| ip -6 neigh show dev tun0 | Empty table | No neighbor entry can exist for a link-local host that is not on this link |
| fe80::/64 inside AllowedIPs or route-ipv6 | Config accepted, traffic still dropped | The tunnel carried the packet; the receiving stack refused to forward it |
三行目が最も時間を浪費します、なぜなら出力のない ssh クライアントは待ち受けサービスの問題に見えるからです。入る手段があるなら、遠い側を確認し、sshd がすべてのアドレスにバインドされているのか 一つだけなのかを確かめてください。
ルーティングされる前提のアドレスを使う
トンネル経由で到達する必要があるものには、サービスがその目的のために与えられたアドレスを使ってください——グローバルユニキャストアドレス、または fc00::/7 からのユニークローカルアドレス、まさにこの内部用途のために予約された範囲です。リスナーが実際にそれをバインドしていること、戻り経路とホストのファイアウォールルールを確認してください。
推測せずにリモートホストを見てください。ip -6 addr show は存在するアドレスを一覧し、ss -6 -ltn はデーモンが何を待ち受けているかを一覧します。fe80:: だけにバインドされたサービスは自身のセグメントからしか到達できず、これは通常、誰も意図的に選んでいない既定値で、管理者の同意を得て変更できます。
サービスをどうしても再バインドできないときは、ブリッジをネットワーク層ではなくアプリケーション層へ上げてください。リモートホスト上のリレーがルーティング可能なアドレスで待ち受け、リンクローカルのアドレスへ接続すれば、セグメントを拡張せずに問題を解決できます:
socat TCP6-LISTEN:8080,bind=[fd00:1:2::10] TCP6:[fe80::1%eth0]:80ここではゾーンが正しい側、つまりそのリンク上に実際に eth0 を持つホストにあります。クライアントは fd00:1:2::10 と通信し、これは通常どおりルーティングされ、リレーがリンクローカルのホップをローカルで行います。同じ技は、リンクローカルアドレスだけを広告するアプライアンスの Web UI や古い BMC にも効きます。
ブリッジングが本当に答えになるとき、何を確認するか
共有セグメントが本当に必要な配備もあります——リンクローカル探索だけを話すデバイス、マルチキャストに依存するアプライアンス、フラットな LAN を再現するラボなどです。それは Layer 2 の問題であり、リモートインターフェースにブリッジされた tap デバイスや、明示的なブリッジモードを持つクライアント製品のような Layer 2 の答えが必要です。その代償はトンネルを越えるブロードキャストとマルチキャストのトラフィック、そして端から端まで機能しなければならない近隣探索です。
ルーテッドプロファイルに fe80::/64 を足して済ませようとすると最悪の状態になります。設定は完成したように見え、ルートテーブルにはエントリが表示され、それでもすべてのパケットは正しく動作している 二つのスタックの間でどこかに破棄され続けます。
ping ではなくインターフェースで確認してください。ip -6 addr show tun0 はトンネルリンクが独自のリンクローカルアドレスを形成したかを教え、ip -6 route get fe80::1%tun0 はスコープ付き宛先に対してカーネルが実際に何をするかを教えます。カーネルがそのインターフェース上で宛先に到達できないと言うなら、どのトンネル設定もそれを変えられないため、プロファイルの編集をやめる時です。
疑問を決着させる短い手順:
- リモートホストのアドレスを読み、それが fe80:: かルーティング可能なプレフィックスかを記録します。
- fe80:: ならサービスを再バインドするか、そのホストでリレーするか、セグメントをブリッジし、ルーティング可能ならルート、ファイアウォール、バインドアドレスを直します。
- 再テストのたびにゾーンを再確認してください、コピーしたコマンドはたいてい間違ったインターフェースを保持しているからです。