Nedavno smo dobili LIR status i /29 blok IPv6. A zatim se pojavila potreba za vođenjem evidencije o dodijeljenim podmrežama. Kako je naš billing napisan u PHP-u, morali smo se malo upoznati s ovom temom i shvatiti da ovaj jezik nije najprijateljskiji kada je riječ o radu s IPv6. Ispod je naše rješenje nastalih zadataka vezanih uz adrese i raspon. Možda nije najelgantnije, ali zadani zadaci se izvršavaju.

Malo teorije
Odricanje od odgovornosti. Ako ste upoznati s tim što je IPv6 i s čim se to jede, ovaj dio može biti dosadan za vas. Možda i ne.
Ljude koji prvi put vide aneks IPv6 može poprilično zbuniti. Nakon elegantnih 64.233.177.101 odjednom se susrećemo s 2607:f8b0:4002:c08::8b i možemo se zbuniti. I jedno i drugo — samo su ljudskom razumljiv prikaz 32 i 128 bita. Svaki IP paket sadrži zaglavlje sa strogo standardiziranim značenjem svake bita. Ne ulazeći dublje u strukturu zaglavlja, odavde možemo zaključiti jedno: za operacije s IP adresama i rasponima najpraktičnije je koristiti binarnu matematiku i bitovne operacije. Pohranjivanje u bazi također je najpraktičnije kao BINARY(4) za IPv4 i BINARY(16) za IPv6.
Još jedan važan aspekt koji bi trebalo spomenuti — to su mrežne maske i CIDR notacija. CIDR je akronim za Classless Inter-Domain Routing (). Ova koncepcija zamijenila je klasičnu u definiranju koja je dio IP adrese prefiks mreže, a koja je adresa mrežnog sučelja unutar te mreže. U praksi prvih n bita koji odgovaraju prefiksu bit će postavljeni na 1, a ostali na 0.
U ljudskom razumljivom formatu to se zapisuje kao ip.add.re.ss/cidr. Na primjer, 64.233.177.0/24 označava da prvih 24 bita pripada prefiksu. Posljednjih 8 bita, što je također posljednji broj u ljudskom razumljivom zapisu, odnosi se na adresu unutar podmreže. Još nekoliko vježbi. 64.233.177.101/32 i 2607:f8b0:4002:c08::8b/128 — jedna specifična adresa. 2607:f8b0:4002:c08::/64 — prvih 64 bita (prvih 4 grupe) su prefiks, ostalih 64 bita su lokalni dio. Usput, ako nekoga zbunjuje "::" u zapisu, dvostruka točka zamjenjuje proizvoljan broj sekcija koje sadrže 0. Može se pojaviti u aneksu samo jednom. Drugim riječima, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.
Što možemo iz svega ovoga naučiti? Prvo, prvu i posljednju adresu podmreže možemo dobiti pomoću binarnih AND i OR, znajući masku u binarnom obliku. Drugo, sljedeću podmrežu veličine (tj. s CIDR) n možemo izračunati dodajući 1 na n-tu poziciju u binarnom prikazu. Pod binarnim oblikom mislim na rezultat korištenja funkcija pack() i inet_pton() i daljnju upotrebu , pod binarnim — prikaz u binarnom sustavu, koji možemo dobiti, recimo, pomoću base_convert().
Povijesne informacijeKlasično je prethodila neklasna segmentacija adresa. U ta davna vremena nitko nije pretpostavljao da će podmreža biti toliko mnogo, dijelili su ih na sve strane velikim blokovima: klasa A — prefiks su bili prva 8 bita (tj. prvo broj), s vođom bita 0; klasa B — prva 16 (prva dva broja), vođi bita 10; klasa C — prva 24 bita, vođi bita 110. Ti vođi bita su određivali raspon, u kojem se dodjeljuje adresa određene klase: 0.0.0.0 — 127.255.255.255 za klasu A, 128.0.0.0 — 191.255.255.255 — klasa B, 192.0.0.0 — 223.255.255.255 — klasa C. Kako se internet širio po planeti, regulatori su shvatili da su pogriješili, i početkom 90-ih razvili su neklasnu koncepciju, koja nije ovisila o vođama bita. Malo više detalja može se naučiti, recimo, u .
Pređimo na praksu
U praksi ćemo implementirati tri najvjerojatnije, kako se meni činilo, zadatka:
- dobitak prve i posljednje adrese raspona;
- dobitak sljedećeg raspona zadane veličine (CIDR);
- provjera pripadnosti adrese rasponu.
Implementacija će biti za IPv6, ali ako je potrebno, logiku se može lako prilagoditi. Neke ideje sam naučio , ali sam ih implementirao malo drugačije. Također, u primjerima nema provjere grešaka ulaza. Pa, krenimo.
Kao što sam već spomenuo, prvu i posljednju adresu raspona možemo odrediti pomoću bitovnih operacija, znajući početak raspona i binarnu masku podmreže. Prema tome, prvo što trebamo učiniti je pretvoriti CIDR u binarnu masku. Za to ćemo skupiti njezino hex predstavljanje i zapakirati u binarno.
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);
}Poziv pack(‘H*’, $mask) pakira hex predstavljanje na isti način kao i inet_pton(). S tom razlikom što prilikom poziva pack() svi 0 trebaju biti na svojim mjestima, a dvotočje u zapisu ne bi smjelo biti, za razliku od ljudski razumljive verzije.
Sljedeći korak je izračun početka i kraja raspona. I tu će se pojaviti određene nijanse. Bitne operacije su ograničene procesorskom architekturom. Stoga, na mom 32-bitnom CubieTrucku, koji ponekad koristim za razne testske eksperimente, neće biti moguće obraditi svih 128 bita adrese u jednoj operaciji. Međutim, ništa nam ne preprečuje da ga razbijemo na grupe od 32 bita (za svaki slučaj, tko zna na kojim procesorima ćemo raditi).
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);
}
}Za buduću upotrebu predvidimo mogućnost prijenosa IP-a i dobivanja rezultata kako u binarnom, tako i u ljudski čitljivom obliku. Parametar $which ovdje određuje želimo li dobiti početak ili kraj raspona (vrijednosti ‘start’ ili ‘end’ odgovarajuće).
Sljedeći zadatak ( također najpraktičniji za našu tvrtku) je izračunavanje sljedećeg raspona. Za ovaj zadatak ništa bolje nije došlo na pamet, osim da adresu razložimo u binarni niz i dodamo 1 na potrebnoj poziciji, nakon čega sve ponovo sastavimo. Da ne bi bilo nikakvih artefakata, odlučio sam prilikom razlaganja i sklapanja adresu dijeliti po bajtovima.
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 izlazu ćemo dobiti prefiks sljedećeg raspona veličine navedene u $cidr. Ovom funkcijom izdvajamo blokove adresa našim klijentima.
Na kraju, provjera pripada li adresa rasponu. Na primjer, dodijelili smo jedan blok /48 za raspodjelu klijentima blokova /64, i moramo osigurati da prilikom dodjeljivanja ne izlazimo iz okvira dodijeljenog bloka (u praksi će se to dogoditi rijetko, ali ipak postoji vjerojatnost). Ovdje je sve jednostavno. Dobivamo početak i kraj raspona u binarnom obliku i provjeravamo je li adresa unutar granica.
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);
}Nadam se da je bilo korisno. Koje još funkcije za rad s adresama mislite da bi mogle biti korisne? Bilo kakve dopune, komentari i kod revizije su srdačno dobrodošli u komentarima.
Ako ste već naš klijent ili tek razmišljate o tome da to postanete, povodom objavljivanja ovog članka, nudimo vam blok /64 potpuno besplatno za sve VPS usluge ili dedicated server u data centru Equinix Tier IV, Nizozemska, na zahtev u prodajnom odeljenju, pružajući link na ovaj članak u tiketu. Ponuda važi do marta 2020. godine.
Malo reklame 🙂
Hvala što ostajete s nama. Sviđa li vam se naše članke? Želite li vidjeti više zanimljivih materijala? Podržite nas narudžbom ili preporučite prijateljima, , jedinstveni analog entry-level servera koji smo razvili za vas: (dostupne opcije s RAID1 i RAID10, do 24 jezgre i do 40GB DDR4).
Dell R730xd je 2 puta jeftiniji u data centaru Equinix Tier IV u Amsterdamu? Samo kod nas u Nizozemskoj! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — od 99 $! Pročitajte o tome
Izvor: habr.com
