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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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

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

1
2
3
apnic|CN|ipv4|42.192.0.0|131072|20110304|allocated

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

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

根据IP获取国别地区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 获取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,判断是否来源是中国大陆,进行对应的业务处理。

1
2
3
4
5
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:禁朝鲜和韩国】

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

1
2
3
4
5
6
7
8
'ban_area' => [
"", // 无
"CN", // 中国大陆
["CN", "TW", "HK", "MO"], //中国大陆港澳台
["JP"], // 日本
["KP"], // 朝鲜
["KP","KR"], //朝鲜韩国
]

根据IP获取被ban区域id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 获取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使用举例 :

1
2
3
4
5
// 根据当前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段的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 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);
}

至此!