Знайомство з модулем ipaddress

автор

Peter Moody

автор

Nick Coghlan

Огляд

Цей документ має на меті надати короткий вступ до модуля ipaddress. Він орієнтований насамперед на користувачів, які ще не знайомі з термінологією IP-мереж, але також може бути корисним для мережевих інженерів, які бажають отримати огляд того, як ipaddress представляє концепції адресації IP-мережі.

Створення об’єктів Адреса/Мережа/Інтерфейс

Оскільки ipaddress — це модуль для перевірки та маніпулювання IP-адресами, перше, що ви захочете зробити, це створити кілька об’єктів. Ви можете використовувати ipaddress для створення об’єктів із рядків і цілих чисел.

Примітка щодо версій IP

For readers that aren’t particularly familiar with IP addressing, it’s important to know that the Internet Protocol is currently in the process of moving from version 4 of the protocol to version 6. This transition is occurring largely because version 4 of the protocol doesn’t provide enough addresses to handle the needs of the whole world, especially given the increasing number of devices with direct connections to the internet.

Пояснення деталей відмінностей між двома версіями протоколу виходить за рамки цього вступу, але читачі повинні принаймні знати, що ці дві версії існують, і іноді буде необхідно примусово використовувати одну версію або інший.

IP-адреси хостів

Адреси, які часто називають «адресами хостів», є основними одиницями роботи з IP-адресуванням. Найпростішим способом створення адрес є використання фабричної функції ipaddress.ip_address(), яка автоматично визначає, чи потрібно створити адресу IPv4 чи IPv6 на основі переданого значення:

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

Адреси також можна створювати безпосередньо з цілих чисел. Припускається, що значення, які вміщуються в 32 біти, є адресами IPv4:

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

Щоб примусово використовувати адреси IPv4 або IPv6, відповідні класи можна викликати безпосередньо. Це особливо корисно для примусового створення адрес IPv6 для малих цілих чисел:

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

Визначення мереж

Адреси хостів зазвичай групуються в IP-мережі, тому ipaddress надає можливість створювати, перевіряти та маніпулювати визначеннями мережі. Об’єкти IP-мережі складаються з рядків, які визначають діапазон адрес хостів, які є частиною цієї мережі. Найпростішою формою цієї інформації є пара «адреса мережі/префікс мережі», де префікс визначає кількість початкових бітів, які порівнюються, щоб визначити, чи є адреса частиною мережі, а мережева адреса визначає очікуване значення ці шматочки.

Що стосується адрес, передбачена заводська функція, яка автоматично визначає правильну версію IP:

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

Мережні об’єкти не можуть мати встановлені біти хоста. Практичний ефект цього полягає в тому, що 192.0.2.1/24 не описує мережу. Такі визначення називаються об’єктами інтерфейсу, оскільки нотація ip-on-a-network зазвичай використовується для опису мережевих інтерфейсів комп’ютера в даній мережі та описана далі в наступному розділі.

За замовчуванням спроба створити мережевий об’єкт із встановленими бітами хоста призведе до появи ValueError. Щоб запитати, щоб додаткові біти натомість привели до нуля, прапорець strict=False можна передати конструктору:

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

Хоча рядкова форма забезпечує значно більшу гнучкість, мережі також можна визначати цілими числами, як і адреси хостів. У цьому випадку вважається, що мережа містить лише одну адресу, визначену цілим числом, тому мережевий префікс включає всю мережеву адресу:

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

Як і у випадку з адресами, створення певного типу мережі можна примусово викликати безпосередньо конструктор класу замість використання функції фабрики.

Інтерфейси хоста

Як згадувалося вище, якщо вам потрібно описати адресу в певній мережі, ні адреси, ні класів мережі недостатньо. Позначення на зразок 192.0.2.1/24 зазвичай використовується мережевими інженерами та людьми, які пишуть інструменти для брандмауерів і маршрутизаторів, як скорочення для «хосту 192.0.2.1 в мережі 192.0.2.0/24», Відповідно, ipaddress надає набір гібридних класів, які пов’язують адресу з певною мережею. Інтерфейс для створення ідентичний інтерфейсу для визначення мережевих об’єктів, за винятком того, що частина адреси не обмежена мережевою адресою.

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

Цілочисельні введення приймаються (як у випадку з мережами), і використання певної версії IP може бути примусово викликано відповідний конструктор безпосередньо.

Перевірка об’єктів адреси/мережі/інтерфейсу

Ви потрудилися зі створенням об’єкта IPv(4|6)(Address|Network|Interface), тож, мабуть, хочете отримати інформацію про нього. ipaddress намагається зробити це легким та інтуїтивно зрозумілим.

Витяг версії IP:

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

Отримання мережі через інтерфейс:

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

Дізнатися кількість окремих адрес у мережі:

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

Перебір «придатних» адрес у мережі:

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

Отримання маски мережі (тобто встановлених бітів, що відповідають префіксу мережі) або маски хоста (будь-яких бітів, які не є частиною маски мережі):

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

Розкладання або стиснення адреси:

>>> 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 не підтримує розгортання чи стиснення, пов’язані об’єкти все одно надають відповідні властивості, щоб нейтральний код версії міг легко гарантувати, що для адрес IPv6 використовується найкоротша чи найбільш докладна форма, водночас правильно обробляючи адреси IPv4.

Мережі як списки адрес

Іноді корисно розглядати мережі як списки. Це означає, що їх можна індексувати таким чином:

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

Це також означає, що мережеві об’єкти піддаються використанню синтаксису перевірки членства в списку таким чином:

if address in network:
    # do something

Тестування обмеження виконується ефективно на основі мережевого префікса:

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

Порівняння

ipaddress надає кілька простих, сподіваюся, інтуїтивно зрозумілих способів порівняння об’єктів, де це має сенс:

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

Виняток TypeError виникає, якщо ви намагаєтеся порівняти об’єкти різних версій або різних типів.

Використання IP-адрес з іншими модулями

Інші модулі, які використовують IP-адреси (такі як socket), зазвичай не приймають об’єкти з цього модуля безпосередньо. Замість цього вони повинні бути приведені до цілого числа або рядка, який прийме інший модуль:

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

Отримання додаткової інформації, коли не вдається створити примірник

Під час створення об’єктів адреси/мережі/інтерфейсу за допомогою функцій фабрики, що не залежать від версії, про будь-які помилки повідомлятиметься як ValueError із загальним повідомленням про помилку, яке просто повідомляє, що передане значення не було розпізнано як об’єкт цього типу. Відсутність конкретної помилки пов’язана з тим, що необхідно знати, чи має значення бути IPv4 чи IPv6, щоб надати докладнішу інформацію про те, чому його було відхилено.

Щоб підтримати випадки використання, де корисно мати доступ до цієї додаткової деталі, окремі конструктори класів фактично створюють підкласи ValueError ipaddress.AddressValueError і ipaddress.NetmaskValueError, щоб точно вказати яку частину визначення не вдалося правильно проаналізувати.

Повідомлення про помилки значно більш детальні при безпосередньому використанні конструкторів класів. Наприклад:

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

Однак обидва винятки, специфічні для модуля, мають ValueError як батьківський клас, тому, якщо вас не хвилює певний тип помилки, ви все одно можете написати наступний код:

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