Práce s IPv6 v PHP

Nedávno jsme získali status LIR a blok IPv6 o velikosti /29. Následně vznikla potřeba sledovat přidělené subnets. A protože naše billingové systémy jsou napsány v PHP, museli jsme se podrobněji zabývat touto problematikou a pochopit, že tento jazyk není zrovna nejpřátelštější při práci s IPv6. Podrobnosti našich řešení vzniklých úloh týkajících se adres a rozsahů jsou níže. Možná to není úplně elegantní, ale cíle, které jsme si stanovili, plní.

Práce s IPv6 v PHP

Trochu teorie

Disklaimer. Pokud víte, co je to IPv6 a k čemu slouží, může být tato část pro vás nudná. A klidně taky nemusí být.

Lidé, kteří poprvé vidí anotaci IPv6, mohou být docela zmateni. Po elegantním 64.233.177.101 nás najednou zasáhne 2607:f8b0:4002:c08::8b a můžeme být rozpačití. Obě varianty jsou totiž jen člověku srozumitelným vyjádřením 32 a 128 bitů. Každý IP paket obsahuje hlavičku se striktně standardizovaným účelem každého bitu. Bez hlubšího zkoumání struktury hlaviček bychom zde měli vyvodit jedno: pro operace s IP adresami a rozsahy je obvykle nejvhodnější používat binární aritmetiku a bitové operace. V databázi je také nejvhodnější je uchovávat jako BINARY(4) pro IPv4 a BINARY(16) pro IPv6.

Dalším důležitým aspektem, který by stálo za zmínku, jsou síťové masky a CIDR notace. CIDR je zkratka pro bezklasové interdoménové směrování (classless addressing). Tento koncept nahradil klasickou otázku určení, která část IP adresy je prefix sítě a která je adresou síťového rozhraní uvnitř této sítě. V praxi n bitů odpovídajících prefixu bude mít hodnotu 1, ostatní 0.

V člověku srozumitelném formátu je to zapisováno jako ip.add.re.ss/cidr. Například, 64.233.177.0/24 označuje, že prvních 24 bitů patří k prefixu. Posledních 8 bitů, což je poslední číslo v srozumitelném zápisu, se vztahuje k adrese uvnitř podsítě. Ještě pár cvičení. 64.233.177.101/32 a 2607:f8b0:4002:c08::8b/128 — jedna konkrétní adresa. 2607:f8b0:4002:c08::/64 — prvních 64 bitů (první 4 skupiny) jsou prefix, zbývajících 64 bitů tvoří lokální část. Mimochodem, pokud někoho zmátla "::" v zápisu, dvojité dvojtečky nahrazují libovolný počet sekcí obsahujících 0. Může se objevit v anotaci pouze jednou. Jinými slovy, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.

Co si z toho všeho máme odnést? Za prvé, první a poslední adresa podsítě lze získat pomocí binárních AND a OR, pokud známe masku v binárním formátu. Za druhé, další podsíť o velikosti (tj. s CIDR) n lze vypočítat přičtením 1 k n-té pozici v binárním zobrazení. Pod binárním formátem chápu výsledek použití funkcí pack() a inet_pton() a následné použití bitových operátorů, pod binárním se rozumí zobrazení v binárním systému, které lze získat například pomocí base_convert().

HistorieKlasifikované adresování předcházelo bezklasovému. V těch dávných letech si nikdo nedokázal představit, že bude tolik podsítí, rozdávaly se bezhlavě ve velkých blocích: třída A — prefix byly první 4 bity (tj. první číslo) s vedoucím bitem 0; třída B — první 16 (první dvě čísla), vedoucí bity 10; třída C — prvních 24 bitů, vedoucí bity 110. Tyto vedoucí bity určovaly rozsahy, v nichž byla vydána adresa dané třídy: 0.0.0.0 — 127.255.255.255 pro třídu A, 128.0.0.0 — 191.255.255.255 — třída B, 192.0.0.0 — 223.255.255.255 — třída C. Jak se internet rozrůstal po planetě, regulátoři pochopili, že udělali chybu, a na začátku 90. let vyvinuli bezklasovou koncepci, která umožnila nezávislost na vedoucích bitech. Trochu podrobnější informace lze získat například v velké a vševědoucí.

