Введение
В предыдущей статье мы познакомились с моделью OSI и узнали, как данные путешествуют по сети. Мы выяснили, что каждое устройство в сети имеет свой IP-адрес, а для разделения адреса на сетевую и узловую части используется маска подсети. Теперь пришло время научиться работать с этими понятиями на практике.
Для успешного решения 13 задания ЕГЭ по информатике требуется уверенно понимать работу IP-адресации: уметь определять адрес сети по адресу узла и маске, находить широковещательный адрес, вычислять количество хостов в сети. Часть заданий можно решать вручную, но гораздо эффективнее и быстрее использовать специализированный модуль Python — ipaddress.
В этой статье мы сначала разберёмся с теорией: что такое маска подсети, как работает CIDR-нотация, как вручную находить адрес сети и узла. А затем познакомимся с модулем ipaddress и его основными функциями.
Маска подсети
Мы уже знаем, что IP-адрес состоит из двух частей: адреса сети и адреса узла. Но как понять, где проходит граница между ними? Для этого используется маска подсети.
Маска подсети — это 32-битное число, которое записывается так же, как IP-адрес: четыре числа от 0 до 255, разделённые точками. Например: 255.255.255.0.
Принцип работы маски простой. В двоичном виде маска состоит из единиц и нулей. Единицы указывают на биты, которые относятся к адресу сети, а нули — на биты адреса узла.
Посмотрим на маску 255.255.255.0 в двоичном виде:
11111111.11111111.11111111.00000000
Здесь 24 единицы и 8 нулей. Это значит, что первые 24 бита IP-адреса (три октета) определяют сеть, а последние 8 бит (один октет) — конкретное устройство в этой сети.
У маски есть важное ограничение: единицы должны идти строго подряд, без пропусков. Сначала все единицы, потом все нули. Поэтому маска 255.255.0.255 недопустима — в ней нули стоят между единицами.
Из-за этого правила каждый октет маски может принимать только определённые значения:
Никакие другие значения в маске встретиться не могут. Например, число 131 (10000011 в двоичной системе) содержит ноль между единицами, поэтому не может быть частью маски.
CIDR-нотация
Писать маску полностью каждый раз неудобно. Поэтому придумали CIDR-нотацию (Classless Inter-Domain Routing) — компактный способ записи.
В CIDR-нотации после IP-адреса через косую черту указывается префикс — количество единиц в маске. Например:
- 192.168.1.0/24 означает маску с 24 единицами, то есть 255.255.255.0
- 10.0.0.0/8 означает маску 255.0.0.0 (8 единиц)
- 172.16.0.0/16 означает маску 255.255.0.0
Префикс может быть от 0 до 32. Чем больше префикс, тем меньше устройств может быть в сети.
Количество адресов в сети вычисляется по формуле: N = 232-префикс
Например, в сети с префиксом /24 будет 28 = 256 адресов. Но не все из них доступны для устройств — два адреса всегда зарезервированы: адрес сети и широковещательный адрес. Значит, для устройств остаётся 254 адреса.
В таблице ниже приведено соответствие длины префикса, маски подсети, количества адресов в сети и количества хостов в сети.
Вычисляем адреса вручную
Прежде чем переходить к Python, давайте разберёмся, как находить адрес сети, адрес узла и широковещательный адрес вручную. Это поможет понять логику работы модуля ipaddress.
Адрес сети
Допустим, у нас есть устройство с IP-адресом 192.168.1.67 и маской 255.255.255.0. Как найти адрес сети?
Для этого выполняем побитовую конъюнкцию (И) между IP-адресом и маской. Переведём оба числа в двоичный вид и применим операцию И к каждой паре битов:
IP-адрес: 11000000.10101000.00000001.01000011 (192.168.1.67)
И
Маска: 11111111.11111111.11111111.00000000 (255.255.255.0)
=
Адрес сети: 11000000.10101000.00000001.00000000 (192.168.1.0)
Правило простое: где в маске единица — оставляем бит из IP-адреса, где ноль — ставим ноль. Результат: адрес сети — 192.168.1.0.
Адрес хоста
Чтобы найти адрес хоста в сети, нужно сначала инвертировать маску (заменить единицы на нули и наоборот), а затем выполнить побитовую конъюнкцию с IP-адресом:
IP-адрес: 11000000.10101000.00000001.01000011 (192.168.1.67)
И
Инверсия маски: 00000000.00000000.00000000.11111111
=
Номер узла: 00000000.00000000.00000000.01000011 (0.0.0.67)
Адрес хоста — 0.0.0.67. Это устройство номер 67 в сети 192.168.1.0.
Широковещательный адрес
Широковещательный адрес используется для отправки сообщений всем устройствам в сети. Чтобы его найти, выполняем побитовую дизъюнкцию (ИЛИ) между адресом сети и инвертированной маской:
Адрес сети: 11000000.10101000.00000001.00000000 (192.168.1.0)
ИЛИ
Инверсия маски: 00000000.00000000.00000000.11111111(0.0.0.255)
=
Широковещательный: 11000000.10101000.00000001.11111111 (192.168.1.255)
В этой сети широковещательный адрес — 192.168.1.255.
Маска сети
Иногда в задачах требуется определить маску, зная адрес сети и адрес узла. Логика такая: сравниваем оба адреса побитово. Там, где биты совпадают с адресом сети — ставим единицу в маске, там, где появляется индивидуальная часть узла — ставим ноль.
Например, адрес сети 192.168.0.0, адрес узла 192.168.0.67. Переводим в двоичный вид:
Адрес сети: 11000000.10101000.00000000.00000000
Адрес узла: 11000000.10101000.00000000.01000011
Видим, что первые 24 бита одинаковые, а последние 8 — разные. Значит, маска: 255.255.255.0 или /24.
Но необязательно вычислять все эти значения вручную, ведь есть встроенный в Python модуль ipaddress, который содержит множество полезных функций для работы с сетями. Давайте познакомимся с этими функциями поближе.
Модуль ipaddress
Модуль содержит три основных класса для работы с IP-адресами:
- ip_address() — для работы с отдельным IP-адресом
- ip_network() — для работы с сетью целиком
- ip_interface() — для работы с конкретным устройством в сети
Разберём каждый из них подробно.
Работа с IP-адресом
Функция ip_address() создаёт объект IP-адреса. Она принимает адрес в виде строки или целого числа:
Любой IP-адрес — это просто 32-битное число. Адрес 192.168.1.3 в десятичном виде равен 3232235779. Функции int() и str() позволяют переводить адрес из одного формата в другой:
Для задач ЕГЭ часто нужно получить двоичное представление адреса. Сделать это можно через f-строки:
Запись «:032b» означает: вывести число в двоичном формате (b), дополнив нулями слева до 32 символов (032).
Работа с сетью
Функция ip_network() создаёт объект сети. Она принимает адрес сети с маской (в любом формате):
Обратите внимание: функция ожидает именно адрес сети, где все биты узловой части равны нулю. Если передать адрес узла, возникнет ошибка!
Чтобы автоматически обнулить узловую часть, используйте параметр strict=False:
Теперь можно получить всю информацию о сети через атрибуты объекта:
Метод hosts() возвращает генератор всех доступных адресов для устройств (без адреса сети и широковещательного):
Объект IP-сети поддерживает протокол итерации, следовательно, можно как перебирать все адреса в сети в цикле, так и обращаться к ним через квадратные скобки (метод __getitem__()).
Переберём все адреса в сети с узлом 192.168.1.3 и префиксом /30:
Создадим объект IP-адреса 192.168.1.5 и проверим, принадлежат ли он сети 192.168.1.0/28:
Но такие проверки лучше делать с использованием объекта IP-интерфейса.
Работа с интерфейсом
Функция ip_interface() — самая универсальная. Она создаёт объект, который объединяет информацию об адресе узла и о сети, к которой он принадлежит.
В отличие от ip_network(), функция ip_interface() не требует параметра strict=False — она изначально предназначена для работы с адресами узлов.
С помощью интерфейса удобно проверять, принадлежит ли адрес определённой сети:
Практические примеры
Давайте закрепим знания на нескольких примерах, типичных для заданий ЕГЭ.
Пример 1: Найти адрес сети
Условие: Даны IP-адрес узла 172.16.45.200, маска 255.255.240.0. Найти адрес сети.
Начнём с того, что импортируем нужную функцию из модуля ipaddress. Поскольку нам дан адрес конкретного устройства (узла) и маска, лучше всего подойдёт функция ip_interface():
Теперь создаём объект интерфейса. Для этого передаём в функцию строку, содержащую IP-адрес и маску через косую черту:
В этот момент Python автоматически вычисляет все параметры сети: адрес сети, широковещательный адрес, префикс и так далее. Нам остаётся только обратиться к нужному атрибуту.
Чтобы получить адрес сети, сначала обращаемся к атрибуту network (который содержит информацию о сети), а затем к его атрибуту network_address:
Соберём всё вместе и запустим:
Откуда взялось число 32 в третьем октете? Давайте проверим вручную. Маска 255.255.240.0 в двоичном виде выглядит так:
11111111.11111111.11110000.00000000
Третий октет маски — 240, что в двоичном виде равно 11110000. Это значит, что из третьего октета IP-адреса в адрес сети попадают только первые 4 бита.
Третий октет нашего IP-адреса — 45, в двоичном виде это 00101101. Применяем побитовую конъюнкцию с маской:
00101101 (45)
11110000 (240)
──────────
00100000 (32)
Получаем 32. Именно это значение и показал нам Python.
Пример 2: Определить количество узлов в сети
Условие: Дана сеть 10.0.0.0/12. Нужно определить, сколько устройств может быть подключено к этой сети.
Здесь нам дан адрес сети с префиксом, поэтому используем функцию ip_network():
Создаём объект сети, передавая адрес с префиксом:
Теперь можем узнать общее количество адресов в сети через атрибут num_addresses:
Но не все адреса можно использовать для устройств. Два адреса всегда зарезервированы: адрес сети (первый) и широковещательный адрес (последний). Поэтому для устройств доступно на два адреса меньше:
Полный код:
Проверим это вычисление. Префикс /12 означает, что 12 бит отведены под адрес сети, а оставшиеся 32 — 12 = 20 бит — под адреса узлов. Количество возможных комбинаций из 20 бит равно 220 = 1 048 576. Всё сходится.
Пример 3: Проверить принадлежность адреса сети
Условие: Дана сеть 192.168.100.0/26. Нужно проверить, принадлежит ли ей адрес 192.168.100.70.
Для решения нам понадобятся две функции: ip_network() для создания объекта сети и ip_address() для создания объекта проверяемого адреса:
Создаём объект сети:
Создаём объект IP-адреса, который хотим проверить:
Теперь используем оператор in, чтобы проверить, входит ли адрес в сеть. Объект сети поддерживает эту проверку, поэтому можно писать просто ip in net:
Полный код:
Почему адрес не принадлежит сети? Давайте разберёмся. Префикс /26 означает, что под адреса узлов отведено 32 — 26 = 6 бит. Это даёт 26 = 64 адреса в сети.
Сеть начинается с адреса 192.168.100.0, значит, она содержит адреса от 192.168.100.0 до 192.168.100.63 (всего 64 адреса: от 0 до 63 включительно).
Адрес 192.168.100.70 больше 63, поэтому он выходит за пределы этой сети. Скорее всего, он принадлежит следующей сети — 192.168.100.64/26.
Давайте это проверим:
Теперь адрес найден в правильной сети.
Пример 4: Найти все параметры сети по адресу узла
Условие: Известно, что компьютер имеет IP-адрес 192.168.50.139 и маску 255.255.255.192. Найти адрес сети, широковещательный адрес, диапазон доступных адресов для устройств.
Это комплексная задача, где нужно получить сразу несколько параметров. Используем ip_interface():
Создаём объект интерфейса:
Через атрибут network получаем доступ ко всем параметрам сети. Выведем их по очереди:
Чтобы найти диапазон доступных адресов, воспользуемся методом hosts(). Он возвращает генератор всех адресов, которые можно назначить устройствам (без адреса сети и широковещательного). Преобразуем генератор в список, чтобы взять первый и последний элементы:
Полный код:
Из результата видно, что наш компьютер с адресом 192.168.50.139 находится в сети, которая начинается с 192.168.50.128 и заканчивается на 192.168.50.191. Для устройств доступны адреса от .129 до .190, то есть 62 адреса.
Это лишь малая часть задач, которые могут встретиться на экзамене. В следующей статье мы подробно разберём типизацию 13 заданий ЕГЭ по информатике и научимся их решать с помощью ipaddress.