Arbeiten mit IPv6 in PHP

Vor kurzem haben wir den Status LIR erhalten und ein /29 IPv6-Block. Außerdem entstand die Notwendigkeit, die zugewiesenen Subnetze zu verwalten. Da unsere Abrechnung in PHP geschrieben ist, mussten wir uns ein wenig mit dem Thema auseinandersetzen und erkennen, dass diese Sprache nicht die benutzerfreundlichste im Umgang mit IPv6 ist. Unter dem Link finden Sie unsere Lösungen für die auftretenden Aufgaben im Zusammenhang mit Adressen und Bereichen. Vielleicht nicht die eleganteste Lösung, aber sie erfüllt die gestellten Aufgaben.

Arbeiten mit IPv6 in PHP

Ein wenig Theorie

Haftungsausschluss. Wenn Sie mit IPv6 vertraut sind und wissen, wie es funktioniert, könnte dieser Teil für Sie langweilig sein. Vielleicht auch nicht.

Menschen, die zum ersten Mal auf das IPv6-Notation stoßen, könnten etwas überfordert sein. Nach den eleganten 64.233.177.101 sehen wir plötzlich 2607:f8b0:4002:c08::8b und könnten uns verwirrt fühlen. Beides ist lediglich eine menschenverständliche Darstellung von 32 und 128 Bit. Jedes IP-Paket enthält einen Header mit einer genau standardisierten Bestimmung für jedes Bit. Ohne weiter in die Struktur der Header einzutauchen, müssen wir hier feststellen: Für Operationen mit IP-Adressen und -Bereichen ist es am praktischsten, binäre Mathematik und bitweise 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 es wert ist, angesprochen zu werden, sind Netzmasken und CIDR-Notation. CIDR ist das Akronym für Classless Inter-Domain Routing (klassenlose Adressierung). Dieses Konzept hat die klassenbasierte Adressierung ersetzt, um zu definieren, welcher Teil der IP-Adresse das Präfix des Netzwerks ist und welcher die Adresse des Netzwerkinterfaces innerhalb dieses Netzwerks. In der Praxis werden die ersten n Bits, die dem Präfix entsprechen, auf 1 gesetzt, während die übrigen auf 0 gesetzt werden.

In einer menschenlesbaren Form wird dies dargestellt als ip.add.re.ss/cidr. Zum Beispiel 64.233.177.0/24 , was bedeutet, dass die ersten 24 Bits zum Präfix gehören. Die letzten 8 Bits, also die letzte Zahl in der menschenlesbaren Darstellung, gehören zur Adresse innerhalb des Subnetzes. Noch ein paar Übungen. 64.233.177.101/32 und 2607:f8b0:4002:c08::8b/128 — eine spezifische Adresse. 2607:f8b0:4002:c08::/64 — die ersten 64 Bits (die ersten 4 Gruppen) sind das Präfix, die restlichen 64 Bits — der lokale Teil. Übrigens, für diejenigen, die sich über "::" in der Darstellung wundern, ersetzt das doppelte Kann eine beliebige Anzahl von Abschnitten, die 0 enthalten. Es kann in der Notation nur einmal vorkommen. Mit anderen Worten, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.

Was können wir daraus lernen? Erstens kann die erste und letzte Adresse eines Subnetzes unter Verwendung von binären AND- und OR-Operationen ermittelt werden, wenn man die Maske in binärer Form kennt. Zweitens kann das nächstgelegene Subnetz in Größe (d.h. mit CIDR) n berechnet werden, indem man 1 zu n-ter Position in der binären Darstellung hinzufügt. Unter binär verstehe ich das Ergebnis der Verwendung von Funktionen pack() und inet_pton() und die weitere Verwendung von Bitoperatoren, während ich unter binär die Darstellung im Binärsystem verstehe, die man beispielsweise mit base_convert().

Historischer Hintergrunderhalten kann. Der klassenlose Vorgänger war die Klassenadressierung. In diesen frühen Jahren hätte niemand gedacht, dass es so viele Subnetze geben würde; sie wurden großzügig in großen Blöcken verteilt: Klasse A — 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) mit den führenden Bits 10; Klasse C — die ersten 24 Bits mit den führenden Bits 110. Diese führenden Bits bestimmten die Bereiche, in denen Adressen von entsprechendem Klassentyp vergeben wurden: 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 es ermöglichte, sich nicht an den führenden Bits festzuhalten. Ein wenig mehr Details finden Sie zum Beispiel in der großen und allwissenden.

Kommen wir zur Praxis

In der Praxis werden wir drei der wahrscheinlichsten Aufgaben umsetzen, die mir aufgefallen sind:

  1. Bestimmung der ersten und letzten Adresse im Bereich;
  2. Bestimmung des nächsten Bereichs mit fester Größe (CIDR);
  3. Überprüfung, ob eine Adresse im Bereich liegt.

