DHCPv6-PDでIPv6のサブネットワークを作成

目次

フレッツ光ネクストは、ひかり電話契約をすると/64 よりも小さなプレフィックスを使用できます。このプレフィックスを使用すると、ひかり電話ルータの下に別のルータを置いて自分のネットワークを持つことができます。今回はこの方法で自分のネットワークを IPv6 の世界に接続してみます。

ひかり電話契約をしていない場合は、IPv6 と IPv4 のデュアルスタックで接続のように NAPT(マスカレード)を使用してネットワークを接続します。

ここでの設定は、ZOOT NATIVE (DS-Lite)でネット接続の状態になっている前提での説明です。追加で必要なradvdのインストール方法は、IPv6 と IPv4 のデュアルスタックで接続を参照して下さい。

サイト構成

Raspberry Pi のインターフェイスは、次のように使用します。

インターフェイス ネットワーク アドレス
eth0 自分のネットワーク/64 DHCPv6-PD で自動設定
eth1 Internet(IPoE) DHCPv6 で自動設定

自分のネットワーク内の端末は、Raspberry Pi が送信する RA で自動的に IPv6 アドレスやデフォルトルータが設定されます。

DHCPv6-PD によるプレフィックスの委譲

自分のネットワークを上位のネットワークへ接続するためには、プレフィックス委譲を受けます。この委譲を受けるのに使われるのが DHCPv6-PD です。上位のルータは、DHCPv6-PD によるやりとりでプレフィックスの払い出しと同時に、払い出したプレフィックスに相当する IPv6 パケットをどこに送ったらよいかを記録しています。

DHCP は IPv4 で IP アドレスなどネット接続に必要な情報を自動設定する時にお馴染みですが、IPv6 用の DHCP(DHCPv6)は名前は同じでも全く別のプロトコールです。もっとも IPv4 で使った DHCP クライアントは、DHCPv6 クライアントとしても機能します。

DHCPv6 対応クライアントには何種類かありますが、今回は Raspbian(jessie)に標準でインストールされているdhcpcdを使用します。他の DHCPv6 クライアントを使用してプレフィックスの委譲を受ける時は、IPv6 プレフィックス委譲 (DHCPv6-PD) – Archlinuxを参照して下さい(注 1)。

dhcpcd を有効に

dhcpcdは、ZOOT NATIVE (DS-Lite)でネット接続で無効にしているので、次のコマンドでシステムが起動時する時に自動的に実行開始されるようにします。

$ sudo systemctl enable dhcpcd.service

dhcpcd の設定

dhcpcd の設定は、/etc/dhcpcd.confに書きます。

# この上はデフォルトのまま

# IPv4は設定しない
ipv6only

# LANのIPv4用
# /etc/network/interfacesで設定される。
interface eth0
noipv6

# https://wiki.archlinuxjp.org/index.php/IPv6#.E3.83.97.E3.83.AC.E3.83.95.E3.82.A3.E3.83.83.E3.82.AF.E3.82.B9.E5.A7.94.E8.AD.B2_.28DHCPv6-PD.29
duid
noipv6rs
waitip 6

# WANのIPv6用 DHCPv6-DP
interface eth1
ipv6rs
iaid 1
# use the interface connected to your LAN
ia_pd 1 eth0

ひかり電話ルータは IPv4 の DHCP サーバ機能を持っているので、ip6only(noipv4)をしないと DHCP により IPv4 のアドレスが eth1 に割り当てられます。

interface 設定を削除

dhcpcdでインターフェイスを設定するので/etc/network/interfacesにある eth1 の設定は不要です。そこで eth1 に関する部分は全て削除してしまいます。

eth0 もdhcpcdで設定できるので不要な気がしますが、これを削除するとシステムを起動する時に DHCP サーバがエラーで実行開始できなくなってしまいます。また DHCPv6-PD でプレフィックスの委譲を受ける時に/etc/resolv.confの内容がひかり電話ルータからのものになってしまいます。そのため eth0 はinterfacesで設定します。

ufw の設定

プレフィックス委譲を使って自分のネットワークを接続すると、ネットワーク内の全ての端末にグローバルな IPv6 が割り振られます。そのためアドレスが分かればほとんど何の制限もなしに世界中からアクセスすることが可能となります。

その裏返しとして想定外の端末が世界に公開されてしまうなど、NAPT(マスカレード)を使ったIPv6 と IPv4 のデュアルスタックで接続より各端末がより危険な状態に置かれ、世界中から自分のネットワーク内の端末が攻撃される危険性にさらされます。

この危険性を少しでも小さくするには、ルータを通過するパケットの選別が非常に大切です。

と言っても今回は実験なので必要最小限の制限とし、次のような一般的な最小限のポリシーとしました。

  • 自分のネットワークから外へは無制限に出ていける。
  • 自分のネットワークへは、出て行ったパケットに関連するパケットのみが入れる。

そこで/etc/default/ufwを次のように変更し、許可されたパケット以外は全て破棄するようにします。

DEFAULT_FORWARD_POLICY="DROP"
DEFAULT_INPUT_POLICY="DROP"
DEFAULT_OUTPUT_POLICY="ACCEPT"

許可するパケットのルールは、ufwの設定ファイルに追加します。

/etc/ufw/before.rules(IPv4 用)

# 以下を追加する。
# accept all from LAN and returned RELATED or ESTABLISHED packets
-A ufw-before-forward -i eth0 -j ACCEPT
-A ufw-before-forward -m state --state RELATED,ESTABLISHED -j ACCEPT

/etc/ufw/before6.rules(IPv6 用)

# 以下を追加する。
# https://www.cert.org/downloads/IPv6/ip6tables_rules.txt
# accept from LAN
-A ufw6-before-input -i eth0 -j ACCEPT

# allow DHCPv6-PD
#-A ufw6-before-input -p udp -s fe80::/10 --sport 547 -d fe80::/10 --dport 546
 -j ACCEPT
-A ufw6-before-input -p udp -s fe80::/10 -d fe80::/10 --dport 546 -j ACCEPT

-A ufw6-before-input -p icmpv6 -s fe80::/10 --icmpv6-type 130 -j ACCEPT

# accept all from LAN and returned RELATED or ESTABLISHED packets
-A ufw6-before-forward -i eth0 -j ACCEPT
-A ufw6-before-forward -m state --state RELATED,ESTABLISHED -j ACCEPT

ufwに元からあったルールを少し緩めて宛先ポート番号だけで許可するようにしているのは、DHCPv6-PD によるプレフィックスの委譲を受けるためです。

DHCPv6 の通信を許可するルールが before6.rules に入っていたので大丈夫だと思ったのですが、最初は DHCPv6-PD によるプレフィックスの委譲を受けることができませんでした。そこで原因を探るために捨てているパケットを見ていたら、ひかり電話ルータの DHCPv6 サーバから送られてくるパケットのポート番号が 547 ではありませんでした。このため送信元のポート番号を無視して宛先のポート番号だけを見るようにしました。

IPv6 の転送を許可

IPv4 をトンネルするために IPv4 の転送を許可していましたが、加えて IPv6 も転送するように設定します。

/etc/ufw/sysctl.conf

# 追加する
net/ipv6/conf/default/forwarding=1
net/ipv6/conf/all/forwarding=1

radvd の設定

radvdを使って RA 送信します。自分のネットワークに接続された端末は、この RA を受信することで自動的に IPv6 の設定します。

radvdが配布する RA の設定は/etc/radvd.conf/radvd.confに書きます。

interface eth0 {
  AdvSendAdvert on;
  #MaxRtrAdvInterval 30;
  prefix ::/64 {
    AdvRouterAddr on;
  };
};

ここで指定しているプレフィックス::/64は、マジックワードです。このアドレスを指定すると、指定された interface の全グローバルアドレスからプレフィックスを決定して RA を送り出してくれます。今回のように DHCPv6-PD で指定されるアドレスが何になるかわからない場合に便利な指定方法です。

