# Задача 13 — арифметика подсетей IPv4 (C++) Адреса передаются в **host byte order** (`0x0A000001` = 10.0.0.1), маска — длиной префикса. ```c++ struct Subnet { uint32_t network; // адрес сети uint32_t broadcast; // широковещательный адрес uint32_t first_host; // первый адрес для узла uint32_t last_host; // последний адрес для узла long long host_count; // сколько адресов узлов доступно (для /0 не влезает в int) }; Subnet subnet_of(uint32_t ip, int prefix); // 0 <= prefix <= 32 bool same_subnet(uint32_t a, uint32_t b, int prefix); int prefix_for_hosts(int hosts); // самая узкая подсеть, куда влезает hosts ``` Определения, которые нужно соблюсти: - для `prefix <= 30`: `host_count = 2^(32-prefix) - 2`, `first_host = network + 1`, `last_host = broadcast - 1`; - `/31` (RFC 3021): `host_count = 2`, `first_host = network`, `last_host = broadcast`; - `/32`: `host_count = 1`, `first_host = last_host = network = broadcast = ip`; - `prefix_for_hosts(h)`: **наибольший** `prefix` из диапазона `0..30` (то есть самая узкая подсеть), при котором `host_count >= h`. `h < 1` или `h > 2^32-2` → `-1`. Исключения `/31` и `/32` в подборе не участвуют — они для линков и одиночных адресов. Эталонные примеры, которые обязаны сойтись: `/26` → 62 узла; `/24` → 254; `/30` → 2; `10.0.1.130/26` → сеть `10.0.1.128`, broadcast `10.0.1.191`, узлы `129..190`; `prefix_for_hosts(62)` → 26, `prefix_for_hosts(63)` → 25, `prefix_for_hosts(254)` → 24, `prefix_for_hosts(1)` → 30. Проверка: `python3 grade.py 13`. Критерий: все `ok`, сборка без предупреждений. **Факты для карточек** - base | Сколько узлов даёт /24? — 254 - base | Сколько узлов даёт /26? — 62 - base | Сколько узлов даёт /30? — 2 - base | Сеть, broadcast и диапазон узлов для `10.0.1.130/26`? — сеть `10.0.1.128`, broadcast `10.0.1.191`, узлы `129..190` Почему дальше: чтобы получить эти числа программно, а не подбором, нужен механизм вычисления маски, границ сети и обратной задачи — подбора префикса по числу узлов. ## Механизм: маска, границы сети, обратный подбор префикса **Маска по префиксу.** Маска — это `prefix` единиц в старших битах и `32-prefix` нулей в младших: `mask = 0xFFFFFFFFu << (32 - prefix)` для `prefix > 0`. Для `prefix == 0` формула не годится напрямую — сдвиг на 32 бита для 32-битного типа в C/C++ является неопределённым поведением (UB), поэтому этот случай обрабатывается отдельно: `mask = 0`. **Границы сети.** `network = ip & mask` — обнуляет все биты хостовой части (сохраняет только биты сети). `broadcast = network | ~mask` — выставляет все биты хостовой части в единицу (`~mask` — это как раз маска хостовой части, инвертированная сетевая). Число бит, отданных под узлы, — `32 - prefix`; всего адресов в блоке — `2^(32-prefix)`. **Разбор эталонного примера `10.0.1.130/26`.** `/26` оставляет `32-26=6` бит под узлы, блок из `2^6 = 64` адресов. Адрес `.130` попадает в блок `128..191` (границы блоков кратны 64): `network = 10.0.1.128`, `broadcast = 10.0.1.191`. Обычные узлы — `first_host = network+1 = .129`, `last_host = broadcast-1 = .190`. Из 64 адресов блока минус 2 служебных (сеть и broadcast) — 62 узла, что совпадает с `/26 → 62 узла`. **Почему `/26` даёт именно 62 узла.** `host_count = 2^(32-26) - 2 = 2^6 - 2 = 64 - 2 = 62`. Аналогично `/24`: `2^8 - 2 = 254`; `/30`: `2^2 - 2 = 2` — ровно минимум для соединения точка-точка между двумя маршрутизаторами. **`/31` — исключение (RFC 3021).** По общей формуле `/31` дал бы `2^1 - 2 = 0` узлов — бессмысленный результат для блока из 2 адресов. RFC 3021 разрешает для каналов точка-точка (ровно 2 узла на линке) отдавать под хосты оба адреса блока: широковещательный адрес такому каналу физически не нужен (получателей всего два, и оба известны заранее), а резервировать половину 2-адресного блока под network/broadcast — чистая потеря адресного пространства. Поэтому `host_count = 2`, `first_host = network`, `last_host = broadcast`. **`/32` — единичный адрес.** Блок из одного адреса: `network = broadcast = first_host = last_host = ip`, `host_count = 1`. Используется для маршрутов на конкретный узел (host route) или loopback-адресов маршрутизатора. **Обратная задача — `prefix_for_hosts(h)`.** Нужен наибольший `prefix` (самая узкая подсеть), при котором `2^(32-prefix) - 2 >= h`, то есть `2^(32-prefix) >= h+2`, то есть `32-prefix >= log2(h+2)`. Так как число хостовых бит — целое, наименьшее подходящее значение — `32-prefix = ceil(log2(h+2))`, откуда: ``` prefix_for_hosts(h) = 32 - ceil(log2(h+2)) ``` Проверка на эталонных примерах: `h=62` → `h+2=64`, `log2=6`, `prefix=26` ✓. `h=63` → `h+2=65`, `log2(65)≈6.02`, `ceil=7`, `prefix=25` ✓ (62 узла `/26` уже не хватает на 63-й хост, нужен на бит шире — `/25` с 126 узлами). `h=254` → `h+2=256`, `log2=8`, `prefix=24` ✓. `h=1` → `h+2=3`, `log2(3)≈1.58`, `ceil=2`, `prefix=30` ✓. **Факты для карточек** - base | Формула маски для префикса p (0= h+2` - deep | Почему `prefix_for_hosts(63) = 25`, а не 26, хотя 62 меньше 63 всего на 1? — /26 даёт только 62 узла, этого не хватает даже на 1 хост меньше требуемых 63; нужно расширить хостовую часть на 1 бит — /25 с 126 узлами ## Ловушки - Вычислить маску как `0xFFFFFFFF << (32 - prefix)` при `prefix == 0` без отдельной ветки → сдвиг на 32 бита для 32-битного типа — неопределённое поведение в C/C++ (компилятор может дать любое значение, не обязательно 0) → UBSan ловит это как сдвиг за пределы разрядности типа. - Применить общую формулу `host_count = 2^(32-p) - 2` к `/31` без проверки исключения → получится 0 узлов вместо 2 → тест на `/31` (RFC 3021) падает. - Использовать `int` вместо `long long` для `host_count` → для `/0` значение `2^32 - 2` не влезает в `int` (переполнение со знаком — UB) → в условии структуры явно указан `long long` именно из-за этого случая. - Перепутать host byte order с network byte order при сравнении с реальным трафиком или утилитами вроде `tcpdump` → `10.0.0.1` в host order этой задачи — это `0x0A000001`, но в сетевом порядке байты переставлены (`0x0100000A` при little-endian хосте) — конвертация через `htonl`/`ntohl`, а не прямое сравнение чисел. ## Проверь себя
1. Почему /31 не подчиняется общей формуле «минус 2 служебных адреса»? Потому что у 2-адресного блока резервирование network и broadcast по общей формуле оставило бы 0 узлов — бессмысленно для канала точка-точка, где всего два участника и оба заранее известны, широковещание не нужно. RFC 3021 явно отдаёт оба адреса блока под хосты.
2. Дано `ip=10.0.1.130`, `prefix=26`. Посчитать network, broadcast, first_host, last_host по шагам. Хостовых бит: `32-26=6`, размер блока `2^6=64`. Адрес `.130` попадает в блок, начинающийся на границе, кратной 64: `128 <= 130 < 192`, значит `network=10.0.1.128`, `broadcast=10.0.1.128+63=10.0.1.191`, `first_host=network+1=10.0.1.129`, `last_host=broadcast-1=10.0.1.190`.
3. Почему `prefix_for_hosts(63) = 25`, а не 26, хотя 62 меньше 63 только на единицу? `/26` физически даёт ровно 62 адреса под хосты — этого не хватает даже на один хост меньше требуемых 63. Следующий шаг «расширения» подсети — не +1 узел, а удвоение блока: снятие одного бита из префикса (`/25`) сразу даёт 126 узлов. Промежуточных вариантов между /26 и /25 не существует, поэтому единственный подходящий ответ — /25.
4. Почему для `prefix=0` нельзя вычислять маску как `0xFFFFFFFF << (32-0)`? Это сдвиг на 32 бита для 32-битного беззнакового типа — стандарт C/C++ определяет сдвиг только для величины меньше ширины типа в битах, сдвиг на саму ширину или больше — UB (на практике часто даёт исходное значение без сдвига вместо ожидаемого 0). Поэтому `prefix == 0` обрабатывается отдельной веткой: `mask = 0` напрямую.
Разбор после сдачи: как считать префикс по числу узлов за O(1) (`32 - ceil(log2(h+2))`), почему `/31` — исключение, что такое маска в бинарном виде.