Bepaal het land van IP - IPv6

In mijn project heb ik een functie in postgres (plpgsql) die land van een bepaald ip-adres bepaalt:

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;

Dit is misschien niet de meest efficiënte, maar het voldoet. Merk op dat het een interne tabel gebruikt ( ip_to_country ), die de gegevens bevat zoals hieronder (de getallen n_from en n_to zijn de long -waarden van het begin- en eindadresbereik:

  n_from  |   n_to   | t_country_code 
----------+----------+----------------
        0 | 16777215 | ZZ
 16777216 | 16777471 | AU
...

Nu beginnen we ook de IPv6-adressering te bekijken - en ik moet soortgelijke functionaliteit toevoegen voor IPv6-adressen. Ik heb een vergelijkbare set gegevens voor IPv6, die er als volgt uitziet:

 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
...

Nu, gegeven een IP-adres :: 1 , hoe kan ik (1) controleren of het een geldig IPv6-adres is en (2) de bijbehorende landtoewijzing krijgen?

0
@Ramhound: We gebruiken informatie van Webnet77 ( webnet77.com ). Het heeft ons goed gediend voor IPv4, dus we zijn begonnen met hun database voor IPv6. Ik maak me niet al te veel zorgen om 100% van de tijd correct te zijn (voorlopig), maar moet ergens aan de slag.
toegevoegd de auteur Aleks G, de bron
Weet je zeker dat IPv6 op dezelfde manier wordt toegewezen als IPv4? Er zijn genoeg IPv6-adressen voor de gehele populatie van de aarde en vervolgens een aantal. Uw toewijzingstechniek lijkt niet geldig te zijn als ip-adressen opnieuw worden gereproduceerd. Wat is uw gegevensbron? Er zijn reguliere expressies die u kunnen helpen valideren als een IPv6 geldig is of niet.
toegevoegd de auteur Security Hound, de bron

2 antwoord

Ik geloof dat ik de oplossing heb gevonden. Het gaat om het eerst modificeren van de gegevens en vervolgens het masseren van de invoer. Dit is wat werkte.

Ten eerste moeten de gegevens worden geconverteerd zodat alle adressen vol zijn, zonder te verkorten, met scheidingstekens met puntkomma verwijderd. De voorbeeldgegevens in mijn vraag worden geconverteerd naar:

 t_start                          | t_end                            | t_country_code
----------------------------------+----------------------------------+----------------
 00000000000000000000000000000000 | 00ffffffffffffffffffffffffffffff | ZZ
 01000000000000000000000000000000 | 01ffffffffffffffffffffffffffffff | ZZ
...
 20000000000000000000000000000000 | 2000ffffffffffffffffffffffffffff | ZZ
...
 20011200000000000000000000000000 | 20011200ffffffffffffffffffffffff | MX
...

Dit is wat in de database is opgeslagen.

De volgende stap was om het in de code ontvangen IP-adres in dezelfde indeling om te zetten. Dit gebeurt in PHP met de volgende code (neem aan dat $ ip_adres het binnenkomende IPv6-adres is):

$addr_bin = inet_pton($ip_address);                                                                                                              
$bytes = unpack('n*', $addr_bin);
$ip_address = implode('', array_map(function ($b) {return sprintf("%04x", $b); }, $bytes));

Nu zal variabele $ ip_adres het volledige IPv6-adres bevatten, bijvoorbeeld

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

enzovoorts.

Nu kunt u eenvoudig dit volledige adres vergelijken met de bereiken in de database. Ik heb een tweede functie aan de database toegevoegd om met IPv6-adressen om te gaan, die er als volgt uitziet:

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;

Ten slotte heb ik in mijn php-code de code toegevoegd die de ene of de andere Postgres-functie op basis van het IP-adres van invoer aanroept.

0
toegevoegd

Ten eerste zie ik een paar dingen die je doet die problemen opleveren. De eerste is dit gebruik van varchar en lang om IP-adressen te vertegenwoordigen wanneer PostgreSQL perfect geldige INET- en CIDR-typen heeft die alleen maar beter en sneller doen wat u wilt. Merk op dat deze GIN-indexering momenteel niet goed ondersteunen, zodat u beperkingen op hen niet kunt uitsluiten. Als je dat nodig hebt, kijk dan naar de ip4r-extensie die dit ondersteunt.

Noteer als een patch want nu kun je je varchar naar inet casten. Inet ondersteunt ook zowel ipv4- als ipv6-adressen net als cidr, en vergelijkbare typen bestaan ​​op ip4r.

Hiermee wordt het validatieprobleem van ipv6 voor u opgelost en neemt uw opslag waarschijnlijk af en zorgen voor betere operationele controles en betere prestaties.

Wat landen betreft, ben ik ook van mening dat de toewijzingen misschien niet zo eenvoudig zijn.

0
toegevoegd
Bedankt voor het antwoord, maar dit is niet in het minst nuttig. Er zijn geldige redenen waarom de implementatie is zoals deze is, ondanks het feit dat het een cross-database moet zijn, minimaal in postgres en MySQL werken; details zijn anders, maar de datatypes zijn hetzelfde. Ik vroeg niet om commentaar op mijn implementatie, ik vroeg specifiek over het omgaan met ipv6 - en alles wat je zei is "Ik denk dat de toewijzingen niet zo eenvoudig zijn". Dat is geen antwoord, dat is een opmerking.
toegevoegd de auteur Aleks G, de bron
@ypercube Je lijkt me verkeerd begrepen te hebben. Het belangrijkste probleem hier is dat je de vraag niet beantwoordt, je reageert op iets waar ik niet om vraag.
toegevoegd de auteur Aleks G, de bron
We hebben tenminste de vraag opgelost, toch? Pl/pgsql werkt echter niet op MySQL dus als je eenmaal die route hebt gekozen, denk ik dat je echt met de inet-typen wilt gaan. Plus ze lossen je validatieprobleem op. Als niets anders kun je eenvoudig een kaste van het type maken en functies van varchar draaien.
toegevoegd de auteur Chris Travers, de bron
@AleksG Je hebt gevraagd hoe je ipv6-adressen kunt valideren. Ik stelde voor om hiervoor een type te gebruiken. Hoe reageert dat niet?
toegevoegd de auteur Chris Travers, de bron
@Aleks G: als je wilt dat antwoorden worden beperkt zodat ze werken/gemakkelijk kunnen worden geconverteerd naar MySQL, doe dat dan in je vraag. Niet in de reacties en zeker niet nadat mensen tijd hebben verspild met het proberen een vraag van Postgres te beantwoorden, terwijl u een oplossing voor meerdere databases wilt.
toegevoegd de auteur ypercubeᵀᴹ, de bron