Files

92 lines
7.8 KiB
Markdown
Raw Permalink 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.
# Задача 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 {
uint8_t version; // должно быть 4
uint8_t ihl; // длина заголовка в 32-битных словах
uint8_t protocol; // 6 = TCP, 17 = UDP, 1 = ICMP
uint16_t total_length; // из заголовка (хост-порядок)
uint32_t src_ip; // байты как на проводе: 10.0.0.1 -> 0x0A000001
uint32_t dst_ip;
uint16_t header_checksum; // поле из заголовка (хост-порядок)
};
// false: буфер короче 20 байт, version != 4, ihl < 5, ihl*4 > len,
// total_length < ihl*4 или total_length > len
bool parse_ipv4(const uint8_t* buf, size_t len, Ipv4Header* out);
// сумма в дополнительном коде по 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);
```
Проверка: `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.