Apprenez les concepts d'adresse IP avec le module ipaddress de Python – Real Python

By | juillet 13, 2020

Formation gratuite Python

Python adresse IP module est un joyau sous-estimé de la bibliothèque standard Python. Vous n'avez pas besoin d'être un ingénieur réseau à part entière pour avoir été exposé à des adresses IP dans la nature. Adresses IP et réseaux sont omniprésents dans le développement et l'infrastructure de logiciels. Ils sous-tendent la façon dont les ordinateurs, eh bien, adresse L'une et l'autre.

L'apprentissage par la pratique est un moyen efficace de maîtriser les adresses IP. le adresse IP Le module vous permet de faire exactement cela en affichant et en manipulant les adresses IP en tant qu'objets Python. Dans ce didacticiel, vous obtiendrez une meilleure compréhension des adresses IP en utilisant certaines des fonctionnalités de Python adresse IP module.

Dans ce didacticiel, vous apprendrez:

  • Comment Adresses IP travail, à la fois en théorie et en code Python
  • Comment Réseaux IP représenter des groupes d'adresses IP et comment inspecter les relations entre les deux
  • Comment Python adresse IP module utilise intelligemment un modèle de conception classique pour vous permettre de faire plus avec moins

Pour suivre, vous avez juste besoin de Python 3.3 ou supérieur depuis adresse IP a été ajouté à la bibliothèque standard Python dans cette version. Les exemples de ce didacticiel ont été générés à l'aide de Python 3.8.

Adresses IP en théorie et en pratique

Si vous vous souvenez d'un seul concept concernant les adresses IP, n'oubliez pas ceci: une adresse IP est un entier. Cette information vous aidera à mieux comprendre à la fois le fonctionnement des adresses IP et leur représentation en tant qu'objets Python.

Avant de vous lancer dans un code Python, il peut être utile de voir ce concept étoffé mathématiquement. Si vous êtes ici juste pour quelques exemples d'utilisation du adresse IP module, vous pouvez passer à la section suivante, sur l'utilisation du module lui-même.

Mécanique des adresses IP

Vous avez vu ci-dessus qu'une adresse IP se résume à un entier. Une définition plus complète est qu’un Adresse IPv4 est un entier 32 bits utilisé pour représenter un hôte sur un réseau. Le terme hôte est parfois utilisé comme synonyme d'une adresse.

Il s'ensuit qu'il y a 232 adresses IPv4 possibles, de 0 à 4 294 967 295 (où la limite supérieure est 232 – 1). Mais c'est un tutoriel pour les êtres humains, pas pour les robots. Personne ne veut cingler l'adresse IP 0xdc0e0925.

La façon la plus courante d'exprimer une adresse IPv4 est d'utiliser notation à quatre points, qui se compose de quatre nombres décimaux séparés par des points:

Il n’est pas immédiatement évident quel est l’entier sous-jacent de l’adresse 220.14.9.37 représente, cependant. Formulairement, vous pouvez casser l'adresse IP 220.14.9.37 dans ses quatre octuor Composants:

>>>

>>> (
...     220 * (256 ** 3) +
...      14 * (256 ** 2) +
...       9 * (256 ** 1) +
...      37 * (256 ** 0)
... )
3691907365

Comme indiqué ci-dessus, l'adresse 220.14.9.37 représente l'entier 3 691 907 365. Chaque octet est un octet ou un nombre compris entre 0 et 255. Cela étant, vous pouvez déduire que l'adresse IPv4 maximale est 255.255.255.255 (ou FF.FF.FF.FF en notation hexadécimale), tandis que le minimum est 0.0.0.0.

Ensuite, vous verrez comment Python adresse IP Le module effectue ce calcul pour vous, vous permettant de travailler avec le formulaire lisible par l'homme et de laisser l'arithmétique d'adresse se produire à l'abri des regards.

Le Python adresse IP Module

Pour suivre, vous pouvez récupérer l'adresse IP externe de votre ordinateur pour travailler avec sur la ligne de commande:

$ curl -sS ifconfig.me/ip
220.14.9.37

Cela demande votre adresse IP au site ifconfig.me, qui peut être utilisé pour afficher un tableau de détails sur votre connexion et votre réseau.

