IP без ошибок. Изучаем принципы работы с IP-адресами

IP без ошибок. Изучаем принципы работы с IP-адресами

Лю­бое при­ложе­ние, которое хоть как‑то работа­ет с сетью, дол­жно валиди­ровать пра­виль­ность IP-адре­сов. Это слож­нее, чем может показать­ся. Здесь лег­ко впасть в край­нос­ти: при излишне стро­гой валида­ции поль­зователь не смо­жет ввес­ти вер­ные дан­ные, при недос­таточ­ной — ока­жет­ся наеди­не с низ­коуров­невыми сооб­щени­ями об ошиб­ках (если они вооб­ще переда­ются). В этой статье мы раз­берем ряд слож­ностей, воз­ника­ющих при валида­ции адре­сов, а потом пос­мотрим на готовые биб­лиоте­ки, которые с этим помога­ют.

ВАЛИДАЦИЯ АДРЕСОВ

Ошиб­ки в адре­сах могут появить­ся тре­мя спо­соба­ми:

  • опе­чат­ки;
  • не­допо­нима­ние;
  • на­мерен­ные попыт­ки сло­мать при­ложе­ние.

От попыток сло­мать при­ложе­ние одна валида­ция адре­сов не поможет. Она может зат­руднить такие попыт­ки, но не заменит пол­ноцен­ную про­вер­ку авто­риза­ции и обра­бот­ку оши­бок на всех эта­пах работы прог­раммы, так что улуч­шение безопас­ности нуж­но рас­смат­ривать ско­рее как полез­ный побоч­ный эффект. Основная цель — упростить жизнь поль­зовате­лям, которые слу­чай­но вве­ли невер­ный адрес или неп­равиль­но поняли, что от них тре­бует­ся.

Про­вер­ки мож­но условно раз­делить на про­вер­ки по фор­ме и по сущес­тву. Цель фор­маль­ной про­вер­ки — убе­дить­ся, что вве­ден­ная поль­зовате­лем стро­ка вооб­ще может быть допус­тимым адре­сом. Мно­гие прог­раммы огра­ничи­вают­ся имен­но этим. Мы же пой­дем даль­ше и пос­мотрим, как мож­но про­верять, что адрес не толь­ко пра­виль­ный, но и под­ходящий для кон­крет­ной цели, но об этом поз­же.

 

Проверки по форме

Про­вер­ка пра­виль­нос­ти фор­мата толь­ко на вид может показать­ся задачей для нес­ложно­го регуляр­ного выраже­ния — на деле все не так прос­то.

В IPv4 слож­ности начина­ются со стан­дарта на этот фор­мат — такого стан­дарта не сущес­тву­ет. Фор­мат dot-decimal ( 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 .

INFO

Мо­жет ли адрес хос­та закан­чивать­ся на ноль? Конеч­но, может — в любой сети раз­мером боль­ше /23 най­дет­ся хотя бы один такой. Нап­ример, 192.168.0.0/23 содер­жит адре­са хос­тов 192.168.0.1–192.168.1.254 , вклю­чая 192.168.1.0 .

Ес­ли огра­ничить­ся под­дер­жкой толь­ко пол­ного dot-decimal из четырех групп, без воз­можнос­ти опус­кать нулевые раз­ряды, то выраже­ние (\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, для валида­ции адре­сов нужен пол­ноцен­ный пар­сер. Писать его самим нет смыс­ла, пос­коль­ку сущес­тву­ют готовые биб­лиоте­ки, которые пре­дос­тавля­ют и дру­гие полез­ные фун­кции.

 

Проверки по существу

Ес­ли уж мы взя­лись под­клю­чать биб­лиоте­ку и пар­сить адре­са, давай пос­мотрим, какие допол­нитель­ные про­вер­ки мы можем про­вес­ти, что­бы отсе­ять оши­боч­ные зна­чения и сде­лать сооб­щения об ошиб­ках более информа­тив­ными.

Нуж­ные про­вер­ки будут зависеть от того, как будет исполь­зовать­ся адрес. Нап­ример, пусть поль­зователь хотел ввес­ти в поле адре­са сер­вера DNS зна­чение 124.1.2.3 , но опе­чат­ка прев­ратила его в 224.1.2.3 . Про­вер­ка фор­мата эту опе­чат­ку не пой­мает — фор­мат пра­виль­ный. Одна­ко этот адрес никак не может быть адре­сом сер­вера DNS, пос­коль­ку сеть 224.0.0.0/4 зарезер­вирова­на для мно­гоад­ресной мар­шру­тиза­ции, которую DNS не исполь­зует никог­да.

Ес­ли ты хочешь отсе­ять все адре­са, которые не могут быть адре­сами хос­тов в пуб­личном интерне­те, поч­ти пол­ный спи­сок зарезер­вирован­ных сетей мож­но най­ти в RFC 5735 (Special use IPv4 addresses). «Поч­ти пол­ный» он потому, что не вклю­чает сеть 100.64.0.0/10 , выделен­ную для CG-NAT (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

Да­же для чис­того С мож­но най­ти биб­лиоте­ку с удоб­ным интерфей­сом, такую как libcidr Мэттью Фул­лера. В Debian ее мож­но пос­тавить из репози­тори­ев. Для при­мера напишем про­вер­ку при­над­лежнос­ти адре­са к сети multicast и положим ее в файл 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");

}

}

$ sudo aptitude install libcidr-dev

$ gcc -o is_multicast -lcidr ./is_multicast.c

$ ./is_multicast 8.8.8.8

The argument is not an IPv4 multicast address

$ ./is_multicast 239.1.2.3

The argument is an IPv4 multicast address

ЗАКЛЮЧЕНИЕ

Ва­лида­ция адре­сов и выдача информа­тив­ных сооб­щений об оши­боч­ных нас­трой­ках вро­де бы нез­начитель­ная часть интерфей­са, но вни­мание к деталям — приз­нак про­фес­сиона­лиз­ма, тем более что готовые биб­лиоте­ки сущес­твен­но упро­щают эту задачу.

Источник

0

Автор публикации

не в сети 2 часа

Андрей Маргулис

334
С организацией DDoS атак завязал.
Выкладываю новости технологий и интересные статьи с темной стороны интернета.
32 года
День рождения: 14 Мая 1991
Комментарии: 117Публикации: 3001Регистрация: 12-12-2015
Понравилась статья? Поделиться с друзьями:
РЭНБИ - Europe
Авторизация
*
*
Регистрация
*
*
*
Генерация пароля