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

автор

Питер Муди

автор

Ник Коглан

Обзор

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

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

Поскольку 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)
Вернуться на верх