Python: полный обзор инструментов для работы с сетевыми адресами

Раздел: Прочее -> Сетевые технологии

Основные способы работы с IP адресами в Python

Наиболее эффективным решением для работы с IP адресами является встроенный модуль ipaddress, доступный начиная с Python 3.3. Он предоставляет классы для представления IPv4 и IPv6 адресов, подсетей и интерфейсов, а также методы для проверки принадлежности, вычисления масок и генерации хостов.

Пример создания IPv4 адреса и проверки его свойств:

import ipaddress

addr = ipaddress.IPv4Address('192.168.1.10')
print(addr)
print(addr.is_private)
print(addr.version)

Python ip адрес (работа с ip-адресами в python)

192.168.1.10
True
4

Создание сети и перебор всех адресов:

net = ipaddress.IPv4Network('192.168.1.0/24')
for ip in net.hosts():
    print(ip)
192.168.1.1
192.168.1.2
...
192.168.1.254

Проверка принадлежности адреса сети:

net = ipaddress.IPv4Network('10.0.0.0/8')
addr = ipaddress.IPv4Address('10.20.30.40')
print(addr in net)
True

Обработка ошибок при неверном формате:

try:
    ipaddress.IPv4Address('256.0.0.1')
except ValueError as e:
    print('Ошибка:', e)
Ошибка: '256.0.0.1' does not appear to be an IPv4 or IPv6 address

Частая проблема: путаница между IPv4Address и IPv4Network. Если передать в IPv4Network адрес без маски, возникнет ошибка. Решение: всегда указывать маску в формате CIDR или явно создавать интерфейс через IPv4Interface.

Как преобразовать IP адрес в целое число с помощью socket и struct?

Для низкоуровневых операций (например, сравнение адресов или хранение в базе) часто требуется конвертировать строку в 32-битное число. Модули socket и struct позволяют сделать это без сторонних библиотек.

import socket
import struct

def ip_to_int(ip_str):
    packed = socket.inet_aton(ip_str)
    return struct.unpack('!I', packed)[0]

def int_to_ip(int_val):
    packed = struct.pack('!I', int_val)
    return socket.inet_ntoa(packed)

print(ip_to_int('10.0.0.1'))
print(int_to_ip(167772161))
167772161
10.0.0.1

На основе этого можно вычислять диапазоны подсетей вручную. Однако для IPv6 модуль socket.inet_pton и struct.pack применяются по‑другому, так как IPv6 занимает 128 бит.

Ошибка: socket.inet_aton выбрасывает OSError для неверного адреса. Рекомендуется оборачивать вызов в try/except, или предварительно валидировать строку регулярным выражением.

Как упростить работу с подсетями при помощи библиотеки ipcalc?

Сторонняя библиотека ipcalc предоставляет более лаконичный интерфейс для работы с CIDR-нотацией. Устанавливается через pip install ipcalc.

import ipcalc

# Создание сети
net = ipcalc.Network('192.168.0.0/24')
print('Сеть:', net.network())
print('Маска:', net.netmask())
print('Broadcast:', net.broadcast())

# Список хостов (без первого и последнего)
hosts = list(net.hosts())
print('Первый хост:', hosts[0])
print('Последний хост:', hosts[-1])
Сеть: 192.168.0.0
Маска: 255.255.255.0
Broadcast: 192.168.0.255
Первый хост: 192.168.0.1
Последний хост: 192.168.0.254

Библиотека также поддерживает IPv6. Основное преимущество - простой синтаксис и отсутствие необходимости помнить классы IPv4Address/IPv4Network.

Проблема: ipcalc не обновляется с 2015 года. На современных версиях Python могут возникать неявные ошибки из‑за устаревших зависимостей. Рекомендуется использовать ipaddress как основное решение, а ipcalc - только в учебных целях или при миграции кода.

Как разобрать CIDR строку вручную без сторонних модулей?

Иногда требуется реализовать минимальную поддержку CIDR без использования внешних библиотек (например, в ограниченном окружении). Можно написать небольшую функцию.

def parse_cidr(cidr_str):
    ip_part, mask_part = cidr_str.split('/')
    mask = int(mask_part)
    # проверка ip_part с помощью socket
    import socket
    packed = socket.inet_aton(ip_part)
    ip_int = struct.unpack('!I', packed)[0]
    # Вычисление адреса сети
    net_int = ip_int & (0xFFFFFFFF << (32 - mask))
    # Обратное преобразование в строку
    net_packed = struct.pack('!I', net_int)
    net_str = socket.inet_ntoa(net_packed)
    return net_str, mask

print(parse_cidr('10.1.2.3/8'))
('10.0.0.0', 8)

Подход требует аккуратности при работе с битовыми масками. Для IPv6 алгоритм усложняется (128 бит).

Ошибка: неверный формат CIDR (например, /33 или без маски). Приводит к ValueError или IndexError. В реальном коде необходимо добавлять проверки: mask_part.isdigit() и 0 <= int(mask_part) <= 32 (для IPv4).

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

Библиотека netaddr предлагает гибкие возможности для работы с IP‑диапазонами, сортировки, слияния и разбиения подсетей. Установка: pip install netaddr.

