№ 13 кодом. Библиотека ipaddress

Автор статьи: Андрей Роженцов

В предыдущей статье мы научились решать № 13 аналитическим способом, а теперь рассмотрим решение с помощью программирования. Для работы с IP-адресами в Python нам понадобится модуль ipaddress. С его помощью мы сможем создавать сети и перебирать в них узлы. Это избавит нас от необходимости ручного преобразования в двоичный вид и выполнения битовых операций.

Импортируем модуль

from ipaddress import *

Сеть можно задать с помощью функции ip_network(address, strict=True)
Рассмотрим параметры функции:

  • address — строка или число, представляющие сеть\
    Мы будем рассматривать только строковое представление сети.
ip_network('192.168.1.0/255.255.255.0')

Сначала указывается адрес сети, а после символа / — маска сети.

ip_network('192.168.1.0/24')

Это тот же пример, но маска задана как число, равное количеству единиц в её двоичной записи. IP-адрес так задавать нельзя, т. к. в нём расположение единиц может быть любым, а в маске они идут в самом начале.

  • strict проверяет, что указанный IP-адрес — это адрес сети, т. е. на месте номера компьютера в нём стоят все нули
  • strict=True по умолчанию, в этом случае IP-адрес должен быть адресом сети. Если это не так, то вызывается исключение\
    Пример:
network = ip_network('192.168.1.0/255.255.255.0', True)
print(network)

Вывод:

192.168.1.0/24

Всё сработает, т. к. 192.168.1.0 — это адрес сети, в номере компьютера стоят все 0.

img

Пример:

network = ip_network('192.168.1.15/255.255.255.0',True)
print(network)

Ошибка:

ValueError: 192.168.1.15/24 has host bits set

При переводе в двоичную систему счисления заметим, что IP-адрес 192.168.1.15 — это не адрес сети.

img

Но! Если использовать strict=False, то сеть можно задать с помощью любого IP-адреса, принадлежащего этой сети. Функция сама рассчитает её адрес.

Рассмотрим тот же пример, но второй параметр передадим False.

network = ip_network('192.168.1.15/255.255.255.0',False)
print(network)

Вывод:

192.168.1.0/24

Был рассчитан адрес сети: 192.168.1.0. Чтобы вывести только его, без количества единиц в маске, можно воспользоваться свойством.

network_address

Пример:

network = ip_network('192.168.1.15/255.255.255.0',False)
print(network.network_address)

Вывод:

192.168.1.0

На самом деле, в качестве второго параметра всегда можно использовать strict=False: если был передан не адрес сети, то функция его рассчитает, а если он сам, то ничего не изменится.

Перебор IP-адресов в одной сети

После создания сети IP-адреса в ней можно перебрать с помощью цикла for.

network = ip_network('192.168.1.0/255.255.255.252',False)# создаём сеть
for ip in network:# перебираем все IP-адреса в ней
    print(ip)

Вывод:

192.168.1.0
192.168.1.1
192.168.1.2
192.168.1.3

Сеть одна, меняются только номера компьютеров.

В некоторых задачах нам предстоит работать с двоичным представлением IP-адресов, и в этом могут помочь f-строки.

Пример:

a = 100
print('Я хочу набрать a баллов')

Вывод:

Я хочу набрать a баллов

Так как всё, что передано в функцию print, считается строкой, будет выведена буква а, а не значение переменной. Если перед строкой использовать букву f, а имя переменной взять в фигурные скобки, то значение в строке «подставиться» будет равно 100.

Пример:

a = 100
print(f'Я хочу набрать {a} баллов')

Вывод:

Я хочу набрать 100 баллов

Также можно вывести значение переменной, но в двоичном виде:

a = 100
print(f'Я хочу набрать {a:b} баллов')

Вывод:

Я хочу набрать 1100100 баллов

Рассмотрим пример вывода IP-адресов в двоичной системе.

network = ip_network('192.168.1.0/255.255.255.252',False)
for ip in network:
    print(f'{ip:b}')

Вывод:

11000000101010000000000100000000
11000000101010000000000100000001
11000000101010000000000100000010
11000000101010000000000100000011

Все 4 байта записаны друг за другом без разделителей, поэтому, если нужно будет обратиться к одному из них (или нескольким), стоит воспользоваться срезами строк. Всего IP-адрес состоит из 32 символов, по 8 на каждое число. Напомним, что срез — это [start:stop]:

  • start — с какого символа включительно начинаем обрезать строку
  • stop — по какой символ не включительно

Поэтому если мы хотим взять с нулевого по седьмой, то пишем [0:8].

Пример:

for ip in network:
    ip_2 = f'{ip:b}' # Переводим в двоичную систему счисления
    a = ip_2[:8] # Первый байт с 0 символа по 8 не включительно
    b = ip_2[8:16] # Второй байт с 8 символа по 16 не включительно
    c = ip_2[16:24] # Третий байт с 16 символа по 24 не включительно
    d = ip_2[24:] # Четвёртый байт с 24 символа и до конца строки
    print(f'Первый байт {a},второй байт {b}, третий байт {c}, четвёртый байт {d}')

Вывод:

Первый байт 11000000,второй байт 10101000, третий байт 00000001, четвёртый байт 00000000
Первый байт 11000000,второй байт 10101000, третий байт 00000001, четвёртый байт 00000001
Первый байт 11000000,второй байт 10101000, третий байт 00000001, четвёртый байт 00000010
Первый байт 11000000,второй байт 10101000, третий байт 00000001, четвёртый байт 00000011

Задача 1: перебор IP-адресов

Ссылка на задачу

img

from ipaddress import *
count = 0 # счётчик для подходящих IP-адресов
network = ip_network('235.86.56.0/255.255.248.0',0) # создаём сеть
for ip in network: # перебираем IP-адреса в этой сети
    if f'{ip:b}'[-2:] == '11': # переводим IP-адрес в двоичную систему счисления и проверяем две последние цифры
        count+=1 #увеличиваем счётчик
print(count)

Вывод:

512

Создаём сеть с помощью функции ip_network. Начинаем перебор всех узлов в ней, переводим их в двоичную систему счисления и берём срез двух последних символов. Напомним, что [start:stop], −2 — это номер символа, с которого мы начинаем обрезать строку. stop не указан, значит, следуем до конца строки. В строках нумерация символов начинается как с начала, так и с конца.

img

Ответ: 512

Задача 2: поиск маски

Ссылка на задачу

img

Дан узел IP-адреса 111.91.200.28 и адрес этой сети 111.91.192.0. Необходимо найти подходящую маску, в которой наименьшее количество единиц, оно же наибольшее.

Будем перебирать маски и, используя известный узел, создавать разные сети. Найдём ту, которая совпадает с адресом сети из условия, и тогда выведем количество единиц в маске.

from ipaddress import *
for count_1_mask in range(33): # Перебираем всё возможное количество единиц в маске
    network = ip_network(f'111.91.200.28/{count_1_mask}',0) # По IP-адресу и количеству единиц в маске задаём сеть
    if str(network.network_address) == '111.91.192.0': # Проверяем, что адреса сетей сходятся
        print(count_1_mask) # Выводим количество ЕДИНИЦ!

Вывод:

18
19
20

Наибольшее количество единиц — 20, а значит, в этой маске 32 − 20 = 12 нулей.

Ответ: 12

Задача 3: поиск количества IP-адресов

Ссылка на задачу

img

from ipaddress import *
count = 0 # счётчик для подходящих IP-адресов
network = ip_network('172.16.160.0/255.255.240.0',0) # создаём сеть
for ip in network: # перебираем IP-адреса в этой сети
    if f'{ip:b}'.count('1') % 3  != 0: # переводим IP-адрес в двоичную систему счисления и считаем количество единиц
        count+=1 #увеличиваем счётчик
print(count)

Поскольку мы переводим IP-адрес в строку, мы можем пользоваться и всеми методами для строк, а именно count(). С его помощью находим количество единиц и проверяем, не кратно ли оно 3.

Ответ: 2731

Задача 4: поиск маски

Ссылка на задачу

img

from ipaddress import *
for count_1_in_mask in range(33):
    network_1 = ip_network(f'101.96.170.244/{count_1_in_mask}', 0)  # создаём первую сеть
    network_2 = ip_network(f'101.96.126.212/{count_1_in_mask}', 0)  # создаём вторую сеть
    if network_1 != network_2: # проверяем, что сети разные
        print(count_1_in_mask)
        break

Вывод:

17

Так как цикл for идёт по возрастанию, первое найденное значение переменной count_1_in_mask нам подойдёт. Дальше выйдем из цикла с помощью оператора break.

Количество нулей: 32 − 17 = 15

Ответ: 15

Задача 5: поиск номера компьютера

Ссылка на задачу

img

Для решения задачи рассмотрим метод hosts(), который позволяет перебирать все хосты (IP-адреса) внутри заданной сети, исключая сетевой и широковещательный адреса. Уделим внимание двум циклам:

from ipaddress import *
network = ip_network(f'73.148.145.65/255.224.0.0', 0)  # создаём сеть
count_1 = 0 # счётчик 1
count_2 = 0 # счётчик 2
for ip in network:
   count_1+=1
for ip in network.hosts():
   count_2+=1
print(count_1,count_2)

Вывод:

2097152 2097150

Количество итераций во втором цикле на 2 меньше, т. к. не рассматриваются сетевой и широковещательный адреса. К тому же, как и при обычном переборе, IP-адреса идут в порядке увеличения номера компьютера. Для поиска наибольшего адреса достаточно взять последний, который появится во втором цикле.

Создадим список хостов и возьмём оттуда элемент с индексом −1.

from ipaddress import *
network = ip_network(f'73.148.145.65/255.224.0.0', 0)  # создаём сеть
hosts = list(network.hosts()) # получаем список IP-адресов в этой сети
max_host = hosts[-1] # находим последний из списка
print(max_host)

Вывод:

73.159.255.254

Ответ: 73159255254

Итог

Для решения задания № 13 с помощью программирования стоит не только изучить новый модуль ipaddress, но и комплексно вспомнить темы по работе со строками.

  • Функция ip_network («ip/mask»,0) создаёт сеть по IP-адресу, который указывается как 4 числа, разделённых точками и маской. Адрес задаётся аналогично или числом, показывающим количество единиц в маске. В качестве второго аргумента передаём 0
  • Метод .network_address позволяет узнать адрес сети
  • Метод .hosts() позволяет перебрать все IP-адреса в сети, исключая сетевой и широковещательный
  • С помощью f-строк мы можем подставлять значение переменной в строку и переводить её в двоичную систему счисления. После этого можем применять все методы и функции для строк
  • Для отделения отдельных байтов от всего IP-адреса в двоичной системе счисления пользуемся срезами

Для закрепления материала предлагаем решить подборку задач.

Источник: Яндекс Учебник — № 13 кодом. Библиотека ipaddress. Каталог разборов: education.yandex.ru.

Назад к статьям Поделиться