Rad s IPv6 u PHP

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.

Rad s IPv6 u PHP

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 (bezkategorijska adresacija). 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 bitovnih operatora, 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 velikoj i svemogućoj.

Pređimo na praksu

U praksi ćemo implementirati tri najvjerojatnije, kako se meni činilo, zadatka:

  1. dobitak prve i posljednje adrese raspona;
  2. dobitak sljedećeg raspona zadane veličine (CIDR);
  3. provjera pripadnosti adrese rasponu.

Implementacija će biti za IPv6, ali ako je potrebno, logiku se može lako prilagoditi. Neke ideje sam naučio odavde, 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, oblačni VPS za developere od 4,99 $, jedinstveni analog entry-level servera koji smo razvili za vas: Sva istina o VPS-u (KVM) E5-2697 v3 (6 jezgri) 10GB DDR4 480GB SSD 1Gbps od 19 $ ili kako pravilno dijeliti server? (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 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TB od 199 $ u Nizozemskoj! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — od 99 $! Pročitajte o tome Kako izgraditi infrastrukturu korporacijske klase koristeći Dell R730xd E5-2650 v4 servere po cijeni od 9000 eura za sitniš?

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster