Files
eduplan-cpp-eltex/lessons/D4_net.md
T

484 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# D4, часть 2. L2/L3: Ethernet, IP, подсети (урок)
Это не проверка, а урок: сначала разбираем механизм, потом решаешь `tasks/04_ipv4` и
`tasks/13_subnet`. Тема дня — то, что физически лежит в байтах пакета от момента, когда
приложение вызвало `send`, до момента, когда кадр ушёл в провод.
## 1. Модель OSI и модель TCP/IP
OSI — эталонная модель из **семи** независимых уровней: физический, канальный, сетевой,
транспортный, сеансовый, представления, прикладной. Каждый уровень решает свою задачу и
разговаривает только с соседними уровнями через фиксированный интерфейс, не зная деталей их
реализации — физическую среду можно сменить с меди на оптику, ничего не трогая в IP и TCP,
потому что канальный уровень скрывает эту деталь от сетевого. На практике верхние три уровня
(сеансовый, представления, прикладной) отдельно не реализуют — приложение само решает вопросы
сессии и кодирования данных.
**TCP/IP** — практическая **четырёхуровневая** модель, которой реально пользуется стек Linux:
канальный, интернет, транспортный, прикладной. Соответствие: канальный уровень TCP/IP = L1+L2
OSI (доставка кадра внутри сегмента), интернет-уровень = L3 OSI (IP, маршрутизация между
сетями), транспортный = L4 OSI (TCP/UDP, порты), прикладной уровень TCP/IP поглощает сразу
L5–L7 OSI. Сокет создаётся на транспортном уровне — отдельного программного «уровня сессии»
в реальном стеке не существует.
**Факты для карточек**
- base | Сколько уровней в модели OSI? — 7
- base | Сколько уровней в модели TCP/IP? — 4
- core | Какие три уровня OSI объединяет прикладной уровень TCP/IP? — сеансовый, представления, прикладной
- core | На каком уровне создаётся сокет? — транспортном (L4 OSI / транспортный TCP/IP)
Почему дальше: раз данные проходят через уровни сверху вниз при отправке, нужно понять, что
физически происходит с байтами на каждом уровне — это инкапсуляция.
## 2. Инкапсуляция: во что заворачиваются данные
Каждый нижний уровень оборачивает данные верхнего уровня в свой заголовок (а канальный —
ещё и в трейлер) при передаче:
```
данные приложения
→ сегмент TCP (заголовок 20 байт)
→ пакет IP (заголовок 20 байт)
→ кадр Ethernet (заголовок 14 байт + трейлер FCS 4 байта)
```
На приёмной стороне процесс идёт в обратном порядке: каждый уровень снимает свой заголовок и
передаёт содержимое выше, ориентируясь на поле типа протокола (EtherType в Ethernet-кадре
говорит, что внутри IP; поле `protocol` в IP-заголовке говорит, что внутри TCP/UDP/ICMP).
Так устроено потому, что каждый уровень должен работать независимо от содержимого —
коммутатору не нужно знать про TCP, чтобы передать кадр дальше, ему хватает MAC-адреса в
заголовке L2. В дампе `tcpdump` эта вложенность видна буквально как последовательность байт:
Ethernet, затем IP, затем TCP, затем данные.
**Факты для карточек**
- base | Размер заголовка TCP-сегмента (без опций)? — 20 байт
- base | Размер заголовка IP-пакета (без опций)? — 20 байт
- base | Размер заголовка Ethernet-кадра и трейлера FCS? — 14 байт заголовок + 4 байта FCS
- core | По какому полю верхний уровень при приёме узнаёт, что лежит внутри нижнего? — по полю типа протокола (EtherType в Ethernet, `protocol` в IP)
Почему дальше: раз данные заворачиваются в Ethernet-кадр последними перед проводом, логично
разобрать этот заголовок по байтам первым.
## 3. Ethernet и MAC-адрес
Ethernet-заголовок — **14 байт**: 6 байт MAC-адрес получателя, 6 байт MAC-адрес отправителя,
2 байта EtherType (тип содержимого кадра — например, IPv4 или ARP). После полезной нагрузки
кадр завершается 4-байтовым трейлером **FCS** (frame check sequence) — контрольной суммой для
проверки целостности при приёме; трейлер частью заголовка не считается. Фиксированная и
компактная структура нужна для того, чтобы коммутатор мог разобрать заголовок на аппаратной
скорости без анализа содержимого выше — все поля имеют строго фиксированную длину и позицию.
**MAC-адрес** — 48-битный (6-байтный) физический адрес интерфейса, действующий только внутри
одного сегмента (широковещательного домена). Первые **3 байта (OUI)** назначаются
производителю оборудования организацией IEEE, оставшиеся 3 байта производитель присваивает
конкретному интерфейсу. Когда пакет покидает сегмент через маршрутизатор, MAC-адрес
получателя в заголовке меняется на MAC следующего узла на пути, а IP-адрес остаётся прежним —
L2 отвечает за доставку «из рук в руки» внутри сегмента, L3 — за доставку через множество
сегментов.
**Факты для карточек**
- base | Длина Ethernet-заголовка? — 14 байт
- base | Длина трейлера FCS? — 4 байта
- base | Длина MAC-адреса в битах и байтах? — 48 бит, 6 байт
- core | Из скольки байт состоит OUI в MAC-адресе и кто его назначает? — 3 байта, назначает IEEE производителю
- core | Что меняется в заголовках при переходе пакета через маршрутизатор — MAC или IP получателя? — MAC-адрес получателя, IP остаётся прежним
Почему дальше: чтобы отправить кадр внутри сегмента, нужен MAC получателя, а известен обычно
только его IP — протокол, который их связывает, это ARP.
## 4. ARP: разрешение IP в MAC
ARP (Address Resolution Protocol) по известному IP-адресу узла в локальном сегменте находит
его MAC-адрес. Механизм: отправитель рассылает **широковещательный (broadcast)** кадр «кто
владеет IP X, сообщите свой MAC»; получают его все узлы сегмента, но отвечает только владелец
адреса — уже адресным **unicast**-пакетом со своим MAC. Полученную пару IP-MAC отправитель
кладёт в локальный **ARP-кэш (таблицу)**, чтобы не повторять broadcast на каждый пакет —
именно поэтому первый пакет к новому соседу в сети чуть медленнее последующих, он ждёт
ARP-ответа.
**Gratuitous ARP** — ARP-запрос или ответ, который узел рассылает не в ответ на чужой запрос,
а сам, без повода, про собственный IP-адрес (запрашивает MAC для своего же IP). Назначение —
объявить остальным узлам сегмента свою пару IP-MAC заранее (обновить их кэши до первого
реального обмена) или обнаружить конфликт IP-адресов: если кто-то в сегменте уже отвечает за
тот же IP, это сразу видно. Используется при старте интерфейса и при переключении на резервный
узел (failover), чтобы соседи сразу обновили устаревшую запись в ARP-кэше на MAC нового узла.
Если ARP не проходит (узел выключен, неверная подсеть, фильтрация), это видно в `tcpdump` как
повторяющиеся «who has X» без ответа, а приложение получит таймаут соединения без объяснения
причины.
**Факты для карточек**
- base | Как рассылается ARP-запрос — broadcast или unicast? — broadcast
- base | Как отправляется ARP-ответ? — unicast, от владельца адреса
- core | Зачем нужен ARP-кэш? — не повторять broadcast-запрос для каждого исходящего пакета к уже известному узлу
- core | Что такое gratuitous ARP и для чего он нужен? — незапрошенный ARP про собственный IP; обновление чужих кэшей заранее и обнаружение конфликта адресов
Почему дальше: ARP работает внутри одного L2-сегмента. Если в сети настроены виртуальные
сегменты поверх одной физической — VLAN, — это меняет сам Ethernet-заголовок.
## 5. VLAN 802.1Q
VLAN (Virtual LAN) по стандарту **802.1Q** делит один физический сегмент на несколько
логически изолированных широковещательных доменов — кадры из разных VLAN не видят
широковещательный трафик друг друга, хотя идут по одному кабелю и через один коммутатор.
Механизм — тег из **4 дополнительных байт**, вставляемый в Ethernet-заголовок между
MAC-адресом отправителя и полем EtherType:
- **TPID** (Tag Protocol Identifier) — 2 байта, фиксированное значение **0x8100**, сигнализирует
коммутатору, что дальше идёт VLAN-тег, а не обычный EtherType;
- **TCI** (Tag Control Information) — 2 байта, из них: **PCP** (Priority Code Point, 3 бита) —
приоритет кадра для QoS, **DEI** (Drop Eligible Indicator, 1 бит) — кандидат на отбрасывание
первым при перегрузке, **VID** (VLAN ID, **12 бит**) — номер VLAN, диапазон 0–4095 (0 и
4095 зарезервированы, реально используется 1–4094).
Из-за тега заголовок Ethernet-кадра с VLAN вырастает с 14 до **18 байт**. Если это не
учесть при расчёте MTU или максимального размера кадра, получится ошибка ровно на 4 байта —
типично ловится сравнением дампа `tcpdump` с ожидаемой длиной.
**Факты для карточек**
- base | Сколько байт добавляет VLAN-тег 802.1Q к Ethernet-заголовку? — 4 байта (14 → 18)
- base | Значение поля TPID для VLAN-тега? — 0x8100
- core | Из каких трёх полей состоит TCI и сколько бит занимает VID? — PCP (3 бита), DEI (1 бит), VID (12 бит)
- core | Диапазон реально используемых VLAN ID? — 1–4094 (0 и 4095 зарезервированы)
Почему дальше: заголовок Ethernet ограничивает не только формат, но и максимальный размер
полезной нагрузки в одном кадре — MTU.
## 6. MTU и фрагментация
MTU (Maximum Transmission Unit) — максимальный размер полезной нагрузки, который канальный
уровень передаёт в одном кадре; для стандартного Ethernet это **1500 байт**. Если IP-пакет
крупнее MTU исходящего интерфейса, происходит одно из двух:
- **фрагментация** — пакет режется на несколько IP-пакетов меньшего размера, каждый со своим
IP-заголовком, собираются они уже на узле-получателе;
- если в заголовке выставлен флаг **DF** (Don't Fragment) — узел на пути отбрасывает пакет и
отправляет источнику ICMP-сообщение о необходимости фрагментации (см. раздел про ICMP).
Ограничение в 1500 байт исторически идёт из спецификации Ethernet и балансирует накладные
расходы заголовка против задержки и вероятности ошибки на длинном кадре: чем крупнее кадр,
тем дороже обходится его повторная передача при ошибке. Фрагментация — дорогая операция:
она нагружает маршрутизаторы на пути и делает передачу уязвимой к потере одного фрагмента, из-
за которого теряется весь исходный пакет целиком. Поэтому современные стеки заранее подбирают
размер пакета под MTU всего пути — механизм **PMTUD** (Path MTU Discovery), разобранный
дальше вместе с ICMP.
**Факты для карточек**
- base | Значение MTU для стандартного Ethernet? — 1500 байт
- core | Что происходит с IP-пакетом крупнее MTU, если флаг DF не выставлен? — фрагментируется на несколько IP-пакетов меньшего размера
- core | Что происходит, если пакет крупнее MTU и выставлен флаг DF? — пакет отбрасывается, отправителю летит ICMP о необходимости фрагментации
- deep | Почему фрагментация считается дорогой операцией? — нагружает маршрутизаторы на пути, и потеря одного фрагмента роняет весь исходный пакет
Почему дальше: и флаг DF, и размер, и адреса — это конкретные поля одной структуры,
IP-заголовка. Разберём его целиком по байтам.
## 7. IPv4-заголовок по полям
IPv4-заголовок — минимум **20 байт** (без опций), опции могут увеличить его до 60 байт.
Поля в порядке следования:
| Поле | Размер | Смысл |
|---|---|---|
| Version | 4 бита | версия протокола, для IPv4 всегда 4 |
| IHL | 4 бита | длина заголовка в 32-битных словах (минимум 5 → 20 байт) |
| TOS (Type of Service) | 8 бит | приоритет/качество обслуживания пакета |
| Total Length | 16 бит | общая длина пакета (заголовок + данные), байты |
| Identification | 16 бит | идентификатор для сборки фрагментов одного исходного пакета |
| Flags | 3 бита | бит DF (не фрагментировать), бит MF (есть ещё фрагменты) |
| Fragment Offset | 13 бит | смещение этого фрагмента в исходном пакете |
| TTL | 8 бит | время жизни пакета в хопах |
| Protocol | 8 бит | протокол следующего уровня: 6 = TCP, 17 = UDP, 1 = ICMP |
| Header Checksum | 16 бит | контрольная сумма заголовка (не данных) |
| Source / Destination IP | по 32 бита | адреса отправителя и получателя |
Это ровно поля структуры `Ipv4Header` из `tasks/04_ipv4`. Задача требует парсить их из сырого
буфера побайтово (через сдвиги и маски), а не приведением указателя `reinterpret_cast` — на
невыровненных адресах и при другом порядке байт это UB. Обязательные проверки при разборе:
буфер короче 20 байт, `version != 4`, `ihl < 5`, `ihl*4 > len`, `total_length < ihl*4` или
`total_length > len` — пакет с любым из этих условий отбрасывается как некорректный, ещё до
попытки читать поля выше заголовка.
**Факты для карточек**
- base | Минимальный размер IPv4-заголовка? — 20 байт
- base | Значение поля Protocol для TCP / UDP / ICMP? — 6 / 17 / 1
- core | Сколько бит занимает поле IHL и что оно означает? — 4 бита, длина заголовка в 32-битных словах
- core | Какие условия делают IPv4-пакет некорректным при разборе (по `tasks/04_ipv4`)? — буфер <20 байт, version≠4, ihl<5, ihl·4>len, total_length<ihl·4 или >len
- deep | Почему в `tasks/04_ipv4` запрещён `reinterpret_cast` на буфер? — невыровненный адрес и другой порядок байт на проводе дают UB при чтении полей как структуры напрямую
Почему дальше: одно из полей заголовка — TTL — существует специально для защиты от
зацикленной маршрутизации, и с ним напрямую связан отдельный протокол ошибок, ICMP.
## 8. TTL и ICMP Time Exceeded: как работает traceroute
**TTL** (Time To Live) уменьшается на единицу **на каждом маршрутизаторе**, через который
проходит пакет. Это сделано специально: чтобы зацикленный по ошибке маршрут не гонял пакет по
сети бесконечно. Когда TTL достигает нуля, пакет отбрасывается, а отправителю отправляется
**ICMP Time Exceeded** (тип 11).
Именно на этом механизме построен **traceroute**: он последовательно отправляет пакеты с
TTL 1, 2, 3, ... Пакет с TTL 1 гарантированно "умирает" на первом же маршрутизаторе — тот
шлёт ICMP Time Exceeded с собственным адресом, это и есть первая строка вывода traceroute.
Пакет с TTL 2 доходит до второго маршрутизатора и умирает там, и так далее, пока пакет не
дойдёт до конечного получателя. Так по цепочке ICMP-ответов восстанавливается список всех
промежуточных маршрутизаторов на пути, без какого-либо специального протокола обнаружения
маршрута — только манипуляция TTL и стандартный побочный эффект его истечения.
**Факты для карточек**
- base | На сколько уменьшается TTL на каждом маршрутизаторе? — на 1
- base | Какой ICMP-тип отправляется при обнулении TTL? — Time Exceeded, тип 11
- core | Как traceroute находит промежуточные маршрутизаторы, не имея отдельного протокола обнаружения пути? — последовательно шлёт пакеты с TTL=1,2,3..., каждый умирает на очередном хопе и присылает ICMP Time Exceeded с адресом этого хопа
Почему дальше: Time Exceeded — лишь один из типов ICMP-сообщений. Остальные закрывают другие
сценарии ошибок доставки и обнаружения MTU пути.
## 9. ICMP: echo, destination unreachable, PMTUD
ICMP (Internet Control Message Protocol) — протокол сетевого уровня для диагностических
сообщений; у него нет портов, он не переносит пользовательские данные приложений. Ключевые
типы:
- **Echo Request / Echo Reply** (тип 8 / тип 0) — основа команды `ping`;
- **Time Exceeded** (тип 11) — TTL обнулился (раздел выше, основа traceroute);
- **Destination Unreachable** (тип 3) — пакет физически не может быть доставлен; код внутри
этого типа уточняет причину, в частности код **"fragmentation needed and DF set"** —
посылается, когда пакет крупнее MTU промежуточного линка, а флаг DF запрещает
фрагментацию.
Именно код "fragmentation needed" лежит в основе **PMTUD** (Path MTU Discovery): отправитель
шлёт пакеты с выставленным DF, начиная с MTU своего интерфейса; если по пути встречается
линк с меньшим MTU, тот роутер отбрасывает пакет и присылает ICMP Destination Unreachable с
этим кодом (в современных реализациях — вместе со значением MTU узкого места); отправитель
уменьшает размер пакета и повторяет попытку. Так стек заранее подбирает размер, не полагаясь
на фрагментацию по пути.
ICMP существует отдельно от TCP/UDP потому, что диагностика нужна на уровне, где ещё нет
понятия соединения или порта — маршрутизатор должен уметь сообщить об ошибке доставки, даже не
зная, что за протокол был внутри. Именно поэтому `ping` и `traceroute` работают даже к узлу,
на котором не открыт ни один сервис поверх TCP/UDP. Если ICMP заблокирован файрволом
(частая практика), `ping` не пройдёт, хотя TCP-соединение на конкретный порт может работать
нормально — это видно, если сравнить `ping host` и `curl host` с разным результатом.
**Факты для карточек**
- base | Номера типов Echo Request и Echo Reply? — 8 и 0
- base | Тип ICMP-сообщения Destination Unreachable? — тип 3
- core | Какой код Destination Unreachable запускает PMTUD? — "fragmentation needed and DF set"
- core | Почему ICMP не имеет портов? — диагностика работает на сетевом уровне, где ещё нет понятия транспортного соединения
- deep | Почему `ping` может не проходить, а `curl` на тот же хост — работать? — ICMP заблокирован файрволом отдельно от TCP-порта, это два разных уровня фильтрации
**Ловушки**
- Судить о доступности хоста только по `ping` → файрвол может глушить ICMP, не трогая
реальный TCP-сервис — вывод «хост недоступен» будет ложным.
- Не учитывать PMTUD при жёстко заданном MTU в туннеле (VPN, GRE) → пакеты с DF молча
теряются на узком месте, если ICMP-ответы блокируются по пути — проявляется как
«маленькие пакеты проходят, большие — зависают».
Почему дальше: контрольная сумма заголовка из раздела про IPv4-поля устроена нетривиально —
разберём отдельно, как она считается и почему покрывает только заголовок.
## 10. Контрольная сумма IPv4-заголовка
Алгоритм — сумма в **дополнительном коде (one's complement)** по 16-битным словам заголовка,
затем **инверсия** результата (побитовое НЕ) — это и есть значение, которое пишут в поле
`header_checksum`. При сложении в дополнительном коде перенос из старшего бита не отбрасывается,
а прибавляется обратно к младшему биту суммы (end-around carry).
`tasks/04_ipv4` требует ровно эту схему в двух функциях:
```c++
// сумма в дополнительном коде по len байтам, затем инверсия — значение для записи в поле
// (вызывается на буфере, где поле контрольной суммы уже обнулено)
uint16_t compute_checksum(const uint8_t* buf, size_t len);
// true, если контрольная сумма заголовка верна: сумма в дополнительном коде по ihl*4 байтам
// заголовка (вместе с полем контрольной суммы) равна 0xFFFF; нагрузка не участвует
bool checksum_valid(const uint8_t* buf, size_t len);
```
Асимметрия проверки и вычисления не случайна: при **вычислении** поле суммы ещё не заполнено
(обнулено), поэтому оно не участвует своим значением; при **проверке** поле уже содержит
записанную сумму, и если она верна, повторное суммирование всех 16-битных слов заголовка
(включая это поле) в дополнительном коде обязано дать **все единицы — 0xFFFF**. Это свойство
дополнительного кода: сумма X и инверсии X всегда даёт все единицы.
**Почему сумма считается только по заголовку, а не по всему пакету с данными**: заголовок
меняется на каждом хопе — как минимум TTL уменьшается на 1 на каждом маршрутизаторе, а значит
контрольную сумму заголовка пришлось бы пересчитывать на каждом хопе заново. Пересчитывать её
ещё и по всей полезной нагрузке на каждом маршрутизаторе было бы избыточно дорого и не нужно:
целостность самих данных уже проверяется отдельно контрольными суммами более высокого уровня
(TCP/UDP-заголовок содержит свою контрольную сумму, покрывающую данные) и трейлером FCS на
канальном уровне. Если контрольная сумма заголовка не сходится, пакет молча отбрасывается —
ошибка ловится счётчиками ошибок интерфейса или отсутствием ожидаемого ответа в `tcpdump`.
**Факты для карточек**
- base | Алгоритм контрольной суммы IPv4-заголовка? — сумма в дополнительном коде по 16-битным словам, затем инверсия
- core | Какое значение должна давать сумма при проверке валидности (с учётом поля суммы)? — 0xFFFF
- core | Почему контрольная сумма покрывает только заголовок, а не данные? — заголовок (минимум TTL) меняется на каждом хопе и пересчитывается заново; целостность данных проверяют TCP/UDP-checksum и FCS канального уровня
- deep | Что такое end-around carry в one's complement сложении? — перенос из старшего бита не отбрасывается, а прибавляется обратно к младшему биту суммы
**Ловушки**
- Считать контрольную сумму по буферу с уже заполненным полем `header_checksum` вместо
обнулённого → результат `compute_checksum` окажется неверным, потому что старое значение
поля участвует в сумме.
- Включить в сумму данные после заголовка (payload) → сумма не сойдётся ни у отправителя, ни
при проверке — контрольная сумма IPv4 считается строго по `ihl*4` байтам, не по всей длине
пакета.
Почему дальше: сами IP-адреса в заголовке не существуют сами по себе — узел должен понимать,
какая их часть определяет сеть, а какая — конкретный хост. Это маска и подсеть.
## 11. Маски, подсети и CIDR
Маска подсети делит 32-битный IPv4-адрес на две части: номер сети и номер узла внутри неё —
это определяет, какие адреса «свои» для локальной доставки внутри сегмента, а какие требуют
выхода через шлюз. Запись `/N` (**CIDR**, Classless Inter-Domain Routing) — это и есть длина
префикса сети в битах вместо устаревшей классовой адресации (A/B/C), что позволяет выделять
подсети произвольного размера, а не только фиксированных 8/16/24 бит.
**Широковещательный адрес** подсети — адрес, где все биты хостовой части выставлены в 1; он
зарезервирован для рассылки всем узлам сегмента и не выдаётся конкретному устройству. Первый
адрес подсети (все хостовые биты — 0) зарезервирован как адрес самой сети.
Число доступных хостов по маске (для обычных подсетей — минус 2 служебных адреса, сеть и
broadcast):
| Маска | Хостовых бит | Всего адресов | Доступно хостам |
|---|---|---|---|
| /24 | 8 | 256 | **254** |
| /26 | 6 | 64 | **62** |
| /30 | 2 | 4 | **2** |
| /31 | 1 | 2 | **2** (RFC 3021, оба адреса — хосты, без сети/broadcast) |
| /32 | 0 | 1 | **1** (сеть = broadcast = сам адрес) |
`/31` и `/32` — исключения из общего правила «минус 2»: `/31` (RFC 3021) используется на
линках точка-точка между двумя маршрутизаторами, где резервировать сеть и broadcast из
всего двух адресов расточительно — оба адреса становятся хостовыми; `/32` — адрес единственного
узла, маршрут на конкретный хост.
По `tasks/13_subnet`: для `prefix <= 30` — `host_count = 2^(32-prefix) - 2`, `first_host =
network + 1`, `last_host = broadcast - 1`. Эталонный пример: `10.0.1.130/26` → сеть
`10.0.1.128`, broadcast `10.0.1.191`, диапазон хостов `129..190`. Обратная задача —
`prefix_for_hosts(h)`: найти **самую узкую** (наибольший `prefix`) подсеть, вмещающую `h`
хостов — считается как `32 - ceil(log2(h+2))` за O(1), без перебора. Примеры: `62` → `/26`,
`63` → `/25`, `254` → `/24`, `1` → `/30` (`/31` и `/32` в подборе не участвуют — они не для
произвольного числа хостов, а под конкретные сценарии линка и одиночного адреса).
**Факты для карточек**
- base | Сколько хостов доступно в подсети /24? — 254
- base | Сколько хостов доступно в подсети /26? — 62
- base | Сколько хостов доступно в подсети /30? — 2
- core | Чем /31 отличается от остальных масок по числу служебных адресов? — оба адреса хостовые (RFC 3021), нет отдельного сетевого/broadcast адреса
- core | Что означает запись `/N` в CIDR? — длина префикса сети в битах вместо классовой адресации A/B/C
- deep | Формула подбора самой узкой подсети под h хостов за O(1)? — `32 - ceil(log2(h+2))`
**Ловушки**
- Забыть исключить /31 и /32 из общего расчёта `2^(32-prefix) - 2` → для /31 формула даст 0
хостов вместо верных 2, для /32 — отрицательное число.
- Спутать адрес сети (все хостовые биты 0) с первым доступным хостом (`network + 1`) → off-by-one
в диапазоне выдаваемых адресов.
Почему дальше: маска определяет, доставлять ли пакет напрямую внутри сегмента или через
устройство более высокого уровня. Логично развести устройства L1/L2/L3 по тому, что каждое
из них реально делает с кадром/пакетом.
## 12. Хаб, коммутатор, маршрутизатор; таблица MAC-адресов (FDB)
Три устройства работают на разных уровнях и с разным объёмом понимания трафика:
- **Хаб (L1)** — просто повторитель: любой бит, пришедший на один порт, электрически
копируется на все остальные порты без какого-либо разбора кадра. Все порты хаба — один
общий домен коллизий; трафик одной пары узлов виден всем остальным. Практически вытеснен
коммутаторами.
- **Коммутатор (L2)** — разбирает Ethernet-заголовок и пересылает кадр только на порт, где
находится MAC-адрес получателя, а не на все порты сразу. Каждый порт — отдельный домен
коллизий, но все порты (без VLAN) остаются одним широковещательным доменом.
- **Маршрутизатор (L3)** — разбирает IP-заголовок и пересылает пакет между разными подсетями
по таблице маршрутов, уменьшая TTL на 1 на каждом пересланном пакете. Разделяет
широковещательные домены — broadcast из одной подсети не проходит через маршрутизатор в
другую.
Коммутатор знает, на какой порт слать кадр, благодаря **таблице MAC-адресов** (FDB, Forwarding
Database, также называют CAM-таблицей) — по сути хеш-таблице, где ключ — MAC-адрес, значение —
номер порта. Заполняется она самообучением: коммутатор смотрит **source MAC** каждого
входящего кадра и запоминает пару (MAC, порт, на который кадр пришёл) — так через обычный
трафик таблица заполняется без отдельного протокола объявления адресов. Если адреса
получателя нет в таблице (адрес ещё не встречался как источник), коммутатор пересылает кадр
на все порты, кроме входного (flooding) — точно так же, как хаб, но только для этого одного
кадра, пока адрес не станет известен.
Записи в FDB не хранятся вечно — у каждой запись есть **aging**: таймер, который сбрасывается
при каждом новом кадре с этим source MAC, и если запись не обновлялась дольше таймаута
(типичное значение в реализациях — 300 секунд, 5 минут), она удаляется из таблицы. Это нужно,
чтобы таблица не разрасталась записями отключённых или перемещённых на другой порт устройств
— переключить сетевой кабель с одного порта на другой без aging означало бы, что коммутатор
продолжал бы слать кадры на старый, уже неверный порт.
**Факты для карточек**
- base | На каком уровне работает хаб / коммутатор / маршрутизатор? — L1 / L2 / L3
- base | Что такое FDB коммутатора по структуре данных? — хеш-таблица MAC-адрес → порт
- core | Как коммутатор заполняет FDB без отдельного протокола объявления? — самообучением по source MAC каждого входящего кадра
- core | Что делает коммутатор с кадром, чей MAC получателя не найден в FDB? — рассылает на все порты кроме входного (flooding)
- core | Зачем нужен aging записей FDB? — удалять устаревшие пары MAC-порт, если устройство отключилось или переехало на другой порт
- deep | Что разделяет маршрутизатор, чего не делает коммутатор? — широковещательные домены (домены коллизий разделяет уже коммутатор)
**Ловушки**
- Ожидать, что коммутатор ограничивает broadcast-трафик → он передаёт broadcast-кадры на все
порты одного широковещательного домена так же, как хаб; ограничивает его только
маршрутизатор (или разбиение на VLAN).
- Перепутать основание пересылки: коммутатор решает по MAC (L2), маршрутизатор — по IP (L3) →
попытка «настроить маршрутизацию на коммутаторе» без функции L3 физически невозможна.
Ссылки на задачи этого дня: `tasks/04_ipv4` (разбор IPv4-заголовка и контрольная сумма),
`tasks/13_subnet` (арифметика подсетей).
<details>
<summary>Проверь себя</summary>
1. Кадр Ethernet с VLAN-тегом занимает 18 байт заголовка вместо 14. Откуда взялись эти лишние
4 байта и что конкретно в них закодировано?
<details><summary>Ответ</summary>VLAN-тег 802.1Q: 2 байта TPID (фиксированное значение
0x8100) + 2 байта TCI, где TCI = PCP (3 бита приоритета) + DEI (1 бит) + VID (12 бит номер
VLAN, диапазон 1–4094 реально используемых значений).</details>
2. Почему PMTUD зависит от ICMP, и что происходит, если файрвол на пути блокирует ICMP
целиком?
<details><summary>Ответ</summary>PMTUD узнаёт об узком месте по ICMP Destination
Unreachable с кодом «fragmentation needed and DF set» от промежуточного маршрутизатора;
если ICMP заблокирован, отправитель никогда не получит этот сигнал и будет слать пакеты,
которые молча теряются на узком месте — классическая причина «маленькие пакеты проходят,
большие зависают» через VPN/туннель.</details>
3. При проверке контрольной суммы IPv4-заголовка сумма всех 16-битных слов (включая само
поле суммы) даёт 0xFFFF. Почему именно это число, а не 0?
<details><summary>Ответ</summary>Поле контрольной суммы — это инверсия суммы остальных
слов; сумма числа и его инверсии в дополнительном коде всегда даёт все единицы (0xFFFF),
это математическое свойство one's complement, а не произвольный выбор.</details>
4. Подсеть `10.0.1.130/26`. Назови адрес сети, broadcast и диапазон хостов, не считая заново
с нуля — по правилу из этого урока.
<details><summary>Ответ</summary>Сеть `10.0.1.128`, broadcast `10.0.1.191`, хосты
`10.0.1.129`–`10.0.1.190` (62 адреса, /26 = 64 адреса минус 2 служебных).</details>
5. Почему `/31` — единственная маска (кроме /32) с `host_count`, который не считается по
формуле `2^(32-prefix) - 2`?
<details><summary>Ответ</summary>RFC 3021: на линке точка-точка из всего двух адресов
резервировать отдельно сеть и broadcast бессмысленно — оба адреса используют как хостовые,
поэтому `host_count = 2`, а не `2^1 - 2 = 0`.</details>
6. Коммутатор получил кадр с MAC получателя, которого нет в его FDB. Что он сделает и чем
это временно похоже на поведение хаба?
<details><summary>Ответ</summary>Разошлёт кадр на все порты, кроме входного (flooding) —
как и хаб, который всегда рассылает на все порты; разница в том, что у коммутатора это
разовое поведение для конкретного неизвестного адреса, а не постоянный режим работы для
всего трафика.</details>
</details>
## Материалы
- RFC 791, Internet Protocol (IPv4) — https://datatracker.ietf.org/doc/html/rfc791
- RFC 826, An Ethernet Address Resolution Protocol (ARP) — https://datatracker.ietf.org/doc/html/rfc826
- RFC 792, Internet Control Message Protocol (ICMP) — https://datatracker.ietf.org/doc/html/rfc792
- RFC 4632, Classless Inter-domain Routing (CIDR) — https://datatracker.ietf.org/doc/html/rfc4632
- RFC 3021, Using 31-Bit Prefixes on IPv4 Point-to-Point Links — https://datatracker.ietf.org/doc/html/rfc3021
- man7.org, `ip(7)` — https://man7.org/linux/man-pages/man7/ip.7.html