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

A2. Власні ping і traceroute

базовийспирається на модуль 6

ping і traceroute вважаються найпростішими мережевими інструментами, хоча майже нічого не ховають: перший надсилає ICMP echo request і чекає echo reply, другий виставляє пакетам малий TTL і збирає повідомлення Time Exceeded від маршрутизаторів на шляху. Модуль 6 пояснює заголовок IPv4, поле TTL і місце ICMP поруч із IP. Тут ви побачите ці байти власними очима: самі збудуєте ICMP-пакет, порахуєте контрольну суму й розберете заголовок відповіді.

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

  • відкрити raw-сокет і зібрати пакет ICMP без допомоги ядра;
  • порахувати контрольну суму Інтернету й пояснити, чому пакет із хибною сумою ядро цілі мовчки викидає;
  • виміряти RTT і частку втрат і відрізнити свою відповідь від чужої за ідентифікатором і номером послідовності;
  • отримати шлях пакета через TTL і читати в traceroute рядки із зірочками;
  • прочитати ttl= у виводі ping і за ним оцінити, скільки маршрутизаторів пакет пройшов.

Це корисно щоразу, коли «пінг є, а сайт не відкривається»: ви знатимете, що саме ping довів, а чого не довів.

Написати на C дві програми, myping і mytraceroute. Обидві беруть IPv4-адресу в десятковій формі; імена й DNS не потрібні.

Terminal window
sudo ./myping [-c кількість] [-i інтервал_с] [-W таймаут_с] адреса
sudo ./mytraceroute [-m макс_стрибків] [-w таймаут_с] [-q проб_на_стрибок] адреса

Значення за замовчуванням: для myping чотири пакети, інтервал 1 с, таймаут 1 с; для mytraceroute тридцять стрибків, таймаут 1 с, три проби. Інтервал і таймаут можуть бути дробовими.

myping має вміти:

  • надсилати ICMP echo request із 56 байтами даних і правильною контрольною сумою, з ідентифікатором процесу й номером, що зростає з одиниці;

  • для кожної відповіді друкувати рядок у форматі 64 bytes from 10.0.0.2: icmp_seq=1 ttl=62 time=0.321 ms; розмір — це довжина ICMP-повідомлення, ttl береться з IP-заголовка відповіді, час вимірюється в мілісекундах;

  • приймати лише свої відповіді: тип echo reply, відповідний id і icmp_seq, правильна контрольна сума, відправник збігається з адресою цілі; інакше пакет мовчки пропускається; повторна відповідь на вже отриманий номер не рахується;

  • наприкінці друкувати підсумок у такому вигляді:

    --- 10.0.0.2 ping statistics ---
    4 packets transmitted, 4 received, 0% packet loss
    rtt min/avg/max = 0.069/0.080/0.090 ms

    Рядок rtt друкується, лише якщо була хоча б одна відповідь;

  • завершуватися з кодом 0, якщо прийшла хоч одна відповідь, з кодом 1, якщо жодної, і з кодом 2 при помилці аргументів чи відсутності прав на raw-сокет, пояснивши це в stderr.

mytraceroute має вміти:

  • для кожного значення TTL від 1 до -m надсилати задану кількість проб і друкувати по одному рядку на стрибок: номер, адреса маршрутизатора й час кожної проби, для проби без відповіді *; приклад: 2 10.201.2.2 0.010 ms 0.005 ms 0.005 ms; рядок без жодної відповіді має вигляд 3 * * *;
  • способом проб можна вибрати UDP на великі порти або ICMP echo; перевірка приймає обидва;
  • зупинятися, коли відповідь прийшла від самої цілі (для UDP це Destination Unreachable з кодом «порт недосяжний»), і завершуватися з кодом 0; якщо цілі не досягнуто за -m стрибків, код 1;
  • не приймати чужих ICMP-повідомлень: у тілі помилки лежить початок вашої проби, за ним і треба впізнавати свою.

