IP-Adressen optimal speichern

In (fast) allen Webprojekten müssen IP-Adressen gespeichert werden. Wenn nur einige wenige Einträge in der Datenbank vorhanden sind, ist das sicher kein Problem. Interessant wird es, wenn mehrere 1000 Einträge durchsucht werden müssen, um festzustellen, ob eine IP vorhanden ist, oder nicht. Wie man das ganze effektive steuert wird im folgenden erläutert.

Neuer Redakteur

Wie vielleicht bemerkt wurde, hat Jan momentan etwas weniger Zeit. Deshalb werde ich (hoffentlich) regelmäßig über Interessante Themen bloggen. Für alle, die mich nicht kennen: Mein Name ist Martin Kiesewetter (25). Ich bin seit vielen Jahren Webentwickler bei Kies-Media.de und werde bald mein Informatik-Studium abschließen.

Das Problem: Wie IP-Adressen optimal speichern?

In (fast) allen Webprojekten müssen IP-Adressen gespeichert werden. Wenn nur einige wenige Einträge in der Datenbank vorhanden sind, ist das sicher kein Problem. Interessant wird es wenn mehrere 1000 Einträge durchsucht werden müssen, um festzustellen, ob eine bestimmte IP vorhanden ist, oder nicht. Leider sehe Ich regelmäßig PHP-Scripte, die mir Tränen in die Augen treiben:

1. Möglichkeit: Datentyp: Text

Braucht in MySQL genau 17 Byte (Textlänge + 2 Byte)

2. Möglichkeit: Datentyp: VarChar(15)

Braucht in MySQL genau 16 Byte (Textlänge + 1 Byte)

3. Möglichkeit: Datentyp: Long

Nur Wenige kennen die PHP-Funktion: IP2LONG, die eine IP-Adresse in einen Longwert umwandelt. Dieser verbraucht in MySQL mit 4 Byte den wenigsten Speicher. “Kleiner” Nebeneffekt: Die Suche über Zahlen ist in MySQL wesentlich schneller als über Texte (VarChar oder Text). Nebenbei: es gibt auch die Umkehrfunktion Long2IP.

Benchmark

Mein Benchmarkscript funktioniert so:

  • Tabelle Anlegen (in der die Datentypen hinterlegt sind)
  • Tabelle mit IPs füllen
  • je 500 Anfragen an jede Spalte der Tabelle
  • Ergebnis Ausgeben

Bemerkung: Ich benutze für jede SQL-Anfrage eine andere IP (ip2long($ip)+$x), damit die Anfrage wirklich ausgeführt wird, und das Ergebnis nicht aus dem Query-Cache kommt!

Benchmarkergebnisse

Variante Speicherbedarf Suchzeit in s
INT (LONG) mit Index 4 Byte 0.104
VarChar(15) mit Index 16 Byte 2.794
VarChar(15) ohne Index 16 Byte 2.799
Text 17 Byte 2.804
INT (LONG) ohne Index 4 Byte 2.981

Bemerkung zu ip2long

Man könnte auf die Idee kommen, dass die Performance, die man durch den besseren Datentyp erreicht durch die zusätzliche Umrechnung der IP in Longwerte aufgefressen wird. Deshalb habe ich die Funktion IP2LONG ausgiebig getestet und festgestellt dass auf meinem Shared-Server 100.000 Berechnungen etwa 0.05 Sekunden dauern, d.h. die Funktion braucht bei mir etwa 0,0000005 Sekunden, was absolut zu vernachlässigen ist.

$ip=rand(150,244).'.'.rand(0,254).'.'.rand(0,254).'.'.rand(0,254);
$t=microtime(true);
for ($x=1;$x<100000;$x++)
{
ip2long($ip);
}
echo microtime(true)-$t;

Alternativ zur PHP-Funktion IP2LONG kann man auch die MySQL funktion INET_ATON verwenden. Performancemäßig sind die Unterschiede zwischen beiden zu vernachlässigen.

Bemerkung zur Speicherengine ARCHIVE

