IPv4 アドレスを 32bit 整数に変換
アクセス元の IP を並べ替えたら、9.0.0.0 が 10.0.0.0 より後ろに来た。文字列のまま比べると辞書順になるので、こういうことが起きます。IP を数として扱えば、比較も範囲の判定も素直に書けます。
文字列のままだと、大小が狂う
IPv4 アドレスを点で区切って書くのは、人が読むための表記です。中身は 32 ビットの整数 1 つです。整数に直しておくと、大小の比較がそのまま使えて、DB の 1 カラムに入れて索引も効き、ある範囲に入っているかどうかも 1 回の計算で判定できます。ネットワーク機器の内側では、最初から整数として扱われています。
アクセスログを日付でまたいで集計するときも、遮断したい範囲を設定に書くときも、比べる相手が数であれば話が早くなります。文字列の比較は 1 文字ずつ辞書の順で進むので、桁数の違う数を並べた瞬間に破綻します。
4 つの数を、8 ビットずつ棚に載せる
点で区切られた 4 つの数は、それぞれ 0 から 255 までで、ちょうど 8 ビットに収まります。これをオクテットと呼びます。4 つを 1 つの整数にまとめるには、置きたい場所まで左へずらしてから重ねます。
プレーンテキスト
192 . 168 . 1 . 1
| | | +--- 一番下の 8 ビット
| | +------- その上の 8 ビット
| +------------ その上の 8 ビット
+------------------ 一番上の 8 ビット左へずらすのが << です。x << 8 は 8 ビットぶん左へ動かすことで、10 進で言えば 256 倍にあたります。ずらす量が違えば、それぞれの 8 ビットは重なりません。重ならないので、まとめる操作は | でも + でも同じ結果になります。
同じ形は色でも使われています。赤と緑と青の 3 成分を 1 つの整数にまとめる書き方です。
Python
r, g, b = 0x33, 0xAA, 0x66
color = (r << 16) | (g << 8) | b
print(hex(color)) # 0x33aa66取り出すときは逆の手順です。>> で右へ戻してから、& 0xFF で下 8 ビットだけを残します。0xFF は 8 ビットすべてが 1 の値で、これと重ねると下 8 ビット以外が消えます。
Python
print((color >> 8) & 0xFF) # 170 緑の成分重ねる位置を 1 つでも取り違えると、隣のオクテットと重なって両方の値が壊れます。16 進数で書き出してみると、8 ビットの区切りが 2 桁ごとの区切りとしてそのまま目に見えるので、確かめやすくなります。
点で区切られた文字列を数の並びに直すところは、区切り文字での分割で書けます。分割した結果は文字列のままなので、数に直してから使います。
上のオクテットを触ると、JavaScript では負になる
JavaScript のビット演算は、値を 32 ビットの符号付き整数として扱います。最上位のビットが 1 になる位置まで左へずらすと、結果が負の数になります。IPv4 は先頭のオクテットが 128 以上になることがあるので、この罠は必ず踏みます。
避け方は 2 つあります。>>> 0 を通して符号なしに戻すか、ずらす代わりに掛け算で書くかです。Python の整数には桁の上限が無いので、この心配はいりません。言語ごとに整数の器が違う、ということ自体が、ここで覚えておく値打ちのある話です。
要件
- ドット区切りの IPv4 文字列を 32bit 整数に変換する
- オクテットの順序は (a << 24) | (b << 16) | (c << 8) | d とする
- 255.255.255.255 が 4294967295 になるよう、符号なしの値として扱う
入出力例
ipToInt("0.0.0.0") → 0
ipToInt("127.0.0.1") → 2130706433
ipToInt("192.168.1.1") → 3232235777
ipToInt("255.255.255.255") → 4294967295
ipToInt("10.0.0.1") → 167772161
ipToInt("172.16.0.1") → 2886729729
ipToInt("8.8.8.8") → 134744072