Чого робити не треба. IPv6, DNS-імена, підтримку довільного розміру даних, прапор -f, звернення до ядра через SOCK_DGRAM з IPPROTO_ICMP (для нього потрібна настройка ping_group_range, а ми вчимося писати заголовки самі).

Обмеження. C, raw-сокет SOCK_RAW з IPPROTO_ICMP, контрольну суму рахуєте самі. Запускати треба від root або з CAP_NET_RAW.

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

  • sudo ./check.sh ./myping ./mytraceroute проходить усі перевірки;
  • ви можете пояснити, чому ядро, яке прийняло ваш echo request із нульовою контрольною сумою, не відповіло б на нього (і що побачите в tcpdump);
  • у звіті є відповідь на запитання з етапу 5.
  • Прочитайте в модулі 6 про заголовок IPv4, TTL і повідомлення ICMP. Формат ICMP-заголовка візьміть із man 7 icmp, а про raw-сокети подивіться man 7 raw.
  • Розпакуйте архів курсу: чекер лежить у labs/a2-ping-traceroute/check.sh, поруч є Makefile.
  • Знадобляться gcc, python3, nftables (для однієї з перевірок), tcpdump. Потрібні права root, а також ядро з просторами імен, тож підійде ВМ, WSL2 чи контейнер із CAP_NET_ADMIN (див. огляд).
  1. Топологія для дослідів.

    Чекер будує ланцюжок із чотирьох просторів імен: lab-a2-h, два маршрутизатори lab-a2-r1 і lab-a2-r2 та ціль lab-a2-t:

    lab-a2-h 10.201.1.1 ─ 10.201.1.2 lab-a2-r1 10.201.2.1 ─ 10.201.2.2 lab-a2-r2 10.201.3.1 ─ 10.201.3.2 lab-a2-t

    Збудувати його без перевірки можна командою sudo ./check.sh --up, а sudo ./check.sh --down його прибирає. Запустіть системний ping і traceroute як еталон поведінки:

    Terminal window
    sudo ip netns exec lab-a2-h ping -c2 10.201.3.2
    sudo ip netns exec lab-a2-h traceroute -n 10.201.3.2

    Зверніть увагу на ttl=62 у відповідях: ціль відповіла з TTL 64, і два маршрутизатори відняли по одиниці.

  2. Одна проба ICMP echo.

    Відкрийте socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), зберіть 8-байтовий заголовок (тип 8, код 0, контрольна сума, id, seq) і 56 байтів даних. Поки порахуйте суму наосліп, надішліть пакет і подивіться tcpdump -ni any icmp у просторі імен цілі: це корисно, щоб побачити, що відбувається до будь-якої перевірки. Потім порахуйте правильну суму.

    Контрольна сума Інтернету: складіть усі 16-бітні слова пакета, у якому поле суми дорівнює нулю, додайте перенос зі старших розрядів до молодших, а результат інвертуйте. Для перевірки прийнятого пакета складіть усі слова разом із полем суми: має вийти 0xffff, тобто нуль після інверсії.

  3. Відповідь.

    Raw-сокет для ICMP віддає пакет разом з IP-заголовком, і довжина заголовка (поле IHL) змінна. Звідти ж береться TTL. Так само raw-сокет одержує всі ICMP-пакети хоста, а не лише відповіді на ваші запити, тож кожен треба перевірити: тип, код, id, seq, суму й відправника. Виміряйте RTT за clock_gettime(CLOCK_MONOTONIC).

    Перевірте, що sudo ./check.sh ./myping ./mytraceroute уже проходить групи про ping, а перевірки traceroute ще падають.

  4. Серія пакетів і підсумок.

    Цикл по -c із інтервалом -i, таймаутом -W, підрахунком втрат, мінімуму, середнього й максимуму. Обробіть випадок, коли відповіді немає: myping не має зависати і має завершитися з кодом 1.

  5. Traceroute.

    UDP-сокет із setsockopt(IP_TTL) і окремий raw-сокет для ICMP. Перша проба з TTL 1 дійде до першого маршрутизатора, який зменшить TTL до нуля, викине пакет і надішле Time Exceeded. Кожна нова проба збільшує TTL на одиницю. Ціль пізнається по Destination Unreachable з кодом 3 (порт недосяжний), бо на портах 33434 і вище зазвичай ніхто не слухає.

    Запишіть у звіт: чому адреса маршрутизатора у рядку traceroute зазвичай є адресою інтерфейсу, який дивиться назад до вас, і що станеться в traceroute, якщо маршрутизатор на шляху не шле Time Exceeded (перевірте це: чекер робить саме так із lab-a2-r2).

  6. Звірка із системними утилітами.

    Порівняйте власний вивід із системним ping і traceroute -n на тому самому ланцюжку, а потім перевірте себе чекером.

