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

IPv4 і адресація

ping до сервера проходить, ssh заходить, а сайт не відкривається: сторінка починає вантажитися й зависає. Або навпаки: маленькі запити працюють, а завантаження файлу зупиняється на першому ж мегабайті. Типова причина таких випадків, і саме про неї цей модуль: пакет завбільшки 1500 байтів не пролазить через якусь вузьку ланку, а повідомлення про це загубилося по дорозі.

Щоб до цього дійти, доведеться розібратися з IPv4 і ICMP: з чого складається пакет, як хост вирішує, чи адресат «свій», і що означають відповіді, які приходять від маршрутизаторів.

Передумови. Кадр, MAC-адреса, MTU — модуль 4. ARP і таблиця сусідів — модуль 5. Двійкова система: вміння записати число 77 як 01001101.

IPv4-адреса складається з 32 бітів. Їх пишуть як чотири десяткові числа від 0 до 255 через крапку: 192.168.10.77. Кожне число — один байт, тому й називається октетом.

Адреса нічого не означає без знання, де закінчується «мережа» і починається «вузол у ній». Хост має це знати, бо від цього залежить вибір: якщо адресат у тій самій мережі, кадр адресується йому напряму (через ARP); якщо в іншій, кадр іде на шлюз. Межу задає префікс: число лівих бітів, які належать мережі. Запис 192.168.10.77/26 каже, що перші 26 біт адреси — номер мережі, а останні 6 — номер вузла всередині неї.

Той самий префікс записують і маскою: 26 одиниць і 6 нулів, тобто 255.255.255.192. Щоб з адреси дізнатися мережу, достатньо побітового «І» адреси й маски.

Адреса 192.168.10.77 з префіксом 26 у двійковому записі. Перші 26 біт належать мережі, останні 6 — вузлу. Побітове І адреси з маскою 255.255.255.192 дає адресу мережі 192.168.10.64.26 біт мережі6 біт вузлаадреса11000000101010000000101001001101192.168.10.77маска /2611111111111111111111111111000000255.255.255.192мережа (адреса І маска)11000000101010000000101001000000192.168.10.64
Адреса 192.168.10.77/26 у двійковому записі. Зафарбовані 26 лівих бітів належать мережі, решта шість — вузлу. «І» з маскою обнуляє біти вузла й залишає адресу мережі 192.168.10.64.

Для 192.168.10.77/26 звідси виходить:

  • Адреса мережі — біти вузла всі нулі: 192.168.10.64. Вона ідентифікує мережу й вузлу не призначається.
  • Широкомовна адреса — біти вузла всі одиниці: 192.168.10.127. Пакет на неї адресований усім у підмережі. Її теж не призначають.
  • Вузлів у цій підмережі 2⁶ − 2 = 62: від 192.168.10.65 до 192.168.10.126. Кожен додатковий біт префікса вдвічі зменшує розмір підмережі.

Розбити 10.0.0.0/24 на чотири рівні підмережі означає взяти два біти зі сфери вузла: 10.0.0.0/26, 10.0.0.64/26, 10.0.0.128/26, 10.0.0.192/26. Кожна має по 62 вузли. Не треба ділити в десятковому записі: достатньо подивитися, де в двійковому записі закінчується префікс.

Навіщо префікс, а не класи

Section titled “Навіщо префікс, а не класи”

До 1993 року межа визначалася самою адресою: «клас A» (/8), «клас B» (/16), «клас C» (/24). Організації, яким не вистачало 254 адрес класу C, отримували цілий клас B на 65 тисяч і марнували решту. Тому вигадали CIDR (classless inter-domain routing): префікс може мати довільну довжину, а маршрутизатори пересилають пакети за префіксами, не зважаючи на класи. Це також дозволило об’єднувати сусідні мережі в один запис в таблицях інтернету. Про вибір запису в таблиці маршрутів — модуль 7.

Не кожна адреса придатна для інтернету. Запам’ятайте ті, що траплятимуться:

Діапазон Призначення
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 приватні мережі (RFC 1918): не маршрутизуються в інтернеті, кожна організація користується ними вільно
100.64.0.0/10 спільна адресація операторів (CGNAT, RFC 6598), про неї в модулі 9
127.0.0.0/8 loopback: пакет ніколи не залишає хост; 127.0.0.1 зазвичай називається localhost
169.254.0.0/16 link-local (RFC 3927): хост сам обирає адресу, якщо не отримав ніякої, і вона діє лише в межах сегмента
192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 для документації й прикладів (RFC 5737); саме їх використовує цей курс
224.0.0.0/4 multicast
255.255.255.255 обмежене широкомовлення: «усім у цьому сегменті»
0.0.0.0 «невизначена адреса»: «будь-яка» для сервера, що слухає, або «ще не маю адреси»

