Acredito que encontrei a solução. Envolve a modificação dos dados primeiro e, em seguida, alguma massagem da entrada. Aqui está o que funcionou.
Primeiro, os dados precisam ser convertidos para que todos os endereços fiquem completos, sem encurtar, com os separadores de ponto e vírgula removidos. Os dados de exemplo mostrados na minha pergunta são convertidos em:
t_start | t_end | t_country_code
----------------------------------+----------------------------------+----------------
00000000000000000000000000000000 | 00ffffffffffffffffffffffffffffff | ZZ
01000000000000000000000000000000 | 01ffffffffffffffffffffffffffffff | ZZ
...
20000000000000000000000000000000 | 2000ffffffffffffffffffffffffffff | ZZ
...
20011200000000000000000000000000 | 20011200ffffffffffffffffffffffff | MX
...
Isso é o que está armazenado no banco de dados.
O próximo passo foi converter o endereço IP recebido no código para ficar no mesmo formato. Isso é feito em PHP com o seguinte código (suponha que
$ip_address
é o endereço IPv6 de entrada):$addr_bin = inet_pton($ip_address);
$bytes = unpack('n*', $addr_bin);
$ip_address = implode('', array_map(function ($b) {return sprintf("%04x", $b); }, $bytes));
Agora variável
$ip_adress
conterá o endereço IPv6 completo, por exemplo :: => 00000000000000000000000000000000
2001:1200::ab => 200112000000000000000000000000ab
e assim por diante.
Agora você pode simplesmente comparar esse endereço completo com os intervalos no banco de dados. Adicionei uma segunda função ao banco de dados para lidar com endereços IPv6, que se parece com isso:
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;
Finalmente, no meu código php eu adicionei o código que chama uma ou outra função do Postgres com base na entrada ip_address.