PHP.de Wissenssammlung

IP-Adressen

Grundlagen
Formate
IPv4, IPv6
PHP und extrem große Zahlen
32bit, 64bit, 128bit
Magie der Binär-Strings
zur Darstellung von IPs
Der falsche Weg
ip2long, long2ip
Der richtige Weg
Binäre Darstellung
Der MySQL-Weg
die Konvertierung auslagern
Quellen

Diese Anleitung zeigt übliche Methoden um IPv4- und IPv6-Adressen in ihre binäre Darstellung zu konvertieren.

Grundlagen

IP-Adressen sind für Menschen lesbare Zeichenfolgen, um Geräte innerhalb von TCP/IP-basierten Netzwerken eindeutig zu identifizieren. In der Vergangenheit des Internets waren IPv4-Adressen der übliche Weg, um einen Server zu adressieren, Clients zu Servern oder zu anderen Clients zu verbinden, um E-Mails zu versenden oder andere netzwerkbezogene Aufgaben durchzuführen. IPs sind im Prinzip wie Telefonnummern (mit lediglich anderem Format), jedoch sind Telefonnummern nicht durch einen bestimmten Bereich beschränkt.

Heutzutage haben wir die Grenzen des Adressbereiches von IPv4-Adressen erreicht, d.h. 4.294.967.296 gleichzeitg angeschlossene Clients. Aus diesem Grund wurde ein neues Adressformat festgelegt, das die Aufgaben des IPv4 auch in der Zukunft erfüllen kann: IPv6. Mittels IPv6 kann das Internet für eine Weile weiter wachsen, denn 340.282.366.920.938.463.463.374.607.431.768.211.456 Clients können so zum größten, weltweiten Netzwerk verbunden werden. Ja, das ist eine Zahl mit 39 Stellen.

Formate

IPv4-Adressen bestehen aus einer Folge von vorzeichenlosen Ganzzahlen (unsigned integer), die in 4 Gruppen, getrennt durch ein ., unterteilt sind. Jede Gruppe kann einen Wert von 0 bis 255 haben.

127.0.0.1 Der lokale Loopback, der gut bekannte localhost.

IPv6-Adressen sind eine Folge von Hexadezimalzahlen, die in 8 Gruppen, getrennt durch ein :, dargestellt werden. Jede Gruppe kann einen Wert zwischen 0 und ffff haben, führende Nullen können, ebenso wie Folgen von Nullen, ausgelassen werden:

0000:0000:0000:0000:0000:0000:0000:0001 oder: ::1 Ja, auch das ist der lokale Loopback.

PHP, Typen und der Umgang mit extrem großen Zahlen

PHP kommt “out of the box” mit Unterstützung für eine “32-Bit-Integer mit Vorzeichen” auf 32-Bit-Plattformen und mit einer “64-Bit-Integer mit Vorzeichen” auf einem 64-Bit-Plattform. Was weiter unten aufgezeigt wird: 32-Bit-Integers sind die falsche Wahl um IPv4 Adressen zu speichern, denn diese scheitern bereits an dem Wert der höchsten IPv4-Adresse. 64-Bit-Integers sind ebenfalls nicht in der Lage, IPv6-Adressen zu verarbeiten, denn diese benötigen 128-Bit. PHP ist in der Lage, eine große Zahl bitweise mit Hilfe von bcmath zu behandeln. Im Detail: Mit bcmath jede Zahl wird als Zeichenfolge dargestellt werden. Wir werden jedoch Binär-Strings verwenden.

Die Magie der Binär-Strings

Binär-Strings werden im weitern Verlauf zur Darstellung von IP-Adressen unsere Wahl sein. Denn in dieser Form können die IP-Adressen auch in der Datenbank abgespeichert, verarbeitet bzw. mit anderen IP-Adressen verglichen, oder aber auch direkt in PHP verarbeitet werden.

Der falsche Weg

Verwenden Sie nicht ip2long oder long2ip. ip2long wandelt eine IPv4-Adresse in einer für Menschen lesbaren integer-Darstellung. Auf 32-Bit-Plattformen, kann das vorzeitig mit dem Wert der PHP_MAX_INT als Obergrenze enden, wenn die angegebene IP-Adresse Darstellung höher als die PHP_MAX_INT ist. Dies wird definitiv passieren wenn das erste Segment der IP-Adresse höher als 127 ist.

Der richtige Weg

Zuerst beginnen wir mit der Umwandlung von 127.0.0.1 in die binäre Darstellung:

$binaryIP = inet_pton('127.0.0.1');

