Что проверяет задание
Нужно работать с IP-адресами, маской сети, адресом сети и числом узлов. Величины задают в десятичном виде по октетам или через префикс.
Определения
IP-адрес (IPv4), 32-битовое число, которое записывают четырьмя октетами: числами от 0 до 255, разделёнными точками.
Октет, 8 бит. Четыре октета вместе дают адрес.
Маска подсети, 32-битовое число, в котором сначала идут сплошные единицы, затем сплошные нули. Единицы отмечают сетевую часть адреса, нули, узловую.
Префикс /n, другой способ задать маску: ровно n старших бит равны единице. Например, /24 означает маску 255.255.255.0.
Адрес сети, результат побитового И между адресом узла и маской. В узловой части адреса сети стоят нули.
Широковещательный адрес, адрес, у которого узловая часть заполнена единицами. Он указывает все узлы сети и обычно не выдаётся компьютеру.
Допустимый адрес узла, адрес в этой сети, который не совпадает с адресом сети и не является широковещательным.
Побитовое И, операция над битами: единица в результате только если оба бита равны единице. Именно ею выделяют сетевую часть.
Номер компьютера в сети, порядковое значение узловой части. Нужно уточнять по условию, с какого значения начинают отсчёт и входят ли служебные адреса.
Свойства маски
Маска не может иметь вид «единицы, потом нули, потом снова единицы». Если выписали двоичное представление и увидели разрыв единиц, это не маска.
Число адресов в сети равно 2^k, где k, число нулей в маске. Число узлов обычно 2^k − 2, если исключены адрес сети и широковещание. Если условие считает все адреса, вычитать не нужно.
Общий алгоритм решения
- Перевести адрес и маску в 32 бита, не теряя ведущие нули октетов.
- Найти адрес сети: побитовое И. Проверить, что он совпадает с данным, если сеть уже указана.
- Определить, сколько бит в узловой части, и какое множество адресов ей соответствует.
- Исключить служебные адреса, если условие говорит о компьютерах, а не обо всех адресах.
- Если спрашивают количество сетей, перебор допустимых префиксов согласовать с тем, что адрес сети при этой маске должен совпасть с данным.
- Ответ записывать в требуемом виде: десятичный октетный адрес, префикс, количество или порядковый номер.
Программа построчно
Задания про IP-адреса можно решать вручную, переводя адреса и маски в двоичную систему. Программа на Python делает то же самое без ошибок в переводе: в стандартном модуле ipaddress уже есть всё - адрес сети, маска, количество адресов, перебор адресов.
Разберём программу на условии:
Сеть задана IP-адресом 42.29.114.10 и маской 255.255.255.240. Сколько в этой сети IP-адресов, для которых в двоичной записи ровно 16 единиц?
from ipaddress import *
net = ip_network("42.29.114.10/255.255.255.240", strict=False)
count = 0
for ip in net:
b = bin(int(ip))[2:].zfill(32)
if b.count("1") == 16:
count += 1
print(count)
from ipaddress import * - подключаем модуль ipaddress. Звёздочка означает «взять из модуля всё»: после этого можно писать просто ip_network(...), без ipaddress. перед именем. Подробно - в разделе «Модуль ipaddress».
net = ip_network("42.29.114.10/255.255.255.240", strict=False) - создаём объект «сеть». В кавычках через косую черту - адрес и маска из условия. strict=False нужен, потому что 42.29.114.10 - адрес узла, а не самой сети. Подробно - в разделе «Адрес сети».
count = 0 - счётчик подходящих адресов.
for ip in net: - перебираем все адреса этой сети по очереди: 42.29.114.0, 42.29.114.1, …, 42.29.114.15. Подробно - в разделе «Перебор адресов сети».
b = bin(int(ip))[2:].zfill(32) - двоичная запись адреса ровно из 32 цифр. Разобрана по шагам в разделе «Двоичная запись адреса».
if b.count("1") == 16: - условие из задачи: в записи ровно 16 единиц.
count += 1 - адрес подходит, увеличиваем счётчик.
print(count) - после перебора печатаем, сколько набралось.
Модуль ipaddress
Главные инструменты модуля:
ip_network("адрес/маска", strict=False)- сеть. Маску можно записать как четыре числа (255.255.255.240) или как количество единиц (28):ip_network("42.29.114.10/28", strict=False)- то же самое;net.network_address- адрес сети;net.broadcast_address- широковещательный адрес (последний адрес сети, в узловой части все единицы);net.netmask- маска в виде четырёх чисел, например255.255.255.240;net.prefixlen- количество единиц в маске, например 28;net.num_addresses- сколько всего адресов в сети, вместе с адресом сети и широковещательным;int(ip)- адрес как одно 32-битное целое число; с ним можно считать:адрес - 1,адрес + 5.
Адреса, которые выдаёт модуль, - не строки, а особые объекты. Печатаются они как обычно (42.29.114.0), но чтобы сравнить с адресом из условия или разрезать по точкам, их переводят в строку: str(net.network_address).
Адрес сети
net = ip_network("145.92.137.88/255.255.240.0", strict=False)
print(net.network_address)
Адрес сети - результат поразрядной конъюнкции (побитового «и») адреса узла и маски: там, где в маске единицы, биты адреса сохраняются, там, где нули, - обнуляются. Модуль делает это сам: программа напечатает 145.92.128.0.
Почему нужен strict=False. По умолчанию ip_network ждёт «чистый» адрес сети, в котором узловая часть - нули. Если дать адрес узла 145.92.137.88, программа остановится с ошибкой «has host bits set». С strict=False модуль сам обнулит узловую часть, то есть найдёт адрес сети.
Байт адреса
Адрес (и маска) записывается четырьмя числами - байтами. «Третий слева байт» - третье число записи. Чтобы достать его, запись переводят в строку и режут по точкам:
octets = str(net.network_address).split(".")
print(octets[2])
str(net.network_address)- адрес сети строкой:'145.92.128.0';.split(".")- режем по точкам:['145', '92', '128', '0'];octets[2]- элемент с номером 2. Номера в Python начинаются с нуля, поэтому первый слева байт -octets[0], второй -octets[1], третий -octets[2], четвёртый -octets[3].
Так же берут байт маски: str(net.netmask).split(".").
Ответ буквами из таблицы
Если ответ нужно записать буквами из таблицы, таблицу переписывают в словарь «число → буква» и заменяют каждый байт адреса сети буквой:
table = {0: "A", 145: "B", 255: "C", 137: "D", 128: "E", 240: "F", 88: "G", 92: "H"}
answer = ""
for octet in str(net.network_address).split("."):
answer += table[int(octet)]
print(answer)
table[128]- буква, записанная под числом 128, то есть"E";- цикл перебирает байты адреса сети слева направо:
'145','92','128','0'; int(octet)- байт из строки превращаем в число, потому что ключи словаря - числа;answer += …- дописываем букву в конец ответа. Для адреса 145.92.128.0 получитсяBHEA.
Перебор масок
Если маска неизвестна, а известны адрес узла и адрес сети, перебирают все возможные маски - их всего 33: от 0 до 32 единиц.
for mask in range(33):
net = ip_network(f"98.162.75.183/{mask}", strict=False)
if str(net.network_address) == "98.162.75.128":
print(mask, net.netmask)
range(33)- числа от 0 до 32: количество единиц в маске;f"98.162.75.183/{mask}"- f-строка: на место{mask}подставляется текущее число, получается"98.162.75.183/25"и так далее;- условие оставляет только маски, при которых из узла получается данный адрес сети.
Программа печатает все подходящие маски. Дальше по вопросу задачи:
- «сколько различных значений маски» - считаем подходящие маски счётчиком;
- «наибольшее / наименьшее значение байта маски» - собираем байты в список
valuesи печатаемmax(values)илиmin(values); - «наименьшее количество адресов в сети» - самая длинная из подходящих масок (адресов тем меньше, чем больше единиц в маске).
Маска по двум адресам
Два узла в одной сети - значит, при этой маске у них одинаковый адрес сети. Перебираем маски от самой длинной к короткой и останавливаемся на первой подходящей:
for mask in range(32, 0, -1):
net1 = ip_network(f"202.3.20.24/{mask}", strict=False)
net2 = ip_network(f"202.3.27.11/{mask}", strict=False)
if net1.network_address == net2.network_address:
print(mask, net1.netmask)
break
range(32, 0, -1)- числа 32, 31, 30, …, 1: шаг-1идёт вниз;- первая подошедшая маска - самая длинная, у неё больше всего единиц и наибольшие байты;
breakостанавливает цикл: более короткие маски тоже подходят, но они меньше.
Количество адресов
В маске сначала единицы, потом нули. Нули отмечают узловую часть адреса. Если нулей k, то узловая часть может принимать 2^k значений - столько адресов в сети:
net = ip_network("42.29.114.10/255.255.255.240", strict=False)
print(net.num_addresses)
print(net.num_addresses - 2)
- маска 255.255.255.240 - это 28 единиц и 4 нуля, адресов 2^4 = 16;
- адрес сети (узловая часть из нулей) и широковещательный адрес (узловая часть из единиц) компьютерам не выдаются, поэтому адресов компьютеров 16 − 2 = 14;
- если дана только маска, адрес сети можно взять любой, например
ip_network("0.0.0.0/255.255.255.240"): количество адресов зависит только от маски; - без модуля:
2 ** (32 - 28) - 2.
Перебор адресов сети
Объект сети можно перебирать циклом for, как список: он выдаёт все адреса от адреса сети до широковещательного.
for ip in net:
print(ip)
Если в условии сказано «адреса компьютеров» (без адреса сети и широковещательного), берут адреса без первого и последнего: for ip in list(net)[1:-1]:. list(net) - список всех адресов, срез [1:-1] отбрасывает первый и последний.
Двоичная запись адреса
b = bin(int(ip))[2:].zfill(32)
По шагам для адреса 42.29.114.3:
int(ip)- адрес как одно целое число (все 32 бита подряд);bin(...)- двоичная запись числа строкой с приставкой0b:'0b101010000111010111001000000011';[2:]- срез отбрасывает приставку0b;.zfill(32)- дописывает нули слева до 32 цифр. Для адреса, у которого первый байт меньше 128,binтеряет ведущие нули, а они важны, если считают нули или смотрят на позиции битов.
Дальше с записью работают как со строкой:
b.count("1")- сколько единиц,b.count("0")- сколько нулей;b.count("1") % 2 == 0- единиц чётное количество,% 2 != 0- нечётное;b.count("1") % 5 != 0- количество единиц не кратно 5;b.endswith("101")- запись оканчивается на 101.
Наибольший адрес компьютера
net = ip_network("77.80.208.0/255.255.240.0", strict=False)
last = net.broadcast_address - 1
print(last)
Последний адрес сети - широковещательный (broadcast_address), его компьютеру не назначают. Наибольший адрес, который можно выдать компьютеру, - предыдущий, поэтому вычитаем 1. Адреса модуля поддерживают сложение и вычитание чисел: адрес - 1 - предыдущий адрес. Для этой сети широковещательный адрес 77.80.223.255, наибольший адрес компьютера - 77.80.223.254. Аналогично наименьший адрес компьютера - net.network_address + 1.
Разбиение на подсети
Сеть делят на одинаковые подсети, удлиняя маску. Каждый добавленный бит маски делит сеть пополам: при маске длиннее на d бит получается 2^d подсетей.
net = ip_network("27.244.197.160/255.255.255.224", strict=False)
for mask in range(32, net.prefixlen - 1, -1):
hosts = 2 ** (32 - mask) - 2
if hosts >= 14:
print(2 ** (mask - net.prefixlen))
break
net.prefixlen- длина маски исходной сети (здесь 27);- маски подсетей перебираем от самой длинной (32) к исходной: чем длиннее маска, тем больше подсетей, но тем меньше компьютеров в каждой;
2 ** (32 - mask) - 2- компьютеров в подсети: все адреса минус адрес подсети и широковещательный;- первая маска, при которой компьютеры помещаются, даёт наибольшее количество подсетей: 2 в степени «насколько маска длиннее исходной».
Для примера: маска 28 даёт 2^4 − 2 = 14 компьютеров - хватает; подсетей 2^(28 − 27) = 2.
