Использование модуля ipaddress для работы с IP-адресами в Python

Раздел: Стандартная библиотека Python -> Использование модулей

Обзор модуля ipaddress

Как быстро получить объект IP-адреса или подсети из строки?

Для работы с IP-адресами и подсетями в стандартной библиотеке Python предусмотрен модуль ipaddress. Он поддерживает обе версии протокола - IPv4 и IPv6. Основные классы: IPv4Address, IPv6Address, IPv4Network, IPv6Network. Удобство заключается в том, что модуль автоматически определяет версию протокола по переданной строке, если вызвать фабричную функцию ipaddress.ip_address() или ipaddress.ip_network().


import ipaddress

ip = ipaddress.ip_address('192.168.1.10')
print(type(ip).__name__, ip)
# IPv4Address 192.168.1.10

network = ipaddress.ip_network('192.168.1.0/24')
print(type(network).__name__, network)
# IPv4Network 192.168.1.0/24

Python ipaddress module (модуль ipaddress для работы с ip-адресами)

Функция ip_address() принимает строку и возвращает либо IPv4Address, либо IPv6Address. Она проверяет корректность формата и выбрасывает ValueError при ошибке. Аналогично ip_network() создаёт сетевой объект, по умолчанию требуя, чтобы адрес был сетевым (хостовые адреса запрещены).

Типичная ошибка: попытка создать сеть из адреса, не являющегося широковещательным или сетевым. Например, ipaddress.ip_network('192.168.1.10/24') вызовет ValueError: 192.168.1.10/24 has host bits set. Решение - использовать параметр strict=False:


net = ipaddress.ip_network('192.168.1.10/24', strict=False)
print(net)
# 192.168.1.0/24

Как создать IPv4-адрес без автоматического определения версии?

Если версия протокола заранее известна, можно напрямую использовать класс IPv4Address:


from ipaddress import IPv4Address
addr = IPv4Address('10.0.0.1')
print(addr)

Такой подход исключает накладные расходы на проверку версии, но требует уверенности в формате.

Как преобразовать IP-адрес в целое число и обратно?

Модуль предоставляет свойство packed для байтового представления и int для целого числа:


from ipaddress import ip_address
ip = ip_address('8.8.8.8')
print(ip.packed)
# b'\x08\x08\x08\x08'
print(int(ip))
# 134744072
# Обратное преобразование:
ip2 = ip_address(134744072)
print(ip2)
# 8.8.8.8

Проблема: целое число, превышающее диапазон IPv4, будет интерпретироваться как IPv6. Чтобы принудительно получить IPv4, нужно проверить число через IPv4Address явно.

Как проверить, является ли адрес частным или глобальным?

Объекты адресов имеют булевы свойства:


from ipaddress import ip_address
ip = ip_address('192.168.0.1')
print('is_private:', ip.is_private)
print('is_global:', ip.is_global)
# is_private: True
# is_global: False

Свойства is_multicast, is_loopback, is_link_local тоже доступны.

Как разбить сеть на подсети?

Метод subnets() возвращает итератор по подсетям заданного размера:


from ipaddress import ip_network
net = ip_network('10.0.0.0/24')
for subnet in net.subnets(prefixlen_diff=2):
    print(subnet)
# 10.0.0.0/26
# 10.0.0.64/26
# 10.0.0.128/26
# 10.0.0.192/26

Параметр prefixlen_diff указывает, на сколько бит увеличить префикс. В примере /24 стало /26.

Как проверить принадлежность адреса сети?

Оператор in проверяет вхождение:


from ipaddress import ip_address, ip_network
net = ip_network('192.168.1.0/24')
ip = ip_address('192.168.1.55')
print(ip in net)  # True

Работает для любых комбинаций IPv4 и IPv6.

Как работать с IPv6 адресами и сетями?

Модуль полностью поддерживает IPv6. Пример:


