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

автор:

Питер Муди

автор:

Ник Коглан

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

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

Замечание о версиях IP

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

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

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)