Validation 从IP确定国家/地区-IPv6

Validation 从IP确定国家/地区-IPv6,validation,postgresql,ip-address,ipv6,Validation,Postgresql,Ip Address,Ipv6,在我的项目中,我在postgres(plpgsql)中有一个函数,它根据给定的ip地址确定国家: CREATE OR REPLACE FUNCTION get_country_for_ip(character varying) RETURNS character varying AS $BODY$ declare ip ALIAS for $1; ccode varchar; cparts varchar[]; nparts bigint[];

在我的项目中,我在postgres(plpgsql)中有一个函数,它根据给定的ip地址确定国家:

CREATE OR REPLACE FUNCTION get_country_for_ip(character varying)
  RETURNS character varying AS
$BODY$
declare
    ip  ALIAS for $1;
    ccode   varchar;
    cparts  varchar[];
    nparts  bigint[];
    addr    bigint;
begin
    cparts := string_to_array(ip, '.');
    if array_upper(cparts, 1) <> 4 then
        raise exception 'gcfi01: Invalid IP address: %', ip;
    end if;
    nparts := array[a2i(cparts[1])::bigint, a2i(cparts[2])::bigint, a2i(cparts[3])::bigint, a2i(cparts[4])::bigint];
    if(nparts[1] is null or nparts[1] < 0 or nparts[1] > 255 or
       nparts[2] is null or nparts[2] < 0 or nparts[2] > 255 or
       nparts[3] is null or nparts[3] < 0 or nparts[3] > 255 or
       nparts[4] is null or nparts[4] < 0 or nparts[4] > 255) then
        raise exception 'gcfi02: Invalid IP address: %', ip;
    end if;

    addr := (nparts[1] << 24) | (nparts[2] << 16) | (nparts[3] << 8) | nparts[4];
    addr := nparts[1] * 256 * 65536 + nparts[2] * 65536 + nparts[3] * 256 + nparts[4];

    select into ccode t_country_code from ip_to_country where addr between n_from and n_to limit 1;
    if ccode is null then
        ccode := '';
    end if;
    return ccode;
end;$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
现在我们也开始研究IPv6地址——我需要为IPv6地址添加类似的功能。我有一组类似的IPv6数据,如下所示:

 t_start     | t_end                                   | t_country_code
-------------+-----------------------------------------+----------------
 ::          | ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff   | ZZ
 100::       | 1ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff  | ZZ
...
 2000::      | 2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff | ZZ
...
 2001:1200:: | 2001:1200:ffff:ffff:ffff:ffff:ffff:ffff | MX
...
CREATE OR REPLACE FUNCTION get_country_for_ipv6(character varying)
  RETURNS character varying AS
$BODY$
declare
    ip  ALIAS for $1;
    ccode   varchar;
begin
    select into ccode t_country_code from ipv6_to_country where addr between n_from and n_to limit 1;
    if ccode is null then
        ccode := '';
    end if;
    return ccode;
end;$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

现在,给定一个IP地址::1,我如何(1)检查它是否是有效的IPv6地址,以及(2)获取相应的国家/地区映射?

首先,我看到您正在做的一些事情会带来问题。第一种是使用varchar和long来表示IP地址,而PostgreSQL具有完全有效的INET和CIDR类型,它们只会更好更快地完成您想要的任务。请注意,这些当前不支持正确的GIN索引,因此不能排除对它们的约束。如果需要,请查看支持此功能的ip4r扩展

注意,作为一个补丁,现在您可以将varchar强制转换为inet。Inet和cidr一样也支持ipv4和ipv6地址,ip4r上也存在类似类型的地址

这将为您解决ipv6验证问题,并可能减少存储,提供更好的操作检查和更好的性能


至于国家,我也认为映射可能不那么直接。

首先,我看到你们正在做的一些事情会带来问题。第一种是使用varchar和long来表示IP地址,而PostgreSQL具有完全有效的INET和CIDR类型,它们只会更好更快地完成您想要的任务。请注意,这些当前不支持正确的GIN索引,因此不能排除对它们的约束。如果需要,请查看支持此功能的ip4r扩展

注意,作为一个补丁,现在您可以将varchar强制转换为inet。Inet和cidr一样也支持ipv4和ipv6地址,ip4r上也存在类似类型的地址

这将为您解决ipv6验证问题,并可能减少存储,提供更好的操作检查和更好的性能


至于国家,我也认为映射可能不是那么直接。

我相信我找到了解决方案。它首先涉及修改数据,然后对输入进行一些处理。以下是有效的方法

首先,需要转换数据,以便在不缩短的情况下,所有地址都已满,并删除分号分隔符。我的问题中显示的示例数据转换为:

 t_start                          | t_end                            | t_country_code
----------------------------------+----------------------------------+----------------
 00000000000000000000000000000000 | 00ffffffffffffffffffffffffffffff | ZZ
 01000000000000000000000000000000 | 01ffffffffffffffffffffffffffffff | ZZ
...
 20000000000000000000000000000000 | 2000ffffffffffffffffffffffffffff | ZZ
