はじめに
インターネットサービスをやっていると、アクセスしてくるホストの地域1を判別したいってことが時折あります。
たとえば日本では「IPアドレス逆引きすると*.hkd.mesh.ad.jp
だったので北海道からのアクセスっぽい」という判別ができたりします2が、今回はそこまで細かいことはしません。地域という表現ですが国ぐらいの粒度3で考えます。
IPアドレスと地域の関連付け
IPアドレスはIANAから各RIRに割り振られ、それを受けたRIRが各地域に割り振り・割り当てています。
なので、各RIRが提供する「どの地域へ分配したか」リストを処理すれば、各地域への分配IPアドレス一覧をつくることができます。
割り振りと割り当てについて
記事タイトルに「割り振り・割り当て」と書いたように、分配には2つの表現があります。また、RIRの出しているリストにも「allocated」と「assigned」のふたつが存在しています。
これはJPNICが出しているドキュメント割り振り(Allocation)、割り当て(Assignment)とはにある通りなのですが、分配されたアドレス領域を受け取った側が自分で使うかどうかの違いがあります。
- allocate(割り振り)
- 管理組織へ分配(傘下メンバーに分配するところに配る)
- assign(割り当て)
- エンドユーザへの分配(分配されたアドレス領域を実際に使うところに配る)
この記事では、これら2つを区別せずに使うとき「分配」と書いています。
先達
このニーズは昔から存在するので、インターネットには既に上記の考えのもと作成されたリストがいくつか存在します。
-
IPv4
-
IPv6
せっかくなので自分で作りたい
リストあるやん、で終わったら面白くないので、自分でも作ることにしました。ネットに正解とみなせるデータが存在しているので、生成結果をそれと比較すれば正しさの評価ができそう。
リストを作る上での問題
※この項は先達の記事を適当にサマライズしています。
RIRのリストフォーマットはAPNICなどが公開しています。これを読んで作れば良いのですが、いくつか問題があります。
IPv4のときCIDR表記じゃない
IPv4は「開始アドレス+個数」という表記4をしています。プログラムでIPアドレスから地域判定するときはCIDRで考えることが多いのでCIDRに変換した方が便利です。また、1レコードにCIDR表記1つで表記しきれないブロック5も存在します6。
IPv6の場合はCIDR表記7なので、この問題はありません。
CIDRにしてもブロックが小分けのときがある
これはRIRのドキュメントには書いてありません。先達の記事にはその旨記述があるし自分で実装しても確かにリストが縮みます6。
リストが冗長になって良いことは特にないので、短くできるに越したことはない。
CIDRの結合についてはCIDR+CIDRはどんな処理をするの?という記事がおすすめ。
Pythonで作ってみる
CIDRの生成と結合をやるわけですが、PythonでCIDR結合などの操作はライブラリnetaddrがあります。
CIDRレコードの生成
from netaddr import IPRange,IPAddress
# `start`と`value` は RIRのレコード情報
start_ip = IPAddress(start, version = 4)
end_ip = IPAddress(int(start_ip) + value - 1) # `value` は個数なので-1する
cidr_list = IPRange(start, end_ip).cidrs()
CIDRブロックの結合
-
IP Set
- CIDR情報(
IPNetwork
オブジェクト)のリストを食わせると、適当に結合してくれます。 - 結合されたCIDRはiter_cidrs()で取り出せます。
- CIDR情報(
from netaddr import IPSet
v4set = IPSet(v4_cider_list)
for cidr in v4set.iter_cidrs():
print(cidr)
つくってみたもの
色々書いてきましたが、シュッと書けてしまいました。ライブラリすごい。
大したものでは有りませんが、Gistに放り込んでおきました。
他言語ではどうなのか
Perlの場合はNet::CIDR::Liteライブラリ9でCIDR結合ができそうです。実際に書いてみようと思ったんですが、cpan
叩いたら「Free to wrong pool 1f7d20 not 89034600d957d249 at C:\Perl64\site\lib/IO/Socket/SSL.pm line 2739.」とか言って落ちてしまう。調べると、Windowsでのみ生じる既知かつ未解決のの問題10の模様で断念。
Goの場合はcidrmanというnetaddrにインスパイアされた11ライブラリがありますが、現状IPv4のみ実装されています12。
PHPの場合は、自前の国別IPv6、IPv4アドレス割当リストを作成しようという記事が存在します。
-
本当に欲しいのはアクセスしてくるユーザの居住地域とかな気もしますが、アクセスしてくるホストのIPアドレスしか分からないという前提。現実問題としてアクセス元のIPアドレスから分かるのは分配された組織の所在する地域でしかなく実際にホストやユーザがどこにいるかは別の話になります(VPNありますし)。 ↩
-
雑に国って言うと、「アメリカとは別にグアムへの割り振りがあるやん」とかなってくる。 ↩
-
原文では
value
の項に「In the case of IPv4 address the count of hosts for this range. This count does not have to represent a CIDR range.」と書かれています。CIDRじゃないよって書いてありますね。 ↩ -
例えば、US割り当てのレコード「ripencc|US|ipv4|13.116.0.0|524288|19860425|assigned|61f026d4-8289-4155-b117-b70688eb33ff」を見ると、プレフィクス長は/13(=32-log2(524288))っぽく見えます。だけど、実際に13.116.0.0/13するとホストアドレスが0になりません。例えばネットワーク計算ツールで確認できます。 ↩
-
原文では「In the case of an IPv6 address the value will be the CIDR prefix length from the ‘first address’ value of .」とあります。 ↩
-
これは前述したとおり、IPアドレスレンジが1つのCIDRで表せるとは限らないからです。 ↩
-
Merging IPv6 CIDR ranges unimplementedというissueが経っていますが、1年ほど動きがない。 ↩