Terminal window
cd labs/a2-ping-traceroute
make
sudo ./check.sh ./myping ./mytraceroute

Чекер сам будує простори імен lab-a2-*, запускає ваші програми всередині lab-a2-h і після себе все прибирає. Він перевіряє:

  • ping проти ядра цілі: 4 з 4 відповідей, ttl=62, 64 байти, зростання icmp_seq, узгодженість min/avg/max із рядками відповідей;
  • ping проти відповідача, керованого чекером: затримка рівно 100 мс вимірюється правильно; перед справжньою відповіддю приходять чужа (інший id) і пошкоджена (хибна сума), а після неї дублікат, і нічого з цього не має потрапити в статистику; на кожен другий запит відповіді немає (50% втрат); адреса, якої немає, не зависає;
  • traceroute: шлях 10.201.1.2 → 10.201.2.2 → 10.201.3.2, нумерація, час у мілісекундах, зупинка на цілі й на першому маршрутизаторі; маршрутизатор, що не відповідає, дає рядок із зірочками, а трасування триває; -m 2 обмежує довжину.

Вигляд рядків між полями, кількість пробілів, заголовок перед виводом і порядок опцій значення не мають. Порядок проб в одному рядку теж: чекер дивиться лише на номер стрибка, адреси, час і зірочки.

Контрольна сума при ненульовому полі. Суму рахують тоді, коли поле суми вже заповнене чимось. Обнуліть його перед підрахунком. Якщо суми немає або вона хибна, ядро цілі не відповість, а ping покаже 100% втрат, і нічого в коді не підкаже, де помилка: подивіться tcpdump.

Порядок байтів. Поля id і seq лежать у пакеті в мережевому порядку (htons). Якщо ви порівнюєте без ntohs, то відповіді є, але «не ваші».

Довжина IP-заголовка. Припущення, що вона завжди 20 байтів, працює майже завжди. Використайте поле IHL.

Чужі пакети. Raw-сокет віддає копію кожного ICMP, який прийшов на хост. Якщо не перевіряти id, відповідь на чужий ping стане вашою.

ttl береться з власного запиту. У виводі ping це TTL відповіді, і він лежить в IP-заголовку, який ви отримали від raw-сокета.

Перша відповідь у traceroute приходить від цілі. Ви не виставили IP_TTL на сокеті, що відправляє, або виставили після sendto.

Час не в тих одиницях. tv_nsec лічить наносекунди, tv_usec мікросекунди. Чекер одразу покаже помилку: відповідач затримав відповідь на 100 мс, а ви надрукували 100000.

Додати -s (розмір даних) і побачити, на яких розмірах ping починає фрагментувати, а з прапором заборони фрагментації (IP_MTU_DISCOVER) перетворити власний ping на інструмент для пошуку MTU шляху. Перейти на SOCK_DGRAM з IPPROTO_ICMP і порівняти, що ядро вже робить за вас. Спробувати ICMP-проби замість UDP і зрозуміти, чому traceroute -I інколи проходить там, де UDP-проби відкидає файрвол.