それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

python の netaddr で ip6( ipv6) アドレスを扱う

前の、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)