Ouvrez maintenant un Python REPL. Vous pouvez utiliser le IPv4Address pour construire un objet Python qui encapsule une adresse:

>>>

>>> de adresse IP importer IPv4Address

>>> addr = IPv4Address("220.14.9.37")
>>> addr
IPv4Address ('220.14.9.37')

Passer un str tel que "220.14.9.37" à la IPv4Address constructeur est l'approche la plus courante. Cependant, la classe peut également accepter d'autres types:

>>>

>>> IPv4Address(3691907365)  # D'un int
IPv4Address ('220.14.9.37')

>>> IPv4Address(b" xdc  x0e  t% ")  # À partir d'octets (forme compactée)
IPv4Address ('220.14.9.37')

Tout en construisant à partir d'un élément lisible par l'homme str est probablement la façon la plus courante, vous pourriez voir octets si vous travaillez avec quelque chose comme des données de paquets TCP.

Les conversions ci-dessus sont également possibles dans l'autre sens:

>>>

>>> int(addr)
3691907365
>>> addr.emballé
b ' xdc  x0e  t%'

En plus de permettre l'entrée et la sortie aller-retour vers différents types de Python, les instances de IPv4Address sont également lavable. Cela signifie que vous pouvez les utiliser comme clés dans un type de données de mappage tel qu'un dictionnaire:

>>>

>>> hacher(IPv4Address("220.14.9.37"))
4035855712965130587

>>> num_connections = 
...     IPv4Address("220.14.9.37"): 2,
...     IPv4Address("100.201.0.4"): 16,
...     IPv4Address("8.240.12.2"): 4,
... 

En plus de cela, IPv4Address implémente également des méthodes qui permettent des comparaisons en utilisant l'entier sous-jacent:

>>>

>>> IPv4Address("220.14.9.37") > IPv4Address("8.240.12.2")
Vrai

>>> addrs = (
...     IPv4Address("220.14.9.37"),
...     IPv4Address("8.240.12.2"),
...     IPv4Address("100.201.0.4"),
... )
>>> pour une dans trié(addrs):
...     impression(une)
...
8.240.12.2
100.201.0.4
220.14.9.37

Vous pouvez utiliser l'un des opérateurs de comparaison standard pour comparer les valeurs entières des objets d'adresse.

Comme vous l'avez vu ci-dessus, le constructeur lui-même pour IPv4Address est court et doux. C’est lorsque vous commencez à regrouper des adresses en groupes ou en réseaux que les choses deviennent plus intéressantes.

Réseaux IP et interfaces

UNE réseau est un groupe d'adresses IP. Les réseaux sont décrits et affichés comme des plages d'adresses contiguës. Par exemple, un réseau peut être composé des adresses 192.4.2.0 à travers 192.4.2.255, un réseau contenant 256 adresses.

Vous pouvez reconnaître un réseau par ses adresses IP supérieure et inférieure, mais comment pouvez-vous l'afficher avec une convention plus succincte? C’est là que la notation CIDR entre en jeu.

Notation CIDR

Un réseau est défini à l'aide d'un adresse réseau plus un préfixe dans Notation CIDR (Classless Inter-Domain Routing):

>>>

>>> de adresse IP importer IPv4Network
>>> net = IPv4Network("192.4.2.0/24")
>>> net.num_addresses
256

La notation CIDR représente un réseau comme /

