PHP 之识别访客 IP 归属地来提供不同的区域化服务|封禁某些地区訪客

如果你和我一样有一个需求,通过访客的 IP 地址获得其归属地,来实现区域化的信息服务。

对于以上,我们可以通过 Apanic 提供的亚太地区 IP 数据分配情况来实现,且本文只用到 IPV4,IPV6 可以自行扩展。

APNIC 官方 IP 分配表

这个地址的数据是持续更新的,所以可以定期更新这个地址的内容到你的本地。

http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest

文件格式参见:

http://ftp.apnic.net/apnic/stats/apnic/README.TXT

Format:

	registry|cc|type|start|value|date|status[|extensions...]

Where:

	registry  	The registry from which the data is taken.
			For APNIC resources, this will be:

			    apnic

	cc        	ISO 3166 2-letter code of the organisation to
	                which the allocation or assignment was made. 

	type      	Type of Internet number resource represented
			in this record. One value from the set of 
			defined strings:

			    {asn,ipv4,ipv6}

	start     	In the case of records of type 'ipv4' or
			'ipv6' this is the IPv4 or IPv6 'first
			address' of the	range.

			In the case of an 16 bit AS number, the
			format is the integer value in the range:
			
			    0 - 65535
			
			In the case of a 32 bit ASN,  the value is
			in the range:
			
			    0 - 4294967296
			    
		    	No distinction is drawn between 16 and 32
		    	bit ASN values in the range 0 to 65535.

	value     	In the case of IPv4 address the count of
			hosts for this range. This count does not 
			have to represent a CIDR range.

			In the case of an IPv6 address the value 
			will be the CIDR prefix length from the 
			'first address'	value of <start>.
			
			In the case of records of type 'asn' the 
			number is the count of AS from this start 
			value.

	date      	Date on this allocation/assignment was made
			by the RIR in the format:
			
			    YYYYMMDD

			Where the allocation or assignment has been
			transferred from another registry, this date
			represents the date of first assignment or
			allocation as received in from the original
			RIR.

			It is noted that where records do not show a 
			date of	first assignment, this can take the 
			0000/00/00 value.

    	status    	Type of allocation from the set:

                            {allocated, assigned}

                	This is the allocation or assignment made by 
                	the registry producing the file and not any
                	sub-assignment by other agencies.

   	extensions 	In future, this may include extra data that 
   			is yet to be defined.

国别编码可以参照 ISO  3166-2 的 2 字母国别编码

https://zh.wikipedia.org/wiki/ISO_3166-2

我们以其中一条数据举例说明

apnic|CN|ipv4|42.192.0.0|131072|20110304|allocated

注册商|国别地区编码|类型|IP 起始地址|数量|时间|分配情况

PHP 实现获取不同地区的服务功能

根据 IP 获取国别地区

/**
 * 获取 IP 所在地区代码
 * @param $ip
 * @return string
 */
function getIPAreaCode($ip)
{
    $ipInt = ip2long($ip);
    $apnic = "app/delegated-apnic-latest";
    $handle = fopen($apnic,"r");

    while(!feof($handle)){
        $line = fgets($handle);
        if(substr($line,0,1) == "#"){
            unset($line);
            continue;
        }
        $buffer = explode("|", $line);

        if(isset($buffer[2]) && $buffer[2] == 'ipv4' && isset($buffer[4])){
            $bufferIpInt = ip2long($buffer[3]);
            if($bufferIpInt <= $ipInt && $ipInt <= $bufferIpInt+intval($buffer[4])){
                fclose($handle);
                return trim($buffer[1]);
            }
        }
        unset($line);
        unset($buffer);
    }
    fclose($handle);
    return "未知";
}

使用方法:

禁止中国大陆访客访问

通过获取到的用户 IP,判断是否来源是中国大陆,进行对应的业务处理。

if(getIPAreaCode("113.110.225.1") == "CN"){
    echo "根据您所在地区(中国大陆),无法为您提供相应服务!";die;
}else{
    echo "访问正常";
}

过滤中国大陆、港澳台全境访问

通过获取到的访客 IP,只检索出适合其的数据服务。以文章列表举例,

id title contents ban_ip 1 Nginx 从入门到精通 Nginx.... 0 2 Java 从入门到入狱 Java... 2

字段解释:

ban_ip【0:无 1:禁大陆 2:禁中国大陆港澳台 3:禁日本 4:禁朝鲜 5:禁朝鲜和韩国】

配置文件要和数据库的字段对应,如下:

'ban_area' => [
        "",    // 无
        "CN",  // 中国大陆
        ["CN", "TW", "HK", "MO"], //中国大陆港澳台
        ["JP"], // 日本
        ["KP"], // 朝鲜
        ["KP","KR"], //朝鲜韩国
    ]

根据 IP 获取被 ban 区域 id

/**
 * 获取 IP 被 ban 区域
 * @param $ip
 * @return string
 */
function getIPBanArea($ip)
{
    $AREA_CODE = config('app.ban_area');
    $ipInt = ip2long($ip);
    $apnic = storage_path("app/delegated-apnic-latest");
    $handle = fopen($apnic,"r");

    while(!feof($handle)){
        $line = fgets($handle);
        if(substr($line,0,1) == "#"){
            unset($line);
            continue;
        }
        $buffer = explode("|", $line);

        if(isset($buffer[2]) && $buffer[2] == 'ipv4' && isset($buffer[4])){
            $bufferIpInt = ip2long($buffer[3]);
            if($bufferIpInt <= $ipInt && $ipInt <= $bufferIpInt+intval($buffer[4])){
                $result = [];
                foreach($AREA_CODE  as $key => $codeBuffer){
                    if((is_array($codeBuffer) && in_array(trim($buffer[1]), $codeBuffer)) or
                        (is_string($codeBuffer) && $codeBuffer== trim($buffer[1]))){
                        $result[] = $key;
                    }
                }
                fclose($handle);
                return $result;
            }
        }
        unset($line);
        unset($buffer);
    }
    fclose($handle);
    return [];
}

根据当前 IP 地址,排除不适用于当地的服务,获取可用的服务。laravel 使用举例 :

// 根据当前 IP,获取配置中所有包含该 IP 的键,即地区 ID。
$banAreaCode = getIPBanArea($request->getClientIp());

// 通过数据的 not in 实现排除其他的服务,获取可用的服务。
$posts = Posts::where("is_private",0)->whereNotIn('ban_ip',$banAreaCode)->orderBy('created_at','DESC')->paginate(10);

判断 IP 地址是否同段

也可以通过这个方法判断访客的 IP 地址是否是白名单 IP 段的。

    /**
     * testMatchFilterIP("192.168.0.2", "192.168.0.0/24");
     * @param $ip
     * @return int
     */
    function testMatchFilterIP($ip, $ipSegment)
    {
        echo "需要匹配的 IP:$ip\n";
        $ipInt = ip2long($ip);
        list($ipBegin, $type) = explode('/',$ipSegment);
        $ipBegin =  ip2long($ipBegin);
        echo "IP 段起始位置:$ipBegin\n";
        $mask = 0xFFFFFFFF << (32 - intval($type));
        echo "掩码:$mask\n";
        echo "需要匹配的 IP 网络地址:".long2ip($ipInt&$mask)."\n";
        echo "被匹配的掩码网络地址:".long2ip($ipBegin& $mask)."\n";
        echo sprintf("%s == %s\n",$ipInt&$mask,$ipBegin& $mask);
        return intval($ipInt&$mask) == intval($ipBegin& $mask);
    }

至此!

Comments