PHP中判断IP是否在某个给定的IP段中
在Linux中,可以往iptables防火墙中添加规则,如-A INPUT -s 180.76.5.0/24 -j
DROP,这样客户端的IP如果落在180.76.5.0/24这个段中,那么这个IP过来的数据包将被丢弃,这里iptables帮我们判断了IP是否落在某给定的IP段中,但是在PHP中如何实现这个判断呢?
解决方案,可以从IP段中读出段的网络地址和广播地址,然后再把它们转换成数字,判断访问者IP的数字值是否落在其中,如果在,则说明在这个段中,否则就不在这个段中。
思路看起来很简单,不过实现过程就要非常小心。
//把客户端访问IP转换成数字,使用ip2long()函数
$ip = "202.105.77.179";
//这个输出在32位系统下是负值,原因是ip2long()回返回PHP的整型数,而PHP的整型是带符号的,数得简单点就是整型用32位,但是第32位是符号位,0表示正,1表示负,这样正负最大能到达的是2^31。而这里的IP转换成整型数时,它超过了PHP整型数能表示的范围,叫做溢出。
$ip_long = ip2long($ip);
//虽然转换成整数时产生了溢出,但是并不影响转换成二进制的正确性,返回一个二进制串,注意这个函数并没有假定它一定返回二进制串的长度
$ip_bin = decbin($ip_long);
//这个时候再从二进制转换成十进制,使用bindec()函数,这个函数返回的数字串是不受符号位影响的,就是通过这样的转换获得了正确的数字串
$ip_dec = bindec($ip_bin);
接下来需要一些函数:
//判断是否是合法IP
function isIp($str) {
$ip = explode(".", $str);
if (count($ip) < 4 || count($ip) > 4) return
FALSE;
foreach($ip as $ip_addr) {
if ( !is_numeric($ip_addr) ) return FALSE;
if ( $ip_addr < 0 || $ip_addr > 255 ) return
FALSE;
}
return
(preg_match("/^([0-9]{1,3}\.){3}[0-9]{1,3}$/is", $str));
}
//根据给出的数字生成二进制掩码串
function mask2bin($n){
$n = intval($n);
if($n < 0 || $n > 32) return
FALSE;
return str_repeat( "1",$n).str_repeat(
"0",32-$n);
}
//反转掩码串
function revBin($s) {
$p = array('0','1','2');
$r = array('2','0','1');
return
str_replace($p,$r,$s);
}
//根据IP和掩码得到网络地址 十进制
function getSubnet($ip, $mask){
//这里的按为运算务必要保证长度一致,都是32位
$bin_ip =
str_pad(decbin(ip2long($ip)),32,'0',STR_PAD_LEFT);
$msk = mask2bin($mask);
return bindec($bin_ip & $msk);
}
//根据IP和掩码得到广播地址 十进制
function getBroadcast($ip, $mask){
$bin_ip =
str_pad(decbin(ip2long($ip)),32,'0',STR_PAD_LEFT);
$msk = mask2bin($mask);
return bindec($bin_ip | revBin($msk));
}
以上的函数getSubnet() 和
getBroadcast()可以获得网络地址和广播地址,返回的是十进制的数字串(不是整型数),这个数字串可能已经超过了PHP整型能表示的范围,所以,如果要判断$ip_dec是否落在它们之间,绝对不能转换成整型然后比较。必须得使用bccomp()函数,这个函数前两个是操作数,第三参数表示精度,如果第一操作数大于第二操作数,返回整型数1,否则返回-1,如果相等返回0。
以下为判断$ip_dec是否落在给定段的代码:
$passlist[] = "192.168.0.0/16";
$passlist[]= "10.0.0.1/8";
$passlist[]= "8.8.8.8";
$ip = $ip_dec;
function inTheList($passlist,$ip){
foreach ($passlist as $pass){
//$dec_ip = bindec(decbin(ip2long($ip)));
$dec_ip = $ip;
$tm = explode("/",$pass);
if(count($tm) > 1){
// 合法IP 和 掩码数
if(isIp($tm[0]) === FALSE){ continue; }
if( ((int)$tm[1] < 1) || ((int)$tm[1] > 32)){ continue;
}
$dec_from = getSubnet($tm[0],(int)$tm[1]);
$dec_end = getBroadcast($tm[0],(int)$tm[1]);
// 在段中
if( (bccomp($dec_ip,$dec_from) == 1) &&
(bccomp($dec_ip,$dec_end) == -1) ){
return TRUE;
break;
}
}else if(trim($ip) ==
bindec(decbin(ip2long(trim($pass))))){
return TRUE;
break;
}
}
return FALSE;
}
if(inTheList($passlist,$ip)){
echo "The ip -> $ip is in the list.";
}else{
echo "The ip -> $ip is not in the
list.";
}