IPv6 マルチホーム+マルチルーターは難しいという話 - JANOG57 NOC

こんにちは、segreです。

JANOG57 NOC(会場WiFiを提供するチーム)でバックボーンチームのリーダーをやっていて、せっせとバックボーンを作っています。 そんなJANOG57 NOCでの挑戦の一つとして冗長化・負荷分散というものがあります。

NOC構築中にIPv6でマルチホーム+マルチルーターの構成を取ると困ったことになる事がわかったので共有します。解決策があるのなら助けてほしい。

今回の構成

ざっくり今回構想している構成です。

  • 上位回線: IC-01は上位プロバイダーのルーターです。そこからダークを2回線会場に引き込んで接続しています。
  • ルーター: 2台のYAMAHA RTX3510で、RT-C-01とRT-C-02です。SFP+が2つあるのでアップリンク、ダウンリンクそれぞれに10Gで接続しています。
  • コアスイッチ: Juniper EX4400で、SW-C-01とSW-C-02です。Virtual Chassisを組んでいます。まとめてSW-C-COREと呼びます。
  • エッジスイッチ: 可能なものに関してはコアスイッチとLACPで接続しています。実際にはエッジスイッチ配下のスイッチも存在します。ここからAPと接続します。

上位からはそれぞれ異なるPrefix(/64)が払い出されており、この2台のルーターを用いて負荷分散と冗長化を実現するのが今回の目標です。

論理構成

IPv4

IPv4の構成です。 IPv4DHCPVRRPを組み合わせることで冗長・負荷分散を達成しています。

負荷分散は、DHCPスコープを半分に分割し、それぞれのGatewayをRT-C-01とRT-C-02に設定して配布しています。クライアントはリース時間等条件が同じ場合はDHCP Offerを早く返した方のIP/Gatewayを採用するため、自然とトラフィックが分散されます。

DHCPはSW-C-COREでDHCP Relayを設定して、Kea DHCPさくらのクラウドで構築しています。 DHCPサーバーは2つ用意してそれぞれで別のDHCPプールを設定しています。

冗長化は、各ルーターGatewayアドレスにVRRPを設定しています。 以下の記事が参考になります。Active-Standbyで2つのVRRPを組み、VIPを各ルーターに用意しています。片系障害時にはVIPがもう片系に移動することで通信を維持します。

WAN回線障害時に備える(VRRPの 2つのグループで負荷を分散 + フレキシブルLAN/WANポートを使用) : コマンド設定

IPv4論理構成

DHCPプール

実際にチューニング無しでどのくらいGatewayが分散されるのか試してみたところ、大体7:3くらいでした。思ったより偏りますね。

そこで、Kea DHCPのclient-classを使用して、MACアドレスベースで配るDHCPプールを変化させることにしました。

16. Client Classification — Kea 3.0.2 documentation が参考になります。MACアドレスの末尾が偶数か奇数かによって配るGatewayを変化させています。

    "client-classes": [
            {
                "name": "Class-MAC-Odd",
                "test": "match('[13579bdfBDF]', substring(hexstring(pkt4.mac, ''), -1, 1))"
            },
            {
                "name": "Class-MAC-Even",
                "test": "match('[02468aceACE]', substring(hexstring(pkt4.mac, ''), -1, 1))"
            }
        ],