Якщо хост несподівано має адресу з 169.254.0.0/16, значить, DHCP-сервера він не знайшов. Це перше, що варто перевіряти.

Кожен пакет IPv4 починається з заголовка мінімум 20 байтів.

Заголовок IPv4 без опцій, 20 байтів у п’яти рядках по 32 біти: версія і довжина заголовка, DSCP і ECN, повна довжина; ідентифікатор, прапорці DF і MF, зсув фрагмента; TTL, протокол, контрольна сума; адреса відправника; адреса одержувача.08162431версія 4IHLDSCP / ECNповна довжинаідентифікаторпрапорцізсув фрагментаTTLпротоколконтрольна сума заголовкаадреса відправникаадреса одержувача
Заголовок IPv4 без опцій. Виділено поля, з якими працюють цей і наступні розділи: TTL, прапорці з фрагментацією й адреси. Кожен рядок — 32 біти.

Справжній заголовок пакета ping, знятий tcpdump -x (шістнадцяткові байти), читається так:

45 00 0054 d7b5 4000 40 01 4bf0 0a00 0102 0a00 0202
  • 4 — версія, 5 — довжина заголовка в 32-бітних словах (5 × 4 = 20 байтів);
  • 00 — DSCP і ECN (пріоритет і позначка перевантаження);
  • 0054 — повна довжина пакета: 84 байти (20 заголовка + 64 ICMP);
  • d7b5 — ідентифікатор, за ним збирають фрагменти;
  • 4000 — прапорці й зсув: 010 у старших трьох бітах — встановлений DF (don’t fragment), зсув нуль;
  • 40 — TTL 64;
  • 01 — протокол верхнього рівня: 1 — ICMP, 6 — TCP, 17 — UDP;
  • 4bf0 — контрольна сума заголовка;
  • 0a00 0102 — адреса відправника 10.0.1.2; 0a00 0202 — отримувача 10.0.2.2.

Контрольна сума покриває лише заголовок, не дані. Її складають у 16-бітових словах з перенесенням старшого розряду, і коректний заголовок дає 0xffff. Кожен маршрутизатор зменшує TTL, тож мусить перерахувати суму. Контрольну суму даних перевіряють протоколи вище (TCP, UDP), а для IPv6 її в заголовку взагалі прибрали.

TTL (time to live) кожен маршрутизатор зменшує на одиницю, і коли значення стає нулем, пакет відкидається. Так пакет не ходитиме мережею вічно, якщо утворилася маршрутна петля. Початкове значення кожна ОС обирає свою, для Linux це 64. За TTL у відповіді можна грубо оцінити, скільки маршрутизаторів вона пройшла, якщо знати, з якого значення вона стартувала.

ICMP: мережа відповідає про себе

Section titled “ICMP: мережа відповідає про себе”

Пакет може не дійти, і мережа має повідомити про це відправника. Для цього є ICMP (internet control message protocol). Це окремий протокол із номером 1, що їде всередині IP. Хост чи маршрутизатор, який щось не зміг зробити, надсилає ICMP-повідомлення відправникові. Повідомлення про помилку містить початок пакета, через який воно виникло, щоб отримувач зіставив його з власним потоком.

Важливі типи:

Тип Значення Коли виникає
8 / 0 echo request / echo reply саме їх використовує ping
3 destination unreachable код 0 — мережа недоступна, 1 — хост, 3 — порт (UDP-порт закритий), 4 — потрібна фрагментація, але встановлено DF
11 time exceeded код 0 — TTL дійшов до нуля дорогою; код 1 — не вдалося зібрати фрагменти вчасно
5 redirect маршрутизатор радить використати інший шлюз

Даних ICMP не переносить, він потрібен для діагностики. Проте коли файрволи його блокують, з’являються дивні поломки, про них нижче.