Die Implementierung wird für IPv6 sein, aber bei Bedarf kann die Logik leicht angepasst werden. Einige Ideen habe ich hier herunter, jedoch etwas anders umgesetzt. Auch in den Beispielen fehlt die Eingabefehlerüberprüfung. Also, los geht's.

Wie schon erwähnt, können die erste und letzte Adresse im Bereich durch bitweise Operationen bestimmt werden, wenn man den Anfang des Bereichs und die binäre Subnetzmaske kennt. Daher müssen wir zuerst CIDR in die binäre Maske umwandeln. Dazu sammeln wir ihre hexadezimale Darstellung und verpacken sie in binär.

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 hex-Darstellung auf die gleiche Weise wie inet_pton(). Mit dem Unterschied, dass bei dem Aufruf pack() alle 0 an ihren Platz gehören, und Doppelpunkte in der Darstellung nicht vorhanden sein dürfen, im Gegensatz zur menschenlesbaren Notation.

Der nächste Schritt besteht darin, den Anfang und das Ende des Bereichs zu berechnen. Hier entstehen einige Nuancen. Bitoperationen sind durch die Prozessorarchitektur begrenzt. Daher kann ich mit meinem 32-Bit-CubieTruck, den ich manchmal für verschiedene Testspielereien benutze, nicht alle 128 Bits der Adresse in einem Schritt verarbeiten. Allerdings steht es uns frei, sie in Gruppen zu je 32 Bits aufzuteilen (für jeden Fall, wer weiß, auf welchen Prozessoren wir laufen 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 Verwendung werden wir die Möglichkeit einplanen, eine IP zu übergeben und das Ergebnis sowohl im binären als auch im menschenlesbaren Format zu erhalten. Der Parameter $which gibt an, ob wir den Anfang oder das Ende des Bereichs erhalten möchten (Werte ‘start’ oder ‘end’ entsprechend).

Die nächste Aufgabe (die zudem die praktischste für unser Unternehmen ist) besteht darin, den nächsten Adressbereich zu berechnen. Für diese Aufgabe kam mir nichts Besseres in den Sinn, als die Adresse in eine binäre Zeichenkette zu zerlegen und an der entsprechenden Stelle 1 hinzuzufügen, um alles anschließend wieder zusammenzuführen. Um Artefakte zu vermeiden, entschied ich mich, die Adresse beim Zerlegen und Zusammenbauen 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);
    }
}

Das Ergebnis wird ein Präfix des nächsten Adressbereichs mit der angegebenen Größe in $cidr. Mit dieser Funktion weisen wir unseren Kunden Adressblöcke zu.

Schließlich überprüfen wir, ob die Adresse im Bereich liegt. Zum Beispiel haben wir einen Block /48 für die Zuteilung an Kunden von Blöcken /64 zugewiesen, und wir müssen sicherstellen, dass wir bei der Zuweisung nicht über den zugewiesenen Block hinausgehen (in der Praxis wird das wohl nicht so schnell passieren, aber die Möglichkeit besteht). Hier ist es einfach. Wir ermitteln den Anfang und das Ende des Bereichs in binärer Form und prüfen, ob die Adresse im Bereich 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, das war hilfreich. Welche weiteren Funktionen im Umgang mit Adressen könnten Ihrer Meinung nach nützlich sein? Zusätzliche Anmerkungen, Kommentare und Code-Reviews sind in den Kommentaren herzlich willkommen.

Wenn Sie bereits unser Kunde sind oder 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 Dedicated-Server-Dienste im Equinix Tier IV Rechenzentrum in den Niederlanden an. Bitte senden Sie dazu einen Anfrage an den Vertrieb und geben Sie diesen Artikel-Link im Ticket an. Das Angebot ist bis März 2020 gültig.

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 Ihren Freunden empfehlen. Cloud-VPS für Entwickler ab 4,99 $, eine einzigartige Alternative zu Einsteiger-Servern, die wir für Sie entwickelt haben: Alles über VPS (KVM) E5-2697 v3 (6 Kerne) 10GB DDR4 480GB SSD 1Gbps ab 19 $ oder wie man einen Server richtig teilt? (Verfügbar sind Optionen mit RAID1 und RAID10, bis zu 24 Kerne und bis zu 40GB DDR4).

Dell R730xd im Equinix Tier IV Rechenzentrum in Amsterdam zum halben Preis? Nur bei uns 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TB ab 199 $ in den Niederlanden! Dell R420 — 2x E5-2430 2.2GHz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — ab 99 $! Lesen Sie darüber Wie man eine Unternehmenskosten-Infrastruktur mit Dell R730xd E5-2650 v4-Servern für ein paar Euro aufbaut?

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster