Eine Einführung in das ipaddress-Modul

Autor:

Peter Moody

Autor:

Nick Coghlan

Erstellen von Adress-/Netzwerk-/Schnittstellenobjekten

Da ipaddress ein Modul zur Inspektion und Manipulation von IP-Adressen ist, werden Sie als Erstes einige Objekte erstellen wollen. Sie können ipaddress verwenden, um Objekte aus Strings und Integern zu erstellen.

Ein Hinweis zu IP-Versionen

Für Leser, die mit IP-Adressierung nicht besonders vertraut sind, ist es wichtig zu wissen, dass das Internetprotokoll (IP) derzeit dabei ist, von Version 4 des Protokolls auf Version 6 umzusteigen. Dieser Übergang geschieht größtenteils, weil Version 4 des Protokolls nicht genügend Adressen bietet, um den Bedarf der ganzen Welt zu decken, insbesondere angesichts der zunehmenden Anzahl von Geräten mit direkter Verbindung zum Internet.

Die Details der Unterschiede zwischen den beiden Versionen des Protokolls zu erklären, liegt außerhalb des Rahmens dieser Einführung, aber die Leser müssen sich zumindest bewusst sein, dass diese beiden Versionen existieren, und es wird manchmal notwendig sein, die Verwendung einer 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. Der einfachste Weg, Adressen zu erstellen, ist die Verwendung der Factory-Funktion ipaddress.ip_address(), die automatisch bestimmt, ob eine IPv4- oder IPv6-Adresse basierend auf dem übergebenen Wert erstellt werden soll.

>>> ipaddress.ip_address('192.0.2.1')
IPv4Address('192.0.2.1')
>>> ipaddress.ip_address('2001:DB8::1')
IPv6Address('2001:db8::1')

Adressen können auch direkt aus Integern erstellt werden. Werte, die in 32 Bit passen, werden als IPv4-Adressen angenommen.

>>> 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, können die entsprechenden Klassen direkt aufgerufen werden. Dies ist besonders nützlich, um die Erstellung von IPv6-Adressen für kleine Integer zu erzwingen.

>>> ipaddress.ip_address(1)
IPv4Address('0.0.0.1')
>>> ipaddress.IPv4Address(1)
IPv4Address('0.0.0.1')
>>> ipaddress.IPv6Address(1)
IPv6Address('::1')

Definition von Netzwerken

Host-Adressen werden normalerweise zu IP-Netzwerken zusammengefasst, daher bietet ipaddress eine Möglichkeit, Netzwerkdefinitionen zu erstellen, zu inspizieren und zu manipulieren. IP-Netzwerkobjekte werden aus Strings erstellt, die den Bereich der Host-Adressen definieren, die zu diesem Netzwerk gehören. Die einfachste Form für diese Informationen ist ein „Netzwerkadresse/Netzwerkpräfix“-Paar, wobei das Präfix die Anzahl der führenden Bits definiert, die verglichen werden, um festzustellen, ob eine Adresse Teil des Netzwerks ist, und die Netzwerkadresse den erwarteten Wert dieser Bits definiert.

Wie bei Adressen wird eine Factory-Funktion bereitgestellt, die automatisch die richtige IP-Version 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')

Netzwerkobjekte dürfen keine Host-Bits gesetzt haben. Die praktische Auswirkung davon ist, dass 192.0.2.1/24 kein Netzwerk beschreibt. Solche Definitionen werden als Schnittstellenobjekte bezeichnet, da die IP-auf-einem-Netzwerk-Notation üblicherweise zur Beschreibung von Netzwerkschnittstellen eines Computers in einem bestimmten Netzwerk verwendet wird und im nächsten Abschnitt näher erläutert wird.

Standardmäßig führt der Versuch, ein Netzwerkobjekt mit gesetzten Host-Bits zu erstellen, dazu, dass ValueError ausgelöst wird. Um zu verlangen, dass die zusätzlichen Bits stattdessen auf null gesetzt werden, kann das Flag strict=False an den Konstruktor übergeben werden.

>>> 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')

Während die String-Form erheblich mehr Flexibilität bietet, können Netzwerke auch mit Integern definiert werden, genau wie Host-Adressen. In diesem Fall wird das Netzwerk so betrachtet, dass es nur die einzelne Adresse enthält, die durch den Integer identifiziert wird, so dass das Netzwerkpräfix die gesamte Netzwerkadresse umfasst.

>>> ipaddress.ip_network(3221225984)
IPv4Network('192.0.2.0/32')
>>> ipaddress.ip_network(42540766411282592856903984951653826560)
IPv6Network('2001:db8::/128')

Wie bei Adressen kann die Erstellung einer bestimmten Art von Netzwerk erzwungen werden, indem der Klassenkonstruktor anstelle der Factory-Funktion aufgerufen wird.

Host-Schnittstellen