import netaddr

# Создание диапазона адресов
ip_range = netaddr.IPRange('10.0.0.0', '10.0.0.255')
print('Размер:', len(ip_range))

# Разбиение на подсети /24
subnets = ip_range.subnet(24)
for subnet in subnets:
    print(subnet)

# Проверка вхождения
print('10.0.0.100 в диапазоне:', '10.0.0.100' in ip_range)
Размер: 256
10.0.0.0/24
True

Класс IPSet позволяет объединять несколько диапазонов и выполнять логические операции (пересечение, разность).

set1 = netaddr.IPSet(['10.0.0.0/24'])
set2 = netaddr.IPSet(['10.0.1.0/24'])
union = set1 | set2
print('Объединение:', union.cidrs())
Объединение: ['10.0.0.0/23']

Проблема: netaddr может быть избыточной, если требуется только базовая проверка. Кроме того, она имеет иное поведение при работе с IPv6 по сравнению с ipaddress - необходимо внимательно читать документацию.

Продвинутые примеры использования IP адресов в Python.

1. Генерация списка всех IP адресов в заданном диапазоне

Пример
import ipaddress

def generate_ips(start, end):
    start_int = int(ipaddress.IPv4Address(start))
    end_int = int(ipaddress.IPv4Address(end))
    for ip_int in range(start_int, end_int + 1):
        yield ipaddress.IPv4Address(ip_int)

ips = list(generate_ips('192.168.1.1', '192.168.1.5'))
for ip in ips:
    print(ip)
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4
192.168.1.5

Такой генератор позволяет обойти ограничения по памяти для больших диапазонов.

2. Сравнение двух подсетей на вложенность

Пример
import ipaddress

net1 = ipaddress.IPv4Network('10.0.0.0/16')
net2 = ipaddress.IPv4Network('10.0.1.0/24')
print('net2 является частью net1:', net2.subnet_of(net1))
print('net1 является частью net2:', net1.subnet_of(net2))
net2 является частью net1: True
net1 является частью net2: False

Метод subnet_of появился в Python 3.7. Для более старых версий необходимо писать собственную проверку.

3. Работа с IPv6 адресами и подсетями

Пример
import ipaddress

ipv6_addr = ipaddress.IPv6Address('2001:db8::1')
net = ipaddress.IPv6Network('2001:db8::/32')
print('Адрес:', ipv6_addr)
print('Сеть:', net)
print('Адрес в сети:', ipv6_addr in net)
# Сокращённая запись
print('Сокращённый адрес:', ipv6_addr.compressed)
Адрес: 2001:db8::1
Сеть: 2001:db8::/32
Адрес в сети: True
Сокращённый адрес: 2001:db8::1

IPv6 адреса поддерживаются всеми методами, аналогичными IPv4. Следует помнить, что длина сети для IPv6 обычно кратна 4.

4. Проверка принадлежности адреса к нескольким подсетям (наборы подсетей)

Пример
import ipaddress

def in_any_network(ip, networks):
    addr = ipaddress.IPv4Address(ip)
    for net in networks:
        if addr in net:
            return True
    return False

networks = [
    ipaddress.IPv4Network('192.168.0.0/16'),
    ipaddress.IPv4Network('10.0.0.0/8')
]

print(in_any_network('10.10.10.10', networks))  # True
print(in_any_network('172.16.0.1', networks))   # False
True
False

Можно использовать any() для краткой записи: any(addr in net for net in networks).

5. Сериализация и десериализация IP объектов в JSON

Пример
import ipaddress
import json

class IPEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
            return str(obj)
        if isinstance(obj, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
            return str(obj)
        return super().default(obj)

data = {
    'ip': ipaddress.IPv4Address('8.8.8.8'),
    'network': ipaddress.IPv4Network('8.8.8.0/24')
}

json_str = json.dumps(data, cls=IPEncoder)
print(json_str)
{"ip": "8.8.8.8", "network": "8.8.8.0/24"}

Обратное преобразование выполняется путём разбора строки и вызова ipaddress.IPv4Address(s).

6. Использование модуля socket для проверки доступности порта (не совсем IP адрес, но смежное)

Пример
import socket

def is_port_open(ip, port, timeout=2):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(timeout)
    result = sock.connect_ex((ip, port))
    sock.close()
    return result == 0

print('Порт 80 на 8.8.8.8 открыт?', is_port_open('8.8.8.8', 80))
False (или True в зависимости от сети)

Этот метод может давать ложные результаты при блокировке ICMP или наличии firewall.

7. Быстрое вычисление broadcast адреса

Пример
import ipaddress

net = ipaddress.IPv4Network('10.0.0.0/24')
print('Broadcast:', net.broadcast_address)

# Для /32 адресов broadcast совпадает с самим адресом
net32 = ipaddress.IPv4Network('10.0.0.1/32')
print('Broadcast для /32:', net32.broadcast_address)
Broadcast: 10.0.0.255
Broadcast для /32: 10.0.0.1

Атрибут broadcast_address доступен только для IPv4 сетей; для IPv6 используется multicast.

Работа с IP-адресами в Python - comments

En
Python ip адрес (python)