を用いて

    "subnet4": [
        {
            "id": 1,
            "subnet": "10.57.0.0/18",
            "pools": [ { "pool": "10.57.32.1 - 10.57.63.254" } ],
            "option-data": [
                {
                    "name": "routers",
                    "data": "10.57.0.4"
                },
                {
                    "name": "routers",
                    "data": "10.57.0.5",
                    "client-class": "Class-MAC-Even"
                }
            ],

のように設定してやればGatewayが完全に半分に分散されます。やったね。

IPv6

本題のIPv6です。

回線のアドレスとしてIC-01は3fff:1:1:3011::3/64(図中で3011::3)、RT-C-01は3011::11, RT-C-02は3011::12を持っています(これをND Proxyすればいいじゃないかという話なんですが、RTXではRA以外で設定したアドレスはND Proxyできない仕様でした)。

IC-01では以下のstatic routeが入っており各Prefixが各ルーターに分散されています(StaticのNext-Hopのみ変更可能です)。

3fff:1:1:2000::/64 via 3011::11
3fff:1:1:2006::/64 via 3011::12

ルーターはVLAN20としてそれぞれPrefix: 2000::/64、Gateway: 2000::1(正確にはRT-C-01のLink-Local Unicast)Prefix: 2006::/64、Gateway: 2006::1 を下位にRouter Advertiseします。

RT-C-01と02はそれぞれ別のRAを返すためWiFiクライアントには2つのPrefixが割り当てられます。 この構成を取ることによって、クライアントが自律的にSource AddressとGatewayを選択することでルーターの負荷分散を図ること、片系が落ちても別のPrefixを用いることでもう片系に切り替わる冗長性を担保することを目的としています。

IPv6論理構成

実際にWiFiに接続されたRaspberry Piの様子です。想定通り2つのPrefixのアドレスがついていることがわかります。

3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether dc:a6:32:91:cd:75 brd ff:ff:ff:ff:ff:ff
    inet6 3fff:1:1:2006::cd75/64 scope global dynamic mngtmpaddr proto kernel_ra
       valid_lft 2591860sec preferred_lft 604660sec
    inet6 3fff:1:1:2003::cd75/64 scope global dynamic mngtmpaddr proto kernel_ra
       valid_lft 2591547sec preferred_lft 604347sec
    inet6 fe80::cd75/64 scope link proto kernel_ll
       valid_lft forever preferred_lft forever

また、想定通りGatewayが2つ設定されています。

3fff:1:1:2003::/64 dev wlan0 proto kernel metric 256 expires 2591976sec pref medium
3fff:1:1:2006::/64 dev wlan0 proto kernel metric 256 expires 2591819sec pref medium
fe80::/64 dev wlan0 proto kernel metric 256 pref medium
default via fe80::34ef dev wlan0 proto ra metric 1024 expires 1776sec hoplimit 64 pref medium
default via fe80:34e7 dev wlan0 proto ra metric 1024 expires 1619sec hoplimit 64 pref medium

ルーターでIC-01向けに疎通を確認して(RTXでは ip keepalive)、ICMP Replyが3回連続返ってこなかった場合にdefault-lifetimeとpreferred_lifetimeを0としたRAを広告することにより(RTXではLuaスクリプトにより実現)正常に片系にトラフィックが寄ることを確認しました。これにより冗長性は確保されています。

各クライアントは例えば図中のphone1は

2000::10/64 via 2000::1
2006::10/64 via 2006::1

というアドレスを付けているので、Source Addressとして 2000::10/64 を選択した場合には 2000::1 であるRT-C-1をGatewayとして出ていけば負荷分散も実現できるはずでした。

発生した問題

この設計の致命的な問題は、PrefixとGatewayが紐づいているという勘違いです。

2000::10/64 via 2000::1

のように記述しましたが、実際にRAにより取得される情報はPrefixが2000::/64であることとGatewayとして2000::1を設定することです(その様子は先程のRaspberry Piでも見て取れます)。 そのため、Source Addressに2000::10/64を選択した場合に2006::1であるRT-C-02をGatewayとする可能性もあるということです。

実際にRaspberry Piでインターネット向けにpingを実行するとSource Addressは 3fff:1:1:2006::cd75Gateway3fff:1:1:2003::1 (RT-C-01, 正確にはLink-Local Unicast) を選択していることがわかりました。今回上流はPrefix FilterやuPRF等を実装していないため、このようなねじれが発生しても即座に通信不能になることはないのですが、各ルーターでStateful Firewallをかけた場合に通信不能になることが想定されます。

なお、Source Addressの選択についてはRFC6724 Default Address Selection for Internet Protocol Version 6 (IPv6) の5. Source Address Selectionに言及がありますが、Gatewayに紐づいたPrefixを使用するといったルールは存在しません。

また、この問題はRFC8028 First-Hop Router Selection by Hosts in a Multi-Prefix Networkの3.2で言及されています。

A host SHOULD select default routers for each prefix it is assigned an address in.

しかしどのOSも実装が進んでいないのが現状のようです。

調べてみたところ、本事象の解決を試みるInternet-Draftは複数提出されていますがいずれも進展はないようです。

draft-gont-6man-rfc8028-update-00

draft-link-6man-gulla-01

解決策

根本的な解決策はルーター側からクライアントが例えばRT-C-01から割り当てられたPrefixをSource Addressに使用するときにGatewayをRT-C-01とするように強制するといった挙動をすることです。ただし、現在そのような実装はないようです。

そのため、現実的なラインとしてはStateful Firewallを未実装にする、もしくはRT-C-01, 02間を接続して正しいGatewayを通るようにルーティングするといった対応が必要になります。

もっと良い解決策をお持ちの方はぜひ教えて下さい。

最後に

IPv6マルチホームは難しいですね。

最終的にどのような実装を行ったか、どのくらいトラフィックが分散されたか等はJANOG57の以下の登壇プログラムにてお話する予定です。事の顛末が気になる方はぜひ議論に参加しに来てください。Day3の最後のプログラムです。

イベントネットワークの最前線を語ろう ― JANOG57会場ネットワークの知見とこれから - JANOG57 Meeting in Osaka

ちなみにこれはコングレB2Fという本会議場がある会場のネットワークですが、今回のJANOG57では3会場を使うので、各会場に別の回線が引かれています。そのため、同時に3つのNOCをこなしているようなもんです。大変!覚悟を持って頑張っていますが、優しくしてください。

当日は慌ただしくしているかもしれませんが、お暇なときにNOCルームにも遊びに来てくださいね。NOCツアーもあります。