Vor kurzem haben wir den LIR-Status und ein \/29 IPv6-Block erhalten. Danach entstand die Notwendigkeit, die zugewiesenen Subnetze zu verfolgen. Da unser Billing-System in PHP geschrieben ist, musste ich mich ein wenig mit dem Thema befassen und erkennen, dass diese Sprache nicht die benutzerfreundlichste für die Arbeit mit IPv6 ist. Im Folgenden finden Sie unsere Lösung der damit verbundenen Aufgaben zur Arbeit mit Adressen und Bereichen. Vielleicht nicht die eleganteste, aber die gestellten Aufgaben werden erfüllt.

Ein wenig Theorie
Haftungsausschluss. Wenn Sie wissen, was IPv6 ist und was man damit macht, könnte dieser Teil für Sie langweilig sein. Möglicherweise aber auch nicht.
Menschen, die zum ersten Mal auf die IPv6-Beschreibung stoßen, können davon ziemlich überrascht sein. Nach den eleganten 64.233.177.101 stoßen wir plötzlich auf 2607:f8b0:4002:c08::8b und könnten verwirrt sein. Beides ist lediglich eine menschenlesbare Darstellung von 32 bzw. 128 Bit. Jedes IP-Paket enthält einen Header mit einer streng standardisierten Zuordnung jedes Bits. Ohne zu sehr in die Struktur der Header einzutauchen, sollten wir hier eines mitnehmen: Bei Operationen mit IP-Adressen und Bereichen ist es meist praktisch, binärer Mathematik und bitweisen Operationen zu verwenden. Es ist auch am bequemsten, sie in der Datenbank als BINARY(4) für IPv4 und BINARY(16) für IPv6 zu speichern.
Ein weiterer wichtiger Aspekt, den man ansprechen sollte, sind die Subnetzmasken und die CIDR-Notation. CIDR steht für Classless Inter-Domain Routing (). Dieses Konzept hat die klassenbasierte Adressierung abgelöst, um zu bestimmen, welcher Teil einer IP-Adresse das Netzwerkpräfix und welcher Teil die Adresse des Netzwerkinterfaces innerhalb dieses Netzwerks ist. Praktisch werden die ersten n Bits, die dem Präfix entsprechen, auf 1 gesetzt, die restlichen auf 0.
In menschenlesbarer Form wird dies als ip.add.re.ss\/cidr. Zum Beispiel, 64.233.177.0/24 dargestellt, was bedeutet, dass die ersten 24 Bits zum Präfix gehören. Die letzten 8 Bits, die auch die letzte Zahl in der menschenlesbaren Darstellung sind, gehören zur Adresse innerhalb des Subnetzes. Noch ein paar Übungen. 64.233.177.101/32 und 2607:f8b0:4002:c08::8b\/128 ist eine spezifische Adresse. 2607:f8b0:4002:c08::\/64 bezeichnet die ersten 64 Bits (die ersten 4 Gruppen) als Präfix, die restlichen 64 Bits als lokalen Teil. Übrigens, falls jemand von "::" in der Darstellung irritiert ist, ersetzt der doppelte Doppelpunkt eine beliebige Anzahl von Abschnitten, die 0 enthalten. Er kann in der Beschreibung nur einmal vorkommen. Mit anderen Worten, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.
Was müssen wir aus alledem lernen? Erstens kann die erste und letzte Adresse eines Subnetzes mithilfe von binären AND- und OR-Operationen ermittelt werden, wenn man die Maske in binärer Form kennt. Zweitens kann das nächste Subnetz dieser Größe (d.h. mit CIDR) n berechnet werden, indem man 1 zu n-ter Position der binären Darstellung addiert. Unter binärer Form verstehe ich das Ergebnis der Anwendung der Funktionen pack() und inet_pton() und die weitere Verwendung von , unter binär verstehe ich die Darstellung im Binärsystem, die man zum Beispiel mit Hilfe von base_convert().
Historisches NachschlagewerkVor der klassenlosen Adressierung gab es eine klassenbasierte Segmentierung. In jenen fernen Jahren konnte niemand ahnen, dass es so viele Subnetze geben würde, sie wurden rechts und links in großen Blöcken verteilt: Klasse A – das Präfix waren die ersten 8 Bits (d.h. die erste Zahl), mit einem führenden Bit von 0; Klasse B – die ersten 16 (die ersten zwei Zahlen), führende Bits 10; Klasse C – die ersten 24 Bits, führende Bits 110. Diese führenden Bits bestimmten die Bereiche, in denen die Adresse einer bestimmten Klasse vergeben wurde: 0.0.0.0 — 127.255.255.255 für Klasse A, 128.0.0.0 — 191.255.255.255 – Klasse B, 192.0.0.0 – 223.255.255.255 – Klasse C. Als das Internet sich über den Planeten ausbreitete, erkannten die Regulierungsbehörden, dass sie einen Fehler gemacht hatten, und entwickelten Anfang der 90er Jahre das klassenlose Konzept, das erlaubte, sich nicht an die führenden Bits zu binden. Ein paar mehr Details kann man zum Beispiel in der .
Kommen wir zur Praxis
In der Praxis werden wir drei der wahrscheinlichsten Aufgaben umsetzen, die ich für sinnvoll halte:
- Bestimmung der ersten und letzten Adresse des Bereichs;
- Bestimmung des nächsten Bereichs einer bestimmten Größe (CIDR);
- Überprüfung, ob eine Adresse zu einem Bereich gehört.
Die Implementierung wird für IPv6 erfolgen, jedoch kann die Logik bei Bedarf leicht angepasst werden. Einige Ideen habe ich , aber ich habe sie etwas anders umgesetzt. Außerdem gibt es in den Beispielen keine Überprüfung auf Eingabefehler. Also, los geht's.
Wie bereits erwähnt, kann die erste und letzte Adresse des Bereichs mit Hilfe von Bitoperationen bestimmt werden, wenn man den Start des Bereichs und die binäre Subnetzmaske kennt. Daher müssen wir zunächst CIDR in eine binäre Maske umwandeln. Dazu werden wir ihre hexadezimale Darstellung ermitteln und in binäre Form packen.
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);
}Aufruf pack(‘H*’, $mask) packt die hexadezimale Darstellung auf dieselbe Weise wie inet_pton(). Mit dem einen Unterschied, dass bei der Aufruf pack() Alle 0 müssen an ihren Plätzen sein, und Doppelpunkte sollten in der Aufzeichnung nicht vorkommen, im Gegensatz zu einer für Menschen verständlichen Aufzeichnung.
Der nächste Schritt besteht darin, den Beginn und das Ende des Bereichs zu berechnen. Hier kommen Nuancen ins Spiel. Bitoperationen sind durch die Prozessorarchitektur begrenzt. Daher werden auf meinem 32-Bit-CubieTruck, den ich gelegentlich für verschiedene Testspielereien benutze, nicht alle 128 Bits der Adresse in einer einzigen Operation bearbeitet. Nichts hindert uns jedoch daran, sie in Gruppen von 32 Bits zu unterteilen (für den Fall, dass jemand weiß, auf welchen Prozessoren wir starten werden).
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);
}
}Für die zukünftige Nutzung sehen wir die Möglichkeit vor, IPs zu übergeben und Ergebnisse sowohl in binärer als auch in menschenlesbarer Form zu erhalten. Der Parameter $which legt fest, ob wir den Anfang oder das Ende des Bereichs erhalten wollen (Werte 'start' oder 'end' entsprechend).
Die nächste Aufgabe (die zudem die praktischste für unser Unternehmen ist) besteht darin, den nächsten Bereich zu berechnen. Für diese Aufgabe fiel mir nichts Besseres ein, als die Adresse in eine Binärzeichenfolge zu zerlegen und 1 an der erforderlichen Stelle hinzuzufügen, bevor ich alles wieder zusammenfasse. Um sicherzustellen, dass keine Artefakte auftreten, habe ich beschlossen, die Adresse bei der Zerlegung und dem Zusammenfügen byteweise zu behandeln.
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);
}
}Am Ende erhalten wir das Präfix des nächsten Bereichs in der Größe, die in $cidrangegeben ist. Mit dieser Funktion reservieren wir Adressblöcke für unsere Kunden.
Schließlich überprüfen wir, ob die Adresse im Bereich liegt. Zum Beispiel haben wir einen Block /48 für die Zuweisung an Kunden mit /64-Blöcken reserviert, und wir müssen sicherstellen, dass wir bei der Zuweisung nicht über den zugewiesenen Block hinausgehen (in der Praxis wird das sicherlich nicht bald passieren, aber es besteht dennoch die Möglichkeit). Hier ist alles einfach. Wir erhalten den Anfang und das Ende des Bereichs in binärer Form und prüfen, ob die Adresse innerhalb dieser Grenzen liegt.
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);
}Ich hoffe, es war hilfreich. Welche anderen Funktionen zur Arbeit mit Adressen könnten Ihrer Meinung nach nützlich sein? Zusätzliche Ergänzungen, Kommentare und Code-Reviews sind in den Kommentaren herzlich willkommen.
Wenn Sie bereits unser Kunde sind oder nur darüber nachdenken, es zu werden, bieten wir Ihnen anlässlich der Veröffentlichung dieses Artikels einen Block /64 völlig kostenlos für alle VPS- oder dedizierten Serverdienste im Equinix Tier IV Rechenzentrum in den Niederlanden auf Anfrage beim Vertrieb an, indem Sie den Link zu diesem Artikel in Ihrem Ticket angeben. Das Angebot gilt bis März 2020.
Ein wenig Werbung 🙂
Danke, dass Sie bei uns bleiben. Gefallen Ihnen unsere Artikel? Möchten Sie mehr interessante Inhalte sehen? Unterstützen Sie uns, indem Sie eine Bestellung aufgeben oder uns Freunden empfehlen, , ein einzigartiges Äquivalent zu Einsteigerservern, das wir für Sie entwickelt haben: (es sind Optionen mit RAID1 und RAID10 bis zu 24 Kernen und bis zu 40GB DDR4 verfügbar).
Dell R730xd ist im Equinix Tier IV Rechenzentrum in Amsterdam doppelt so günstig? Nur bei uns in den Niederlanden! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — ab $99! Lesen Sie, wie
Quelle: habr.com
