如果你和我一样有一个需求,通过访客的 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