IP-адрес (от англ. Internet Protocol) — уникальный числовой идентификатор устройства в компьютерной сети, работающий по протоколу TCP / IP.
В Интернете требуются глобально уникальные адреса; в случае работы в локальной сети уникальность адреса внутри сети обязательна. В версии IPv4 IP-адрес составляет 4 байта, а в версии IPv6 — 16 байтов.Каждое приложение, которое так или иначе работает с сетью, должно проверять правильность IP-адресов. Это сложнее, чем кажется. Здесь легко впасть в крайности: при чрезмерно строгой проверке пользователь не сможет ввести правильные данные, при недостаточной проверке — только с низкоуровневыми сообщениями об ошибках (если они вообще будут переданы). В этой статье мы разберем ряд трудностей, возникающих при валидации адресов, затем рассмотрим готовые библиотеки, помогающие в этом.
Валидация адресов
Ошибки в адресах могут появиться тремя способами:
- опечатки;
- недопонимание;
- намеренные попытки сломать приложение.
Сама по себе проверка адреса не поможет против попыток взлома приложения. Это может затруднить такие попытки, но не заменяет полную проверку авторизации и обработку ошибок на всех этапах программы, поэтому повышение безопасности следует рассматривать скорее как полезный побочный эффект. Основная цель — облегчить жизнь пользователям, которые случайно ввели неверный адрес или неправильно поняли, что от них требуется.
Проверки условно можно разделить на проверки по форме и содержанию. Цель формальной проверки — убедиться, что строка, введенная пользователем, может быть действительным адресом. Многие программы этим ограничиваются. Мы пойдем дальше и посмотрим, как проверить, что адрес не только правильный, но и подходит для конкретной цели, но об этом позже.
Проверки по форме
Проверка правильности формата может показаться задачей для простого регулярного выражения только внешне — на самом деле все не так просто.
В IPv4 сложность начинается со стандарта для этого формата — такого стандарта не существует. Точечно-десятичный формат (0.0.0.0–255.255.255.255) является обычным, но не стандартным. Стандарт IPv4 вообще не содержит упоминания о формате записи адресов. Ни в одном другом RFC ничего не говорится о формате адресов IPv4, поэтому принятый формат — не более чем соглашение.
И это даже не единственная сделка. Функция inet_aton () позволяет не записывать нулевые биты в конце адреса, например 192.0.2 = 192.0.2.0. Кроме того, она позволяет вводить адрес с одним целым числом 511 = 0.0.1.255.
Может ли адрес хоста заканчиваться нулем? Конечно может — в любой сети больше / 23 будет хотя бы один такой. Например, 192.168.0.0/23 содержит адреса хостов 192.168.0.1–192.168.1.254, включая 192.168.1.0.
Если мы ограничимся обработкой только полной десятичной точки из четырех групп, без возможности пропуска нулевых цифр, тогда выражение (\ d +) \. (\ D +) \. (\ D +) \. (\ D +) может обнаруживать большое количество опечаток. Если вы поставили цель, вы можете написать выражение для любого действительного адреса, хотя это будет довольно сложно. Лучше воспользоваться тем, что его легко разделить на группы и четко проверить, находится ли каждая из них в диапазоне 0-255:
def check_ipv4(s):
groups = s.split('.')
if len(groups) != 4:
for g in groups:
num = int(g)
if (num > 255) or (num < 0):
raise ValueError("Invalid octet value")
С IPv6 все одновременно и проще, и сложнее. Проще, потому что авторы IPv6 приняли во внимание опыт IPv4 и добавили формат нотации адресов в RFC 4291. Можно смело сказать, что альтернативные форматы противоречат стандарту и будут проигнорированы. С другой стороны, сами форматы более сложные. Основная трудность заключается в сокращении: группы нулевых битов можно заменить символом ::, например Б. 2001: db8 :: 1 вместо 2001: db8: 0: 0: 0: 0: 0: 1. Для пользователя это, конечно, полезно, но для разработчика все как раз наоборот: невозможно. Чтобы получить адрес через разделение двоеточий на группы, требуется гораздо более сложная логика. Кроме того, стандарт запрещает использование :: более одного раза в адресе, что еще больше усложняет задачу.
Так что, если приложение поддерживает IPv6, для проверки адресов необходим полноценный анализатор. Самостоятельно писать не имеет смысла, так как есть несколько готовых библиотек, которые предоставляют другие полезные функции.
Проверки по существу
Если мы уже начали подключать библиотеку и анализировать адреса, давайте посмотрим, какие дополнительные проверки мы можем выполнить, чтобы отфильтровать неверные значения и сделать сообщения об ошибках более информативными.
Необходимые проверки зависят от того, как используется адрес. Например, предположим, что пользователь попытался ввести 124.1.2.3 в поле адреса DNS-сервера, но опечатка сделала его 224.1.2.3. Проверка формата не распознает эту опечатку — формат правильный. Однако этот адрес никоим образом не может быть адресом DNS-сервера, поскольку сеть 224.0.0.0/4 зарезервирована для многоадресной маршрутизации, которую DNS никогда не использует.
Если вы хотите отфильтровать все адреса, которые не могут быть хостами в общедоступном Интернете, см. RFC 5735 (Специальное использование адресов IPv4) для почти полного списка зарезервированных сетей. Он «почти завершен», потому что не включает сеть CG-NAT 100.64.0.0/10 (RFC 6598). Полный список всех зарезервированных диапазонов IPv4 и IPv6 можно найти в RFC 6890, но он не так удобно организован.
В этом случае нужно обратить внимание на маски подсети. Некоторые люди предполагают, что сеть для частного использования — это 172.16.0.0/16 (172.16.0.0–172.16.255.255). Чтение RFC5735 легко развеет этот миф: на самом деле он значительно больше, 172.16.0.0/12 (172.16.0.1–172.31.255.254). Реальным примером этой ошибки в GoatCounter является скрипт сбора статистики, который неправильно подсчитывал посещения из локальной сети.
Следует иметь в виду, что сети, «зарезервированные для использования в будущем», больше не могут быть зарезервированы. Сети RFC 5735 зарезервированы навсегда и в этом смысле безопасны. Но авторы некогда популярной среди геймеров виртуальной сети Hamachi считали, что сеть 5.0.0.0/8 может быть использована для их нужд, поскольку она была зарезервирована для будущего использования — до тех пор, пока не наступило будущее и IANA не передала эту сеть RIPE.
Библиотеки
netaddr
В стандартной библиотеке Python 3 уже есть модуль ipaddress, но если есть возможность предоставить стороннюю библиотеку, netaddr может значительно облегчить жизнь. Например, в нем есть встроенные функции для проверки принадлежности адреса зарезервированному диапазону.
>>> import netaddr
>>> def is_public_ip(s):
... ip = netaddr.IPAddress(s)
... return (ip.is_unicast() and not ip.is_private() and not ip.is_reserved())
...
>>> is_public_ip('192.0.2.1') # Reserved for documentation
False
>>> is_public_ip('172.16.1.2') # Reserved for private networks
False
>>> is_public_ip('224.0.0.5') # Multicast
False
>>> is_public_ip('8.8.8.8')
True
Даже если бы этих функций не было, мы могли бы легко реализовать их сами. Библиотека очень умно использует магические методы, чтобы сделать интерфейс таким же простым в использовании, как и встроенные объекты Python. Например, проверить, принадлежит ли адрес сети или диапазону, можно с помощью оператора in, поэтому работать с ними не сложнее, чем со списками или словарями.
def is_public_ip(s):
loopback_net = netaddr.IPNetwork('127.0.0.0/8')
multicast_net = netaddr.IPNetwork('224.0.0.0/4')
...
ip = netaddr.IPAddress(s)
if ip in multicast_net:
raise ValueError("Multicast address found")
elif ip in loopback_net:
raise ValueError("Loopback address found")
...
libcidr
Даже для чистого C можно найти удобную библиотеку, такую как libcidr Мэтью Фуллера. В Debian его можно установить из репозиториев. В качестве примера напишем проверку адреса, принадлежащего многоадресной сети, и поместим ее в файл is_multicast.c.
#include <stdio.h>
#include <libcidr.h>
void main(int argc, char** argv) {
const char* ipv4_multicast_net = "224.0.0.0/4";
CIDR* ip = cidr_from_str(argv[1]);
CIDR* multicast_net = cidr_from_str(ipv4_multicast_net);
if( cidr_contains(multicast_net, ip) == 0 ) {
printf("The argument is an IPv4 multicast address\n");
} else {
printf("The argument is not an IPv4 multicast address\n");
}
}
$
$
$ ./
The argument is not an IPv4 multicast address
$ ./
The argument is an IPv4 multicast address
Заключение
Проверка адресов и выдача информационных сообщений о неправильных настройках кажется незначительной частью интерфейса, но внимание к деталям — признак профессионализма, тем более что готовые библиотеки значительно облегчают эту задачу.