Использование модуля ipaddress для работы с IP-адресами в 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
Этот пример показывает, как работать с произвольными диапазонами, которые нельзя описать одной подсетью.