Задача: дізнатися, через які маршрутизатори пройшов пакет до адресата. Жоден вузол не повідомляє про себе, тому traceroute хитрує з TTL.

  1. Надіслати пакет із TTL 1. Перший маршрутизатор зменшить його до нуля, відкине пакет і відповість ICMP time exceeded зі своєї адреси. Це перший рядок виводу.

  2. Надіслати пакет із TTL 2. Перший маршрутизатор пропустить, другий відкине й відповість. Другий рядок.

  3. Так далі, поки пакет не дійде до адресата. За замовчуванням traceroute відправляє UDP на високий порт, який ніхто не слухає (починаючи з 33434). Адресат відповідає ICMP port unreachable, і за цією відповіддю traceroute розуміє, що дійшов.

З висновками з виводу краще не поспішати. Маршрутизатор відповідає зі своєї адреси на інтерфейсі, до якого потрапив пакет, а зворотна дорога пакета може бути зовсім іншою. Рядок із зірочками * * * означає, що відповіді не було: маршрутизатор нічого не надіслав (файрвол або обмеження швидкості ICMP, яке ядро Linux вмикає за замовчуванням: net.ipv4.icmp_ratelimit дорівнює 1000 мс, тому при швидких повторних запусках відповіді можуть губитися), але це не значить, що пакети через нього не йдуть. Якщо в мережі кілька рівноцінних маршрутів, різні пакети одного трасування можуть піти різними шляхами.

Фрагментація і Path MTU Discovery

Section titled “Фрагментація і Path MTU Discovery”

Модуль 4 закінчився тим, що кадр Ethernet несе до 1500 байтів навантаження, а пакет IP може мати розмір до 65 535 байтів. Коли пакет більший за MTU ланки, куди його треба відправити, IPv4 дозволяє фрагментацію: маршрутизатор розрізає пакет на частини, які влізають, і надсилає кожну окремим пакетом IP. Усі фрагменти мають однаковий Identification. Поле Fragment Offset (у вісімках байтів) каже, де фрагмент стояв у початковому пакеті, а прапорець MF (more fragments) — що за ним є ще. Склеює фрагменти лише адресат, не проміжні вузли. Подивіться, як виглядає відповідь на ping -s 3000 у tcpdump -v:

IP (ttl 64, id 34195, offset 0, flags [+], proto ICMP (1), length 1500)
IP (ttl 64, id 34195, offset 1480, flags [+], proto ICMP (1), length 1500)
IP (ttl 64, id 34195, offset 2960, flags [none], proto ICMP (1), length 68)

З фрагментацією багато клопоту. Один втрачений фрагмент означає втрату всього пакета, бо решту доведеться чекати даремно. Адресат мусить тримати фрагменти в пам’яті, що давало простір для атак. Файрволи й NAT, які дивляться на порти, не бачать їх у фрагментах, крім першого. Сучасні програми фрагментації уникають.

Її можна й не допустити: відправник виставляє біт DF і дізнається, яким має бути розмір. Так працює Path MTU Discovery (PMTUD):

  1. Відправник посилає пакети з DF, за розміром свого MTU.

  2. Якщо якась ланка має менший MTU, маршрутизатор не може ні передати пакет, ні фрагментувати його, тож відкидає й надсилає ICMP: тип 3, код 4 («потрібна фрагментація, але встановлено DF»). У повідомленні вказано MTU, який підходить.

  3. Відправник запам’ятовує цей MTU для шляху до адресата й надалі шле пакети меншого розміру. TCP для цього зменшує розмір сегмента.

Path MTU Discovery. Відправник шле пакет 1500 байтів із бітом DF. Ланка між маршрутизаторами має MTU 1400, маршрутизатор відкидає пакет і повертає ICMP «потрібна фрагментація, MTU 1400». Вгорі відправник отримує ICMP і зменшує пакети. Внизу файрвол відкидає ICMP, і відправник нескінченно повторює пакет, який ніколи не пройде.PMTUD працюєMTU 1500MTU 1400MTU 15001500 Б, DFчорна діраMTU 1500MTU 1400MTU 15001500 Б, DFICMP: frag needed, MTU 1400повтори без відповідіICMP відкинуто
Угорі PMTUD працює: маршрутизатор повідомляє, що можна 1400, відправник підлаштовується. Унизу файрвол відкидає ICMP, відправник не отримує відповіді й повторює той самий пакет, який ніколи не пройде.

Увесь механізм тримається на тому, що ICMP-повідомлення доходить. Якщо якийсь файрвол відкидає весь ICMP «заради безпеки», відправник ніколи не дізнається про малий MTU. TCP-з’єднання при цьому встановлюється, бо пакети рукостискання малі. Потім великі пакети тихо зникають, відповіді нема, і після вичерпання спроб з’єднання зависає. ping працює, бо його пакети теж малі, і виходить та сама картина, що у вступі.