Wie gerade erwähnt, sind weder die Adress- noch die Netzwerkklassen ausreichend, wenn Sie eine Adresse in einem bestimmten Netzwerk beschreiben müssen. Notationen wie 192.0.2.1/24 werden von Netzwerktechnikern und den Machern von Firewall- und Router-Tools häufig als Kurzform für „der Host 192.0.2.1 im Netzwerk 192.0.2.0/24“ verwendet. Entsprechend bietet ipaddress eine Reihe von Hybridklassen, die eine Adresse mit einem bestimmten Netzwerk verknüpfen. Die Schnittstelle für die Erstellung ist identisch mit der für die Definition von Netzwerkobjekten, mit der Ausnahme, dass der Adressanteil 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')

Integer-Eingaben werden akzeptiert (wie bei Netzwerken), und die Verwendung einer bestimmten IP-Version kann erzwungen werden, indem der entsprechende Konstruktor direkt aufgerufen wird.

Untersuchen von Adress-/Netzwerk-/Schnittstellenobjekten

Sie haben sich die Mühe gemacht, ein IPv(4|6)(Address|Network|Interface)-Objekt zu erstellen, daher möchten Sie wahrscheinlich Informationen darüber erhalten. ipaddress versucht, dies einfach und intuitiv zu gestalten.

Extrahieren der IP-Version

>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> addr6 = ipaddress.ip_address('2001:db8::1')
>>> addr6.version
6
>>> addr4.version
4

Abrufen des Netzwerks von einer Schnittstelle

>>> 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 in einem Netzwerk sind

>>> 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

Iterieren über die „verwendbaren“ Adressen in einem Netzwerk

>>> 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

Abrufen der Netzmaske (d. h. gesetzte Bits, die dem Netzwerkpräfix entsprechen) oder der Hostmaske (alle Bits, die nicht Teil der Netzmaske sind)

>>> 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')

Explodieren oder Komprimieren der Adresse

>>> 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'

Obwohl IPv4 keine Explosion oder Komprimierung unterstützt, stellen die zugehörigen Objekte dennoch die relevanten Eigenschaften bereit, sodass versionsneutrale Code leicht die kompakteste oder ausführlichste Form für IPv6-Adressen verwenden kann, während IPv4-Adressen weiterhin korrekt behandelt werden.

Netzwerke als Listen von Adressen

Es ist manchmal nützlich, Netzwerke als Listen zu behandeln. Das bedeutet, dass sie auf diese Weise indiziert werden können.

>>> 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')

Das bedeutet auch, dass Netzwerkobjekte sich für die Verwendung der Listenzugehörigkeits-Testsyntax wie folgt eignen.

if address in network:
    # do something

Die Inklusivitätsprüfung erfolgt effizient basierend auf dem Netzwerkpräfix.

>>> 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 bietet einige einfache, hoffentlich intuitive Möglichkeiten, Objekte zu vergleichen, wo dies sinnvoll ist.

>>> ipaddress.ip_address('192.0.2.1') < ipaddress.ip_address('192.0.2.2')
True

Eine TypeError wird ausgelöst, wenn Sie versuchen, Objekte unterschiedlicher Versionen oder unterschiedlicher Typen zu vergleichen.

Verwendung von IP-Adressen mit anderen Modulen

Andere Module, die IP-Adressen verwenden (wie z. B. socket), akzeptieren Objekte aus diesem Modul normalerweise nicht direkt. Stattdessen müssen sie in einen Integer oder String umgewandelt werden, den das andere Modul akzeptiert.

>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> str(addr4)
'192.0.2.1'
>>> int(addr4)
3221225985

Mehr Details erhalten, wenn die Instanzerstellung fehlschlägt

Beim Erstellen von Adress-/Netzwerk-/Schnittstellenobjekten mit den versionsagnostischen Factory-Funktionen werden Fehler als ValueError mit einer generischen Fehlermeldung gemeldet, die einfach besagt, dass der übergebene Wert nicht als Objekt dieses Typs erkannt wurde. Das Fehlen einer spezifischen Fehlermeldung liegt daran, dass es notwendig ist zu wissen, ob der Wert *als* IPv4 oder IPv6 *gedacht* ist, um detailliertere Informationen darüber zu geben, warum er abgelehnt wurde.

Um Anwendungsfälle zu unterstützen, bei denen es nützlich ist, auf diese zusätzlichen Details zuzugreifen, lösen die einzelnen Klassenkonstruktoren tatsächlich die ValueError-Unterklassen ipaddress.AddressValueError und ipaddress.NetmaskValueError aus, um genau anzugeben, welcher Teil der Definition beim Parsen fehlgeschlagen ist.

Die Fehlermeldungen sind bei direkter Verwendung der Klassenkonstruktoren deutlich detaillierter. 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 jedoch ValueError als Oberklasse. Wenn Sie sich also nicht für den spezifischen Fehlertyp interessieren, können Sie immer noch Code wie den folgenden schreiben.

try:
    network = ipaddress.IPv4Network(address)
except ValueError:
    print('address/netmask is invalid for IPv4:', address)