Getestet mit einer Spalte: Datentyp: INT: und 10.000 Datensätzen (IPs als INT)
MYISAM
Speicherverbrauch: ca. 140kbyte + 1kbyte (index)
Einfügungszeit für 10.000 Einträge: 2,35s (gemittelt)
ARCHIVE
Speicherverbrauch: ca. 89kbyte + kein extra index
Einfügungszeit für 10.000 Einträge: 2,37s (gemittelt)

Wie man sieht, ist der Zeitunterschied extrem klein (kann also auch an Meßungenauigkeit liegen). Der Speicherbedarf ist bei ARCHIVE erwartungsgemäß wesentlich kleiner als bei MYISAM.

Fazit

Wie man an der Benchmarkergebnistabelle sehen kann, sind die Abfragen der IP mit Datentyp INT (LONG) mit Index mit Abstand die schnellsten. Alles Andere ist etwa gleich langsam. Man beachte auch die Speichereinsparung von 75% der INT-Werte gegenüber Varchar oder Text! Mich hat das schlechte abschneiden der INT-Anfragen ohne Index etwas verwundert. Falls jemand weiß warum das so ist, kann er sich gerne bei mir melden.

Martin-Kiesewetter hat 4 Beiträge geschrieben

34 Kommentare zu “IP-Adressen optimal speichern

  1. Jan sagt:

    Ich nutze für die Speicherung solcher Daten gern die Archive-Storage-Engine von MySQL, denn die IPs müssen ja nur ganz selten ausgelesen werden (bei mir zumindest).
    So spart man natürlich noch jede Menge Speicherplatz.

    Archive = schnelles, (gepuffertes) Einfügen, komprimierte Speicherung und langsames Lesen (da on-the-fly dekomprimiert werden muss)

    Was meinst Du dazu?

  2. Reto sagt:

    In MySQl gibt es die Funktion “INET_ATON()” die äquivalent zu der PHP Funtion IP2LONG zu sein scheint. Ich selber kannte vorher die Funktion IP2LONG noch nicht.
    Hast du dazu schon Erfahrungen gemacht?
    Allfällige Vergleiche von INET_ATON zu IP2LONG?

  3. danilo sagt:

    ich verwende auch INET_ATON bzw. INET_NTOA (siehe hier). an einem vergleich wär ich interessiert, ich könnte mir vorstellen dass es schneller ist als IP2LONG…

  4. Mariusz sagt:

    Schoener Artikel! ip2long ist ne interessante Funktion, aber ich sehe da noch Maengel bei der Sicherheit (Stichwort: Injections).

    Aber generell habe ich das mit dem IPs mit/ohne Index nicht wirklich verstanden.
    Es werden beim INSERT Vorgang jeweils INT Werte uebergeben und nicht SERIAL. Wo ist dann der Index?
    Ist weder AUTOINCREMENT, noch UNIQUE. Oder verwechsel ich da grad was?

  5. Martin-Kiesewetter sagt:

    Erstmal schön, dass mein erster Artikel so viel Anklang gefunden hat!
    @Jan: die Speicherengine kannst du ja auswählen wie du möchtest, aber auch bei “Archive” werden die geschwindigkeitsverhältnisse etwa gleich bleiben (alle Zeiten werden halt etwas größer sein) (du kannst mein Test-Script ja durch Änderung der Speicherengine anpassen, und gucken…).
    @Reto: (danke für den Hinweis) ich habe INET_ATON() nicht wirklich umfassend getestet. Mein SQL sagt mir bei einmaligen “SELECT INET_ATON(‘192.89.3.3’ )” eine Ausführungszeit von 0.0002s, was ja nicht wirklich aussagekräftig ist. Da ich ja festgestellt hatte das IP2LONG schon extrem schnell ist halte ich es auch nicht für nötig, eine noch schnellere Funktion zu finden (In PHP ist das Umwandeln der IP in Long mit ein paar wenigen Bitschiebeoperationen erledigt==>sehr schnell).
    Ich habs mal mit VarChar(15) mit index getestet (also anstatt der PHP funktion hab ich INET_ATON verwendet – kein unterschied.
    Ich werde in meinem Beitrag trotzdem einen Vermerk auf INET_ATON() einfügen.
    @Sascha: Sry – gewohnheit, werd gleich die deutsche Seite verlinken
    @Mariusz: meinst du mein Benchmark-Script? Sicherheit ist ein ganz anderes Thema (auf das ich ggf. demnächst eingehen könnte 🙂 ) … natürlich sollte man in öffentlichen Projekten alle Nutzereingaben prüfen.
    Mit Index meine ich, dass einige Spalten in der Datenbank mit einem Index versehen sind.
    @ED: Ja, weil du nicht aus der IP auf eine Person schließen kannst – es also keine personenbezogene Daten sind. Bei deinem Provider sieht das schon ganz anders aus – der weiß wer hinter einer IP steht, und damit sind IPs für Ihn serwohl personenbezogen

  6. Sören sagt:

    Hallo,

    gibt ip2long bzw. INET_ATON nur positive oder auch negative Zahlen aus?

    Mir ist aufgefallen, dass ich für IPs INT UNSIGNED Felder nutze…

  7. Coder sagt:

    Genau über dieses Thema hab ich mir letztens Gedanken gemacht.
    Hab mich aber für die VARCHAR-Variante entschieden,
    da die anderen einen entscheidenden Nachteil haben:
    ip2long unterstüzt keine IPv6-Adressen!

    Alternative wäre inet_pton, jedoch wird dies unter Win
    erst ab 5.3 unterstützt und da ich unter Win entwickle (Server ist Linux) und es noch Problemchen gibt ist es momentan noch kritisch.

  8. Sören sagt:

    Verwundern tut mich nämlich, dass im MySQL Manual empfohlen wird, eine UNSIGNED Spalte zu nehmen (was negative Werte unspeicherbar macht).

    Bei der PHP-Funktion ist sowohl die nackte ip2long-Version vorhanden, als auch die vorzeichenentfernende Variante sprintf(“%u”, ip2long($ip)).

    Benötige ich das sprintf überhaupt?

  9. Jan sagt:

    @Sören: Die ip2long-Funktion kann tatsächlich auch negative Werte ausgeben:
    http://us.php.net/manual/en/function.ip2long.php

    Note: Because PHP’s integer type is signed, and many IP addresses will result in negative integers, you need to use the “%u” formatter of sprintf() or printf() to get the string representation of the unsigned IP address.

    Also: Ja, Du benötigst das printf (bzw. sprintf), wenn Du es in eine UNSIGNED-Spalte schreiben möchtest. Wenn Du nur PHP-intern damit arbeitest, brauchst Du es nicht.

    @Martin, zu der Archive-Storage-Engine:
    Ganz so einfach ist es nicht, denn bei Archive kann man keine Indizes anlegen. Es wäre interessant, wenn Du noch einen Test machen könntest, in dem keine Selects gemacht werden. In Deinem Benchmarkscript fügst Du ja ein und selektierst auch IPs. In der Praxis speichert man aber ja oft IP-Adressen, ohne Sie unbedingt auszulesen.
    Und dann eben mal den Speicherbedarf und die Ausführungszeit von MyISAM vs. Archive vergleichen.

  10. @Sören: ip2long gibt auch negative zahlen aus. im verlinkten post bei php.net findest du auch code-schnipsel, um alles in positiv zahlen umzuwandeln. Für die normal abfrage: ist eine IP vorhanden oder nicht, spielt es aber auch keine Rolle ==> ich hab printf noch nie gebraucht! … oh jan war schneller 🙂
    @Jan: habs kurz getestet und den Beitrag aktualisiert (Am Ende kurz vor Fazit)

  11. Foobar2k sagt:

    Hat jemand schon eine Lösung für Ipv6? Leider habe ich noch nirgends eine ip2long()-Funktion inkl. Umkehr gefunden, die auch IPv6fähig wäre.

  12. @ Foobar2k Dies würde mich auch interessieren, da man die Umstellung dann machen könnte solange keine Anpassung gemacht worden ist bezüglich Ipv6 werde ich mir noch nicht die Mühe machen meine Skripte umzustellen.

    @ Martin Sehr schöner und interessanter Artikel, ich hoffe das noch viele weitere Artikel folgen werden. Auf jeden Fall ein kleines Prosit für den Einstand 🙂

  13. Buergermaster sagt:

    [quote]In (fast) allen Webprojekten müssen IP-Adressen gespeichert werden.[/quote]

    Ehm…Nein? Und warum müssen?
    Der einzige Grund warum man IP-Adressen speichern sollte, ist vielleicht der, um DDoS Attacken zu regeln, und da wird meistens nicht PHP benutzt.

    MfG Buergermaster

  14. Jan sagt:

    @Buergermaster: (Fast) jedes Projekt, bei dem Nutzer Inhalte erstellen, die nicht allumfänglich kontrolliert werden können, speichern IP-Adressen, da nur dadurch die Strafverfolgung angestoßen werden kann, falls rechtswidrige Inhalte eingestellt werden.

  15. strafverfolgung ist bei (fast) allen Projekten das zentrale Problem: Ich denke da auch an alle Arten von Shops.

    Außerdem werden IPs gespeichert bei: Besucherstatistik, wenn man wissen möchte wieviele Besucher aus welchem Land kommen und man die Daten keinen externen (*hust* google analytics *hust*) anvertrauen möchte

    Außerdem zum erkennen von Doppelaccounts (ein User legt z.b. illegalerweise mehrere Accounts an, um sich vorteiel zu verschaffen…

    Also: Es gibt also jede menge Gründe IPs zu speichern!

    Grüße!

  16. Sammie sagt:

    @Buergermaster: Selbst ein gutes Session-Management (welches neben Cookies auch URL-basierende Sessions anbietet) verwendet IP-Checks, die einer Session-ID zugeordnet werden.

    Einfach aus dem Grund, damit zufällig weitergegebene/kopierte Links, die eine Session-ID in der URL beinhalten, anderen Usern keinen Zugriff auf fremde User-Accounts ermöglichen. Ohne IP-Überprüfung wäre das ein enormes Sicherheitsrisiko – und deshalb müssen IPs auch gespeichert werden (in welcher Form auch immer).

  17. @Sammie Genau so ein Session Management wäre total unbrauchbar, da Firmen, die eine eigene feste IP Adresse haben immer noch auf die Session Daten zugreifen könnten. Denn es kommt relativ häufig vor, dass große Firmen über eine IP Adresse mit 10, 20 oder mehr Rechnern unterwegs sind. Daher kommt man eigentlich nicht darum, eine Session Cookie Basis zu verwenden.

  18. @Martin-Kiesewetter Da muss ich dir widersprechen, wenn man es richtig macht und PHP ordnungsgemäß konfiguriert ist, kann es kein Sicherheitsproblem sein. Man sollte sich auf jeden Fall gut mit der Dokumentation auseinandersetzen und jeden einzelnen Punkt beachten. Wenn man dies nicht gemacht hat, dann sollte man lieber die Finger davon lassen.

  19. da muss ich dir leider wiedersprechen: einfaches Szenario: unverschlüsselte Verbindung (die zu 99% im Internet verwendet wird), und deine HTTP-Request wird abgehört (z.b. öffentliches wlan oder wie auch immer) ==> derjenige muss nur deine SESSION-ID übernehmen und hat deine Session geklaut

  20. Wenn der Client nicht mehr aktiv ist und dann automatisch die Session beendet wird, wie möchtest Du dann die Session übernehmen?

    Die einzige Möglichkeit die man hat, ist die Session direkt abzufangen und weiter zu verwenden. Bloß wenn standardmäßig der User sieht, dass sich was an seinen Daten geändert hat und automatisch die Session beendet. Dann kann man nichts mehr machen mit der Session. Natürlich muss man in dieser Geschichte den Aufwand, sowie die Sicherheitsrelevanten Aspekte beachten und sich Gedanken machen ob es überhaupt Sinn macht, einen solchen großen Aufwand zu betreiben ohne eine SSL Verbindung zu nutzen.

    Ich würde an dieser Stelle mir auf jeden Fall Gedanken machen, über all wo man dementsprechend was bestellen kann. Dort würde ich jeden Betreiber einer Webseite, raten eine SSL Verbindung kostenlos für den User zur Verfügung zu stellen.

    Auf jeden Fall kann man über dieses Thema sehr lange debattieren und würde wahrscheinlich früher oder später auf dem gleichen Nenner kommen, bloß ich muss sagen das ich gerade dafür nicht die Zeit und Lust habe.

  21. GhostGambler sagt:

    Natürlich ist die Übergabe der Session-ID per URL ein riesiges Sicherheitsloch!
    Zudem kommen noch Unschönheiten bei der Weitergabe von Links und der Indizierung von Suchmaschinen von mit SID geposteten Links.

    SSL bessert bei den komplizierten Verfahren wie Sniffing nach, aber bei den ganz einfachen Gefahren, dass ein Benutzer die URL von sich aus jemandem weiter gibt oder ohne Wissen die SID per Referer an einen fremden Host übergibt, bringt SSL rein gar nichts.

    Die automatische Schließung einer Session erhöht die Sicherheit marginal. Es geht ja doch meist darum “akut” – heißt JETZT – eine Session zu knacken. Da interessiert es den Hijacker nicht, ob die Session in 2 Stunden abläuft.

    Im aktuellen Jahrzehnt sehe ich keine Existenzberechtigung mehr für SIDs in URLs.

    Dafür gibt es auch mehr als einen Beleg:
    http://www.slideshare.net/guest0e6d5e/high-security-php-applications
    Folie 18, einer/eine mit Commit-Recht für PHP auf Internationalen PHP-Konferenz über “High Security PHP Applications” empfiehlt offensichtlich “use_trans_id” abzuschalten.
    http://www.acros.si/papers/session_fixation.pdf
    Ein generell sehr lohnenswertes Paper. Wie dem geneigten Leser des Papers auffallen wird, ist nicht nur das Hijacken der Session-ID einfacher, sondern es erlaubt auch eine einfache neue Angriffsmöglichkeit der Fixation mit vorgegebenen SIDs. Im Vergleich dazu ist der Aufwand um eine rein auf Cookies basierende Session-Verwaltung dahingehend zu brechen, deutlich höher. Dem weniger geneigten Leser empfehle ich die kürzere Version: http://www.owasp.org/index.php/Session_fixation#Description

    Vielleicht zum Abschluss noch ein Paper vom MIT
    http://pdos.csail.mit.edu/cookies/pubs/webauth:tr.pdf
    Seite 8, “A second method of setting an authenticator is to include it as part of the URL. Through the HTTP 1.1 specification [14] recommends against this, it [is] easy to do and sites still use this.” und folgend. Hier ist sogar konkret der Vergleich von Cookie (im Paper der Absatz darüber) und URL via SSL, und auch bei der Variante rät das Paper offensichtlich davon ab die Session-IDs in URLs zu übergeben, während man bei den Cookies (nur) darauf achten muss, dass das sec-Flag gesetzt ist.

    Wenn du jetzt immer noch der Meinung bist, dass SIDs in URLs nicht weiter schlimm ist, dann belege deine Aussage bitte entsprechend. Ansonsten gehe ich nicht davon aus, dass hier noch ein gemeinsamer Nenner gefunden wird.

  22. Jan sagt:

    Ich gehe ganz d’accord mit Martin und ghostgambler (ja, ich kenne tolle Worte, gell?).

    Wer keine Cookies aktiviert hat, bleibt eben draußen. Problematisch sind diese neuartigen Private-Browsing-Funktionen aktueller Browser. Ich kenne mich damit nicht so gut aus, aber ich glaube, damit werden keine Cookies gespeichert, oder?

    Übrigens: Was ghostgambler in einem Nebensatz erwähnt hat, halte ich für extrem wichtig: Session-Cookies immer mit Secure-Flag ausstatten. Dann kann ein Angreifer nicht mehr per XSS-Angriff mittels JavaScript (oder anderer clientseitiger Sprachen) auf die Cookies zugreifen, sondern der Zugriff kann lediglich per HTTP erfolgen.

  23. @all Hallo, natürlich sollte man, wenn es möglich ist darauf verzichten und auf eine Cookie Session Basis ausweichen. Wo ich mir gerade die Frage stelle, ich habe bis jetzt noch nie einen Server gesehen wo die Session länger als 30 Minuten aktiv war.

    Danach wird diese standardmäßig vom Server gelöscht. Nun gut, wenn Du in der Session her gehst und die IP Adresse speicherst sowie gegebenenfalls einen Browser Kennung, wird es schon bedeutend schwieriger an die Sessiondaten heranzukommen. Es ist natürlich nicht unbedingt die sicherste Variante, aber ich habe nicht behauptet, das die Session erstens solange verfügbar ist, zweitens keine IP Adresse sowie Browserkennung direkt in der Session gespeichert ist.

    @GhostGambler

    Wenn du so der PHP Profi im Bereich von Session bist, dann kannst du doch ganz einfach mal meine nachfolgenden PHP Einstellungen überprüfen und gegebenenfalls mir den einen oder anderen Ratschlag geben. 🙂

    session.auto_start Off
    session.bug_compat_42 On
    session.bug_compat_warn On
    session.cache_expire 180
    session.cache_limiter nocache
    session.cookie_domain no value
    session.cookie_httponly Off
    session.cookie_lifetime 0
    session.cookie_path /
    session.cookie_secure Off
    session.entropy_file no value
    session.entropy_length 0
    session.gc_divisor 100
    session.gc_maxlifetime 1440
    session.gc_probability 0
    session.hash_bits_per_character 4
    session.hash_function 0
    session.name PHPSESSID
    session.referer_check no value
    session.save_handler files
    session.save_path /var/lib/php5 /
    session.serialize_handler php
    session.use_cookies On
    session.use_only_cookies Off
    session.use_trans_sid 0
    suhosin.session.checkraddr 0
    suhosin.session.cryptdocroot On
    suhosin.session.cryptkey [ protected ]
    suhosin.session.cryptraddr 0
    suhosin.session.cryptua On
    suhosin.session.encrypt On
    suhosin.session.max_id_length 128

  24. GhostGambler sagt:

    Ich empfehle das Lesen von:
    http://de.php.net/manual/en/session.configuration.php
    Ich kenne nämlich auch nicht alle der Einstellungen auswendig aus dem Kopf. Dafür gibt es Dokus. (Vor allem mit den suhosin-Einstellungen kann ich überhaupt nichts anfangen…)

    Wirklich empfehlen würde ich das Lesen des acros-Papers unter http://www.acros.si/papers/session_fixation.pdf. Es gibt konkrete Hinweise um ein eigenes Login ggf. noch sicherer zu gestalten.
    Wenn man das Paper komplett gelesen und verstanden hat, sollte man auch in der Lage sein (mit Hilfe der Doku) die PHP-Einstellungen so zu wählen, dass sie perfekt (für den aktuellen Fall) sind.

  25. Buergermaster sagt:

    [quote]
    @Buergermaster: Selbst ein gutes Session-Management (welches neben Cookies auch URL-basierende Sessions anbietet) verwendet IP-Checks, die einer Session-ID zugeordnet werden.
    [/quote]

    Jepp, aber für ein Session-Management ist es nicht nötig die IP direkt zu speichern. Der einzige Fall bei dem es erlaubt ist, ist es die IP mit verschiedenen Faktoren in einer Session zusammenzufassen (mittels Hash-Funktion).

    Jetzt kann man mit einem “IP-Check” überprüfen ob die Session zum Client passt. Eine direkte IP-Speicherung ist nicht nötig.

    MfG Buergermaster

  26. Vllt sollte man die IP aufgeteilt in 4 Tinyint -Feldern-Testen. Und 3 Tests durchführen:
    1. Index auf das erste Feld
    2. Index auf erstes und zweite Feld
    3. Index auf erstes, zweites und drittes Feld

    Grüße Patrick

Eine Antwort schreiben

Ihre E-Mail-Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert mit *

You may use these HTML tags and attributes: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>