はじめに
タイトルの後半部分を言いたかっただけです
こんにちは。株式会社インティメート・マージャーの十文字です。
IPv6の仕様って何度見ても覚えられないですよね。
ネットワークエンジニアやインフラエンジニアならまだしも、普段バックエンド開発をしている身としてはIPv6に触れる機会というのは少なく、毎回部分的に調べて満足してしまっています。
そこで今回は、IPv6の仕様を調べてまとめていこうと思ったのですが、私のような初学者にはRFCの仕様はとっつきづらかったため、今回はPythonの標準ライブラリであるipaddress
のコードを読みながらIPv6について学びつつ、理解を深めていこうと思います。
ipaddress
ライブラリ
今回はこの ipaddress
ライブラリ内の IPv6Address
というクラスを見ていくのですが、その前にライブラリ全体の構成を見てこいうと思います。
ipaddress
ライブラリは以下のようになっており、リストだと分かりづらいのでクラス図も貼っておきます。
- _IPAddressBase
- _BaseAddress(_IPAddressBase)
- _BaseNetwork(_IPAddressBase)
- _BaseV4
- IPv4Address(_BaseV4, _BaseAddress)
- IPv4Interface(IPv4Address)
- IPv4Network(_BaseV4, _BaseNetwork)
- _BaseV6
- IPv6Address(_BaseV6, _BaseAddress)
- IPv6Interface(IPv6Address)
- IPv6Network(_BaseV6, _BaseNetwork)
このままではまだ複雑なのでここでは Address
関連のもののみを見ていくことにします。
すると _BaseV6
と IPv6Address
クラスを見れば大体IPv6の特徴が掴めそうということが分かります。
- _IPAddressBase
- _BaseAddress(_IPAddressBase)
- _BaseV4
- IPv4Address(_BaseV4, _BaseAddress)
- _BaseV6
- IPv6Address(_BaseV6, _BaseAddress)
IPv6Address
IPv4とIPv6の一番大きな違いとして使用可能なIPアドレスの数の違いがあります。
IPV4LENGTH = 32
IPV6LENGTH = 128
また、表現方法も異なるため、その辺の違いが _BaseV4
と _BaseV6
にそれぞれ実装されています。
(表現方法や省略方法についてはここでは割愛します)
ということでここからは IPv6Address
を見ていくことにします。
IPv6Address
プロパティ
IPv6Address
には以下のプロパティが定義されています。
-
scope_id
* -
packed
(バイナリ表現) -
is_multicast
(マルチキャストアドレス) -
is_reserved
(予約済みアドレス) -
is_link_local
*(リンクローカルアドレス) -
is_site_local
*, **(サイトローカルアドレス) -
is_private
(プライベートアドレス) -
is_global
(グローバルアドレス) -
is_unspecified
(未定義アドレス) -
is_loopback
(ループバックアドレス) -
ipv4_mapped
***(IPv4-mappedアドレス) -
teredo
* -
sixtofour
*
*: IPv4Address
には無いプロパティ
**: 「サイトローカルアドレス」は廃止されています
***: IPv4Address
に ipv6_mapped
がある
IPv6のアドレスアーキテクチャ関連
IPv6もIPv4同様、IPアドレスの先頭のビットパターンでIPアドレスの種類を区別します。
上記のプロパティを見たところ、is_
で始まるプロパティがそれらを区別するためのプロパティのようです。
以下に実装の一部を載せます。
(IPv4-mappedアドレスの際の実装は省きます)
-
is_multicast
(マルチキャストアドレス)-
@property def is_multicast(self): """Test if the address is reserved for multicast use. Returns: A boolean, True if the address is a multicast address. See RFC 2373 2.7 for details. """ return self in IPv6Network('ff00::/8')
-
-
is_reserved
(予約済みアドレス)-
@property def is_reserved(self): """Test if the address is otherwise IETF reserved. Returns: A boolean, True if the address is within one of the reserved IPv6 Network ranges. """ return any(self in x for x in [ IPv6Network('::/8'), IPv6Network('100::/8'), IPv6Network('200::/7'), IPv6Network('400::/6'), IPv6Network('800::/5'), IPv6Network('1000::/4'), IPv6Network('4000::/3'), IPv6Network('6000::/3'), IPv6Network('8000::/3'), IPv6Network('A000::/3'), IPv6Network('C000::/3'), IPv6Network('E000::/4'), IPv6Network('F000::/5'), IPv6Network('F800::/6'), IPv6Network('FE00::/9'), ])
-
-
is_link_local
(リンクローカルアドレス)-
@property def is_link_local(self): """Test if the address is reserved for link-local. Returns: A boolean, True if the address is reserved per RFC 4291. """ return self in IPv6Network('fe80::/10')
-
-
is_site_local
(サイトローカルアドレス)-
@property def is_site_local(self): """Test if the address is reserved for site-local. Note that the site-local address space has been deprecated by RFC 3879. Use is_private to test if this address is in the space of unique local addresses as defined by RFC 4193. Returns: A boolean, True if the address is reserved per RFC 3513 2.5.6. """ return self in IPv6Network('fec0::/10')
-
-
is_private
(プライベートアドレス)-
@property @functools.lru_cache() def is_private(self): """``True`` if the address is defined as not globally reachable by iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ (for IPv6) with the following exceptions: * ``is_private`` is ``False`` for ``100.64.0.0/10`` * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the semantics of the underlying IPv4 addresses and the following condition holds (see :attr:`IPv6Address.ipv4_mapped`):: address.is_private == address.ipv4_mapped.is_private ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10`` IPv4 range where they are both ``False``. """ # Not globally reachable address blocks listed on # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml _private_networks = [ IPv6Network('::1/128'), IPv6Network('::/128'), IPv6Network('::ffff:0:0/96'), IPv6Network('64:ff9b:1::/48'), IPv6Network('100::/64'), IPv6Network('2001::/23'), IPv6Network('2001:db8::/32'), # IANA says N/A, let's consider it not globally reachable to be safe IPv6Network('2002::/16'), IPv6Network('fc00::/7'), IPv6Network('fe80::/10'), ] _private_networks_exceptions = [ IPv6Network('2001:1::1/128'), IPv6Network('2001:1::2/128'), IPv6Network('2001:3::/32'), IPv6Network('2001:4:112::/48'), IPv6Network('2001:20::/28'), IPv6Network('2001:30::/28'), ] return ( any(self in net for net in _private_networks) and all(self not in net for net in _private_networks_exceptions) )
-
-
is_global
(グローバルアドレス)-
@property def is_global(self): """``True`` if the address is defined as globally reachable by iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ (for IPv6) with the following exception: For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the semantics of the underlying IPv4 addresses and the following condition holds (see :attr:`IPv6Address.ipv4_mapped`):: address.is_global == address.ipv4_mapped.is_global ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10`` IPv4 range where they are both ``False``. """ return not self.is_private
-
-
is_unspecified
(未定義アドレス)-
@property def is_unspecified(self): """Test if the address is unspecified. Returns: A boolean, True if this is the unspecified address as defined in RFC 2373 2.5.2. """ return self._ip == 0
-
-
is_loopback
(ループバックアドレス)-
@property def is_loopback(self): """Test if the address is a loopback address. Returns: A boolean, True if the address is a loopback address as defined in RFC 2373 2.5.3. """ return self._ip == 1
-
一番気になったのは is_private
プロパティです。
IPv6の文脈では、ローカルやグローバルという言葉はありますが、プライベートという言葉を使っている資料をほとんど見ないので見慣れないのですが、どうやらグローバルから到達出来ないアドレスのことを言っているようで、そのようなIPネットワークがいくつか定義されているようです。
そしてもう一つ、is_site_local
プロパティですが、こちらはコメントにもある通り廃止されている(セキュリティ上の問題があった)ようで、調べてみると新たに「ユニークローカルアドレス」というアドレスに置き換わっており、_private_networks
内の IPv6Network('fc00::/7')
として定義されています。
その他
-
scope_id
*-
@property def scope_id(self): """Identifier of a particular zone of the address's scope. See RFC 4007 for details. Returns: A string identifying the zone of the address if specified, else None. """ return self._scope_id
-
-
teredo
*-
@property def teredo(self): """Tuple of embedded teredo IPs. Returns: Tuple of the (server, client) IPs or None if the address doesn't appear to be a teredo address (doesn't start with 2001::/32) """ if (self._ip >> 96) != 0x20010000: return None return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF), IPv4Address(~self._ip & 0xFFFFFFFF))
-
-
sixtofour
*-
@property def sixtofour(self): """Return the IPv4 6to4 embedded address. Returns: The IPv4 6to4-embedded address if present or None if the address doesn't appear to contain a 6to4 embedded address. """ if (self._ip >> 112) != 0x2002: return None return IPv4Address((self._ip >> 80) & 0xFFFFFFFF)
-
こちらもそれぞれあまりピンとこず、とくに「Teredo」という単語は初めて目にしました。
これも調べてみたところ、どうやらTeredoトンネリングという、経路上にIPv6を理解しない通信機器が存在していてもIPv4を用いて通信を行うことを可能にするためのIPv6移行技術に利用されるものでした。
「sixtofour」に関してはTeredo同様、6to4トンネリングというIPv6移行技術の一つで、IPv6パケットをIPv4パケットのペイロードとしてカプセル化するためのものでした。
そして「スコープID」は RFC4007
に定義されているようで、こちらはリンクローカルアドレスの後ろにインターフェース名(ID)を fe80::1%eth0
のように付与して利用する際に用いられるもので、どのインターフェースからパケットが送出されているか分かるようになっています。
まとめ
ということで、今回はPythonの標準ライブラリである ipaddress
の実装からIPv6に関する技術を見ていきました。
RFCの仕様ほど深い内容にはそこまで触れることは出来ませんでしたが、知識の浅い自分にとってはちょうどいい内容でした。
アドベントカレンダーも残ることあと2日ですが、残りの記事もぜひ楽しんでいってください。