Проверка IP-адреса с помощью Python

В вашей программе Python вы можете захотеть проверить IP-адрес. Это может быть требованием, если вы пишете программы уровня ОС и не только.

Для проверки IP-адреса с помощью Python можно использовать функцию ip_address() модуля ipaddress. Это работает как для адресов IPv4, так и для IPv6. Вы также можете проверить IP-адрес, используя пользовательскую функцию или регулярное выражение, которые проверяют наборы чисел, из которых состоит IP-адрес.

Давайте узнаем, как проверить IP-адрес с помощью Python!

Как проверить IP-адрес в Python?

Самый простой способ проверить, представляет ли строка IP-адрес, — использовать модуль Python ipaddress.

Давайте откроем оболочку Python и посмотрим, что возвращает функция ipaddress.ip_address(), когда мы передаем ей строки, представляющие действительный и недействительный адрес IPv4.

Сначала действительный…

>>> ipaddress.ip_address("10.10.10.10")
IPv4Address('10.10.10.10') 

Функция ip_address() возвращает объект типа IPv4Address, это означает, что она способна преобразовать строку в допустимый IP-адрес.

Теперь давайте попробуем с недействительным IP-адресом…

>>> ipaddress.ip_address("10.10.10.300")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/anaconda3/lib/python3.8/ipaddress.py", line 53, in ip_address
    raise ValueError('%r does not appear to be an IPv4 or IPv6 address' %
ValueError: '10.10.10.300' does not appear to be an IPv4 or IPv6 address 

На этот раз функция ip_address() вызывает исключение ValueError, поскольку переданная нами строка не представляет собой допустимый IP-адрес.

Мы можем создать простую функцию, которая определяет, является ли IP-адрес допустимым или нет, в зависимости от того, что ipaddress.ip_address() вызывает исключение ValueError для недопустимых IP-адресов.

import ipaddress 

def validate_ip_address(address):
    try:
        ip = ipaddress.ip_address(address)
        print("IP address {} is valid. The object returned is {}".format(address, ip))
    except ValueError:
        print("IP address {} is not valid".format(address)) 

Обратите внимание, как мы используем метод строки format() для генерации сообщений об успешном и неудачном выполнении.

Передайте этой функции несколько IP-адресов, чтобы убедиться, что она работает правильно.

validate_ip_address("10.10.10.10")
validate_ip_address("10.10.10.01")
validate_ip_address("10.10.10.300")
validate_ip_address("10.260.10.300")
validate_ip_address("192.168.1.20")

[output]
IP address 10.10.10.10 is valid. The object returned is 10.10.10.10
IP address 10.10.10.01 is valid. The object returned is 10.10.10.1
IP address 10.10.10.300 is not valid
IP address 10.260.10.300 is not valid
IP address 192.168.1.20 is valid. The object returned is 192.168.1.20 

Функция работает хорошо, также обратите внимание, что во втором тесте из строки «10.10.10.01» мы получаем обратно объект для «10.10.10.1».

Модуль удаляет начальный ноль в четвертой части IP-адреса.

Примечание: вы также можете обновить функцию validate_ip_address(), чтобы она возвращала True для допустимого IP-адреса и False для недопустимого IP-адреса вместо вывода сообщения.

Проверка IP-адреса в Python с помощью пользовательской функции

Давайте немного попрактикуемся в Python и посмотрим, как написать логику, которая проверяет адрес IPv4 без использования модуля ipaddress.

Адрес IPv4 имеет следующий формат:

a.b.c.d

Где a, b, c, d — четыре числа от 0 до 255. Мы можем использовать эту спецификацию для написания нашей собственной логики.

def validate_ip_address(address):
    parts = address.split(".")

    if len(parts)!= 4:
        print("IP address {} is not valid".format(address))
        return False

    for part in parts:
        if not isinstance(int(part), int):
            print("IP address {} is not valid".format(address))
            return False

        if int(part) < 0 or int(part) > 255:
            print("IP address {} is not valid".format(address))
            return False
 
    print("IP address {} is valid".format(address))
    return True 

В этой функции мы проходим следующие этапы:

  • Разделите адрес по символу точки и сохраните каждую часть IP-адреса в списке строк.
  • Убедитесь, что строка IP состоит из 4 цифр, разделенных точками (используя функцию len()).
  • Для каждого числа в строке IP выполните проверки, указанные ниже:
    • Убедитесь, что число является целым числом.
    • Проверьте, что целое число имеет значение от 0 до 255.

Выполним нашу функцию для тех же IP-адресов, которые использовались ранее:

validate_ip_address("10.10.10.10")
validate_ip_address("10.10.10.01")
validate_ip_address("10.10.10.300")
validate_ip_address("10.260.10.300")
validate_ip_address("192.168.1.20")

[output]
IP address 10.10.10.10 is valid
IP address 10.10.10.01 is valid
IP address 10.10.10.300 is not valid
IP address 10.260.10.300 is not valid
IP address 192.168.1.20 is valid 

Вывод правильный.

Проверка IP-адреса с помощью регулярного выражения

IP-адрес можно проверить с помощью регулярного выражения (regex).

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

Вот шаблон, который мы можем использовать:

  • ^ представляет начало строки, которую мы хотим сопоставить.
  • $ представляет собой конец строки.
  • \d{1,3} — целое число, содержащее от 1 до 3 цифр.
  • \. соответствует одной точке.
^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$ 

Откройте оболочку Python и проверьте это регулярное выражение на нескольких IP-адресах.

>>> address = "10.10.10.10"
>>> re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", address)
<re.Match object; span=(0, 11), match='10.10.10.10'>
>>> 
>>> address = "10.10.10.300"
>>> re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", address)
<re.Match object; span=(0, 12), match='10.10.10.300'>
>>> 
>>> address = "10.10.10.3000"
>>> re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", address)
>>>

Первый — это действительный IP-адрес (10.10.10.10), и он соответствует регулярному выражению.

Второй IP-адрес также сопоставляется с регулярным выражением, даже если он содержит число 300 в четвертой части.

Это потому, что мы сопоставляем целые числа с 1-3 цифрами. Это означает, что после использования регулярного выражения мы также должны проверить, имеет ли конкретная числовая часть IP-адреса значение меньше 255.

Третий IP-адрес не соответствует выражению, поскольку четвертая часть содержит 4 цифры (3000).

Теперь напишите функцию, которая использует это регулярное выражение, а также проверяет, что каждая часть имеет значение от 0 до 255.

Сначала мы хотим преобразовать объект re.Match, возвращаемый функцией re.match(). Для этого мы будем использовать функцию bool().

>>> address = "10.10.10.10"
>>> match = re.match(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$", address)
>>> print(bool(match))
True 
>>> 
>>> address = "10.10.10.3000"
>>> match = re.match(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$", address)
>>> print(bool(match))
False 

При преобразовании в логическое значение возвращаемый re.match() объект имеет значение True, если переданная ему строка соответствует шаблону. В противном случае он имеет значение false.

Итак, начнем с возврата значения False в нашей функции, если строка (в данном случае IP-адрес) не соответствует шаблону формата IP-адреса.

def validate_ip_address(address):
    match = re.match(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$", address)

    if bool(match) is False:
        print("IP address {} is not valid".format(address)
        return False

    return True 

И завершите функцию, проверив, что каждое число имеет значение от 0 до 255, как мы это делали в предыдущем разделе:

import re 

def validate_ip_address(address):
    match = re.match(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$", address)

    if bool(match) is False:
        print("IP address {} is not valid".format(address))
        return False

    for part in address.split("."):
        if int(part) < 0 or int(part) > 255:
            print("IP address {} is not valid".format(address))
            return False

    print("IP address {} is valid".format(address))
    return True 

Это результат выполнения этой функции для некоторых IP-адресов.

validate_ip_address("10.10.10.10")
validate_ip_address("10.10.10.01")
validate_ip_address("10.10.10.300")
validate_ip_address("10.260.10.3000")
validate_ip_address("192.168.1.20") 

[output]
IP address 10.10.10.10 is valid
IP address 10.10.10.01 is valid
IP address 10.10.10.300 is not valid
IP address 10.260.10.3000 is not valid
IP address 192.168.1.20 is valid 

Как проверить, относится ли IP к типу IPv4 или IPv6 с помощью Python

Простой способ проверить, относится ли IP-адрес к типу IPv4 или IPv6, — использовать модуль Python ipaddress.

При передаче IP-адреса в строковом формате в функцию ipaddress.ip_address() создается новый объект.

Объект имеет тип ipaddress.IPv4Address или ipaddress.IPv6Address. Используйте встроенную функцию isinstance() для проверки типа созданного объекта.

import ipaddress

def get_ip_type(address):
    try:
        ip = ipaddress.ip_address(address)

        if isinstance(ip, ipaddress.IPv4Address):
            print("{} is an IPv4 address".format(address))
        elif isinstance(ip, ipaddress.IPv6Address):
            print("{} is an IPv6 address".format(address))
    except ValueError:
        print("{} is an invalid IP address".format(address))

Как и раньше, мы используем try except для вывода сообщения в случае, если IP-адрес недействителен.

Давайте назовем нашу функцию…

get_ip_type("192.168.23.34")
get_ip_type("2001:0db8:75a2:0000:0000:8a2e:0340:5625")
get_ip_type("257.168.23.34")

[output]
192.168.23.34 is an IPv4 address
2001:0db8:75a2:0000:0000:8a2e:0340:5625 is an IPv6 address
257.168.23.34 is an invalid IP address

Здорово! 😀

Как проверить, находится ли IP-адрес в заданной подсети

Модуль Python ipaddress позволяет проверить, является ли IP-адрес частью определенной подсети.

Для начала давайте получим все IP-адреса в сети 192.168.1.0/28.

Модуль ipaddress предоставляет функцию ip_network(), которая возвращает объект IPv4Network или IPv6Network в зависимости от типа IP-адреса, переданного функции.

Если преобразовать объект, возвращаемый функцией ip_network(), в список, то вы получите список всех IP-адресов (объекты IPv4Address или IPv6Address), принадлежащих подсети.

>>> list(ipaddress.ip_network("192.168.1.0/28"))
[IPv4Address('192.168.1.0'), IPv4Address('192.168.1.1'), IPv4Address('192.168.1.2'), IPv4Address('192.168.1.3'), IPv4Address('192.168.1.4'), IPv4Address('192.168.1.5'), IPv4Address('192.168.1.6'), IPv4Address('192.168.1.7'), IPv4Address('192.168.1.8'), IPv4Address('192.168.1.9'), IPv4Address('192.168.1.10'), IPv4Address('192.168.1.11'), IPv4Address('192.168.1.12'), IPv4Address('192.168.1.13'), IPv4Address('192.168.1.14'), IPv4Address('192.168.1.15')]

Теперь, зная это, мы можем создать функцию, которая возвращает True, если IP-адрес принадлежит подсети, и False в противном случае.

Начнем с создания функции, которая перебирает IP-адреса в сети 192.168.1.0/28, используя цикл for в Python:

import ipaddress

def verify_ip_subnet(ip_address, subnet_address):
    for address in ipaddress.ip_network(subnet_address):
        print(address)


verify_ip_subnet("192.168.1.8", "192.168.1.0/28")

Вывод:

192.168.1.0
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4
192.168.1.5
192.168.1.6
192.168.1.7
192.168.1.8
192.168.1.9
192.168.1.10
192.168.1.11
192.168.1.12
192.168.1.13
192.168.1.14
192.168.1.15

И теперь мы вернем True, если какой-либо из IP-адресов в подсети совпадает с IP-адресом, переданным функции в качестве первого аргумента.

def verify_ip_subnet(ip_address, subnet_address):
    for address in ipaddress.ip_network(subnet_address):
        if str(address) == ip_address:
            return True

    return False

Для проверки этой функции можно использовать операторы assert …

assert verify_ip_subnet("192.168.1.8", "192.168.1.0/28")
assert verify_ip_subnet("192.168.1.200", "192.168.1.0/28")

[output]
Traceback (most recent call last):
  File "day3_ip_belong_to_subnet.py", line 15, in <module>
    assert verify_ip_subnet("192.168.1.200", "192.168.1.0/28")
AssertionError

Утверждение для IP 192.168.1.200 не выполняется, поскольку IP не принадлежит подсети 192.168.1.0/28.

Мы не видим никаких исключений для IP-адреса 192.168.1.8, поскольку он является частью подсети, а assert не выводит никаких сообщений, если проверяемое условие истинно.

Преобразование IP-адресов в другие форматы для проверки

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

Чтобы преобразовать IP-адрес в строку, можно использовать функцию str().

Примечание: не забудьте сначала импортировать модуль ipaddress, в противном случае вы увидите исключение NameError при попытке использования модуля.

>>> str(ipaddress.IPv4Address('192.168.1.100'))
'192.168.1.100'

Чтобы преобразовать IP-адрес в целое число, можно использовать функцию int().

>>> int(ipaddress.IPv4Address('192.168.1.100'))
3232235876

Чтобы преобразовать IP-адрес из целого числа в байтовый объект, можно использовать функцию v4_int_to_packed().

>>> ipaddress.v4_int_to_packed(3232235876)
b'\xc0\xa8\x01d'

Похожая функция v6_int_to_packed() применяется к адресам IPv6.

Заключение

В этом уроке мы рассмотрели очень простой способ проверки IP-адреса с использованием библиотеки Python ipaddress.

Мы также увидели, как использовать пользовательскую функцию и регулярные выражения для проверки IP.

Наконец, мы увидели, как проверить принадлежность конкретного IP-адреса к подсети.

Какой метод вы собираетесь использовать в своей программе?

Автор

Фото аватара

Владимир Михайлов

Программист на Python с большим количеством опыта и разнообразных проектов.