Найчастіше так буває там, де MTU менший за 1500: тунелі VPN, PPPoE (типове значення MTU 1492), обгортки на кшталт GRE чи VXLAN (модуль 18), де додаткові заголовки з’їдають частину кадра. Що з цим роблять:

  • Не блокувати ICMP тип 3 код 4. Решта ICMP може бути заблокована, цей обов’язково ні.
  • MSS clamping: маршрутизатор на вузькій ланці переписує розмір сегмента в рукостисканнях TCP, і сторони самі не надсилають більших пакетів, ніж здатна пропустити ланка.
  • Packetization-layer PMTUD (RFC 4821): TCP сам пробує розміри й визначає межу за відсутністю підтвердження. У Linux вмикається параметром net.ipv4.tcp_mtu_probing.

Будуємо ланцюжок із двох хостів і двох маршрутизаторів у просторах імен (команди ip netns, ip link add … type veth, ip addr, ip route — за модулем 4; кожен маршрутизатор додатково потребує sysctl -w net.ipv4.ip_forward=1, про це в модулі 7). Топологія така:

h1 10.0.1.2 — 10.0.1.1 r1 10.0.12.1 — 10.0.12.2 r2 10.0.2.1 — 10.0.2.2 h2

У h1 і h2 типовий маршрут веде на сусіднього маршрутизатора, а між r1 і r2 прописані маршрути до далеких підмереж. Перевіримо зв’язок і шлях:

Terminal window
sudo ip netns exec h1 traceroute -n 10.0.2.2
traceroute to 10.0.2.2 (10.0.2.2), 30 hops max, 60 byte packets
1 10.0.1.1 0.019 ms 0.003 ms 0.019 ms
2 10.0.12.2 0.188 ms 0.004 ms 0.003 ms
3 10.0.2.2 0.010 ms 0.008 ms 0.004 ms

Три рядки — три стрибки, кожен відповів зі своєї адреси. Якщо додати -I, traceroute шлє ICMP echo замість UDP. Подивіться на TTL: ping -c1 -t 1 10.0.2.2 закінчується повідомленням Time to live exceeded від першого маршрутизатора.

Тепер підберемо розмір. Зменшимо MTU ланки r1 — r2 до 1400 з обох боків (ip link set c1 mtu 1400 на r1 і ip link set d1 mtu 1400 на r2) і відправимо з h1 найбільший пакет, що влізає в 1500, із забороною фрагментації:

Terminal window
sudo ip netns exec h1 ping -c1 -M do -s 1472 10.0.2.2
PING 10.0.2.2 (10.0.2.2) 1472(1500) bytes of data.
From 10.0.1.1 icmp_seq=1 Frag needed and DF set (mtu = 1400)

Тут розмір 1472 — це 1500 мінус 20 байтів заголовка IP і 8 байтів заголовка ICMP. Маршрутизатор r1 відповів типом 3 кодом 4 і повідомив MTU. Ядро h1 запам’ятало це значення:

Terminal window
sudo ip netns exec h1 ip route get 10.0.2.2
10.0.2.2 via 10.0.1.1 dev a1 src 10.0.1.2 uid 0
cache expires 599sec mtu 1400

Запис із mtu 1400 живе близько десяти хвилин. Наступний ping -M do -s 1372 (1400 байтів разом із заголовками) проходить. Щоб побачити PMTUD самому, є tracepath -n 10.0.2.2 (пакет iputils-tracepath): він збільшує TTL, як traceroute, і водночас показує pmtu на кожному кроці. Виводить pmtu 1400 на другому кроці й підсумок Resume: pmtu 1400 hops 3. Те саме вміє traceroute --mtu.

А тепер зробимо чорну діру. На r1 заборонимо вихідні ICMP «destination unreachable» і очистимо кеш у h1:

Terminal window
sudo ip netns exec r1 nft add table inet f
sudo ip netns exec r1 nft 'add chain inet f out { type filter hook output priority 0; }'
sudo ip netns exec r1 nft add rule inet f out icmp type destination-unreachable drop
sudo ip netns exec h1 ip route flush cache
sudo ip netns exec h1 ping -c2 -W1 -M do -s 1472 10.0.2.2
sudo ip netns exec h1 ping -c2 -W1 -M do -s 1372 10.0.2.2
2 packets transmitted, 0 received, 100% packet loss, time 1003ms
2 packets transmitted, 2 received, 0% packet loss, time 1011ms

