LinuxにおけるDHCPv6 Prefix Delegation(DHCPv6-PD)の設定:本番環境実践ガイド

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

課題提起:静的IPv6サブネット運用の苦悩

半年前、私たちのチームは12か所の地方拠点オフィスのエッジルーティングを再設計しました。各拠点にはUbuntuベースのゲートウェイが2台設置され、3つの顧客用VLAN、1つのIoTネットワーク、そして社内ラボセグメントを処理しています。初期展開をシンプルにするため、すべての下位インターフェースに静的な/64プレフィックスをハードコードしました。上流のISPが割り当てブロックを変更することは滅多にないと思い込んでいたのです。

しかし、その想定は甘かったことが判明しました。予告なしに行われた深夜のメンテナンス中に、ISP側が割り当てていた/48ブロックを変更したのです。その瞬間、12拠点すべてのIPv6接続が一斉に失われました。

60以上のVLANインターフェースが通信不能になり、エンジニアが機器を1台ずつ設定更新する羽目になり、4時間近くに及ぶ緊急対応に追われました。手動管理は、実際の運用環境における流動性に耐えられませんでした。必要だったのは、Linuxルーターが上流からプレフィックスブロックを取得し、それを/64サブネットに自動分割し、新しい範囲をクライアントに再通知する仕組みでした。そこで導入したのがDHCPv6 Prefix Delegation(DHCPv6-PD)です。

DHCPv6 Prefix Delegationの基本概念

RFC 3633で策定され、RFC 8415で改定されたDHCPv6-PDは、従来のステートフルなDHCPv4やDHCPv6 IA_NAとは異なる方法でアドレスを扱います。単一のIPアドレスを1つのインターフェースに付与するのではなく、上流のサーバーがルーティングプレフィックス全体(通常は256個のサブネットとなる/56、あるいは65,536個のサブネットとなる/48)を境界ルーターに委譲します。

主要な役割と識別子

  • 委譲ルーター(Delegating Router – DR): 上流のIPv6アドレスプールを管理するISPのルーターやコアスイッチ。
  • 要求ルーター(Requesting Router – RR): 自社のLinuxエッジシステム。WANリンク経由で割り当てブロックを取得し、分割した/64サブネットをローカルインターフェースに割り当てます。
  • プレフィックス委譲用Identity Association(IA_PD): 委譲されたプレフィックス、有効期間(Valid Lifetime、一般的には30日)、推奨期間(Preferred Lifetime、一般的には7日)を保持するDHCPv6コンテナ。
  • SLAACとルーター広告(Router Advertisement – RA): エンドポイント端末がDHCPv6-PDと直接通信することはありません。Linuxマシンが分割された/64をLANポートにバインドすると、ICMPv6 RAを使用してそのプレフィックスを広報し、クライアント端末が自身のアドレスを自動設定できるようにします。

4方向ハンドシェイク

ネゴシエーションには、クライアント側のUDPポート546とサーバー側のポート547が使用されます。

  1. SOLICIT: Linuxルーターがff02::1:2宛てにマルチキャストメッセージを送信し、IA_PDを要求します。
  2. ADVERTISE: ISPルーターが候補となるブロック(例: 2001:db8:1a00::/56)を提示します。
  3. REQUEST: Linuxルーターが提示されたプールを正式に要求します。
  4. REPLY: ISPルーターがリースを確定し、推奨および有効期限のタイマーを通知します。

ハンズオン実践:Linux DHCPv6-PDゲートウェイの構築

以下のリファレンストポロジを想定します。3つの物理NICを備えた専用ゲートウェイ(Ubuntu 24.04 LTS)です。

  • eth0: ISPへのWANアップリンク。
  • eth1: 社内LAN(サブネットID 0x01)。
  • eth2: ゲストおよびIoTネットワーク(サブネットID 0x02)。

ステップ1:カーネルの準備とIPv6パケット転送の有効化

Linuxではパケット転送を有効化すると、デフォルトでルーター広告(RA)が無視される仕様になっています。ルーターは転送処理を行いながらも、WAN側のRAを通じてデフォルトルートを学習する必要があるため、アップリンク側でaccept_ra = 2を設定します。

# /etc/sysctl.d/90-ipv6-routing.conf
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.default.forwarding = 1

# パケット転送時でもWAN側でRAを受信できるように設定
net.ipv6.conf.eth0.accept_ra = 2

# ルーターのインターフェースで一時的なプライバシーアドレスを無効化
net.ipv6.conf.all.use_tempaddr = 0

実行中のカーネルにこれらの設定を反映します。

sudo sysctl --system

ステップ2:systemd-networkdでのDHCPv6-PD設定

現代のLinuxディストリビューションでは、systemd-networkdがプレフィックスの分割処理をネイティブでサポートしています。dhcp6cやradvdなどの個別デーモンを追加する必要はありません。

まず、eth0のWANアップリンクでIA_PDを要求するように設定します。

# /etc/systemd/network/10-wan.network
[Match]
Name=eth0

[Network]
DHCP=ipv6
IPv6AcceptRA=yes
LinkLocalAddressing=ipv6

