Eine Einführung in das Modul ipaddress¶
Adress-, Netzwerk- und Schnittstellenobjekte erzeugen¶
Da ipaddress ein Modul zum Untersuchen und Bearbeiten von IP-Adressen ist, wirst du als Erstes einige Objekte erzeugen wollen. Mit ipaddress kannst du Objekte aus Zeichenketten und aus Ganzzahlen erzeugen.
Eine Anmerkung zu den IP-Versionen¶
Für Leser, die mit IP-Adressierung nicht besonders vertraut sind, ist es wichtig zu wissen, dass das Internet Protocol (IP) derzeit von Version 4 des Protokolls auf Version 6 umgestellt wird. Dieser Übergang findet im Wesentlichen deshalb statt, weil Version 4 des Protokolls nicht genügend Adressen bereitstellt, um den Bedarf der ganzen Welt zu decken – vor allem angesichts der wachsenden Zahl von Geräten mit direkter Internetverbindung.
Die Unterschiede zwischen den beiden Protokollversionen im Detail zu erklären, geht über den Rahmen dieser Einführung hinaus. Die Leser müssen aber zumindest wissen, dass es diese beiden Versionen gibt und dass es manchmal nötig ist, die Verwendung der einen oder der anderen Version zu erzwingen.
IP-Host-Adressen¶
Adressen, oft als „Host-Adressen“ bezeichnet, sind die grundlegendste Einheit bei der Arbeit mit IP-Adressierung. Am einfachsten erzeugst du Adressen mit der Factory-Funktion ipaddress.ip_address(), die anhand des übergebenen Werts automatisch bestimmt, ob eine IPv4- oder eine IPv6-Adresse erzeugt wird:
>>> ipaddress.ip_address('192.0.2.1')
IPv4Address('192.0.2.1')
>>> ipaddress.ip_address('2001:DB8::1')
IPv6Address('2001:db8::1')
Adressen lassen sich auch direkt aus Ganzzahlen erzeugen. Werte, die in 32 Bit passen, werden als IPv4-Adressen aufgefasst:
>>> ipaddress.ip_address(3221225985)
IPv4Address('192.0.2.1')
>>> ipaddress.ip_address(42540766411282592856903984951653826561)
IPv6Address('2001:db8::1')
Um die Verwendung von IPv4- oder IPv6-Adressen zu erzwingen, lassen sich die entsprechenden Klassen direkt aufrufen. Das ist besonders nützlich, um für kleine Ganzzahlen die Erzeugung von IPv6-Adressen zu erzwingen:
>>> ipaddress.ip_address(1)
IPv4Address('0.0.0.1')
>>> ipaddress.IPv4Address(1)
IPv4Address('0.0.0.1')
>>> ipaddress.IPv6Address(1)
IPv6Address('::1')
Netzwerke definieren¶
Host-Adressen werden üblicherweise zu IP-Netzwerken zusammengefasst, daher stellt ipaddress einen Weg bereit, Netzwerkdefinitionen zu erzeugen, zu untersuchen und zu bearbeiten. IP-Netzwerkobjekte werden aus Zeichenketten gebildet, die den Bereich der Host-Adressen festlegen, die zu diesem Netzwerk gehören. Die einfachste Form dieser Angabe ist ein Paar aus „Netzwerkadresse/Netzwerkpräfix“, wobei das Präfix die Anzahl der führenden Bits festlegt, die verglichen werden, um zu bestimmen, ob eine Adresse zum Netzwerk gehört, und die Netzwerkadresse den erwarteten Wert dieser Bits festlegt.
Wie schon für Adressen gibt es auch hier eine Factory-Funktion, die die richtige IP-Version automatisch bestimmt:
>>> ipaddress.ip_network('192.0.2.0/24')
IPv4Network('192.0.2.0/24')
>>> ipaddress.ip_network('2001:db8::0/96')
IPv6Network('2001:db8::/96')
Bei Netzwerkobjekten dürfen keine Host-Bits gesetzt sein. Praktisch bedeutet das, dass 192.0.2.1/24 kein Netzwerk beschreibt. Solche Definitionen werden als Schnittstellenobjekte bezeichnet, weil die Schreibweise „IP-in-einem-Netzwerk“ üblicherweise die Netzwerkschnittstellen eines Rechners in einem bestimmten Netzwerk beschreibt. Sie werden im nächsten Abschnitt näher beschrieben.
Standardmäßig führt der Versuch, ein Netzwerkobjekt mit gesetzten Host-Bits zu erzeugen, dazu, dass ein ValueError ausgelöst wird. Damit die zusätzlichen Bits stattdessen auf null gesetzt werden, lässt sich dem Konstruktor das Flag strict=False übergeben:
>>> ipaddress.ip_network('192.0.2.1/24')
Traceback (most recent call last):
...
ValueError: 192.0.2.1/24 has host bits set
>>> ipaddress.ip_network('192.0.2.1/24', strict=False)
IPv4Network('192.0.2.0/24')
Die Zeichenkettenform bietet deutlich mehr Spielraum, aber Netzwerke lassen sich genau wie Host-Adressen auch mit Ganzzahlen definieren. In diesem Fall gilt das Netzwerk als Netzwerk, das nur die eine durch die Ganzzahl bezeichnete Adresse enthält. Das Netzwerkpräfix umfasst daher die gesamte Netzwerkadresse:
>>> ipaddress.ip_network(3221225984)
IPv4Network('192.0.2.0/32')
>>> ipaddress.ip_network(42540766411282592856903984951653826560)
IPv6Network('2001:db8::/128')
Wie bei Adressen lässt sich die Erzeugung einer bestimmten Netzwerkart erzwingen, indem du den Klassenkonstruktor direkt aufrufst, anstatt die Factory-Funktion zu verwenden.
Host-Schnittstellen¶
Wie gerade erwähnt, reichen weder die Adress- noch die Netzwerkklassen aus, wenn du eine Adresse in einem bestimmten Netzwerk beschreiben musst. Eine Schreibweise wie 192.0.2.1/24 wird von Netzwerktechnikern und von den Leuten, die Werkzeuge für Firewalls und Router schreiben, üblicherweise als Kurzform für „der Host 192.0.2.1 im Netzwerk 192.0.2.0/24“ verwendet. Entsprechend stellt ipaddress eine Reihe von Mischklassen bereit, die eine Adresse mit einem bestimmten Netzwerk verknüpfen. Die Schnittstelle zum Erzeugen ist dieselbe wie beim Definieren von Netzwerkobjekten, nur dass der Adressteil nicht auf eine Netzwerkadresse beschränkt ist.
>>> ipaddress.ip_interface('192.0.2.1/24')
IPv4Interface('192.0.2.1/24')
>>> ipaddress.ip_interface('2001:db8::1/96')
IPv6Interface('2001:db8::1/96')
Ganzzahlige Eingaben werden (wie bei Netzwerken) akzeptiert, und die Verwendung einer bestimmten IP-Version lässt sich erzwingen, indem du den entsprechenden Konstruktor direkt aufrufst.
Adress-, Netzwerk- und Schnittstellenobjekte untersuchen¶
Du hast dir die Mühe gemacht, ein IPv(4|6)(Address|Network|Interface)-Objekt zu erzeugen, also willst du vermutlich auch Informationen darüber bekommen. ipaddress versucht, das einfach und naheliegend zu machen.
Die IP-Version auslesen:
>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> addr6 = ipaddress.ip_address('2001:db8::1')
>>> addr6.version
6
>>> addr4.version
4
Das Netzwerk aus einer Schnittstelle gewinnen:
>>> host4 = ipaddress.ip_interface('192.0.2.1/24')
>>> host4.network
IPv4Network('192.0.2.0/24')
>>> host6 = ipaddress.ip_interface('2001:db8::1/96')
>>> host6.network
IPv6Network('2001:db8::/96')
Herausfinden, wie viele einzelne Adressen ein Netzwerk enthält:
>>> net4 = ipaddress.ip_network('192.0.2.0/24')
>>> net4.num_addresses
256
>>> net6 = ipaddress.ip_network('2001:db8::0/96')
>>> net6.num_addresses
4294967296
Über die „nutzbaren“ Adressen eines Netzwerks iterieren:
>>> net4 = ipaddress.ip_network('192.0.2.0/24')
>>> for x in net4.hosts():
... print(x)
192.0.2.1
192.0.2.2
192.0.2.3
192.0.2.4
...
192.0.2.252
192.0.2.253
192.0.2.254
Die Netzmaske (also die Bits, die dem Netzwerkpräfix entsprechen) oder die Hostmaske (alle Bits, die nicht zur Netzmaske gehören) auslesen:
>>> net4 = ipaddress.ip_network('192.0.2.0/24')
>>> net4.netmask
IPv4Address('255.255.255.0')
>>> net4.hostmask
IPv4Address('0.0.0.255')
>>> net6 = ipaddress.ip_network('2001:db8::0/96')
>>> net6.netmask
IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff::')
>>> net6.hostmask
IPv6Address('::ffff:ffff')
Die Adresse ausschreiben oder verkürzen:
>>> addr6.exploded
'2001:0db8:0000:0000:0000:0000:0000:0001'
>>> addr6.compressed
'2001:db8::1'
>>> net6.exploded
'2001:0db8:0000:0000:0000:0000:0000:0000/96'
>>> net6.compressed
'2001:db8::/96'
IPv4 unterstützt zwar weder das Ausschreiben noch das Verkürzen, die zugehörigen Objekte stellen die entsprechenden Eigenschaften aber dennoch bereit. So kann versionsneutraler Code mühelos sicherstellen, dass für IPv6-Adressen die knappste oder die ausführlichste Form verwendet wird, und gleichzeitig IPv4-Adressen korrekt behandeln.
Netzwerke als Listen von Adressen¶
Manchmal ist es nützlich, Netzwerke wie Listen zu behandeln. Das heißt, dass man sie so indizieren kann:
>>> net4[1]
IPv4Address('192.0.2.1')
>>> net4[-1]
IPv4Address('192.0.2.255')
>>> net6[1]
IPv6Address('2001:db8::1')
>>> net6[-1]
IPv6Address('2001:db8::ffff:ffff')
Es bedeutet auch, dass sich Netzwerkobjekte für die Syntax des Listen-Enthaltenseinstests eignen, etwa so:
if address in network:
# do something
Der Enthaltenseinstest wird anhand des Netzwerkpräfixes effizient durchgeführt:
>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> addr4 in ipaddress.ip_network('192.0.2.0/24')
True
>>> addr4 in ipaddress.ip_network('192.0.3.0/24')
False
Vergleiche¶
ipaddress stellt einige einfache und hoffentlich naheliegende Wege bereit, Objekte zu vergleichen, wo das sinnvoll ist:
>>> ipaddress.ip_address('192.0.2.1') < ipaddress.ip_address('192.0.2.2')
True
Eine TypeError-Ausnahme wird ausgelöst, wenn du versuchst, Objekte unterschiedlicher Versionen oder unterschiedlicher Typen zu vergleichen.
IP-Adressen mit anderen Modulen verwenden¶
Andere Module, die IP-Adressen verwenden (etwa socket), nehmen Objekte aus diesem Modul üblicherweise nicht direkt an. Sie müssen stattdessen in eine Ganzzahl oder eine Zeichenkette überführt werden, die das andere Modul annimmt:
>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> str(addr4)
'192.0.2.1'
>>> int(addr4)
3221225985
Mehr Details bekommen, wenn das Erzeugen einer Instanz scheitert¶
Wenn du Adress-, Netzwerk- oder Schnittstellenobjekte mit den versionsunabhängigen Factory-Funktionen erzeugst, werden alle Fehler als ValueError mit einer allgemeinen Fehlermeldung gemeldet, die lediglich sagt, dass der übergebene Wert nicht als Objekt dieses Typs erkannt wurde. Eine genauere Fehlermeldung fehlt, weil man wissen müsste, ob der Wert IPv4 oder IPv6 sein soll, um näher begründen zu können, warum er abgelehnt wurde.
Für Anwendungsfälle, in denen der Zugriff auf diese zusätzlichen Details nützlich ist, lösen die einzelnen Klassenkonstruktoren tatsächlich die ValueError-Unterklassen ipaddress.AddressValueError und ipaddress.NetmaskValueError aus, um genau anzuzeigen, welcher Teil der Definition nicht korrekt geparst werden konnte.
Bei direkter Verwendung der Klassenkonstruktoren sind die Fehlermeldungen deutlich ausführlicher. Zum Beispiel:
>>> ipaddress.ip_address("192.168.0.256")
Traceback (most recent call last):
...
ValueError: '192.168.0.256' does not appear to be an IPv4 or IPv6 address
>>> ipaddress.IPv4Address("192.168.0.256")
Traceback (most recent call last):
...
ipaddress.AddressValueError: Octet 256 (> 255) not permitted in '192.168.0.256'
>>> ipaddress.ip_network("192.168.0.1/64")
Traceback (most recent call last):
...
ValueError: '192.168.0.1/64' does not appear to be an IPv4 or IPv6 network
>>> ipaddress.IPv4Network("192.168.0.1/64")
Traceback (most recent call last):
...
ipaddress.NetmaskValueError: '64' is not a valid netmask
Beide modulspezifischen Ausnahmen haben allerdings ValueError als Elternklasse. Wenn dich die genaue Fehlerart nicht interessiert, kannst du also weiterhin Code wie den folgenden schreiben:
try:
network = ipaddress.IPv4Network(address)
except ValueError:
print('address/netmask is invalid for IPv4:', address)