. le préfixe de routage (ou longueur du préfixe, ou juste préfixe), qui est de 24 dans ce cas, est le nombre de bits de tête utilisés pour répondre à des questions telles que si une certaine adresse fait partie d'un réseau ou combien d'adresses résident dans un réseau. (Ici bits de tête fait référence au premier N comptage des bits à partir de la gauche de l'entier en binaire.)

Vous pouvez trouver le préfixe de routage avec le .prefixlen propriété:

Passons directement à un exemple. Est l'adresse 192.4.2.12 dans le réseau 192.4.2.0/24? Dans ce cas, la réponse est oui, car les 24 premiers bits de 192.4.2.12 sont les trois premiers octets (192.4.2). Avec un / 24 préfixe, vous pouvez simplement couper le dernier octet et voir que le 192.4.2.xxx les pièces correspondent.

Représenté différemment, le / 24 le préfixe se traduit par un masque de réseau qui, comme son nom l'indique, sert à masquer des bits dans les adresses à comparer:

>>>
>>> net.masque de réseau
IPv4Address ('255.255.255.0')

Vous comparez bits de tête pour déterminer si une adresse fait partie d'un réseau. Si les bits de tête correspondent, alors l'adresse fait partie du réseau:

11000000 00000100 00000010 00001100 # 192.4.2.12 # Adresse IP de l'hôte
11000000 00000100 00000010 00000000 # 192.4.2.0 # Adresse réseau
                          |
                          ^ 24e bit (arrêtez-vous ici!)
| _________________________ |
            |
      Ces bits correspondent

Ci-dessus, les 8 derniers bits de 192.4.2.12 sont masqués (avec 0) et sont ignorés dans la comparaison. Encore une fois, Python adresse IP vous évite la gymnastique mathématique et prend en charge les tests d'adhésion idiomatiques:

>>>

>>> net = IPv4Network("192.4.2.0/24")

>>> IPv4Address("192.4.2.12") dans net
Vrai
>>> IPv4Address("192.4.20.2") dans net
Faux

Ceci est rendu possible par le trésor qu'est la surcharge de l'opérateur, par lequel IPv4Network définit __contient __ () pour permettre le test d'adhésion à l'aide du dans opérateur.

Dans la notation CIDR 192.4.2.0/24, les 192.4.2.0 la partie est la adresse réseau, qui est utilisé pour identifier le réseau:

>>>

>>> net.adresse réseau
IPv4Address ('192.4.2.0')

Comme vous l'avez vu ci-dessus, l'adresse réseau 192.4.2.0 peut être considéré comme le résultat attendu lorsqu'un masque est appliqué à une adresse IP hôte:

11000000 00000100 00000010 00001100 # Adresse IP de l'hôte
11111111 11111111 11111111 00000000 # Masque de réseau, 255.255.255.0 ou / 24
11000000 00000100 00000010 00000000 # Résultat (par rapport à l'adresse réseau)

Lorsque vous y réfléchissez de cette façon, vous pouvez voir / 24 préfixe se traduit en fait par un vrai IPv4Address:

>>>

>>> net.prefixlen
24
>>> net.masque de réseau
IPv4Address ('255.255.255.0') # 11111111 11111111 11111111 00000000

En fait, si cela vous plaît, vous pouvez construire un IPv4Network directement à partir de deux adresses:

>>>

>>> IPv4Network("192.4.2.0/255.255.255.0")
IPv4Network ('192.4.2.0/24')

Au dessus, 192.4.2.0 est l'adresse réseau pendant 255.255.255.0 est le masque de réseau.

À l'autre extrémité du spectre d'un réseau se trouve son adresse finale, ou Adresse de diffusion, qui est une adresse unique qui peut être utilisée pour communiquer avec tous les hôtes de son réseau:

>>>

>>> net.Adresse de diffusion
IPv4Address ('192.4.2.255')

Il y a encore un point à mentionner à propos du masque de réseau. Vous verrez le plus souvent des longueurs de préfixe qui sont des multiples de 8:

Longueur du préfixe Nombre d'adresses Masque de réseau
8 16 777 216 255.0.0.0
16 65,536 255.255.0.0
24 256 255.255.255.0
32 1 255.255.255.255

Cependant, tout entier compris entre 0 et 32 ​​est valide, bien que moins courant:

>>>

>>> net = IPv4Network("100.64.0.0/10")
>>> net.num_addresses
4194304
>>> net.masque de réseau
IPv4Address ('255.192.0.0')

Dans cette section, vous avez vu comment construire un IPv4Network instance et tester si une certaine adresse IP se trouve en son sein. Dans la section suivante, vous apprendrez comment boucler sur les adresses d'un réseau.

Boucle à travers les réseaux

le IPv4Network classe prend en charge l'itération, ce qui signifie que vous pouvez parcourir ses adresses individuelles dans un pour boucle:

>>>

>>> net = IPv4Network("192.4.2.0/28")
>>> pour addr dans net:
...     impression(addr)
...
192.4.2.0
192.4.2.1
192.4.2.2
...
192.4.2.13
192.4.2.14
192.4.2.15

De même, net.hosts () renvoie un générateur qui fournira les adresses indiquées ci-dessus, à l'exclusion des adresses de réseau et de diffusion:

>>>

>>> h = net.hôtes()
>>> type(h)

>>> suivant(h)
IPv4Address ('192.4.2.1')
>>> suivant(h)
IPv4Address ('192.4.2.2')

Dans la section suivante, vous plongerez dans un concept étroitement lié aux réseaux: le sous-réseau.

Sous-réseaux

UNE sous-réseau est une subdivision d'un réseau IP:

>>>

>>> small_net = IPv4Network("192.0.2.0/28")
>>> big_net = IPv4Network("192.0.0.0/16")
>>> small_net.subnet_of(big_net)
Vrai
>>> big_net.supernet_of(small_net)
Vrai

Au dessus, small_net contient seulement 16 adresses, ce qui est suffisant pour vous et quelques cabines autour de vous. Inversement, big_net contient 65 536 adresses.

Une façon courante de réaliser un sous-réseau consiste à prendre un réseau et à augmenter sa longueur de préfixe de 1. Prenons cet exemple de Wikipedia:

Diagramme de sous-réseau du réseau IPv4 de Wikipedia
Sous-réseau IPv4 (source d'image)

Cet exemple commence par un / 24 réseau:

net = IPv4Network("200.100.10.0/24")

Le sous-réseau en augmentant la longueur du préfixe de 24 à 25 implique de déplacer les bits pour diviser le réseau en parties plus petites. C'est un peu poilu mathématiquement. Heureusement, IPv4Network en fait un jeu d'enfant parce que .sous-réseaux () renvoie un itérateur sur les sous-réseaux:

>>>

>>> pour sn dans net.sous-réseaux():
...     impression(sn)
...
200.100.10.0/25
200.100.10.128/25

Vous pouvez également dire .sous-réseaux () quel devrait être le nouveau préfixe. Un préfixe plus élevé signifie des sous-réseaux plus nombreux et plus petits:

>>>

>>> pour sn dans net.sous-réseaux(new_prefix=28):
...     impression(sn)
...
200.100.10.0/28
200.100.10.16/28
200.100.10.32/28
...
200.100.10.208/28
200.100.10.224/28
200.100.10.240/28

Outre les adresses et les réseaux, il existe une troisième partie centrale du adresse IP module que vous verrez ensuite.

Interfaces hôte

Dernier point mais non le moindre, Python adresse IP module exporte un IPv4Interface classe pour représenter une interface hôte. UNE interface hôte est un moyen de décrire, sous une forme compacte unique, à la fois une adresse IP hôte et un réseau dans lequel il se trouve:

>>>

>>> de adresse IP importer IPv4Interface

>>> ifc = IPv4Interface("192.168.1.6/24")
>>> ifc.ip  # L'adresse IP de l'hôte
IPv4Address ('192.168.1.6')
>>> ifc.réseau  # Réseau sur lequel réside l'adresse IP hôte
IPv4Network ('192.168.1.0/24')

Au dessus, 192.168.1.6/24 signifie "l'adresse IP 192.168.1.6 dans le réseau 192.168.1.0/24. "

Autrement dit, une adresse IP seule ne vous dit pas sur quel (s) réseau (s) cette adresse se trouve et une adresse réseau est un groupe d’adresses IP plutôt qu’une seule. le IPv4Interface vous permet d'exprimer simultanément, via la notation CIDR, une seule adresse IP d'hôte et son réseau.

Plages d'adresses spéciales

Maintenant que vous connaissez à la fois les adresses IP et les réseaux à un niveau élevé, il est également important de savoir que toutes les adresses IP ne sont pas créées égales – certaines sont spéciales.

L'Internet Assigned Numbers Authority (IANA), en tandem avec l'Internet Engineering Task Force (IETF), supervise l'allocation des différents plages d'adresses. Le registre d'adresses IPv4 à usage spécifique de l'IANA est un tableau très important dictant que certaines plages d'adresses doivent avoir des significations spéciales.

Un exemple courant est celui d'un privé adresse. Une adresse IP privée est utilisée pour la communication interne entre les appareils d'un réseau qui ne nécessite pas de connectivité à Internet public. Les gammes suivantes sont réservées à un usage privé:

Intervalle Nombre d'adresses Adresse réseau Adresse de diffusion
10.0.0.0/8 16 777 216 10.0.0.0 10.255.255.255
172.16.0.0/12 1 048 576 172.16.0.0 72.31.255.255
192.168.0.0/16 65,536 192.168.0.0 192.168.255.255

Un exemple choisi au hasard est 10.243.156.214. Alors, comment savez-vous que cette adresse est privée? Vous pouvez confirmer qu'il tombe dans le 10.0.0.0/8 gamme:

>>>

>>> IPv4Address("10.243.156.214") dans IPv4Network("10.0.0.0/8")
Vrai

Un deuxième type d'adresse spécial est un lien-local adresse, qui est accessible uniquement à partir d'un sous-réseau donné. Un exemple est Amazon Time Sync Service, qui est disponible pour les instances AWS EC2 sur l'IP de liaison locale 169.254.169.123.

Si votre instance EC2 se trouve dans un cloud privé virtuel (VPC), vous n'avez pas besoin d'une connexion Internet pour indiquer à votre instance l'heure qu'il est. Le bloc 169.254.0.0/16 est réservé aux adresses lien-local:

>>>

>>> timesync_addr = IPv4Address("169.254.169.123")
>>> timesync_addr.is_link_local
Vrai

Ci-dessus, vous pouvez voir cette façon de confirmer que 10.243.156.214 est une adresse à usage privé est de tester qu'elle se trouve dans le 10.0.0.0/8 gamme. Mais Python adresse IP Le module fournit également un ensemble de propriétés pour tester si une adresse est un type spécial:

>>>

>>> IPv4Address("10.243.156.214").is_private
Vrai
>>> IPv4Address("127.0.0.1").is_loopback
Vrai

>>> [[[[je pour je dans dir(IPv4Address) si je.commence avec("est_")]  # "is_X" propriétés
['is_global'['is_global'['is_global'['is_global'
    'is_link_local',
    'is_loopback',
    'is_multicast',
    'is_private',
    'est réservé',
    'is_unspecified']

Une chose à noter .is_private, cependant, est qu'il utilise une définition plus large du réseau privé que les trois plages IANA indiquées dans le tableau ci-dessus. Python adresse IP Le module regroupe également d'autres adresses allouées aux réseaux privés:

Cette liste n'est pas exhaustive, mais elle couvre les cas les plus courants.

Le Python adresse IP Module sous le capot

En plus de son API documentée, le code source CPython pour le adresse IP module et son IPv4Address la classe donne un aperçu de la façon dont vous pouvez utiliser un modèle appelé composition pour prêter à votre propre code une API idiomatique.

Rôle central de la composition

le adresse IP module tire parti d'un modèle orienté objet appelé composition. Ses IPv4Address la classe est un composite qui encapsule un entier Python simple. Les adresses IP sont fondamentalement des entiers, après tout.

Chaque IPv4Address par exemple a un quasi-privé ._ip attribut qui est lui-même un int. La plupart des autres propriétés et méthodes de la classe dépendent de la valeur de cet attribut:

>>>

>>> addr = IPv4Address("220.14.9.37")
>>> addr._ip
3691907365

le ._ip est en fait ce qui est responsable de la production int (addr). La chaîne d'appels est que int (my_addr) appels my_addr .__ int __ (), lequel IPv4Address met en œuvre juste my_addr._ip:

Pile d'appels pour int (my_addr)

Si vous avez demandé aux développeurs CPython à ce sujet, ils pourraient vous dire que ._ip est un détail d'implémentation. Bien que rien ne soit vraiment privé en Python, le principal trait de soulignement indique que ._ip est quasi privé, ne fait pas partie du public adresse IP API, et sujet à changement sans préavis. C'est pourquoi il est plus stable d'extraire l'entier sous-jacent avec int (addr).

Cela dit, c’est le sous-jacent ._ip cela donne le IPv4Address et IPv4Network classe leur magie.

Extension IPv4Address

Vous pouvez démontrer la puissance du sous-jacent ._ip entier par extension la classe d'adresses IPv4:

de adresse IP importer IPv4Address

classe MyIPv4(IPv4Address):
    def __et__(soi, autre: IPv4Address):
        si ne pas isinstance(autre, (int, IPv4Address)):
            élever NotImplementedError
        revenir soi.__classe__(int(soi) & int(autre))

Ajouter .__et__() vous permet d'utiliser le binaire ET (&) opérateur. Vous pouvez maintenant appliquer directement un masque de réseau à une adresse IP hôte:

>>>

>>> addr = MyIPv4("100.127.40.32")
>>> masque = MyIPv4("255.192.0.0")  # A / 10 préfixe

>>> addr & masque
MyIPv4 ('100.64.0.0')

>>> addr & 0xffc00000  # Littéral hexadécimal pour 255.192.0.0
MyIPv4 ('100.64.0.0')

Au dessus, .__et__() vous permet d'utiliser l'un ou l'autre IPv4Address ou un int directement comme masque. Car MyIPv4 est une sous-classe de IPv4Address, les isinstance () le chèque reviendra Vrai dans ce cas.

Outre la surcharge de l'opérateur, vous pouvez également ajouter de nouvelles propriétés:

    1 importer 
    2 de adresse IP importer IPv4Address
    3 
    4 classe MyIPv4(IPv4Address):
    5     @propriété
    6     def binary_repr(soi, SEP=".") -> str:
    7         "" "Représente IPv4 comme 4 blocs de 8 bits." ""
    8         revenir SEP.joindre(F"je:08b" pour je dans soi.emballé)
    9 
dix     @classmethod
11     def from_binary_repr(cls, binary_repr: str):
12         "" "Construire IPv4 à partir d'une représentation binaire." ""
13         # Supprimer tout ce qui n'est pas un 0 ou 1
14         je = int(.sous(r"[^01]", "", binary_repr), 2)
15         revenir cls(je)

Dans .binary_repr (ligne 8), en utilisant .emballé transforme l'adresse IP en un tableau d'octets qui est ensuite formaté comme la représentation sous forme de chaîne de sa forme binaire.

Dans .from_binary_repr, l'appel à int (re.sub (r "[^01]"," ", binary_repr), 2) sur ligne 14 comporte deux parties:

  1. Il supprime tout autre que 0 et 1 de la chaîne d'entrée.
  2. Il analyse le résultat, en supposant la base 2, avec int (, 2).

En utilisant .binary_repr () et .from_binary_repr () vous permet de convertir et de construire à partir d'un str de 1 et de 0 en notation binaire:

>>>

>>> MyIPv4("220.14.9.37").binary_repr
«11011100.00001110.00001001.00100101»
>>> MyIPv4("255.255.0.0").binary_repr  Masque de réseau # A / 16
«11111111.11111111.00000000.00000000»

>>> MyIPv4._from_binary_repr("11011100 00001110 00001001 00100101")
MyIPv4 ('220.14.9.37')

Ce ne sont que quelques façons de montrer comment tirer parti du modèle IP en tant qu'entier peut vous aider à étendre les fonctionnalités de IPv4Address avec une petite quantité de code supplémentaire.

Conclusion

Dans ce didacticiel, vous avez vu comment Python adresse IP Le module peut vous permettre de travailler avec des adresses IP et des réseaux à l'aide de constructions Python courantes.

Voici quelques points importants que vous pouvez retirer:

  • Une adresse IP est fondamentalement un entier, et cela sous-tend à la fois comment vous pouvez faire de l'arithmétique manuelle avec
    adresses et comment les classes Python de adresse IP sont conçus en utilisant la composition.
  • le adresse IP module profite de surcharge de l'opérateur pour vous permettre d'inférer les relations entre les adresses
    et réseaux.
  • le adresse IP module utilise compositionet vous pouvez étendre cette fonctionnalité si nécessaire pour un comportement supplémentaire.

Comme toujours, si vous souhaitez approfondir, lire la source du module est un excellent moyen de le faire.

Lectures complémentaires

Voici quelques ressources approfondies que vous pouvez consulter pour en savoir plus sur le adresse IP module: