Перейти до вмісту

A3. Розбір пакетів із pcap

середнійспирається на модуль 4, модуль 6, модуль 11

tcpdump -r і Wireshark розбирають пакети так буденно, що легко забути, скільки рішень стоїть за кожним рядком: де закінчується заголовок IP, що таке довжина TCP-заголовка, чому кадр довший за IP-пакет, звідки в полі TCP-прапорів літера P. Відповіді лежать у заголовках, які описують модуль 4 (кадр Ethernet), модуль 6 (IPv4) і модуль 11 (TCP і UDP). Тут ви прочитаєте їх самі, з файла на диску.

Після цієї роботи ви зможете:

  • прочитати класичний .pcap у будь-якому порядку байтів і з будь-якою точністю часу, не покладаючись на бібліотеку;
  • розібрати Ethernet (зокрема з тегом VLAN), IPv4 з опціями, TCP з опціями, UDP та ICMP і знати, яке поле за що відповідає;
  • відрізнити довжину кадру від довжини IP-пакета й пояснити, звідки береться різниця;
  • зібрати статистику потоків і побачити, що «з’єднання» у TCP і «обмін» в UDP розпізнаються лише за п’ятіркою (протокол, адреса, порт, адреса, порт);
  • обробити пошкоджений або невідомий вхід, не зависнувши й не повернувши мовчки неправильну відповідь.

Це потрібно щоразу, коли tcpdump показує щось дивне і треба зрозуміти, що саме лежить у файлі, або коли потрібен власний інструмент, який дивиться на трафік не так, як готові.

Написати програму pcap-parse: на C або на Python, за вибором. Чекер запускає виконуваний файл, тож для Python потрібен shebang і chmod +x. Використання libpcap, scapy, dpkt та інших розбирачів заборонене. Стандартні struct в Python і функції для читання файла в C дозволені.

Terminal window
./pcap-parse capture.pcap # рядок на пакет
./pcap-parse --flows capture.pcap # статистика потоків

Формат виводу перевіряється по словах, тож кількість пробілів між ними не має значення. Номер пакета починається з 1. Час — це секунди від першого пакета у файлі з шістьма знаками після крапки. Значення менше мікросекунди відкидаються, не округлюються. Поле len — довжина кадру «на дроті» із заголовка запису (orig_len), а не обсяг збережених байтів.

1 0.000000 TCP 10.0.0.1:40000 -> 10.0.0.2:80 len=74 flags=S seq=1000 ack=0 win=64240 payload=0
2 0.000120 TCP 10.0.0.2:80 -> 10.0.0.1:40000 len=74 flags=SA seq=5000 ack=1001 win=65160 payload=0
3 0.009000 TCP 10.0.0.1:40000 -> 10.0.0.2:80 len=166 flags=PA seq=1001 ack=5001 win=502 payload=100
4 0.010000 UDP 10.0.0.1:5353 -> 10.0.0.53:53 len=71 payload=29
5 0.011000 ICMP 10.0.0.1 -> 10.0.0.2 len=74 type=8 code=0
6 0.012000 IP/47 10.0.0.1 -> 10.0.0.2 len=60
7 0.013000 ETH 0x0806 02:00:00:00:00:01 -> ff:ff:ff:ff:ff:ff len=60
8 0.014000 TCP 10.3.0.1:1 -> 10.3.0.2:2 len=60 flags=S seq=1 ack=0 win=100 payload=0 vlan=100
9 0.015000 TRUNC len=60
10 0.016000 UDP 10.0.0.53:53 -> 10.0.0.1:5353 len=119 payload=77
  • TCP: flags — букви з набору FSRPAUEC (FIN, SYN, RST, PSH, ACK, URG, ECE, CWR) у порядку від молодшого біта до старшого, -, якщо прапорів немає; payload — число байтів даних TCP, обчислене за довжиною в заголовку IP, а не за розміром кадру.
  • UDP: payload — довжина даних.
  • ICMP: тип і код, без портів.
  • Інший протокол поверх IPv4: IP/<номер протоколу>.
  • Не IPv4: ETH 0x<тип в шістнадцятковому вигляді> <джерело> -> <призначення>.
  • Кадр із тегом 802.1Q розбирається як звичайний, а в кінець рядка додається vlan=<номер> (тег не впливає на ETH 0x… для не-IPv4: там тип береться після тегу).
  • Кадр, збережений не повністю, так що заголовків не вистачає (дев’ятий пакет у прикладі), дає рядок TRUNC len=<довжина кадру на дроті>.