Великий пакет пропав без жодного повідомлення, а менший пройшов. Тепер те саме з TCP: простий сервер на h2 читає дані, клієнт на h1 відсилає спершу 10 байтів, а потім 20 000. Сервер отримав перші десять і більше нічого:

server got 10 bytes, then TimeoutError

ss -tin dst 10.0.2.2 на клієнті показує, що сокет застряг: pmtu:1500, є повторні передачі (retrans, backoff), і в черзі лишилися неподані байти. Ядро не знає про вузьку ланку й упирається в неї.

Для порядку про фрагментацію: ping -c1 -s 3000 -M dont 10.0.2.2 відправляє пакет без DF. Якщо ви знімете його tcpdump -v на ланці з MTU 1400, то побачите фрагменти (flags [+], offset). Лічильники nstat -az IpFragCreates IpReasmOKs покажуть, скільки фрагментів створив маршрутизатор і скільки пакетів склав адресат.

Прибрати за собою: видаліть простори імен (sudo ip netns delete h1 тощо).

Типові помилки розуміння

Section titled “Типові помилки розуміння”

«Маска /24 завжди означає 254 хости». Це арифметика для /24: 2⁸ − 2. Для /26 виходить 62, для /30 лише 2. Число хостів дорівнює 2 у степені кількості бітів вузла, мінус адреса мережі й широкомовна.

«Приватна адреса не може бути в інтернеті». Вона нікуди не заборонена, просто маршрутизатори провайдерів її не пересилають. Тому на межі організації потрібен NAT (модуль 9), а помилкове витікання приватних адрес назовні зазвичай означає, що пакет не дійде.

«TTL — це час у секундах». Назва історична. Фактично це лічильник стрибків. Кожен маршрутизатор віднімає одиницю незалежно від того, скільки пакет там простояв.

«traceroute показує шлях, яким піде мій трафік». Показує шлях пробних пакетів, і то лише тих маршрутизаторів, що відповіли. Трафік може ходити іншим шляхом, а зворотний маршрут взагалі не видно.

«ICMP небезпечний, його можна блокувати повністю». Повне блокування ICMP ламає PMTUD і призводить до тих самих «зависань великих передач». Потрібно пропускати хоча б тип 3 код 4 (і «time exceeded» для діагностики).

«Фрагментація — нормальне рішення для великих пакетів». У IPv4 вона можлива, але дорога й ненадійна, а в IPv6 проміжні маршрутизатори не фрагментують зовсім: лише відправник.

Перевір себе

1. Адреса 172.16.5.130/25. Яка адреса мережі й яка широкомовна?
2. Скільки вузлів можна адресувати в підмережі /28?
3. Як traceroute дізнається адресу другого маршрутизатора на шляху?
4. Чому в IPv4 фрагмент пакета не збирається на першому ж маршрутизаторі після вузької ланки?
5. Ping проходить, ssh заходить, а scp великого файлу зависає. Пакети йдуть з бітом DF, а на одній ланці шляху MTU 1400. Яка найімовірніша причина?
6. Що роблять MSS clamping і net.ipv4.tcp_mtu_probing?

A2. Власні ping і traceroute: ви сформуєте заголовок IPv4 і ICMP вручну, порахуєте контрольну суму й зробите те, що описано в розділах про TTL і ICMP, на raw-сокетах.

B2. Linux як маршрутизатор: три підмережі, адреси, маршрути й NAT. Тут знадобиться вміння ділити підмережі.

A3. Розбір пакетів із pcap: ви розберете заголовок IPv4 за байтами, так само, як у розділі «Заголовок IPv4».

B7. Налагодження зламаної мережі: серед несправностей буде й MTU; тепер ви знаєте, як вона виглядає.

  • RFC 791 (IP), RFC 792 (ICMP), RFC 1191 (Path MTU Discovery), RFC 1918 (приватні адреси), RFC 4632 (CIDR), RFC 3927 (link-local), RFC 5737 (адреси для документації), RFC 6598 (спільна адресація)
  • RFC 4821 (Packetization Layer Path MTU Discovery)
  • Kurose, Ross, Computer Networking: A Top-Down Approach, розділ про мережевий рівень
  • Stevens, TCP/IP Illustrated, Vol. 1, розділи про IP, ICMP і фрагментацію
  • man 8 ip-route, man 8 traceroute, man 8 tracepath, man 8 ping
  • Документація ядра Linux: Documentation/networking/ip-sysctl.rst