Recentemente abbiamo acquisito lo status di LIR e un blocco IPv6 /29. E subito è sorto il bisogno di tenere traccia delle subnet assegnate. E poiché il nostro sistema di billing è scritto in PHP, ho dovuto un po' approfondire l'argomento e realizzare che questo linguaggio non è dei più amichevoli quando si tratta di lavorare con IPv6. Di seguito trovi la nostra soluzione alle problematiche relative alla gestione degli indirizzi e delle gamme. Forse non è la più elegante, ma soddisfa gli obiettivi prefissati.

Un po' di teoria
Dichiarazione di non responsabilità. Se sei già familiare con IPv6 e il suo utilizzo, questa parte potrebbe sembrarti noiosa. O magari no.
Le persone che vedono per la prima volta un'annotazione IPv6 potrebbero sentirsi un po' disorientate. Dopo eleganti 64.233.177.101 ci troviamo di fronte a 2607:f8b0:4002:c08::8b e potremmo sentirci confusi. Entrambi sono semplicemente rappresentazioni comprensibili di 32 e 128 bit rispettivamente. Qualsiasi pacchetto IP contiene un'intestazione con una destinazione rigorosamente standardizzata per ciascun bit. Senza entrare troppo nella struttura delle intestazioni, da qui dobbiamo estrarre un concetto: per le operazioni con indirizzi IP e gamme è principalmente comodo utilizzare la matematica binaria e le operazioni bitwise. È anche più semplice memorizzarli nel database come BINARY(4) per IPv4 e BINARY(16) per IPv6.
Un altro aspetto importante da considerare sono le maschere di rete e la notazione CIDR. CIDR è l'acronimo di Classless Inter-Domain Routing (). Questo concetto ha sostituito il precedente approccio classico nella definizione di quale parte dell'indirizzo IP sia il prefisso di rete e quale sia l'indirizzo dell'interfaccia di rete all'interno di tale rete. In pratica, i primi n bit, corrispondenti al prefisso, saranno impostati a 1, mentre gli altri a 0.
In forma comprensibile, questo si scrive come ip.add.re.ss/cidr. Ad esempio, 64.233.177.0/24 che indica che i primi 24 bit appartengono al prefisso. Gli ultimi 8 bit, che sono anche l'ultimo numero nella rappresentazione comprensibile, appartengono all'indirizzo all'interno della subnet. Altri due esercizi. 64.233.177.101/32 e 2607:f8b0:4002:c08::8b/128 è un indirizzo specifico. 2607:f8b0:4002:c08::/64 i primi 64 bit (le prime 4 sezioni) sono il prefisso, gli altri 64 bit sono la parte locale. A proposito, se qualcuno è confuso dal "::" nella rappresentazione, il doppio due punti sostituisce un numero arbitrario di sezioni che contengono 0. Può apparire nell'annotazione solo una volta. In altre parole, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.
Cosa possiamo trarre da tutto questo? Prima di tutto, il primo e l'ultimo indirizzo della subnet possono essere ottenuti usando gli operatori binari AND e OR, conoscendo la maschera in forma binaria. In secondo luogo, la successiva subnet di dimensione (cioè con CIDR) n può essere calcolata aggiungendo 1 alla n-esima posizione nella rappresentazione binaria. Per rappresentazione binaria intendo il risultato dell'uso delle funzioni pack() e inet_pton() e il successivo utilizzo di , per rappresentazione binaria la rappresentazione in sistema binario, che si può ottenere, ad esempio, tramite base_convert().
Nota storicaAlla classless, precedeva l'indirizzamento a classe. In quegli anni lontani nessuno avrebbe immaginato che ci sarebbero state così tante subnet, venivano assegnate a destra e a sinistra in grandi blocchi: classe A — il prefisso erano i primi 8 bit (cioè il primo numero), con un bit iniziale 0; classe B — i primi 16 (le prime due cifre), con i bit iniziali 10; classe C — i primi 24 bit, con i bit iniziali 110. Questi bit iniziali definivano gli intervalli nei quali veniva assegnato un indirizzo di quel determinato tipo: 0.0.0.0 — 127.255.255.255 per la classe A, 128.0.0.0 — 191.255.255.255 — classe B, 192.0.0.0 — 223.255.255.255 — classe C. Con il progredire di Internet, i regolatori si resero conto di aver commesso un errore, e nei primi anni '90 svilupparono un concetto senza classi, che ha permesso di non doversi più vincolare ai bit iniziali. Ulteriori dettagli possono essere acquisiti, ad esempio, nel .
Passiamo alla pratica
In pratica implementeremo tre delle più probabili, a mio avviso, task:
- ottenere il primo e l'ultimo indirizzo di una gamma;
- ottenere la gamma successiva di una dimensione specifica (CIDR);
- verificare se un indirizzo appartiene a una gamma.
L'implementazione sarà per IPv6, ma se necessario la logica può essere facilmente adattata. Alcune idee le ho tratte , ma le ho implementate in modo leggermente diverso. Inoltre, negli esempi non sono previste verifiche sugli errori di input. Quindi, iniziamo.
Come ho già accennato, il primo e l'ultimo indirizzo di una gamma possono essere determinati utilizzando operazioni bitwise, conoscendo l'inizio della gamma e la maschera binaria della subnet. Pertanto, la prima cosa che dobbiamo fare è convertire il CIDR in una maschera binaria. Per fare ciò, raccoglieremo la sua rappresentazione hex e la imballeremo in formato binario.
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);
}La chiamata pack(‘H*’, $mask) imballa la rappresentazione hex in questo modo, come anche inet_pton(). Con l'unica differenza che nell'uso di pack() tutti gli 0 devono essere al loro posto, e i due punti nella rappresentazione non devono apparire, a differenza della rappresentazione comprensibile.
Il passo successivo è calcolare l'inizio e la fine dell'intervallo. Qui sorgono alcuni dettagli. Le operazioni bit a bit sono limitate dalla larghezza del processore. Pertanto, sul mio CubieTruck a 32 bit, che utilizzo a volte per vari esperimenti, non sarà possibile gestire tutti i 128 bit dell'indirizzo in una sola operazione. Tuttavia, nulla ci impedisce di suddividerlo in gruppi di 32 bit (per ogni evenienza, chi sa su quali processori andremo ad eseguire).
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);
}
}Per un uso futuro, prevediamo di poter passare un IP e ricevere il risultato sia in forma binaria che in forma leggibile. Il parametro $which qui indica se vogliamo ottenere l'inizio o la fine dell'intervallo (valori 'start' o 'end' rispettivamente).
Il compito successivo (che è anche il più pratico per la nostra azienda) è calcolare il prossimo intervallo. Per questo compito, nulla di meglio ci è venuto in mente se non scomporre l'indirizzo in una stringa binaria e aggiungere 1 nella posizione necessaria, per poi riassemblare tutto. Per evitare artefatti, ho deciso di scomporre e assemblare l'indirizzo byte per 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);
}
}In output, otterremo il prefisso del prossimo intervallo della dimensione indicata in $cidr.Con questa funzione assegniamo blocchi di indirizzi ai nostri clienti.
Infine, c'è la verifica se l'indirizzo appartiene all'intervallo. Ad esempio, abbiamo assegnato un blocco /48 per distribuire ai clienti blocchi /64, e dobbiamo assicurarci che durante l'assegnazione non superiamo i limiti del blocco assegnato (nella pratica questo non accadrà presto, ma è comunque una possibilità). Qui è semplice. Otteniamo l'inizio e la fine dell'intervallo in forma binaria e verifichiamo se l'indirizzo è compreso nei limiti.
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);
}Spero che sia stato utile. Quali altre funzioni relative agli indirizzi potrebbero essere utili secondo voi? Qualsiasi aggiunta, commento e revisione del codice sono caldamente benvenuti nei commenti.
Se siete già nostri clienti o state considerando di diventarlo, in occasione della pubblicazione di questo articolo, offriamo la possibilità di ottenere un blocco /64 completamente gratuito per tutti i servizi VPS o server dedicati nel data center Equinix Tier IV, Paesi Bassi, su richiesta al nostro reparto vendite, fornendo il link a questo articolo nel ticket. L'offerta è valida fino a marzo 2020.
Un po' di pubblicità 🙂
Grazie per essere con noi. Ti piacciono i nostri articoli? Vuoi vedere più contenuti interessanti? Supportaci effettuando un ordine o raccomandandoci ai tuoi conoscenti, , un'alternativa unica ai server entry-level, che abbiamo creato per te: (disponibili opzioni con RAID1 e RAID10, fino a 24 core e fino a 40GB DDR4).
Dell R730xd a metà prezzo nel data center Equinix Tier IV ad Amsterdam? Solo da noi nei Paesi Bassi! Dell R420 — 2x E5-2430 2.2GHz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — a partire da $99! Scopri di più su
Fonte: habr.com