IPv6 での通信確認

以上でルータの設定は完了です。ここで再起動して自分のネットワーク内の端末から IPv6 で通信できるか試してみます。

確認方法とトラブルシューティングは、IPv6 と IPv4 のデュアルスタックで接続のIPv6 での通信確認を参照して下さい。

設定に間違いがないのに通信できない

今回どこにも間違いはないのに外と通信できないことがありました。自分のルータとひかり電話ルータの間に置いた端末とは通信できるのでルータの設定に問題はないはずですが、なぜかひかり電話ルータを越えて外に出られませんでした。

ひかり電話ルータに Web ブラウザで接続して DHCPv6-PD によるプレフィックスの払い出し状況を確認するとなぜか Raspberry Pi の LAN 側の MAC アドレスが登録されていました。そこから推測するとひかり電話ルータは外からの通信を接続されていない RasPi の LAN 側宛に送ってしまっているのではないかと推測しました。

RaspPi を何度再起動して DHCPv6-PD をしなおしても登録されている MAC アドレスは変わりません。

そこで/etc/dhcpcd.duid/etc/dhcpcd.secretを削除して再起動してみました。するとひかり電話ルータに登録されている MAC アドレスは変化ありませんでしたが問題なく外と通信できるようになりました。

DS-Lite によるトンネル作成

IPv6 に関する設定は以上ですが、interfacesから eth1 に関する設定を全て削除してしまったので IPv4 をトンネルするインターフェイスが作成されません。そこでdhcpcdが設定のフェーズ毎に実行する/etc/dhcpcd.enter-hook(注 2)にスクリプトを書いておいてトンネルを作成します。スクリプトが実行される時に設定される変数については、dhcpcd-run-hooksのマニュアルを参照して下さい。

/etc/dhcpcd.enter-hook

#!/bin/sh
# http://techlog.iij.ad.jp/contents/dslite-raspi

REMOTE='2404:8e01::feed:101'

WAN_IF='eth1'
TUN_IF='ip6tnl1'

dig_tunnel()
{
    #  already tunnel exist?
    ip addr show | grep TUN_IF>/dev/null
    if [? -eq 0 ] ; then
    return
    fi

    LOCAL=`ip -6 addr show WAN_IF scope global |grep inet6 | awk '{print2}'`
    if [ "LOCAL" = '' ]; then
    return
    else
    logger dig DS-Lite tunnelWAN_IF LOCAL ===REMOTE
    fi

    # IPIP6 tunnel linkup
    ip -6 tunnel add TUN_IF mode ip4ip6 remoteREMOTE local LOCAL devWAN_IF
    ip link set dev TUN_IF up

    # IPv4 routing
    route delete default
    route add default devTUN_IF
}

delete_tunnel()
{
    ip addr show | grep TUN_IF>/dev/null
    if [? -ne 0 ] ; then
    return
    fi

    logger delete DS-Lite tunnel
    # delete DS-Lite tunnel
    ip -6 tunnel delete TUN_IF

    # delete default route
    route delete default
}


if [ "interface" = WAN_IF ] ; then
    ifif_up ; then
    case "reason" in
        BOUND6|REBIND6)
        dig_tunnel
        ;;
    esac
    else
    if [ "reason" = EXPIRE6 ] ; then
        delete_tunnel
    fi
    fi
fi

参照と脚注

  1. 今回使った dhcpd 以外にも IPv6 用の DHCP クライアントはいくつかありますが、dibblerWIDE-DHCPv6では上手くプレフィックスの委譲を受けることができませんでした。委譲はしてもらえていたのかも知れませんが、インターフェイスにアドレスが設定されませんでした。自分で設定する必要があったのかも知れません。
  2. dhcpcdは、/etc/dhcpcd.enter-hookだけでなく/etc/dhcpcd.exit-hookも実行します。しかし今回順番は関係ないので、トンネルの作成と破棄の両方共/etc/dhcpcd.enter-hookに書いています。