Přejděme k praxi

V praxi realizujeme tři nejpravděpodobnější úkoly, jak jsem si myslel:

  1. získání první a poslední adresy rozsahu;
  2. získání dalšího rozsahu dané velikosti (CIDR);
  3. kontrola příslušnosti adresy k rozsahu.

Implementace bude pro IPv6, ale v případě potřeby lze logiku snadno přizpůsobit. Některé nápady jsem získal odsud, ale realizoval jsem je trochu jinak. Také v příkladech není kontrola na chyby vstupu. Takže pojďme.

Jak jsem již uvedl, první a poslední adresa rozsahu lze určit pomocí bitových operací, pokud známe začátek rozsahu a binární masku podsítě. Proto nám nejdříve musíme převést CIDR na binární masku. K tomu sestavíme její hexadecimální reprezentaci a zapakujeme ji do binárního formátu.

function cidrToMask ($cidr) {
    $mask = str_repeat('f', ceil($cidr / 4));
    $mask .= dechex(4 * ($cidr % 4));
    $mask = str_pad($mask, 32, '0');
    return pack('H*', $mask);
}

Zavolání pack(‘H*’, $mask) zabalí hexadecimální reprezentaci stejným způsobem, jakým to dělá inet_pton(). S tím jediným rozdílem, že všechny 0 musí být na svých místech a dvojtečky ve záznamu by být neměly, na rozdíl od člověku srozumitelného zápisu. pack() Dalším krokem je výpočet začátku a konce rozsahu. A tady nastanou detaily. Bitové operace jsou omezeny velikostí procesoru. Na mém 32bitovém CubieTrucku, který občas používám pro různé testovací experimenty, nelze zpracovat všech 128 bitů adresy najednou. Nicméně nic nám nebrání rozdělit ji na skupiny po 32 bitech (pro jistotu, kdo ví, na jakých procesorech budeme běžet).

Pro budoucí použití zohledníme možnost předávat IP a získávat výsledek jak v binární, tak v člověku čitelné podobě. Parametr

function getRangeBoundary ($ip, $cidr, $which, $ipIsBin = false, $returnBin = false) {
    $mask = cidrToMask($cidr);
    if (!$ipIsBin) {
        $ip = inet_pton($ip);
    }
    $ipParts   = str_split($ip, 4);
    $maskParts = str_split($mask, 4);
    $rangeParts  = [];
    for ($i = 0; $i < count($ipParts); $i++) {
        if ($which == 'start') {
            /* Побитовый & адреса и маски оставит только биты префикса. */
            $rangeParts[$i] = $ipParts[$i] & $maskParts[$i];
        } else {
            /* Побитовый | с обратной маской (~) оставит биты префикса и установит все биты локальной части в 1. */
            $rangeParts[$i] = $ipParts[$i] | ~$maskParts[$i];
        }
    }
    $rangeBoundary = implode($rangeParts);
    if ($returnBin) {
        return $rangeBoundary;
    } else {
        return inet_ntop($rangeBoundary);
    }
}

$which určuje, zda chceme získat začátek nebo konec rozsahu (hodnoty ‘start’ ‘end’ nebo příslušně). Dalším úkolem (také nejvíce praktickým pro naši společnost) je výpočet dalšího rozsahu. Pro tento úkol mě nenapadlo nic lepšího, než rozložit adresu na binární řetězec a přidat 1 na potřebné pozici, poté všechno zase složit. Aby se nikde neobjevily artefakty, rozhodl jsem se při rozkládání a skládání dělit adresu po bytech.

Na výstupu získáme prefix dalšího rozsahu velikosti uvedené v