Статистика потоків у режимі --flows. Потік двосторонній: пакети A→B і B→A одного протоколу належать одному потокові. Один рядок на потік, у порядку першої появи в файлі; адреса (з портом для TCP і UDP), що йде першою, — це відправник першого пакета потоку:

TCP 10.0.0.1:40000 <-> 10.0.0.2:80 packets=3 bytes=314 duration=0.009000
UDP 10.0.0.1:5353 <-> 10.0.0.53:53 packets=2 bytes=190 duration=0.006000
ICMP 10.0.0.1 <-> 10.0.0.2 packets=1 bytes=74 duration=0.000000
TCP 10.3.0.1:1 <-> 10.3.0.2:2 packets=1 bytes=60 duration=0.000000
total packets=10 flows=4

bytes — сума len пакетів потоку; duration — різниця часу останнього й першого пакета потоку. У total рахуються усі пакети файла, а в потоки потрапляють лише TCP, UDP і ICMP (тобто ARP і IP/47 потоками не є).

Формати файла. Обидва порядки байтів (за сигнатурою в перших чотирьох байтах), мікросекундна й наносекундна сигнатури, канальний рівень Ethernet (тип 1). Захоплення з tcpdump -i any має тип 113 (Linux cooked): для нього потрібна відмова з кодом 2, а не розбір.

Обробка помилок:

  • код 0 — успіх; порожній файл із лише заголовком дає порожній вивід;
  • код 1 — файл обірвано посеред запису: усе, що вдалося прочитати до того, виводиться (і в режимі --flows теж), а в stderr з’являється повідомлення;
  • код 2 — вхід не підтримується: файла немає, сигнатура невідома, це pcapng (повідомлення в stderr має містити слово pcapng), тип каналу не Ethernet. У stdout нічого.

Чого робити не треба. pcapng, IPv6, фрагментацію IP (перший фрагмент розбирається, як звичайний пакет, інші ви не перевіряєте), збирання TCP-потоку з сегментів, перевірку контрольних сум, розбір протоколів прикладного рівня.

Готово, коли:

  • ./check.sh ./pcap-parse проходить усі перевірки;
  • на власному записі (sudo tcpdump -i eth0 -w my.pcap, де eth0 — ваш інтерфейс, кілька запитів curl і ping) ваш вивід збігається з tcpdump -nn -r my.pcap за кількістю пакетів, адресами, портами й прапорами;
  • у звіті є відповіді на запитання з етапів 3 і 4.
  • У модулі 4 знайдіть формат кадру Ethernet і тег 802.1Q, у модулі 6 заголовок IPv4 (поля IHL і Total Length), в модулі 11 заголовки TCP і UDP. Заголовок запису й файла pcap описано в man 5 pcap-savefile.
  • Розпакуйте архів курсу: чекер лежить у labs/a3-pcap-parser/check.sh.
  • Знадобляться gcc чи python3 і tcpdump. Root потрібен лише для перевірки на справжньому захопленні (вона пропускається, якщо ви запускаєте чекер без root) та для власних записів. Переглянути байти зручно через xxd або hexdump -C.
  1. Заголовок файла й записи.

    Перші 24 байти — заголовок файла: сигнатура (вона ж підказує порядок байтів і точність часу), версія, snaplen, тип каналу. Далі йдуть записи: 16 байтів заголовка (секунди, мікро- або наносекунди, caplen, orig_len) і caplen байтів кадру.

    Почніть із програми, яка друкує номер, час, caplen, orig_len для кожного пакета. Порівняйте число пакетів із tcpdump -nn -r файл | wc -l.

  2. Ethernet і IPv4.

    У кадрі 14 байтів заголовка Ethernet; якщо тип 0x8100, далі йдуть ще 4 байти тегу VLAN і справжній тип. Заголовок IPv4 має довжину IHL × 4 байтів, а не завжди 20. Протокол — дев’ятий байт, адреси — з 12-го по 19-й.

    Виведіть ETH-рядки для не-IPv4 і початкові рядки IPv4 без портів.

  3. TCP і UDP.

    Довжина TCP-заголовка береться зі старшої половини байта Data Offset (у четвірках байтів), прапорів вісім, вони лежать в одному байті. Розмір даних у пакеті — це довжина IP-пакета мінус заголовки, яку беруть із поля Total Length, а не з розміру кадру.

    Запишіть у звіт: коли розмір кадру більший за 14 + Total Length і звідки береться цей хвіст. Підказка: порахуйте, який мінімальний розмір кадру Ethernet і скільки байтів має TCP ACK без даних.

  4. ICMP, інші протоколи, обрізані кадри.

    Додайте ICMP, IP/n і TRUNC. Перевірте всі межі: ваш код не має читати за кінець кадру, бо caplen може бути меншим, ніж вимагають заголовки. Запишіть у звіт, чому знімок може бути обрізаний (snaplen) і чим тоді caplen відрізняється від orig_len.

  5. Порядок байтів і наносекунди.

    Підтримайте всі чотири сигнатури. Перевірка: чекер створює файл у big-endian і з наносекундами, а також пакети на межі секунди (100.999990 і 101.000010).

  6. Статистика потоків.

    Ключ потоку однаковий для обох напрямків: відсортуйте пару (адреса, порт) для джерела й призначення. Зберігайте порядок першої появи окремо від словника. Додайте --flows.

  7. Помилки й обсяг.

    Обірваний файл, pcapng, файл, що не є захопленням, відсутній файл. Потім перевірте швидкість на 60 000 пакетів: якщо ви читаєте файл по байту або додаєте рядки до вихідного буфера квадратично, чекер це помітить.