[DHCPv6]
PrefixDelegation=yes
WithoutRA=solicit

分割した最初の/64(サブネット1)をeth1のオフィスLANに割り当てます。

# /etc/systemd/network/20-lan.network
[Match]
Name=eth1

[Network]
LinkLocalAddressing=ipv6
IPv6SendRA=yes
DHCPPrefixDelegation=yes

[DHCPPrefixDelegation]
SubnetId=1

[IPv6SendRA]
Managed=no
OtherInformation=no
RouterLifetimeSec=1800

2つ目の/64(サブネット2)をeth2のゲスト端末向けに切り出します。

# /etc/systemd/network/30-guest.network
[Match]
Name=eth2

[Network]
LinkLocalAddressing=ipv6
IPv6SendRA=yes
DHCPPrefixDelegation=yes

[DHCPPrefixDelegation]
SubnetId=2

[IPv6SendRA]
Managed=no
OtherInformation=no
RouterLifetimeSec=1800

設定を適用します。

sudo systemctl restart systemd-networkd

ステップ3:WIDE-DHCPv6(dhcp6c)とradvdを使用した代替構成

systemdを採用していないDebian 11、Alpine、またはカスタムOpenWrtイメージを使用している場合は、従来のwide-dhcpv6-clientとradvdの組み合わせを利用します。

両パッケージをインストールします。

sudo apt-get update && sudo apt-get install -y wide-dhcpv6-client radvd

/etc/wide-dhcpv6/dhcp6c.confで委譲クライアントを設定します。

interface eth0 {
    send ia-pd 0;
    request domain-name-servers;
};

id-assoc pd 0 {
    prefix-interface eth1 {
        sla-id 1;
        sla-len 8; # 8ビット拡張: 56 + 8 = /64
        ifid 1;    # このインターフェースのルーター自身のアドレスを::1に設定
    };

    prefix-interface eth2 {
        sla-id 2;
        sla-len 8;
        ifid 1;
    };
};

クライアントデーモンを起動します。

sudo systemctl restart wide-dhcpv6-client
sudo systemctl restart radvd

ステップ4:動作確認とトラフィック検証

eth1とeth2にルーティング可能なグローバルユニキャストアドレス(GUA)が設定されたか確認します。

ip -6 addr show dev eth1
ip -6 addr show dev eth2

eth1に2001:db8:1a00:1::1/64のように、上流ブロックに合致したアドレスが付与されているはずです。続いて、WANのRAから学習したデフォルトゲートウェイをカーネルルーティングテーブルで確認します。

ip -6 route show default

WANインターフェース上でリース交換パケットをキャプチャし、タイマーのネゴシエーションを確認します。

sudo tcpdump -vvn -i eth0 port 546 or port 547

出力内のIA_PDブロックを確認してください。割り当てられたプレフィックス、推奨期間(例: preferred-lifetime 604800)、有効期間(例: valid-lifetime 2592000)が確認できます。

ステップ5:DHCPv6-PD向けのファイアウォール設定

厳格なDROPポリシーを設定していると、ハンドシェイクが完了する前にDHCPv6通信が遮断されてしまうことがあります。WANインターフェース上でのリンクローカルネゴシエーションを許可するようにnftablesを更新します。

# /etc/nftables.conf
table inet filter {
    chain input {
        type filter hook input priority filter; policy drop;

        # 確立済みの通信およびループバックトラフィックを許可
        ct state established,related accept
        iifname "lo" accept

        # 上流のリンクローカルルーターからのDHCPv6応答を許可
        iifname "eth0" ip6 saddr fe80::/10 udp sport 547 udp dport 546 accept

        # 重要なICMPv6近隣探索およびルーター探索を許可
        ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert, nd-router-advert } accept
    }
}

本番運用6か月で得られた現場の知見

DHCPv6-PDの自動化により、各拠点における手動でのアドレス管理は完全に不要になりました。先月実施された上流側のリナンバリングテストでは、ISPがプレフィックス割り当てを2001:db8:1a00::/56から2001:db8:2b00::/56へと変更しました。各ゲートウェイはその変更を自動検出し、下位インターフェースに新しいアドレスをバインドして、30秒以内に更新されたRAを送信しました。その間、TCP接続の切断は一切発生しませんでした。

私たちが実践している3つの運用ルールは以下の通りです。

  • DUIDを固定・保護する: マシンの再構築時にも/etc/systemd/networkd.confや/var/lib/dhcpv6/dhcp6c_duidを確実に保持してください。DUIDが変化すると、ISPはゲートウェイを別デバイスと認識し、全く異なるプレフィックスを割り当ててしまいます。
  • 常に/56を要求する: 上流プロバイダーによっては、指定がないと単一の/64しか割り当てない場合があります。/56を要求すれば256個のサブネットを確保でき、Dockerネットワークやハイパーバイザー、ゲストVLANを運用するのに十分な余力を確保できます。
  • 上流のマルチキャストスヌーピングを確認する: WANポートをマネージドスイッチ経由で接続している場合、MLDスヌーピングによってff02::1:2マルチキャストフレームがプロバイダー機器に届く前に破棄されていないか確認してください。
Share: