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

автор:Питер Муди
автор:Ник Коглан

Обзор

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

Создание объектов Адрес/Сеть/Интерфейс

Поскольку ipaddress — это модуль для проверки и управления IP-адресами, первое, что вам нужно сделать, это создать некоторые объекты. Вы можете использовать ipaddress для создания объектов из строк и целых чисел.

Заметка о версиях IP

Для читателей, которые не особенно знакомы с IP-адресацией, важно знать, что интернет-протокол в настоящее время находится в процессе перехода с версии 4 протокола на версию 6. Этот переход происходит в основном из-за того, что версия 4 протокола не предоставляет достаточно адресов для удовлетворения потребностей всего мира, особенно с учётом увеличения числа устройств с прямым подключением к интернету.

Объяснение деталей различий между двумя версиями протокола выходит за рамки этого введения, но читатели должны, по крайней мере, знать, что эти две версии существуют, и иногда будет необходимо принудительно использовать ту или иную версию.

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) (Адрес|Сеть|Интерфейс), поэтому вам, вероятно, захочется получить информацию о нём. 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:
    # выполнить что-нибудь

Тестирование содержания выполняется эффективно на основе префикса сети:

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