#IPアドレスが指定したIPアドレス範囲の中にあるかチェック
##はじめに
いろんなサイトをみるとIPv4とIPv6で書き方が違う。
IPv4は主にip2longでキャストしてチェックしている。
IPv6はintにキャストするとサイズオーバーするのでバイナリに直してチェックしている。
###IPv4もバイナリチェックしよう。
IPv4もIPv6も別にバイナリチェックすれば同じロジックでできるよね?
##実際のコード
<?php
function conevrt_addr_mask(string $ip, int $subnet)
{
$addr = inet_pton($ip);
$len = 8 * strlen($addr);
$mask = str_repeat('f', $subnet >> 2);
switch ($subnet & 3) {
case 1:
$mask .= '8';
break;
case 2:
$mask .= 'c';
break;
case 3:
$mask .= 'e';
break;
default:
break;
}
$mask = pack('H*', str_pad($mask, $len >> 2, '0'));
$filt = $addr & $mask;
return $filt;
}
function checkip(string $ip, string $addr_range)
{
$ret_addr = explode("/", $addr_range);
$chk_mask = conevrt_addr_mask($ret_addr[0], (int)$ret_addr[1]);
$ip_mask = conevrt_addr_mask($ip, (int)$ret_addr[1]);
if ($chk_mask === $ip_mask) {
return true;
}
return false;
}
var_dump(checkip("127.0.0.1", "127.0.0.1/32")); // true
var_dump(checkip("127.0.0.1", "127.0.0.2/32")); // false
var_dump(checkip("1023:4567:89ab:cdef:1023:4567:89ab:cdef", "1023:4567:89ab:cdef:1023:4567:89ab:cdef/128")); // true
var_dump(checkip("1023:4567:89ab:cdef:1023:4567:89ab:cdef", "1023:4567:89ab:cdef:1023:4567:89ab:cde0/128")); // false
?>
やっていることは単純。IPとIPレンジを受け取ったら、バイナリにしてからレンジ分だけ一致するか調べるだけ。
##注意点
###入力値検証
$ip
および$addr_range
は入力検証は行っていないので、この関数を掛ける前にかならず入力検証をしないといけない。
標準のfilter_var関数を利用しましょう。
mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )
http://php.net/manual/ja/function.filter-var.php
###IPv4とIPv6の互換性
IPv6 - Wikipedia #IPv4との相互運用
実際のパケットフォーマットは完全に異なる上、IPアドレス空間の大きさも違うため、一対一対応はできない。
IPv4でIPv6レンジでチェックしたりIPv6をIPv4レンジでチェックしたりすると、誤検知の可能性は低い(ほとんどfalse)にしても
バグが生まれる原因になるのできちんとどちらのパターンで変数が入ってくるか制御はしたほうがいいと思います。
IPv4とIPv6どちらが入ってくるかわかないという場合はビジネスロジック自体は見直したほうがいいとは思いますが、
必要であれば前述しているfilter_var関数で用意してくれているFILTER_FLAG_IPV4
もしくはFILTER_FLAG_IPV6
でどちらのプロトコルを利用しているのか調べてからチェック掛けましょう。
http://php.net/manual/ja/filter.filters.validate.php
##あとがき
速度はよくわからない。実質サブネットマスクの計算の部分が大きいからこっちのほうが遅いかな。
そういえば枯渇問題はどこいったんだろう。