Onlangs hebben we de status van LIR en een /29 IPv6-blok verkregen. Daarna ontstond de behoefte om de toegewezen subnetten bij te houden. Aangezien onze billing in PHP is geschreven, moesten we ons in dit onderwerp verdiepen en realiseren dat deze taal niet de meest gebruiksvriendelijke is als het gaat om IPv6. Onder de klikken — onze oplossing voor de opgekomen vragen met betrekking tot adressen en bereik. Misschien niet de meest elegante, maar het lost de gestelde taken op.

Een beetje theorie
Disclaimer. Als u bekend bent met wat IPv6 is en hoe het werkt, kan dit deel voor u saai zijn. Het hoeft niet saai te zijn.
Voor mensen die voor het eerst een IPv6-annotatie zien, kan dat best ontmoedigend zijn. Na de elegante 64.233.177.101 komen we plotseling uit bij 2607:f8b0:4002:c08::8b en kunnen we overweldigd raken. Beide zijn simpelweg menselijk leesbare weergaven van respectievelijk 32 en 128 bits. Elk IP-pakket bevat een header met een strikte standaardisering van de functie van elke bit. Zonder dieper in de opbouw van headers in te gaan, kunnen we hieruit één zaak afleiden: voor operaties met IP-adressen en reeksen is het over het algemeen het handigst om binaire wiskunde en bitwise bewerkingen te gebruiken. Het is ook het handigst om ze in de database op te slaan als BINARY(4) voor IPv4 en BINARY(16) voor IPv6.
Een ander belangrijk aspect dat we zouden moeten bespreken, is netmaskers en CIDR-notatie. CIDR is een acroniem voor Classless Inter-Domain Routing (). Dit concept verving de klassieke aanpak over welke delen van een IP-adres het netwerkgedeelte zijn en welke het adres van de netwerkinterface binnen dat netwerk. In de praktijk zullen de eerste n bits die overeenkomen met het prefix, de waarde 1 hebben, terwijl de rest 0 zal zijn.
In een menselijk leesbare vorm wordt dit geschreven als ip.add.re.ss/cidr. Bijvoorbeeld, 64.233.177.0/24 wat betekent dat de eerste 24 bits naar het prefix verwijzen. De laatste 8 bits, wat ook het laatste getal is in de menselijk leesbare notatie, verwijzen naar het adres binnen het subnet. Nog een paar voorbeelden. 64.233.177.101/32 en 2607:f8b0:4002:c08::8b/128 is een specifiek adres. 2607:f8b0:4002:c08::/64 de eerste 64 bits (de eerste 4 groepen) zijn het prefix, de overige 64 bits zijn het lokale gedeelte. Trouwens, als iemand zich stoort aan "::" in de notatie, vervangt het dubbele dubbele punt willekeurig aantal secties die 0 bevatten. Het mag slechts één keer in de annotatie voorkomen. Met andere woorden, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.
Wat kunnen we hieruit halen? Ten eerste kunnen we het eerste en laatste subnetadres verkrijgen met behulp van binaire AND en OR, als we de maske in binaire vorm kennen. Ten tweede kan het volgende subnet van grootte (d.w.z. met CIDR) n worden berekend door 1 op te tellen bij n-de positie in de binaire voorstelling. Met binaire voorstelling bedoel ik het resultaat van het gebruiken van de functies pack() en inet_pton() en vervolgens het gebruik van , onder binaire voorstelling — de representatie in het binaire systeem die je kunt verkrijgen, bijvoorbeeld met behulp van base_convert().
Historische achtergrondKlassenloze adressering kwam na de klassen- gebaseerde segmentatie. In die verre jaren had niemand gedacht dat er zo veel subnets zouden zijn, ze werden links en rechts in grote blokken uitgedeeld: klasse A — de prefix was de eerste 8 bits (d.w.z. het eerste nummer), met de leidende bit 0; klasse B — de eerste 16 (de eerste twee nummers), leidende bits 10; klasse C — de eerste 24 bits, leidende bits 110. Deze leidende bits stelden de bereiken in die het adres voor de verschillende klassen bepaalden: 0.0.0.0 — 127.255.255.255 voor klasse A, 128.0.0.0 — 191.255.255.255 — klasse B, 192.0.0.0 — 223.255.255.255 — klasse C. Naarmate het internet zich over de wereld verspreidde, realiseerden de regelgevers zich dat ze een fout hadden gemaakt, en in het begin van de jaren '90 ontwikkelden ze een klasse loze concept, waarmee je niet gebonden was aan leidende bits. Iets meer details kunnen we halen uit, zeg maar, de .
Laten we naar de praktijk gaan
In de praktijk realiseren we de drie meest waarschijnlijke taken, zoals ik het zie:
- het verkrijgen van het eerste en laatste adres van het bereik;
- het verkrijgen van het volgende bereik van een opgegeven grootte (CIDR);
- controleren of een adres binnen het bereik valt.
De implementatie zal voor IPv6 zijn, maar indien nodig kan de logica eenvoudig worden aangepast. Sommige ideeën heb ik gehaald , maar ik heb het iets anders geïmplementeerd. Ook bevat de voorbeelden geen invoerfoutcontroles. Dus laten we beginnen.
Zoals ik al zei, kan het eerste en laatste adres van het bereik worden vastgesteld met behulp van bitwise operaties, als we het begin van het bereik en de binaire subnetmasker kennen. Dus als eerste moeten we CIDR omzetten in een binaire masker. Hiervoor verzamelen we de hex-representatie en verpakken deze in binair.
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);
}Aanroep pack(‘H*’, $mask) verpakt de hex-representatie op dezelfde manier als inet_pton(). Met als enige verschil dat bij de aanroep pack() Alle 0 moeten op hun plaats zijn en er mogen geen dubbele punten in de notatie staan, in tegenstelling tot de menselijk leesbare notatie.
De volgende stap is het berekenen van het begin en het einde van het bereik. En hier gaan nuances naar voren komen. Bitbewerkingen zijn beperkt tot de processorbreedte. Daarom, op mijn 32-bits CubieTruck, dat ik soms gebruik voor allerlei testexperimenten, kan ik niet alle 128 bits van het adres in één operatie verwerken. Maar niets weerhoudt ons ervan om dit in groepen van 32 bits op te splitsen (voor het geval dat, wie weet, op welke processors we gaan draaien).
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);
}
}Voor toekomstig gebruik stellen we de mogelijkheid voor om IP door te geven en het resultaat zowel in binaire als in menselijk leesbare vorm te ontvangen. De parameter $which geeft aan of we het begin of het einde van het bereik willen krijgen (de waarden 'start' of 'end' respectievelijk).
De volgende taak (die bovendien het meest praktisch is voor ons bedrijf) is het berekenen van het volgende bereik. Voor deze taak kwam ik op niets beters dan het adres in een binaire string op te splitsen en 1 op de juiste positie toe te voegen, waarna ik alles weer samenvoeg. Om te zorgen dat er geen artefacten verschijnen, besloot ik het adres tijdens het splitsen en samenvoegen per byte te fragmenteren.
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);
}
}Als resultaat krijgen we het prefix van het volgende bereik met de grootte zoals aangegeven in $cidr. Met deze functie wijzen we adressenblokken toe aan onze klanten.
Ten slotte de controle of het adres binnen het bereik valt. Bijvoorbeeld, we hebben een blok /48 toegewezen voor het toewijzen van blokken /64 aan klanten, en we moeten ervoor zorgen dat we bij toewijzing niet buiten het toegewezen blok komen (in de praktijk zal dit niet snel gebeuren, maar de kans bestaat). Dit is eenvoudig. We krijgen het begin en het einde van het bereik in binaire vorm en controleren of het adres binnen de grenzen ligt.
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);
}Ik hoop dat het nuttig was. Welke andere functies voor het werken met adressen denkt u dat nuttig kunnen zijn? Alle aanvullingen, opmerkingen en code-reviews worden van harte verwelkomd in de opmerkingen.
Als u al onze klant bent of erover denkt dat te worden, bieden wij u ter gelegenheid van het uitkomen van dit artikel een gratis /64-blok aan voor alle VPS- of dedicated serverdiensten in het Equinix Tier IV-datacenter in Nederland, op verzoek bij de verkoopafdeling, onder vermelding van deze artikel link in uw ticket. Dit aanbod geldt tot maart 2020.
Een beetje reclame 🙂
Bedankt dat je bij ons blijft. Houd je van onze artikelen? Wil je meer interessante inhoud zien? Ondersteun ons door een bestelling te plaatsen of ons aan vrienden aan te bevelen, , een unieke variant van entry-level servers, die we voor jou hebben bedacht: (opties beschikbaar met RAID1 en RAID10, tot 24 cores en tot 40GB DDR4).
Dell R730xd is 2 keer goedkoper in datacenter Equinix Tier IV in Amsterdam? Alleen bij ons in Nederland! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — vanaf $99! Lees over hoe
Bron: habr.com
