Nedávno sme získali status LIR a /29 blok IPv6. A hneď sa objavila potreba viesť evidenciu priradených podsietí. A keďže náš billing je napísaný v PHP, museli sme sa trošku zaoberať touto témou a uvedomiť si, že tento jazyk nie je práve najpriaznivejší z pohľadu práce s IPv6. Pod týmto článkom sa nachádza naše riešenie vzniknutých úloh pri práci s adresami a rozsahmi. Možno to nie je najnáročnejšie riešenie, ale úlohy, ktoré sme si dali, spĺňa.

Trochu teórie
Disklejmer. Ak poznáte, čo je IPv6 a ako sa s ním pracuje, táto časť môže byť pre vás nudná. Alebo aj nie.
Ľudí, ktorí prvýkrát vidia anotáciu IPv6, to môže celkom zneistiť. Po elegantných 64.233.177.101 sa zrazu stretávame s 2607:f8b0:4002:c08::8b a môžeme byť zmätení. Obidve sú len ľudsky čitateľné reprezentácie 32 a 128 bitov. Každý IP paket obsahuje hlavičku so striktne štandardizovaným určením každého bitu. Bez toho, aby sme sa ešte hlbšie zaoberali štruktúrou hlavičiek, je potrebné si z toho odniesť jedno: pre operácie s IP adresami a rozsahmi je najpraktickejšie používať binárnu aritmetiku a bitové operácie. Ukladať ich v databáze je najlepšie ako BINARY(4) pre IPv4 a BINARY(16) pre IPv6.
Ďalší dôležitý aspekt, ktorý by bolo vhodné spomenúť, sú masky siete a CIDR notácia. CIDR je skratka pre Classless Inter-Domain Routing (). Tento koncept nahradil triednu notáciu pri určovaní, ktorá časť IP adresy je prefixom siete a ktorá je adresou sieťového rozhrania v rámci tejto siete. V praxi bude prvých n bitov, ktoré zodpovedajú prefixu, nastavených na hodnotu 1, ostatné na 0.
V ľudskej forme sa to zapisuje ako ip.add.re.ss/cidr. Napríklad, 64.233.177.0/24 označuje, že prvých 24 bitov patrí k prefixu. Posledných 8 bitov, ktoré sú zároveň posledným číslom v čitateľskom zápise, patrí k adrese v rámci podsiete. Ešte niekoľko cvičení. 64.233.177.101/32 a 2607:f8b0:4002:c08::8b/128 — jedna konkrétna adresa. 2607:f8b0:4002:c08::/64 — prvých 64 bitov (prvý 4 skupiny) sú prefix, ďalších 64 bitov sú lokálna časť. Mimochodom, ak niekoho mätie "::" v zápise, dvojnásobný dvojbodka nahrádza ľubovoľný počet sekcií, ktoré obsahujú 0. Môže sa objaviť v anotácii len raz. Inými slovami, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.
Čo by sme z toho všetkého mali vyvodiť? Po prvé, prvú a poslednú adresu podsiete môžeme získať pomocou binárnych operácií AND a OR, ak poznáme masku v binárnom formáte. Po druhé, ďalšiu podsieť danej veľkosti (t.j. s CIDR) n môžeme vypočítať pridaním 1 k n-tej pozícii v binárnom zobrazení. Pod binárnym formátom mám na mysli výsledok používania funkcií pack() a inet_pton() a následné využitie , pod binárnym — zobrazenie v dvojkovej sústave, ktoré možno získať napríklad pomocou base_convert().
Historické pozadieBez triednej predchádzala triedna segregácia adresovania. V tých dávnych časoch nikto nepovažoval, že podsietí bude toľko, rozdávali ich doľava a doprava vo veľkých blokoch: trieda A — prefixom boli prvé 8 bitov (t.j. prvé číslo), s vedúcim bitom 0; trieda B — prvé 16 (prvé dve čísla), vedúce bity 10; trieda C — prvé 24 bitov, vedúce bity 110. Tieto vedúce bity určovali rozsahy, v ktorých sa prideľovala adresa toho alebo oného typu: 0.0.0.0 — 127.255.255.255 pre triedu A, 128.0.0.0 — 191.255.255.255 — trieda B, 192.0.0.0 — 223.255.255.255 — trieda C. Ako sa internet rozširoval po planéte, regulátori si uvedomili, že sa mýlili, a na začiatku 90. rokov vyvinuli beztriednu koncepciu, ktorá umožnila neviazať sa na vedúce bity. Trochu viac podrobností možno získať napríklad v .
Prejdime k praxi
V praxi realizujeme tri najpravdepodobnejšie, ako sa mi zdalo, úlohy:
- získanie prvej a poslednej adresy rozsahu;
- získanie nasledujúceho rozsahu danej veľkosti (CIDR);
- kontrola, či adresa patrí do rozsahu.
Implementácia bude pre IPv6, ale ak je to potrebné, logiku možno ľahko prispôsobiť. Niektoré myšlienky som získal , ale realizoval trochu inak. Taktiež v príkladoch nie je kontrola chýb vstupu. Tak teda, poďme na to.
Ako som už spomenul, prvú a poslednú adresu rozsahu môžeme určiť pomocou bitových operácií, ak poznáme začiatok rozsahu a binárnu masku podsiete. Preto najskôr musíme premeniť CIDR na binárnu masku. Na to vytvoríme jej hex reprezentáciu a zabalíme do binárnej.
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);
}Vyvolanie pack(‘H*’, $mask) zabalí hex reprezentáciu rovnakým spôsobom, ako aj inet_pton(). S tou výnimkou, že pri volaní pack() všetky 0 by mali byť na svojich miestach, a dvojbodky v zápise by nemali byť, na rozdiel od prehľadného zápisu.
Nasledujúci krok — výpočet začiatku a konca rozsahu. A tu nastanú nuansy. Bitové operácie sú obmedzené architektúrou procesora. Zodpovedajúco, na mojom 32-bitovom CubieTrucku, ktorý občas používam na rôzne testy, nie je možné spracovať všetkých 128 bitov adresy za jednu operáciu. Napriek tomu nám nič nebráni rozdeliť to na skupiny po 32 bitoch (pre každý prípad, kto vie, na akých procesoroch budeme štartovať).
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);
}
}Pre budúce použitie umožníme možnosť prenášať IP a získať výsledok ako v binárnej, tak aj v čitateľnej podobe. Parameter $which tu určuje, či chceme získať začiatok alebo koniec rozsahu (hodnoty 'start' alebo 'end' prípadne).
Nasledujúca úloha (ktorá je navyše najpraktickejšia pre našu spoločnosť) — výpočet nasledujúceho rozsahu. Pre túto úlohu ma nič lepšie nenapadlo, ako rozložiť adresu na binárny reťazec a pridať 1 na potrebnej pozícii, po čom všetko zložiť späť. Aby sa nikde neobjavili artefakty, rozhodol som sa pri rozkladaní a skladaní rozbiť adresu po byte.
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);
}
}Na výstupe získame prefix nasledujúceho rozsahu veľkosti, uvedenej v $cidr.Touto funkciou vyčleňujeme bloky adries našim klientom.
Nakoniec, overenie, či adresa patrí do rozsahu. Napríklad, pridelili sme jeden blok /48 na pridelenie klientom blokov /64, a musíme sa uistiť, že pri priradení neprekračujeme hranice prideleného bloku (v praxi sa to stane len zriedka, ale predsa len, pravdepodobnosť tu je). Tu je to jednoduché. Získame začiatok a koniec rozsahu v binárnej podobe a skontrolujeme, či sa adresa nachádza v rámci.
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);
}Dúfam, že to bolo užitočné. Aké ďalšie funkcie na prácu s adresami by podľa vás mohli byť užitočné? Akékoľvek dodatky, komentáre a kódové recenzie sú v komentároch srdečne vítané.
Ak ste už naším klientom, alebo len zvažujete, že sa ním stanete, pri príležitosti vydania tohto článku vám ponúkame blok /64 úplne zadarmo pre všetky služby VPS alebo vyhradený server v dátovom centre Equinix Tier IV v Holandsku na požiadanie v predajnom oddelení, pričom poskytnete odkaz na tento článok v tikete. Ponuka platí do marca 2020.
Trochu reklamy 🙂
Ďakujeme, že ostávate s nami. Páčia sa vám naše články? Chcete vidieť viac zaujímavých materiálov? Podporte nás, vytvorením objednávky alebo odporúčaním známym, , unikátny ekvivalent entry-level serverov, ktorý sme pre vás vytvorili: (k dispozícii varianty s RAID1 a RAID10, až 24 jadier a až 40GB DDR4).
Dell R730xd je dvakrát lacnejší v dátovom centre Equinix Tier IV v Amsterdame? Len u nás v Holandsku! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — od 99 dolárov! Prečítajte si o tom
Zdroj: habr.com
