Учебные материалы: механизм вместо тезисов, блоки «Факты для карточек», Ловушки (Claude Code) + diag/cards_src.tsv

This commit is contained in:
Kodlo-chan
2026-09-24 13:38:58 +07:00
parent e0ad8e0fee
commit 4259fbce75
17 changed files with 1688 additions and 115 deletions
+61 -2
View File
@@ -1,7 +1,35 @@
# Задача 04 — разбор IPv4-пакета и контрольная сумма (C++)
Это то, что реально делают сетевые железки: взять буфер из сокета и корректно разобрать
заголовок, не выйдя за границы и не поверив «на слово» полям пакета.
## Глава I. Общая информация
- **Цель:** разобрать буфер байт из сокета в структуру заголовка вручную, безопасно и без
UB — то, что реально делают сетевые железки на приёмном тракте.
- **Почему это в Eltex:** это то, что реально делают сетевые железки: взять буфер из сокета
и корректно разобрать заголовок, не выйдя за границы и не поверив «на слово» полям пакета.
Коммутатор/маршрутизатор обязан отбросить пакет с битой контрольной суммой или враньём в
`total_length`, иначе он либо читает чужую память, либо пересылает мусор дальше.
- **Что сдаётся:** `solution.cpp`, проходящий `python3 grade.py 04`.
## Глава II. Что нужно знать до старта
Минимальная длина IPv4-заголовка — 20 байт (без опций). Байт-порядок в заголовке смешанный:
`total_length` и `header_checksum` хранятся в хост-порядке в структуре `Ipv4Header` (в задании
явно указано «хост-порядок» — значит в `parse_ipv4` их нужно правильно собрать из сетевого
порядка на проводе), а IP-адреса (`src_ip`, `dst_ip`) — «байты как на проводе»:
`10.0.0.1 -> 0x0A000001`, то есть без перестановки байт при сборке в `uint32_t`.
Почему нельзя просто `reinterpret_cast<const Ipv4Header*>(buf)`: у `buf` нет гарантии
выравнивания под `uint32_t` (нужно кратно 4 байтам) — компилятор вправе сгенерировать код,
предполагающий выровненный доступ, и на невыровненном адресе это UB (UBSAN ловит как
misaligned address); плюс раскладка полей в `Ipv4Header` определяется компилятором
(padding), а не байтовым форматом протокола — сети всё равно, как выровнены поля в C++-структуре.
Правильный путь — читать каждый байт по отдельности и собирать сдвигами (тот же приём, что
в задаче 01: маски и сдвиги для сборки многобайтового числа из байт).
Почему дальше: если разбор заголовка освоен, логичный следующий шаг — контрольная сумма TCP/UDP
поверх псевдозаголовка, которая считается тем же алгоритмом сложения в дополнительном коде.
## Глава III. Задание
```c++
struct Ipv4Header {
@@ -30,3 +58,34 @@ bool checksum_valid(const uint8_t* buf, size_t len);
Проверка: `python3 grade.py 04`. Критерий: все `ok`, ASAN/UBSAN чистые.
Запрещено приводить буфер к структуре через `reinterpret_cast` и читать поля как есть —
на невыровненных адресах и в другом порядке байт это UB.
## Глава IV. Ловушки
- `reinterpret_cast<Ipv4Header*>(buf)` вместо побайтового разбора → чтение по невыровненному
адресу и/или с padding-полями структуры вместо реального формата пакета → UB, UBSAN
сообщает misaligned address, а на реальных данных поля читаются со сдвигом.
- Не обнулить поле `header_checksum` перед вызовом `compute_checksum` → в сумму попадает
старое значение поля → результат не совпадёт с ожидаемым, тест на `compute_checksum` падает.
- Пропустить перенос (carry) при сложении 16-битных слов в дополнительном коде, если
промежуточная сумма считается в 16-битном типе → биты переноса теряются вместо того чтобы
прибавиться обратно к младшим битам → неверная контрольная сумма на буферах, где сумма слов
переполняет 16 бит.
- Проверить `ihl*4 > len` до проверки `len >= 20` (или в неверном порядке) → чтение поля `ihl`
из буфера короче 1 байта → выход за границы, ASAN сообщает heap-buffer-overflow.
**Факты для карточек**
- base | Минимальная длина IPv4-заголовка без опций? — 20 байт
- base | Код `protocol` для TCP / UDP / ICMP? — 6 / 17 / 1
- core | В каком порядке лежат `src_ip`/`dst_ip` в буфере согласно заданию? — «как на проводе»,
без перестановки байт при сборке в `uint32_t`
- core | Почему `reinterpret_cast` буфера в `Ipv4Header*` — UB? — нет гарантии выравнивания
адреса под 4-байтные поля структуры
- deep | Чему должна быть равна сумма в доп. коде по `ihl*4` байтам заголовка (вместе с полем
checksum) при верной контрольной сумме? — 0xFFFF
## После сдачи
Разбор: как поле `header_checksum` защищает именно заголовок (не данные), почему на каждом
маршрутизаторе TTL уменьшается на 1 и, как следствие, контрольную сумму заголовка приходится
пересчитывать заново на каждом хопе, и чем отличается контрольная сумма TCP/UDP (считается с
псевдозаголовком) от чисто IP.