前の、ipv6 版です、
ip アドレスを計算を簡便にするライブラリがあったので使ってみることにした。
ライブラリを読み込む
インストールする。
$ pip install netaddr
使う準備をする。
from netaddr import *
v4/ v6 ともに同じライブラリで扱うことが出来ます。
ip6アドレス(ネットマスク含む)を扱う。
サブネットマスク表記を含む場合は IPNetwork
を使う。
>>> ip = IPNetwork('fdc2:be69:2aeb:bd6c::1/64') >>> ip.ip IPAddress('fdc2:be69:2aeb:bd6c::1') >>> ip.network IPAddress('fdc2:be69:2aeb:bd6c::') >>> ip.netmask IPAddress('ffff:ffff:ffff:ffff::')
IPアドレスを扱う。
IPアドレスだけを扱うときは、IPAddress
を使う。
>>> ip = IPAddress('fdc2:be69:2aeb:bd6c::1') >>> ip IPAddress('fdc2:be69:2aeb:bd6c::1')
主に、IPNetworkの戻り値として得られる。
IPアドレスのネットワーク部を扱う。
>>> ip = IPNetwork('fd8f:4f9a:417f:b584::/64') >>> ip.network IPAddress('fd8f:4f9a:417f:b584::')
または、ちゃんとネットマスクを適用する。AND演算でネットマスクを掛けてやる。
>>> ip.ip & ip.network IPAddress('fd8f:4f9a:417f:b584::')
ホスト部を取り出す。
>>> ip IPNetwork('fd8f:4f9a:417f:b584::1/64') >>> ip.hostmask IPAddress('::ffff:ffff:ffff:ffff') >>> ip.hostmask & ip.ip IPAddress('::1')
ホスト部とネットワーク部をあわせてIPアドレスを作る。
>>> ip = IPNetwork('fd8f:4f9a:417f:b584::1/64') >>> ip.hostmask & ip.ip IPAddress('::1') >>> ip.netmask & ip.ip IPAddress('fd8f:4f9a:417f:b584::') >>> ( ip.hostmask & ip.ip ) + ( ip.netmask & ip.ip ) IPAddress('fd8f:4f9a:417f:b584::1') >>>
ホスト部を変更
ホスト部 と ネットワーク部を足し合わせると完成。
>>> ip = IPNetwork('fd8f:4f9a:417f:b584::1/64') >>> ip.ip & ip.netmask IPAddress('fd8f:4f9a:417f:b584::') >>> ip.ip & ip.netmask | IPAddress('::2') IPAddress('fd8f:4f9a:417f:b584::2') >>>
ネットワーク部を変更する。
>>> ip = IPNetwork('fd8f:4f9a:417f:b584::1/64') >>> ip2 = IPNetwork('fdab:ab1d:1ad8:75d8::/64') >>> ip.network IPAddress('fd8f:4f9a:417f:b584::') >>> >>> ip.hostmask & ip.ip IPAddress('::1') >>> ip.hostmask & ip.ip | ip2.network IPAddress('fdab:ab1d:1ad8:75d8::1') >>>
インターフェースIDからホスト部を生成
v6アドレスのホスト部は、インターフェースIDから生成される。 インターフェースIDは、MACアドレスから生成される。
これを覚えておけば、プレフィックスを変更するだけでIPv6アドレスが手軽に得られるようになるはずだ。
インターフェースID生成しホスト部と、ULAやリンクローカル、グローバルと組み合わせるだけでよいのだから。
EUI-64でホスト・アドレスを生成する。
MACアドレスからインターフェースIDを生成し、インターフェースIDとネットワーク部を併せULAを作る。
MACアドレスはEUI48であり、EUI-48→ EUI-64 に変換してインターフェースIDに使う。 インターフェースIDは、EUI-64へ変換したあと、上位7 ビット目をフラグを反転してあげる。
ビットの反転はXORを使う。XORは0を入れるとそのまま出力され、1を入れると反転する。 なので、該当ビットだけを反転させた値をint として使う。
EUI-48 ( MACアドレス)からEUI-64へ変換。
>>> mac = EUI('94:c6:91:3b:09:7c') >>> mac.eui64() EUI('94-C6-91-FF-FE-3B-09-7C') >>> int(mac.eui64()) 10720416491670014332 >>> int(mac.eui64()) ^ 144115188075855872 10864531679745870204 >>> EUI(int(mac.eui64()) ^ 144115188075855872) EUI('96-C6-91-FF-FE-3B-09-7C')
EUI-64に変換が終われば、7ビット目を反転させる。
出来上がったEUIを v6 のネットワーク部と合わせれば 「v6アドレス」をインターフェースIDから生成が完成である。
>>> ip = IPNetwork('fe80::') >>> ip.network | EUI('96-C6-91-FF-FE-3B-09-7C') IPAddress('fe80::96c6:91ff:fe3b:97c')
ここで、ビット反転に使ったINTは次のとおりである。
>>> format(144115188075855872, '064b') '0000001000000000000000000000000000000000000000000000000000000000'
上記の例でで生成したのはリンクローカルアドレスである。 Ubuntuが自動生成したリンクローカルアドレスと比較しよう。
3: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 94:c6:91:3b:09:7c brd ff:ff:ff:ff:ff:ff inet6 fe80::96c6:91ff:fe3b:97c/64 scope link
やったね。一致します。
Uubntu genrated fe80::96c6:91ff:fe3b:97c/64 python genrated IPAddress('fe80::96c6:91ff:fe3b:97c')
ULAを生成する
インターフェースIDが生成出来たので、ユニキャスト・リンクローカルアドレスを作りたい。
ランダムなプレフィックスを作る
$ openssl rand -hex 7 | sed -r 's|^(.{2})(.{4})(.{4})(.{4})|fd\1:\2:\3:\4::/64|' fd8b:eb64:cbf6:b721::/64
このランダムなプレフィックスと、MACアドレスを使って、ネットワーク部と合体させる。
>>> ip = IPNetwork('fd8b:eb64:cbf6:b721::/64') >>> ip.network | EUI(int(EUI('94:c6:91:3b:09:7c').eui64()) ^ 144115188075855872) IPAddress('fd8b:eb64:cbf6:b721:96c6:91ff:fe3b:97c') >>>IPNetwork(str(ip.network | EUI(int(EUI('94:c6:91:3b:09:7c').eui64()) ^ 144115188075855872))+'/64') IPNetwork('fd8b:eb64:cbf6:b721:96c6:91ff:fe3b:97c/64')
やった、これでインターフェースIDを用いたULAが生成できるぞ。
グローバルなプレフィックスが来ても大丈夫。自動生成に頼ってるのでいいんだけど、RA・DHCPv6ーPDとか疎通関連 で考えることが多く、設定しても疎通確認が自動生成に頼るのでテストがめんどくさい。
ULAをインターフェースIDでMACアドレスから作成する、コピペ用
コピペ用にまとめておく。
from netaddr import * ip = IPNetwork('fd34:5f2d:d3cb:8161::/64') mac = EUI('00:16:3e:fc:57:d5') a = IPNetwork(str(ip.network | EUI(int(mac.eui64()) ^ 144115188075855872))+'/64') print(a)
IPアドレスを進める。
>>> ip.ip IPAddress('fd8f:4f9a:417f:b584::1') >>> ip.ip + 1 IPAddress('fd8f:4f9a:417f:b584::2') >>>
/64 アドレスを/66へさらに分割する。
実際に使うことが出来ないが、計算だけはできる。
>>> for i in ip.subnet(66): ... print(i) ... fd8f:4f9a:417f:b584::/66 fd8f:4f9a:417f:b584:4000::/66 fd8f:4f9a:417f:b584:8000::/66 fd8f:4f9a:417f:b584:c000::/66
List comprehension でも表現できる。
演算子で計算がカッコいい
IPv6 でもv4 と同様に演算子(operand) を使ってIPアドレス計算するのがよくわかった。
おまけ
MACアドレス取得
ip link show eth0 | grep -Po '(?<=link/ether ).+'
v6 プレフィックス作成
openssl rand -hex 7 | sed -r 's|^(.{2})(.{4})(.{4})(.{4})|fd\1:\2:\3:\4::/64|'
MACアドレスからv6アドレスを生成
from netaddr import * ip = mac = EUI('00:16:3e:fc:57:d5') a = IPNetwork(str(IPNetwork('fd34:5f2d:d3cb:8161::/64').network | EUI(int(mac.eui64()) ^ 144115188075855872))+'/64' ) print(a)
これらを併せたワンライナー
python3 -c "from netaddr import *;\ print(IPNetwork(str(IPNetwork( \ '$(openssl rand -hex 7 | sed -r 's|^(.{2})(.{4})(.{4})(.{4})|fd\1:\2:\3:\4::/64|')').network \ | EUI(int(EUI('$(ip link show eth0 | grep -Po '(?<=link/ether ).+')').eui64()) \ ^ 144115188075855872))+'/64' ))"
コマンドとして
ipcalc コマンドとして、自動計算する MACアドレスと、IPv6アドレスを与えて、EUIからユニークなv6アドレスを計算するコマンドをこれからサクッと作れるわけです。
#!/usr/bin/env python3 from netaddr import * import sys ip = None if len(sys.argv)>2 : ip = IPNetwork(sys.argv[2]) else: ip = IPNetwork('fe80::/64') if len(sys.argv)>1 : mac = EUI(sys.argv[1]) else: raise Exception(f'{sys.argv[0]} MAC_ADDR') a = IPNetwork(str(ip.network | EUI(int(mac.eui64()) ^ 144115188075855872))+'/64' ) print(a)