Terminal window
cd labs/a3-pcap-parser
./check.sh ./pcap-parse # без root: лише перевірки на згенерованих файлах
sudo ./check.sh ./pcap-parse # із root ще й справжній захват

Чекер складає .pcap-файли за описом пакетів і порівнює ваш вивід з очікуваним, обчисленим із цього опису (а не з розбору цих самих байтів). Окремі файли перевіряють: рукостискання TCP з опціями; UDP, ICMP, чужий протокол, ARP і всі вісім прапорів; IPv4 з опціями; доповнення Ethernet до 60 байтів і хвіст після IP-пакета; теги VLAN; big-endian, наносекунди, межу секунди; кадр, обрізаний у середині TCP-заголовка; порожній, обірваний, pcapng, чужий тип каналу й неіснуючий файл; потоки з двома TCP-з’єднаннями, обміном UDP і ICMP; 60 000 пакетів.

Якщо запустити від root і tcpdump встановлений, чекер створює два простори імен lab-a3-a і lab-a3-b, пересилає 200 000 байтів через TCP, три UDP-датаграми й два ping, записує це tcpdump-ом і перевіряє, що ваш розбір бачить стільки ж пакетів, що й tcpdump, сумарні дані TCP і UDP збігаються з надісланими, а потоків рівно три. Після цього простори імен видаляються.

Кількість пробілів у рядках і порожні рядки значення не мають; мова, якою ви пишете, теж.

Довжина даних за розміром кадру. Ethernet доповнює короткі кадри до 60 байтів (без контрольної суми), і TCP ACK без даних тоді виглядатиме як пакет із шістьма байтами даних. Довжина даних береться з Total Length у заголовку IP.

IHL — у четвірках байтів. Значення 5 означає 20 байтів. Те саме стосується Data Offset у TCP.

Порядок байтів. Числа в заголовках мережевих протоколів лежать у мережевому порядку (big-endian), а числа в заголовку запису pcap — у порядку машини, що писала файл. Їх легко переплутати, і на типовому Linux вони різні.

Час у нано- і мікросекундах. Поле після секунд — частка секунди. У файлі з сигнатурою a1b23c4d це наносекунди, тож ділити його на тисячу треба тільки там.

Читання за кінець кадру. Якщо кадр обрізано, struct.unpack_from кине виняток, а C-код прочитає сміття. Перевіряйте довжину, перш ніж читати поле.

Потік визначено за напрямком. Тоді запит і відповідь потраплять у різні потоки. Ключ не повинен залежати від того, хто написав першим.

Обірваний файл. Програма, що падає з винятком і не виводить прочитане, для tcpdump -w, який ще пише в файл, марна.

Додати pcapng: блоки секції, опису інтерфейсу й розширеного пакета. Збирати TCP-потік із сегментів за seq і знаходити повторні передачі. Підтримати фрагменти IP і IPv6. Замінити власний вивід на формат, який зрозуміє text2pcap, і замкнути цикл із Wireshark.