Zu Erinnerung, wir haben nun einen Binär-String. var_dump() wird hier nicht helfen um herauszufinden was wir im Detail haben, denn var_dump() ist nicht in der Lage, binäre Strings anzuzeigen und wird daher einen leeren String zurückgeben. Daher prüfen wir, ob wir beim Zählen der Byte-Länge des Binär-Strings mit strlen() oder mb_strlen() eine 32-Bit-Darstellung bekommen:

var_dump(strlen($binaryIP));

ergibt int(4)

Weiter mit der Umwandlung von ::1, der IPv6-Darstellung von 127.0.0.1:

$binaryIPv6 = inet_pton('::1');

Wir überprüfen das Ergebnis wie oben, um sicherzustellen, das wir ein 128-Bit-Darstellung (16-Byte-String-Länge) bekommen haben:

var_dump(strlen($binaryIP));

ergibt int(16)

Der nächste Schritt wäre nun die Prüfung, ob sich eine angegebene IPv4-Adresse zwischen zwei anderen IPv4 Adressen befindet:

function ip_between($ip, $ipStart, $ipEnd)
{
     $start = inet_pton($ipStart);
     $end = inet_pton($ipEnd);
     $value = inet_pton($ip);

     return $start <= $value && $end >= $value;
}

var_dump(ip_between('127.0.0.10', '127.0.0.1', '127.0.0.255'));

ergibt bool(true)

Gleiche Funktion, diesmal mit IPv6-Adressen:

var_dump(ip_between('::DD', '::1', '::FFFF'));

ergibt ebenfalls bool(true)

Der MySQL-Weg: Die Konvertierung auslagern

Wie oben dargestellt, ist das Handling mit IP-Adressen, egal welcher Version, mit PHP nicht all zu kompliziert. Dennoch solle es vorgezogen werden, solche Konvertierung auf die Datenbank auszulagern, wenn diese zur Speicherung der IP-Adresse dient und die Datenbank auf einem entsprechend aktuellen Stand ist, was die Verfügbarkeit von inet6_aton und inet6_ntoa betrifft und diese gewährleistet ist. MySQL 5.5 oder höher erfüllt diese Anforderung.

Für einen kleinen Test erstellen wir die folgende Tabelle:

CREATE TABLE `ip_address_ranges`(
     `start` varbinary(16),
     `end` varbinary(16)
)

Und fügen zwei Bereiche ein - einen für IPv4 und einen für IPv6:

INSERT INTO `ip_address_ranges`(`start`, `end`)
VALUES
    ( Inet6_aton('::1'), inet6_aton('::FFFF') ),
    ( Inet6_aton('127.0.0.1'), inet6_aton('127.0.0.255') )

Und zum Schluss noch jeweils eine IPv6- und eine IPv4 basierende BETWEEN Abfrage:

SELECT
     inet6_ntoa(`start`) AS `start-IP`,
     inet6_ntoa(`end`) AS `end-IP`
FROM `ip_address_ranges`
     WHERE
         inet6_aton('::5') BETWEEN `start` AND `end`

SELECT
     inet6_ntoa(`start`) AS `start-IP`,
     inet6_ntoa(`end`) AS `end-IP`
FROM `ip_address_ranges`
     WHERE
         inet6_aton('127.0.0.50') BETWEEN `start` AND `end`

Beide SELECT-Statements geben jeweils ein Zeile aus, mit den für Menschen lesbaren Darstellung des betreffenden Bereiches.

+----------+--------+
| start-IP | end-IP |
+----------+--------+
| ::1      | ::ffff |
+----------+--------+

+-----------+-------------+
| start-IP  | end-IP      |
+-----------+-------------+
| 127.0.0.1 | 127.0.0.255 |
+-----------+-------------+

Hinweis: MySQL hat ebenfalls inet_aton- und inet_ntoa-Funktionen. Die inet6_*-Funktionen sind jedoch wegen der Kompatibilität für IPv6 und IPv4 zu bevorzugen, denn die inet_*-Funktionen sind nur für die Verarbeitung von IPv4-Adressen implementiert.

Quellen

Dieser Beitrag wird zur Zeit diskutiert und wurde zuletzt von hausl verändert.

Beiträge die zur Diskussion gestellt werden, enthalten mitunter Informationen bei denen wir uns noch bezüglich der finalen Darstellung absprechen müssen. Gedulde dich etwas, wir stellen diesen Beitrag fertig, sobald die Diskussion beendet ist.

An diesem Beitrag waren bisher beteiligt: tr0y, hausl