RFC 3484 の呪い

昔、DNS ラウンドロビンが当てにならなくなってきたのは RFC 3484 の影響、という事を書きました。

IPv6 と RFC3484 - JULYの日記

この RFC は 6724 で更新されています。

RFC 6724 - Default Address Selection for Internet Protocol Version 6 (IPv6)

この RFC は、割り当てられた複数のアドレスから、どのアドレスを使って相手との通信を行うか、というもので、それを実装すると、名前解決の結果がソートされ、結果、DNS ラウンドロビンを期待して複数の A レコードを設定している場合、片方にアクセスが寄ってしまう、という現象がありました*1

RFC 3484、6724 自体は、IPv6 上でより近い経路になるアドレスを選択するためのもので、DNS ラウンドロビンの問題は、その結果の巻き添えを食った感じですが、今度は、本当にこの RFC にまつわる問題に遭遇しました。

ソースアドレスの選択

RFC 6724 のポイントの一つに、どのアドレスを発信元のアドレスとして選択するのか、というのがあります。これまでの試行錯誤の結果、

  • グローバルアドレス(MAC アドレスからの生成)
  • グローバル一時アドレス
  • ULA

の3つのアドレスがホストに割り当てられている状態になっています。この状態で実際に通信を行う時に、このアドレスのどれを発信元とするか、というルールを決める必要があります。そのルールが RFC 6724 の「5. Source Address Selection」にある、Rule 1 〜 8 です。

このルールを一つ一つ、確認してみます。

Rule 1: 宛先とアドレスと同じアドレスを優先する。

自分自身へ接続する場合には、宛先アドレスと同じ発信元アドレスを使う、ということになります。

Rule 2: 適切なスコープを選択する。

ざっくり言うとグローバルアドレス相手にはグローバルアドレスを、リンクローカルアドレス相手にはリンクローカルアドレスを、ということのようです。

Rule 3: 非推奨アドレスを避ける。

自動構成のアドレスの有効期限が近づいているアドレスが非推奨アドレスです。自動構成アドレスは時間とともに「優先(Preferred)」「非推奨(Deprecated)」「無効(Invalid)」というようにステータスが変化します。新たな通信を開始する時には、非推奨アドレスを利用せず、非推奨アドレスをつかった既存の通信が無くなるのを待つ、という形で、スムーズなアドレスの更新を実現します。

Rule 4: ホームアドレスを優先する。

ホームアドレスは、Mobaile IPで使われるアドレスです。

Rule 5: 宛先に向かうインタフェース上にあるアドレスを優先する。

複数のインタフェースがあって、宛先アドレスからルーティングテーブルにしたがってパケットを送出するインタフェースが決まると、そのインタフェースに割り当てられているアドレスを優先する、という話です。一見、当たり前ですが、例えば、ルータ自身が LAN 側のホストへ繋ごうとする際に、WAN 側のアドレスを使わない、という話になります。

Rule 5.5: Next-hop 側のインタフェース上にあるアドレスを優先する。

基本的には 5. と同じ話で、宛先がさらに別のゲートウェイの向こうになる場合、一番近いゲートウェイへ向かうインタフェース上のアドレスを優先する、という話です。

Rule 6: ラベルが一致する物を優先する。

NGN 閉域網の問題*2の時に話題になったポリシーテーブルで割り当てられるラベルが一致しているアドレスを優先します。

Rule 7: 一時アドレスを優先する。

一時アドレスがある場合には一時アドレスを優先して使います。

Rule 8: アドレスのプレフィックスを比較して、一致している長さが長いものを優先する。

IPv6 アドレスのネットワーク部であるプレフィクスを比較して、先頭ビットから何ビット一致しているかをチェックし、一致しているビット数の多い方を優先することで、ルーティング経路の有利なアドレスを選択する、という意図があります。

ULA のスコープ

RFC 6724 のルールを見ていくと、Rule 2 を除く 1 〜 5.5 に関しては、割と当たり前のルールです。Rule 4 は Mobile IP を使っている場合のケースで、かなり特殊で縁がない感じで、あとは、「まぁ、そうだよなぁ」という話です。

問題は、Rule 2 です。

IPv6 アドレスのスコープは、

  • グローバル
  • サイトローカル
  • リンクローカル

と言われていました。しかし、サイトローカルアドレスは廃止*3され、その代わりに ULA が作られました。

ULA のスコープがサイトローカルとして定義されていれば分かりやすかったのですが、実は、ULA のスコープはグローバルです。そのことが ULA を定義している RFC 4193 に書かれています。

http://tools.ietf.org/html/rfc4193

3.3 Scope Definition
By default, the scope of these addresses is global.

という事は、本当にグローバルなアドレスだろうが、ULA だろうが、Rule 2 では優先順位に差がない、ということになります。

最長一致より一時アドレス

前回までで、

  • グローバルなアドレスは、MAC アドレスを元にした自動構成アドレスと一時アドレス
  • ULA は DHCPv6 で割り当てられたアドレス

という3つのアドレスを持つ構成になっています。この状態で同じ ULA を持つおうちサーバに繋ごうとすると、Rule 6 より前のルールでは決着が付きません。

Rule 6 はポリシーテーブルで割り当てられるラベルで、デフォルトのポリシーテーブル*4では3つのアドレスに差はありません。

次に Rule 7 で評価されると、3つのアドレスのうち、グローバルな一時アドレスが優先される事が決定します。

つまり、これまで苦労して3つのアドレスが割り当てられるようにした結果、おうちサーバに繋ぐ時に選択されるアドレスは、グローバルなアドレスの一時アドレス、という事になってしまいます。Rule 8 にたどり着けば、プレフィックスの最長一致で、当然、ULA が選択されます。しかし、たとえ相手と同一プレフィックスを持っていても、つまり、相手が同一セグメントにあっても、別のプレフィックスを持つ一時アドレスが選択され、わざわざルータ経由で通信することになります。

屈辱のポリシーテーブル

RFC 3484 が 6724 で更新されたポイントの中に、3484 以降に制定された ULA に対する記述*5と聞いていたのですが、それがどこかというと「10.6. Configuring ULA Preference」で、そこで書かれているのは単に「ポリシーテーブルで ULA の優先順位を上げてね」という話*6で、少なくともデフォルトポリシーテーブルでは、ULA はグローバルアドレスと同じ扱いになってしまいます。

一時アドレスを一切使わない、ということであれば、今回のケースは Rule 8 のプレフィックス最長一致で救われることになりますが、一時アドレスの有無をプレフィックスによって変えると、たとえ全く同じプレフィックスを持つ、隣のホストに対するアクセスでも、一時アドレスを利用してルータ越しの通信を行うことになります。

ということで、結局、ポリシーテーブルを設定する以外に解決策がない、ということになりました。

Windows でのポリシーテーブルの設定方法に関しては、「netsh prefixpolicies」あたりで検索すれば、「IPv4 を優先させる」という文脈でたくさん見つかるでしょう。

手元の Windows 7 でのデフォルトでは下記のようになっています。

優先順位   ラベル  プレフィックス
----------  -----  --------------------------------
        50      0  ::1/128
        40      1  ::/0
        30      2  2002::/16
        20      3  ::/96
        10      4  ::ffff:0:0/96
         5      5  2001::/32

今回のケースだと、宛先アドレスの IPv6 アドレスは ULA のみ*7なので、Rule 6 で宛先とラベルが一致さえしてしまえば、発信元のアドレスとして ULA が選択されることになります。

なので、ULA のプレフィックスのラベルを定義し、優先順位は通常の IPv6 アドレスと同じ 40 で設定しました*8

優先順位   ラベル  プレフィックス
----------  -----  --------------------------------
        50      0  ::1/128
        40      6  fd00::/8
        40      1  ::/0
        30      2  2002::/16
        20      3  ::/96
        10      4  ::ffff:0:0/96
         5      5  2001::/32

こうすることで、ULA を持つおうちサーバに対して、ULA のアドレスを使ってアクセスするようになりました*9

そして、手動設定が残った...

このポリシーテーブルをクライアントへ配信する方法は、Active Directory を使って、参加しているホストに適用する事は出来るみたいですが、そもそも、ドメインコントローラに繋ぐ時に、一時アドレスが選択されたらどうなるのか? という疑問があります。

DHCPv6 で stateful な「管理された自動構成」を実現するなら、このポリシーテーブルも DHCPv6 で配布できないか、と思うのですが、どうも、そういったオプションは無さそうです*10

ポリシーテーブルの設定という手動設定が残る、という屈辱の結果になってしまいました。ULA のプレフィックスがデフォルトのポリシーとして設定されるようになるか、RFC 6724 が更新されて、ULA の宛先の時に、グローバルの一時アドレスより ULA が優先されるようなルールになって欲しいと思うのですが...。

*1:複数の A レコードで、アドレスの上位ビットがある程度等しいと、「行き先のネットワークは同じだろう」ということで、ソートされずに済むケースがあるみたいです。

*2:参照: NTT IPv6閉域網フォールバック問題:Geekなぺーじ

*3:RFC 3879 に廃止する理由などが書かれています

*4:RFC 6724 の「2.1. Policy Table」にデフォルトがあります

*5:RFC6724: Default Address Selection for Internet Protocol Version 6 (IPv6):Geekなぺーじ

*6:RFC 6724 「10.6. Configuring ULA Preference」にある例は、発信元、宛先ともにグローバルと ULA の2つのアドレスを持っている時に、ULA が優先されない、という文脈なので、今回のケースとはちょっと違ってます。

*7:もし、おうちサーバの名前解決の結果にグローバルなアドレスが含まれるのであれば、ULA の優先順位を上げた方が良いでしょう。

*8:実際に設定するときに、netsh の interface ipv6 add prefixpolicies で、デフォルトのポリシーテーブルに追加したつもりでいて、再起動して確認すると、自分が追加したポリシーだけが入っている状態になりました。なので、一度、デフォルトのポリシーを削除し、自分が追加したいポリシーと一緒に、元のデフォルトポリシーも追加してやる必要があるみたいです。

*9:「fd00::/8」よりも「fc00::/7」の方が正しいかもしれませんが、とりあえず「fd00::/8」で実際に利用可能な状態にある ULA をカバーしていることになるはず。

*10:2014-4-28 追記: RFC 7078 で、DHCPv6 を使った配布方法が定義されたようです。但し、発行したのが今年の1月。一般的に実装されるのは、まだ先の話ですね。