Уроки D2-D7 (14 файлов): деревья/куча, epoll, потоки, gdb, графы, L2-L3, ДП, TCP, bash, ядро, docker, мок-интервью + cards_src 613
This commit is contained in:
@@ -0,0 +1,483 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user