KITA Eng.

北海道でサーバー技術者として歩み出したひとが綴るblog。

グローバルIPアドレス国別割り当てのデータを生成するスクリプトが書き上がるまでの道のりは険しかった…(解決編)

kitaeng.hateblo.jp

RIRsが提供してくれている、RIR statistics exchange formatになっているIPアドレスの割り当て状況のデータを加工するスクリプトを書きました。思考、試行過程をちょっと備忘録的に記録しておきますの第二弾です。

RIR statistics exchange formatを扱う時の問題点

詳細は第一弾で書いたとおりですが、一応ざっくりだけ。 kitaeng.hateblo.jp

RIR statistics exchange formatのレコードの中には、1レコードに対して一つの「ネットワークアドレス/CIDRブロック」の形で表せないレコードが存在しています。

例えばこんなレコードがあります。valueというのがIPアドレスの個数です。

registry cc start value date status
ripencc CH 195.242.163.0 768 20080617 assigned

768個って、上の表では出てきません。768 = 256 * 3なので、なるべくブロックが細かくならないように分けるとすると、/23/24で分割してあげないといけません。

registry cc start value date status
ripencc CH 195.242.163.0 256 20080617 assigned
ripencc CH 195.242.164.0 512 20080617 assigned

解決策1~まず始めに考えた方法

問題点としてあげたようなレコードは全レコードの中の大半を占めているという程の量ではなくて、普通に1レコードが一つの「ネットワークアドレス/CIDRブロック」の形で表せるものが大半なので、

  • 一つの「ネットワークアドレス/CIDRブロック」で表せるもの
  • 複数の「ネットワークアドレス/CIDRブロック」で表さなければならないもの

でレコードの処理方法を分ける事にしました。

一つの「ネットワークアドレス/CIDRブロック」で表せるものについては、CIDRブロックとホスト数の対応表を作って置いて、ホスト数(value)に基づいて、開始アドレス(=ネットワークアドレス)に対応するCIDRブロックを結合して出力することにしました。

複数の「ネットワークアドレス/CIDRブロック」で表さなければならないものについては、ホスト数(value)の値をうまく分割できるようなCIDRブロック表に現れるホスト数を見つけるために、valueの値を、CIDRブロック表にあるホスト数の少ない方から順に割っていって、割り切れなくなる(余りが0ではなくなる)ものの一つ前のCIDRブロックで分割することにしました。

一応コードとしては書き上がって、エラーもなく出力が得られたので、ウキウキしたのですが、PostgreSQLに読み込ませてからいろいろとSELECTをしてみていたら、なんだかデータがおかしいような気がしてきました。。。。

なんてこった!!

賢明な方は、すでに自分が陥った失態に気がついていることでしょう。

「RIR statistics exchange formatを扱う時の問題点」の説明のところを見直して見てください。分割すべきCIDRブロックは必ずしも同じ大きさのもので等分するのではなくて、複数の大きさのブロックをつかって分けた方が、効率的な分割になるのです。

等分するとなると結構小さなブロックが選ばれる事が多くて、/10とかは選ばれにくいわけです。そうなると、ある程度多い数のホスト数の部分を分割するときには、分割数が等分だと100個とかになることがありました。後々検索に書けるデータとして使うのには、レコード数はむやみにやたらと増えない方がベターな気がします。

ということで、この解決策1はボツになりました。

解決策2~別にホスト数とCIDRブロックの対応表なんていらないじゃん!

解決策1がボツになったことで、どうしたものかと考え直していたら、そもそも、ホスト数とCIDRブロックの対応表からマッチさせる必要などないのではないかと気がつきました。

ホスト数が分かれば、CIDRブロックなんて数値計算で導出できるではないかと。

計算方法はいくつかあるのでしょうが、科学計算分野でもよく使われるというPythonらしい感じで(!?)いくと、

Python 3.4.3
>>> import math
>>> cidr = 32 - math.log(value,2)

ホスト数(value)の底が2の対数を取って、32から引けば、そのホスト数をぴったり入れるCIDRブロックが分かります。一つのCIDRブロックで表現できないようなホスト数の場合は、この計算の結果が、整数にならないので、そこで判別ができます。

で、整数にならなかったものはどうするの??

例えば、このレコードの場合は、上の計算結果が小数になります。

registry cc start value date status
ripencc CH 195.242.163.0 768 20080617 assigned
Python 3.4.3
>>> import math
>>> cidr = 32 - math.log(768,2)
22.415037499278846

常に切り上げてあげれば、最大のCIDRブロックは出そうです。あとは、ブロックの大きさを一つずつ小さくしながら、ホスト数が0になるまで減らしていけばいいと思うのですが…。

この処理を書こうとしたら、for、while、if地獄に落ちいって、どうもいい書き方が生み出せませんでした。

ということで、解決策2もボツです。

解決策3~3度目の正直!Pythonには、ipaddressという便利なモジュールがあった!

うーん。困ったとうなりながら、最後の出力の部分で使えそうだなと思って目星をつけていたipaddressというPythonのモジュールのドキュメントを読んでいると…。

21.28. ipaddress — IPv4/IPv6 操作ライブラリ — Python 3.5.1 ドキュメントの、「21.28.5. その他のモジュールレベル関数」という項目で、

ipaddress.summarize_address_range(first, last)(原文)
first と last で指定された IPアドレス帯に対するイテレーターを返します。 first はアドレス帯の中の最初の IPv4Address か IPv6Address で、 last はアドレス帯の中の最後の IPv4Address か IPv6Address です。 first か last がIPアドレスでない場合や、2つの型が揃っていない場合に、 TypeError を発生させます。 last が first より大きくない場合や、 first アドレスのバージョンが 4 でも 6 でもない場合は ValueError を発生させます。

という関数があるという紹介がありました。

自分の欲していた機能ではないか!!しかもこれを使えば、別に、

  • 一つの「ネットワークアドレス/CIDRブロック」で表せるもの
  • 複数の「ネットワークアドレス/CIDRブロック」で表さなければならないもの

で処理を分けなくてもいい(=コードが短くなる)ではないか!と感激したのでした。

ipaddressモジュールのおかげで、随分と楽に出来上がりました。Python3.x系からの標準ライブラリのようなのが、難点ですが、Ubuntuとかだと、次の16.04とかではPython3.xがメインのほうになるとかならないとかってのを見たような気がしないでもないので…。

ということで、完成したのが、 kitaeng.hateblo.jp

で紹介しているスクリプトです。