function getNextBlock ($ipStart, $cidr, $ipIsBin = false, $returnBin = false) {
    if (!$ipIsBin) {
        $ipStart = inet_pton($ipStart);
    }
    $ipParts = str_split($ipStart, 1);
    $ipBin   = '';
    foreach ($ipParts as $ipPart) {
        $ipBin .= str_pad(base_convert(unpack('H*', $ipPart)[1], 16, 2), 8, '0', STR_PAD_LEFT);
    }
    /* Добавляем 1 в нужном разряде двоичного представления строки "влоб" :) */
    $i = $cidr - 1;
    while ($i >= 0) {
        if ($ipBin[$i] == '0') {
            $ipBin[$i] = '1';
            break;
        } else {
            $ipBin[$i] = '0';
        }
        $i--;
    }
    $ipBinParts = str_split($ipBin, 8);
    foreach ($ipBinParts as $key => $ipBinPart) {
        $ipParts[$key] = pack('H*', str_pad(base_convert($ipBinPart, 2, 16), 2, '0', STR_PAD_LEFT));
    }
    $nextIp = implode($ipParts);
    if ($returnBin) {
        return $nextIp;
    } else {
        return inet_ntop($nextIp);
    }
}

$cidr . Touto funkcí vyčleňujeme bloky adres našim klientům.Nakonec, kontrola, zda adresa patří do rozsahu. Například jsme vyčlenili jeden blok /48 pro distribuci bloků /64 klientům a musíme se ujistit, že při přiřazování nepřekročíme hranice vyčleněného bloku (v praxi to nastane za dlouho, ale možnost tu je). Tady je to jednoduché. Získáme začátek a konec rozsahu v binární podobě a zkontrolujeme, zda se adresa nachází v mezích.

function ipInRange ($ip, $rangeStart, $cidr) { $start = getRangeBoundary($rangeStart, $cidr, 'start', false, true); $end = getRangeBoundary($rangeStart, $cidr, 'end', false, true); $ipBin = inet_pton($ip); return ($ipBin >= $start && $ipBin <= $end); }

Doufám, že to bylo užitečné. Jaké další funkce pro práci s adresami by podle vašeho názoru mohly být užitečné? Jakékoli doplňky, komentáře a kódové recenze jsou v komentářích vítány.

Надеюсь, было полезно. Какие ещё функции по работе с адресами могут по Вашему мнению быть полезны? Любые дополнения, комментарии и код-ревью горячо приветствуются в комментариях.

Pokud jste již naším klientem nebo o tom teprve přemýšlíte, při příležitosti zveřejnění tohoto článku vám nabízíme zcela zdarma blok /64 pro všechny služby VPS nebo dedikovaný server v datovém centru Equinix Tier IV, Nizozemsko, na žádost v prodeji, přičemž poskytnete odkaz na tento článek v tiketu. Nabídka platí do března 2020.

Malá reklama 🙂

Děkujeme, že s námi zůstáváte. Líbí se vám naše články? Chcete vidět více zajímavých materiálů? Podpořte nás objednávkou nebo doporučením známým, cloudové VPS pro vývojáře od 4,99 $, unikátní alternativa entry-level serverů, kterou jsme pro vás vymysleli: Všechny pravdy o VPS (KVM) E5-2697 v3 (6 jader) 10GB DDR4 480GB SSD 1Gbps od 19 $ nebo jak správně rozdělit server? (k dispozici varianty s RAID1 a RAID10, až 24 jader a až 40GB DDR4).

Dell R730xd je dvakrát levnější v datovém centru Equinix Tier IV v Amsterdamu? Jen u nás 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TB od 199 $ v Nizozemsku! Dell R420 — 2x E5-2430 2.2GHz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — od 99 $! Čtěte o tom Jak postavit infrastrukturu podnikové třídy s použitím serverů Dell R730xd E5-2650 v4 v hodnotě 9000 eur za pakatel?

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster