153 lines
12 KiB
Markdown
153 lines
12 KiB
Markdown
# Задача 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<p≤32)? — `0xFFFFFFFF << (32-p)`; для p=0 маска = 0 отдельным случаем (сдвиг на 32 — UB)
|
||
- base | Как получить network и broadcast из ip и mask? — `network = ip & mask`, `broadcast = network | ~mask`
|
||
- core | Формула host_count для prefix ≤ 30? — `2^(32-prefix) - 2`
|
||
- core | Почему /31 — исключение и даёт 2 узла вместо 0 по общей формуле? — RFC 3021: у канала точка-точка ровно 2 узла, broadcast не нужен, поэтому оба адреса 2-адресного блока отдаются под хосты вместо потери половины блока
|
||
- core | Что возвращает subnet_of для /32? — network=broadcast=first_host=last_host=ip, host_count=1 (host route/loopback)
|
||
- core | Формула `prefix_for_hosts(h)`? — `32 - ceil(log2(h+2))`, из условия `2^(32-prefix) >= 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`, а не прямое сравнение чисел.
|
||
|
||
## Проверь себя
|
||
|
||
<details>
|
||
<summary>1. Почему /31 не подчиняется общей формуле «минус 2 служебных адреса»?</summary>
|
||
Потому что у 2-адресного блока резервирование network и broadcast по общей формуле оставило
|
||
бы 0 узлов — бессмысленно для канала точка-точка, где всего два участника и оба заранее
|
||
известны, широковещание не нужно. RFC 3021 явно отдаёт оба адреса блока под хосты.
|
||
</details>
|
||
|
||
<details>
|
||
<summary>2. Дано `ip=10.0.1.130`, `prefix=26`. Посчитать network, broadcast, first_host,
|
||
last_host по шагам.</summary>
|
||
Хостовых бит: `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`.
|
||
</details>
|
||
|
||
<details>
|
||
<summary>3. Почему `prefix_for_hosts(63) = 25`, а не 26, хотя 62 меньше 63 только на
|
||
единицу?</summary>
|
||
`/26` физически даёт ровно 62 адреса под хосты — этого не хватает даже на один хост меньше
|
||
требуемых 63. Следующий шаг «расширения» подсети — не +1 узел, а удвоение блока: снятие
|
||
одного бита из префикса (`/25`) сразу даёт 126 узлов. Промежуточных вариантов между /26 и
|
||
/25 не существует, поэтому единственный подходящий ответ — /25.
|
||
</details>
|
||
|
||
<details>
|
||
<summary>4. Почему для `prefix=0` нельзя вычислять маску как `0xFFFFFFFF << (32-0)`?</summary>
|
||
Это сдвиг на 32 бита для 32-битного беззнакового типа — стандарт C/C++ определяет сдвиг
|
||
только для величины меньше ширины типа в битах, сдвиг на саму ширину или больше — UB
|
||
(на практике часто даёт исходное значение без сдвига вместо ожидаемого 0). Поэтому
|
||
`prefix == 0` обрабатывается отдельной веткой: `mask = 0` напрямую.
|
||
</details>
|
||
|
||
Разбор после сдачи: как считать префикс по числу узлов за O(1) (`32 - ceil(log2(h+2))`),
|
||
почему `/31` — исключение, что такое маска в бинарном виде.
|