家庭内LANでIPv6のグローバルIP(GUA)とLAN内専用の固定IP(ULA)の両方を使う
プライベートLAN内専用の固定IPv6アドレスがほしい
IPv6対応の家庭用ルータが普及してきた
市販の家庭用ルータを使う我が家のネットワークでは、LAN内はIPv4の固定IPアドレスを各マシンに割り当ててsshしたりプライベートなサーバ立てたりして使ってたんですが、昨今では、プロバイダからIPv4のアドレスだけでなくIPv6のプレフィックスも当然のように割り当てられるようになりました.
いよいよIPv6対応が当然の前提とされるようになると、アプリなどでIPv6アドレスでのテストなどもしなきゃダメなので、IPv6の固定アドレスなんて欲しくなります.
IPv6のリンクローカルアドレスはどうにも使いづらい
通常、プロバイダの接続サービスと家庭用ルータがIPv6に対応していて、マシン側のIPv6が有効になっていれば、プロバイダからグローバルネットワーク識別子の払い出しを受けたルータからのRA(Router Advertisement; ルータ広告)によって、ローカルネットワーク内の各マシンでグローバルスコープのGUA(グローバルユニキャストアドレス)が自動で構成されます.
それと同時にローカルネットワーク内専用のリンクローカルアドレス(fe80::/64)が各マシンで自動的に生成されて、固定IPのように使えることになってるのですが、リンクローカルアドレスを通信で使うには、IPv6アドレスに加えてScopeIDなんてのが必要だったりします.
このScopeID、困ったことにツールやアプリごとに指定する方法が異なっている上に、場合によっては対応していなかったり、どうにもこうにも使いづらいんですよね.
自動構成のGUAと固定のULAを同時に持たせれば・・?
幸いIPv6は一つのインタフェースに対してアドレスを複数持つことが出来る仕組みなので、自動構成によって割り当てられるグローバルIP(GUA)を持たせつつIPv4のプライベートアドレスっぽく使えるULA(ユニークローカルアドレス)を各マシンに固定IPとして割り当てて使うことは可能なはずです.
しかしながら、LinuxのNetworkManagerやmacOSでは、ネットワーク設定などでIPv6アドレスを 手動設定
とすると、ルータからのRAによるGUAの自動構成が行われなくなるため、ローカルネットワークの外とIPv6での通信が行えなくなり、自動構成のGUAを使うのか手動割当のULAかいずれかを選択する、といった状況になってしまいます.
さすがにこれでは本末転倒です.
そこで、もうちょっとなんとかできないかな〜、と色々調べてみました.
あとから追加でULAを割り当てる方法なら意外に簡単
GUAの自動構成はとりあえずそのままにして、ULAを自動構成させるにはルータ側でULAを自動構成させるための設定ができないといけませんが、さすがにそんな機能、安価な家庭用ルータに備わってる訳ありません.
しかし、あとから各マシンでコマンドを使ってULAを追加割当てする方法であれば、比較的に容易に実現できそうです.
(準備) ULAのサブネットプレフィックスを準備する
ULAはプライベートネットワーク内での使用が想定されているとはいえ、グローバルスコープのUnicastAddressとして定義されてるものですから、SubnetPrefix部分はちゃんと RFC4193 に従ってEUI-64識別子を生成しなければなりません.
ただ、このEUI-64識別子の生成は手計算では到底無理なのでツールを使います.
こちらに生成用のpythonコードが公開されてますので、これを使いました.
www.jdoodle.com
ネットワーク内の代表マシンを適当に一つ選び、そのマシンが使用するネットワークインタフェースのMACアドレスから識別子を生成します.
$ ./ula_generator.py Input MAC address: 12:34:56:78:ab:cd # ←ここにMACアドレスを指定する ULA Prefix -> fd98:c3f9:eb41::/48 First Subnet -> fd98:c3f9:eb41::/64 Last Subnet -> fd98:c3f9:eb41:ffff::/64 First IPv6 Address-> fd98:c3f9:eb41::1/64
ULA Prefixが得られたので、サブネット識別部を 1 (0001) とし、fd98:c3f9:eb41:1::/64
をサブネットプレフィックスとします.
(Step1) とりあえず手動で割り当てて試してみる
サブネットプレフィックスができたら、まずは手動でULAを追加割り当てしてみます.
とりあえずこれでGUAとULAとリンクローカルアドレスの3タイプのIPv6アドレスを持つ状態にすることが出来ます.
(Linuxの場合) ip コマンドを使う
Linux系OSは ipコマンド で割り当てます.
# (コマンド) $ sudo ip -6 addr add {IPv6Addr}/{PrefixLength} dev {IntarfeceName} # (例: eno1 に fd98:c3f9:eb41:1::2/64 を割り当て) $ sudo ip -6 addr add fd98:c3f9:eb41:1::2/64 dev eno1
コマンドを実行したら、 ifconfig
コマンドでネットワークの設定を確認します.
$ ifconfig eno1 eno1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.1.2 netmask 255.255.255.0 broadcast 192.168.1.255 inet6 2405:xxxx:xxxx:xxxx:yyyy:yyyy:yyyy:db31 prefixlen 64 scopeid 0x0<global> inet6 fd98:c3f9:eb41:1::2 prefixlen 64 scopeid 0x0<global> ★追加されている inet6 fe80::7404:eeb4:6b91:34b8 prefixlen 64 scopeid 0x20<link> inet6 2405:xxxx:xxxx:xxxx:yyyy:yyyy:yyyy:b06f prefixlen 64 scopeid 0x0<global> ether 70:85:c2:7f:bb:7d txqueuelen 1000 (イーサネット) RX packets 134402 bytes 68324237 (68.3 MB) RX errors 0 dropped 22346 overruns 0 frame 0 TX packets 87596 bytes 20566693 (20.5 MB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 device interrupt 16 memory 0xa3200000-a3220000
さらに、routeコマンドで静的ルーティングテーブルを確認します.
$ route -6 カーネルIPv6 経路テーブル Destination Next Hop Flag Met Ref Use If ip6-localhost/128 [::] U 256 2 0 lo 2405:xxxx:xxxx:xxxx::/64 [::] U 100 1 0 eno1 fd98:c3f9:eb41:1::/64 [::] U 256 3 0 eno1 ★追加されている fe80::/64 [::] U 100 2 0 eno1 [::]/0 _gateway UG 20100 13 0 eno1 : (省略) :
(macOSの場合) ifconfig を使う
macOSでは ipコマンドは標準で組み込まれてないようです.
Homebrewを使って iproute2mac
をインストールすれば ip コマンドが使えるのですが、
まずは標準の ifconfig で行うことにしました.
(コマンド) $ sudo ifconfig {IntarfeceName} inet6 add {IPv6Addr} prefixlen {PrefixLength} (例: en0 に fd98:c3f9:eb41:1::4/64 を割り当て) $ sudo ifconfig en0 inet6 add fd98:c3f9:eb41:1::4 prefixlen 64
コマンド実行後、 ifconfig
でインタフェースのアドレスを確認します.
$ ifconfig : (省略) : en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 options=400<CHANNEL_IO> ether 88:e9:fe:4e:3d:19 inet6 fe80::8a6:a219:8c03:1cb6%en0 prefixlen 64 secured scopeid 0x4 inet6 2405:xxxx:xxxx:xxxx:yyyy:yyyy:yyyy:27ae prefixlen 64 autoconf secured inet6 2405:xxxx:xxxx:xxxx:yyyy:yyyy:yyyy:7a3d prefixlen 64 autoconf temporary inet6 fd98:c3f9:eb41:1::4 prefixlen 64 ★追加されている inet 192.168.1.4 netmask 0xffffff00 broadcast 192.168.1.255 nd6 options=201<PERFORMNUD,DAD> media: autoselect status: active
さらに、 netstat -rn
で静的ルーティングテーブルを確認.
$ netstat -rn Internet6: Destination Gateway Flags Netif Expire default fe80::8a6:a219:8c03:1cb6%en0 UGcg en0 ::1 ::1 UHL lo0 : (省略) : fd98:c3f9:eb41:1::/64 link#4 UC en0 ★追加されている fd98:c3f9:eb41:1::4 88:e9:fe:4e:3d:19 UHL lo0 ★追加されている
(Step2) マシンの起動時にコマンドで自動付与するように構成する
コマンドで付加したULAはマシンをシャットダウンするたびに消えてしまうので、このままでは毎回起動するたびにログインしてコマンドで付与しなければいけません.
ならば、マシンの起動時に自動的にULAを付加できるようになれば本格的に運用できそうです.
(Linuxの場合) NetworkManagerのフックスクリプトを使う
デスクトップ版のLinuxでは、Network関係の設定は通常 NetworkManager が行っているようですね.
NetworkManager には dispatcher の仕組みがあって、 /etc/NetworkManager/dispatcher.d/
にスクリプトや実行ファイルを配置すると、ネットワークデバイスの起動や状態変更のタイミングで呼び出してくれたりするので、これを使います.
以下のスクリプトを配置しました.
ファイルの所有者はrootにしておく必要があります.
#!/usr/bin/env bash set -e # # $1 = interface name. # $2 = interface state. # # $2をみて interface の 'up'と'down'のタイミングででコマンドを実行. if [ "$2" = "connectivity-change" ]; then exit 0; fi if [ -z "$1" ]; then echo "$0: called with no interface" 1>&2 exit 1; fi IP_CMD=$(which ip) # インタフェース名 IFACE_NAME=eno1 # 追加で割当てるIPv6のULAとプレフィックス長 IP6_ULA=fd98:c3f9:eb41:1::2/64 if [ -n "${IP6_ULA}" ]; then # 対象interfaceの呼び出しに絞り込み if [ "$1" = "${IFACE_NAME}" ]; then # 'up' と 'down' の通知をトリガーにしてipv6アドレスを付加/削除するコマンドを実行 case "$2" in up) # インタフェースの起動. ipを追加割当 ${IP_CMD} -6 addr add ${IP6_ULA} dev $1 ;; down) # インタフェースのシャットダウン. ip割当を解除 ${IP_CMD} -6 addr del ${IP6_ULA} dev $1 ;; *) # Do nothing ;; esac fi fi exit 0;
- (参考) ディストリビューションごとの動作の違い
(macOSの場合) launchd を使う
macOSの場合、非ログイン状態でNetworkManagerのようにネットワークデバイス起動/切断時のフックを行う仕組みはちょっと見当たりませんでした.
なにか良い方法があればと思いますが、まずは設定だけでも・・・と、launchdを使って起動時にコマンドを実行させるようにしました.
ただし、コマンドの実行はNetworkデバイスのSetupが完了した後でなければ正しくルーティングテーブルが更新されません.
そこで、launchd のスクリプト起動タイミングには RunAtLoad
ではなく WatchPaths
を使い、マシンの起動後にネットワークサービスが DNS情報/etc/resolv.conf
を変更したタイミングで実行させるようにします.
(※ 今回macOSでは起動後のip付加のみ. downイベント相当のフックに使えそうなものはちょっと見当たらず・・要検討...)
以下の設定ファイルを所有者をrootにして /Library/LaunchDaemons/
に配置します.
呼び出すスクリプトファイルは、 /Users/hoge/bin/reg_ipv6_ula.bash
としました.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!-- jp.or.regv6ula.plist. --> <!-- /Library/LaunchDaemons/ に配置 --> <plist version="1.0"> <dict> <key>Label</key> <string>reg_ipv6_ula</string> <key>ProgramArguments</key> <array> <string>/usr/bin/env</string> <string>bash</string> <string>/Users/hoge/bin/reg_ipv6_ula.bash</string> <string>add</string> </array> <key>Disabled</key> <false/> <key>WatchPaths</key> <array> <string>/etc/resolv.conf</string> </array> <key>KeepAlive</key> <false/> <key>StandardErrorPath</key> <string>/Users/hoge/log/reg_ula_err.log</string> </dict> </plist>
呼び出すスクリプトファイル(reg_ipv6_ula.bash)の内容は以下の通り.
#!/usr/bin/env bash # # 登録/削除用Script # IFCFG_CMD=$(which ifconfig) # インタフェース名 IFACE_NAME=en0 # 割り当てるULA IPV6_ULA=fd98:c3f9:eb41:1::4 if [ -n "$1" ]; then case "$1" in remove) echo "unregister ULA.. ${IPV6_ULA}" ${IFCFG_CMD} ${IFACE_NAME} inet6 remove ${IPV6_ULA} prefixlen 64 echo "Done." ;; add) echo "register ULA.. ${IPV6_ULA}" ${IFCFG_CMD} ${IFACE_NAME} inet6 add ${IPV6_ULA} prefixlen 64 echo "Done." ;; *) ;; esac fi
(参考) ところで・・Windowsではどうするのだろう?
Windowsの場合はネットワークのプロパティからIPv6を 手動割当て
とした場合でも、手動割り当てしたULAに加えてGUAもちゃんと自動構成されるようでした.
この動作だと「ULAだけ使う」という選択肢はなさそうですが、こちらは設定が簡単で良いですね.