...
 20011200000000000000000000000000 | 20011200ffffffffffffffffffffffff | MX
...
这是存储在数据库中的内容

下一步是将代码中接收的IP地址转换为相同的格式。这是在PHP中使用以下代码完成的(假设
$ip\u address
是传入的IPv6地址):

例如,现在变量
$ip\u address
将包含完整的IPv6地址

:: => 00000000000000000000000000000000
2001:1200::ab => 200112000000000000000000000000ab
等等

现在,您可以简单地将这个完整地址与数据库中的范围进行比较。我在数据库中添加了第二个函数来处理IPv6地址,如下所示:

 t_start     | t_end                                   | t_country_code
-------------+-----------------------------------------+----------------
 ::          | ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff   | ZZ
 100::       | 1ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff  | ZZ
...
 2000::      | 2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff | ZZ
...
 2001:1200:: | 2001:1200:ffff:ffff:ffff:ffff:ffff:ffff | MX
...
CREATE OR REPLACE FUNCTION get_country_for_ipv6(character varying)
  RETURNS character varying AS
$BODY$
declare
    ip  ALIAS for $1;
    ccode   varchar;
begin
    select into ccode t_country_code from ipv6_to_country where addr between n_from and n_to limit 1;
    if ccode is null then
        ccode := '';
    end if;
    return ccode;
end;$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

最后,在我的php代码中,我添加了基于输入ip地址调用一个或另一个Postgres函数的代码。

我相信我找到了解决方案。它首先涉及修改数据,然后对输入进行一些处理。以下是有效的方法

首先,需要转换数据,以便在不缩短的情况下,所有地址都已满,并删除分号分隔符。我的问题中显示的示例数据转换为:

 t_start                          | t_end                            | t_country_code
----------------------------------+----------------------------------+----------------
 00000000000000000000000000000000 | 00ffffffffffffffffffffffffffffff | ZZ
 01000000000000000000000000000000 | 01ffffffffffffffffffffffffffffff | ZZ
...
 20000000000000000000000000000000 | 2000ffffffffffffffffffffffffffff | ZZ
...
 20011200000000000000000000000000 | 20011200ffffffffffffffffffffffff | MX
...
这是存储在数据库中的内容

下一步是将代码中接收的IP地址转换为相同的格式。这是在PHP中使用以下代码完成的(假设
$ip\u address
是传入的IPv6地址):

例如,现在变量
$ip\u address
将包含完整的IPv6地址

:: => 00000000000000000000000000000000
2001:1200::ab => 200112000000000000000000000000ab
等等

现在,您可以简单地将这个完整地址与数据库中的范围进行比较。我在数据库中添加了第二个函数来处理IPv6地址,如下所示:

 t_start     | t_end                                   | t_country_code
-------------+-----------------------------------------+----------------
 ::          | ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff   | ZZ
 100::       | 1ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff  | ZZ
...
 2000::      | 2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff | ZZ
...
 2001:1200:: | 2001:1200:ffff:ffff:ffff:ffff:ffff:ffff | MX
...
CREATE OR REPLACE FUNCTION get_country_for_ipv6(character varying)
  RETURNS character varying AS
$BODY$
declare
    ip  ALIAS for $1;
    ccode   varchar;
begin
    select into ccode t_country_code from ipv6_to_country where addr between n_from and n_to limit 1;
    if ccode is null then
        ccode := '';
    end if;
    return ccode;
end;$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

最后,在我的php代码中,我添加了根据输入的ip地址调用一个或另一个Postgres函数的代码。

你确定IPv6将以类似的方式映射到IPv4吗?有足够的IPv6地址供地球上所有人口使用,还有一些人使用。如果重新处理ip地址,您的映射技术似乎是无效的。你的数据来源是什么?有一些正则表达式可以帮助您验证IPv6是否有效。@Ramhound:我们使用的是Webnet77()中的信息,它对IPv4很有用,所以我们从IPv6的数据库开始。我不太担心是否100%正确(目前),但需要从某个地方开始。你确定IPv6将以类似于IPv4的方式映射吗?有足够的IPv6地址供地球上所有人口使用,还有一些人使用。如果重新处理ip地址,您的映射技术似乎是无效的。你的数据来源是什么?有一些正则表达式可以帮助您验证IPv6是否有效。@Ramhound:我们使用的是Webnet77()中的信息,它对IPv4很有用,所以我们从IPv6的数据库开始。我不太担心在任何时候都是100%正确的(现在),但我需要从某个地方开始。谢谢你的回答,但是这一点都没有帮助。尽管它需要跨数据库,至少在postgres和mysql中工作,但有充分的理由说明它的实现方式是这样的;详细信息不同,但数据类型相同。我没有要求对我的实现发表评论,我专门问了关于处理ipv6的问题——你所说的只是“我认为映射可能不是那么直接”。那不是答案,那是评论。至少我们把问题解决了,对吧?然而,pl/pgsql在MySQL上是行不通的,所以一旦你们走上了这条路,我想你们真的很想走这条路