from ipaddress import ip_address, ip_network
ipv6 = ip_address('2001:db8::1')
print(ipv6.version)  # 6
net6 = ip_network('2001:db8::/32')
print(net6.network_address)
# 2001:db8::

Особенность: при вводе IPv6 с зоной (например, fe80::1%eth0) модуль не распознаёт зону и выдаёт ошибку. Необходимо предварительно удалить %....

Дополнительные сложные примеры

Генерация случайного частного адреса в заданном диапазоне

Пример

from ipaddress import IPv4Address
import random
import struct

base = IPv4Address('192.168.1.0')
mask = IPv4Address('255.255.255.0')
# Генерируем случайный хост от 1 до 254
host = random.randint(1, 254)
random_ip = IPv4Address(int(base) + host)
print(random_ip)
# Пример: 192.168.1.137

Вычисление количества адресов в подсети

Пример

from ipaddress import ip_network

net = ip_network('10.0.0.0/28')
print(net.num_addresses)  # 16
print(net.netmask)        # 255.255.255.240
print(net.hostmask)       # 0.0.0.15

Суммаризация списка подсетей (сложение маршрутов)

Пример

from ipaddress import ip_network, collapse_addresses

subnets = [
    ip_network('10.0.0.0/24'),
    ip_network('10.0.1.0/24'),
    ip_network('10.0.2.0/24'),
]
collapsed = list(collapse_addresses(subnets))
print(collapsed)
# [IPv4Network('10.0.0.0/22')]

Функция collapse_addresses() объединяет смежные подсети в более крупные блоки.

Преобразование адреса в строку с ведущими нулями (IPv4)

Пример

from ipaddress import IPv4Address

ip = IPv4Address('10.0.0.1')
# разбиваем на октеты и форматируем
octets = str(ip).split('.')
formatted = '.'.join(octet.zfill(3) for octet in octets)
print(formatted)
# 010.000.000.001

Проверка, является ли сетью широковещательный адрес

Пример

from ipaddress import ip_network

net = ip_network('192.168.1.0/24')
broadcast = net.broadcast_address
print(broadcast)  # 192.168.1.255
print(broadcast in net)  # True (broadcast входит в сеть)

Итерация по всем адресам сети (хостам)

Пример

from ipaddress import ip_network

net = ip_network('10.0.0.0/29')
for ip in net.hosts():
    print(ip)
# 10.0.0.1
# 10.0.0.2
# ...
# 10.0.0.6

Метод hosts() исключает сетевой и широковещательный адреса.

Работа с IPv6 зарезервированными адресами

Пример

from ipaddress import ip_address

ipv6 = ip_address('::1')
print(ipv6.is_loopback)  # True
link_local = ip_address('fe80::1')
print(link_local.is_link_local)  # True

Преобразование маски в CIDR и обратно

Пример

from ipaddress import ip_network, IPv4Network

net = IPv4Network('255.255.255.0/32')  # передаём только маску, но /32 обязателен?
# Корректный путь - создать сеть с маской:
mask = ip_network('255.255.255.0/32')
print(mask.prefixlen)  # 24? Нет, будет 32, так как это адрес.
# Правильный способ:
from ipaddress import ip_address
mask = ip_address('255.255.255.0')
# преобразовать в битовую строку
bits = bin(int(mask))[2:].zfill(32)
# посчитать единицы
prefixlen = bits.count('1')
print('/' + str(prefixlen))  # /24

Создание собственного итератора для диапазона адресов (не обязательно CIDR)

Пример

from ipaddress import ip_address

def ip_range(start, end):
    start_int = int(ip_address(start))
    end_int = int(ip_address(end))
    for i in range(start_int, end_int + 1):
        yield ip_address(i)

for ip in ip_range('10.0.0.0', '10.0.0.5'):
    print(ip)
# 10.0.0.0
# 10.0.0.1
# ...
# 10.0.0.5

Этот пример показывает, как работать с произвольными диапазонами, которые нельзя описать одной подсетью.

Модуль ipaddress для работы с IP-адресами - comments

En
Python ipaddress module (python)