Сети для самых маленьких. Часть 6: IPv4 — заголовок, маршрутизация и NAT

В пятой части мы разобрали адреса и маски и дошли до момента, когда хост решает: сосед или шлюз. Если шлюз — пакет уезжает роутеру, и дальше начинается то, ради чего сетевой уровень существует: маршрутизация, передача пакета от сети к сети, пока он не окажется в сегменте получателя. Сегодня — что роутер при этом читает в пакете, как выбирает путь и что меняет, что происходит с пакетом, который не влезает в кадр, и как домашний роутер прячет всю квартиру за одним адресом.


Заголовок IPv4

Двадцать байт без опций. Рисуют его традиционно строками по 32 бита:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
┌───────┬───────┬───────────┬───┬───────────────────────────────┐
│Version│  IHL  │   DSCP    │ECN│        Total Length           │
├───────┴───────┴───────────┴───┼─────┬─────────────────────────┤
│         Identification        │Flags│    Fragment Offset      │
├───────────────┬───────────────┼─────┴─────────────────────────┤
│      TTL      │   Protocol    │        Header Checksum        │
├───────────────┴───────────────┴───────────────────────────────┤
│                        Source Address                         │
├───────────────────────────────────────────────────────────────┤
│                      Destination Address                      │
├───────────────────────────────────────────────────────────────┤
│                    Options (если IHL > 5)                     │
└───────────────────────────────────────────────────────────────┘

По полям:

  • Version (4 бита) — 4. По этому полю стек различает IPv4 и IPv6, даже если EtherType уже сказал то же самое.
  • IHL (4 бита) — длина заголовка в 32-битных словах. Обычно 5, то есть 20 байт; больше — только с опциями.
  • DSCP (6 бит) — класс обслуживания: метка, по которой роутеры могут обслуживать голосовой трафик раньше загрузки файлов, если их так настроили. В интернете чаще всего игнорируется и обнуляется. ECN (2 бита) — роутер, у которого очередь переполняется, вместо того чтобы выбросить пакет, может поставить здесь метку «перегрузка», и получатель передаст её отправителю через TCP. Работает, когда включено с обеих сторон.
  • Total Length (16 бит) — длина всего пакета с заголовком, до 65 535 байт. Так стек узнаёт, где кончаются данные: Ethernet дописывает нули до 46 байт, и без этого поля их было бы не отличить от данных.
  • Identification, Flags, Fragment Offset — для фрагментации, раздел ниже. Из флагов важен DF (Don't Fragment).
  • TTL (8 бит) — Time To Live. Несмотря на имя, не время, а счётчик хопов: каждый роутер уменьшает его на единицу, и пакет с TTL, дошедшим до нуля, выбрасывается, а отправителю уходит сообщение ICMP (часть 8). Без него пакет, попавший в петлю маршрутизации, крутился бы вечно, как кадр в петле Ethernet. Начальные значения: 64 в Linux и macOS, 128 в Windows, 255 у сетевого оборудования; по TTL ответа часто можно угадать ОС и число роутеров до неё.
  • Protocol (8 бит) — поле «что внутри»: 1 ICMP, 6 TCP, 17 UDP.
  • Header Checksum (16 бит) — контрольная сумма только заголовка. Данные IP не проверяет: это забота TCP и UDP. Поскольку TTL меняется на каждом хопе, каждый роутер пересчитывает сумму заново.
  • Source, Destination — адреса из прошлой части. Доходят до получателя неизменными — пока в дело не вмешается NAT.
  • Options — встречаются так редко, что многие файрволы отбрасывают пакеты с ними. Исторические: записать маршрут, поставить метки времени, задать путь явно.

В tcpdump с ключом -v эти поля видны:

$ sudo tcpdump -i eth0 -n -v -c 1 icmp
12:00:00.000000 IP (tos 0x0, ttl 64, id 54321, offset 0, flags [DF], proto ICMP (1), length 84)
    192.168.1.10 > 203.0.113.10: ICMP echo request, id 7, seq 1, length 64

TTL 64, флаг DF, пакет на 84 байта: 20 заголовка и 64 данных.


Таблица маршрутов

Роутер — это машина с несколькими интерфейсами и включённой пересылкой: пакет, пришедший с одного интерфейса и адресованный не ей самой, она отправляет дальше. Решение «куда» принимается по таблице маршрутов. Каждая строка — префикс и что с ним делать:

$ ip route
default via 198.51.100.1 dev eth1
10.0.0.0/8 via 192.168.1.254 dev eth0
10.1.0.0/16 via 192.168.1.253 dev eth0
10.1.2.0/24 dev eth2 proto kernel scope link src 10.1.2.1
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.1
198.51.100.0/30 dev eth1 proto kernel scope link src 198.51.100.2

Виды строк:

  • Подключённые (dev eth0 без via): сеть прямо за интерфейсом, появляются сами при назначении адреса. Пакет отдаётся получателю напрямую.
  • Через шлюз (via): следующий роутер, next hop, который обязан быть в подключённой сети, иначе до него не добраться.
  • По умолчанию (default, он же 0.0.0.0/0): куда слать всё, что не подошло ни под одну строку. У домашнего роутера это провайдер; у роутеров в ядре интернета маршрута по умолчанию нет, у них полная таблица на ~миллион префиксов.

Откуда строки берутся: подключённые — от адресов, статические — руками администратора (ip route add), динамические — от протоколов маршрутизации, по которым роутеры рассказывают друг другу о своих сетях. Внутри организации это чаще всего OSPF: каждый роутер рассылает описание своих линков, все собирают одинаковую карту сети и считают по ней кратчайшие пути. Между провайдерами — BGP: роутеры обмениваются префиксами вместе со списком сетей, через которые префикс прошёл, и выбирают путь по этому списку и политике. Обоим протоколам можно посвятить по серии; про BGP на практике у меня есть отдельная. Для этой статьи важно, что в таблицу они кладут те же самые строки.

Самый длинный префикс

Один адрес может подходить под несколько строк. Пакет на 10.1.2.50 подходит под 10.0.0.0/8, 10.1.0.0/16, 10.1.2.0/24 и default. Правило одно: побеждает самый длинный префикс (longest prefix match). Логика в том, что более длинный префикс — более точное знание: «вся 10.0.0.0/8 вон там» — общее правило, а «10.1.2.0/24 прямо за eth2» — исключение из него.

Назначение Подходящие строки Выбрана Куда
10.1.2.50 /8, /16, /24, default 10.1.2.0/24 напрямую через eth2
10.1.7.1 /8, /16, default 10.1.0.0/16 шлюзу 192.168.1.253
10.9.9.9 /8, default 10.0.0.0/8 шлюзу 192.168.1.254
203.0.113.10 default 0.0.0.0/0 шлюзу 198.51.100.1

Если и префикс одинаковый, сравнивают метрику — число, которое ставит тот, кто добавил маршрут (меньше — лучше). Так ноутбук с кабелем и Wi-Fi держит два маршрута по умолчанию и пользуется кабельным, пока он есть: у него метрика 100, у беспроводного 600.

Хост из прошлой части, принимавший решение «сосед или шлюз», делал ровно то же самое: его таблица — это подключённая сеть и маршрут по умолчанию, и алгоритм один для всех.

Что роутер делает с пакетом

Проследим пакет от ноутбука 192.168.1.10 к серверу 203.0.113.10 через домашний роутер R1 и роутер провайдера R2. У R1 интерфейсы 192.168.1.1 (в квартиру) и 198.51.100.2 (к провайдеру), у R2 — 198.51.100.1 и дальше в интернет.

 ноутбук ─────── R1 ─────────── R2 ───── … ───── сервер
 192.168.1.10    .1 | .2        .1
                 192.168.1.0/24   198.51.100.0/30

На каждом хопе происходит одно и то же:

  1. Снять заголовок Ethernet, проверить контрольную сумму заголовка IP.
  2. Адрес назначения мой? Нет — пересылать.
  3. TTL − 1. Стал нулём — выбросить, отправителю ICMP Time Exceeded.
  4. Найти маршрут по самому длинному префиксу: исходящий интерфейс и next hop. Нет ни одного — выбросить, отправителю ICMP Destination Unreachable.
  5. Пересчитать Header Checksum.
  6. Упаковать в новый кадр: MAC отправителя — свой исходящий интерфейс, MAC получателя — next hop (или сам получатель, если сеть подключённая). Отправить.
Участок MAC src → dst IP src → dst TTL
ноутбук → R1 ноутбук → R1 eth0 192.168.1.10 → 203.0.113.10 64
R1 → R2 R1 eth1 → R2 192.168.1.10 → 203.0.113.10 63
R2 → дальше R2 → следующий 192.168.1.10 → 203.0.113.10 62

Роутер не хранит ничего о пакете после отправки: каждый пакет — независимое решение по таблице. Он не знает о соединениях, не помнит предыдущий пакет и не заботится, дошёл ли этот. Это главное свойство IP — best effort, «постараемся»: доставка без гарантий, а гарантии, если нужны, строит TCP.

Отсюда же следствие, о которое спотыкаются: путь туда и путь обратно — два независимых решения разных роутеров. Ответ сервера может пойти другой дорогой, и это нормально, пока по дороге не стоит что-то, ожидающее увидеть оба направления. В таблице выше R1 — частный адрес 192.168.1.10 в поле отправителя. В интернет такой пакет не выпустят, а если выпустят — ответ на 192.168.1.10 не найдёт дорогу назад. Поэтому на R1 нужен NAT, и о нём ниже.


Фрагментация

MTU Ethernet — 1500 байт, и это самый частый случай, но не единственный: в PPPoE 1492, в туннелях меньше, а в старых сетях бывало и 576. Что делает роутер, если пакет длиннее MTU исходящего интерфейса?

Если флаг DF не установлен — режет пакет на фрагменты. Каждый получает копию заголовка IP с тем же Identification, в Fragment Offset пишется смещение данных фрагмента от начала исходных данных в восьмибайтовых единицах, а флаг MF (More Fragments) стоит у всех фрагментов, кроме последнего. Пакет на 4000 байт в MTU 1500:

Фрагмент Total Length Данных Offset (×8) MF
1 1500 1480 0 1
2 1500 1480 185 1
3 1040 1020 370 0

Данных в фрагменте 1480, потому что 20 байт уходит на заголовок, а 1480 делится на 8. Собирает фрагменты обратно только получатель: роутеры по пути этого не делают и могут резать уже порезанное. Получатель ждёт все части по Identification, складывает по Offset и, получив последний (MF = 0) без дыр, отдаёт пакет транспорту.

Почему фрагментации стараются избегать:

  • Потеря одного фрагмента — потеря всего пакета, а повторит его TCP целиком, и он снова будет порезан.
  • Заголовок транспорта (порты) есть только в первом фрагменте, и файрволу, NAT, балансировщику приходится либо собирать пакет у себя, либо пропускать фрагменты вслепую. Многие просто выбрасывают.
  • Сборка — это буферы и таймеры на получателе; в 1990-х на этом строились атаки.

Поэтому современные стеки ставят DF на всё подряд. Пакет с DF, не влезающий в MTU, роутер выбрасывает и шлёт отправителю ICMP «Fragmentation Needed» с значением MTU, а отправитель уменьшает размер. Это Path MTU Discovery, и подробно, вместе с тем, что бывает, когда ICMP не доходит, — в части 8.


NAT

Частные адреса не маршрутизируются в интернете, а публичных на все устройства не хватает. NAT (Network Address Translation) — подмена адресов в пакете на границе между частной сетью и интернетом. В быту «NAT» означает его самый распространённый вид, который формально называется NAPT или PAT (с портами), а в Linux — masquerade.

Таблица трансляций

Роутер R1 с публичным адресом 198.51.100.2. Ноутбук 192.168.1.10 открывает соединение с порта 51234 на сервер 203.0.113.10:443.

 ┌──────────────┐                 ┌────────────── R1 ──────────────┐                 ┌──────────────┐
 │ 192.168.1.10 │ ──── (1) ────►  │ 192.168.1.1     198.51.100.2   │ ──── (2) ────►  │ 203.0.113.10 │
 │   :51234     │                 │                                │                 │   :443       │
 │              │ ◄─── (4) ─────  │   таблица трансляций           │ ◄─── (3) ─────  │              │
 └──────────────┘                 └────────────────────────────────┘                 └──────────────┘

 (1) 192.168.1.10:51234 → 203.0.113.10:443
 (2) 198.51.100.2:51234 → 203.0.113.10:443      ← src подменён
 (3) 203.0.113.10:443 → 198.51.100.2:51234      ← сервер отвечает роутеру
 (4) 203.0.113.10:443 → 192.168.1.10:51234      ← dst восстановлен по таблице
  1. Пакет приходит на R1 из частной сети. R1 ищет в таблице трансляций запись для 192.168.1.10:51234 → 203.0.113.10:443. Нет — создаёт: «192.168.1.10:51234 ↔ 198.51.100.2:51234, к 203.0.113.10:443, протокол TCP». Если порт 51234 на публичном адресе уже занят другой внутренней машиной, R1 выберет другой свободный, и во внешней записи будет, например, 198.51.100.2:40001.
  2. R1 переписывает адрес отправителя на 198.51.100.2 и, если нужно, порт отправителя. Пересчитывает контрольную сумму заголовка IP и — внимание — контрольную сумму TCP, потому что в неё входят адреса из заголовка IP. Роутер лезет на уровень выше своего: вот где течёт абстракция из первой части.
  3. Сервер ничего не знает о 192.168.1.10. Он видит соединение от 198.51.100.2:51234 и отвечает на него.
  4. Ответ приходит на R1. По паре «публичный порт 51234, от 203.0.113.10:443» R1 находит запись и переписывает адрес получателя обратно на 192.168.1.10:51234. Снова две контрольные суммы. Отправляет в частную сеть.

Записи живут, пока по ним идёт трафик, и удаляются по таймауту: у установленного TCP-соединения в Linux это пять суток, у UDP — 30 секунд, если трафик шёл в одну сторону, и 180 секунд, если в обе. Поэтому «тихие» соединения через NAT приходится поддерживать keepalive-пакетами, иначе запись исчезнет, и следующий пакет от сервера роутер не сможет никуда отнести.

Один публичный адрес — 65 тысяч портов, значит, десятки тысяч одновременных соединений от всей квартиры. Для дома это бесконечность, и провайдер, делающий то же самое для тысяч абонентов на одном адресе (CGNAT, Carrier-Grade NAT, с адресами 100.64.0.0/10 между абонентом и провайдером), уже вынужден делить порты квотами.

Что ломается

NAT держится на том, что соединение начал кто-то изнутри. Пакет из интернета, для которого в таблице нет записи, R1 некуда отнести: внутренних адресов много, а в пакете только публичный. Он выбрасывается. Отсюда:

  • Входящие соединения невозможны без ручной записи. Такая запись называется проброс портов (port forwarding, DNAT): «всё, что пришло на 198.51.100.2:8080, отправлять на 192.168.1.50:80». Это постоянная строка в таблице, не зависящая от трафика.
  • Один порт — один внутренний сервер. Два веб-сервера за одним адресом на 443 не повесить.
  • Протоколы, передающие адреса в данных. Старый FTP сообщает серверу «подключись ко мне на 192.168.1.10:5000», SIP-телефония вставляет свой адрес в сообщение. NAT их не видит. Для них в роутерах живут ALG (application layer gateway): модули, которые разбирают данные конкретного протокола и переписывают адреса и там. Работают через раз.
  • Два устройства за одним NAT, пытающиеся соединиться напрямую (звонок, игра), оба за своими роутерами. Ни один не может начать. Техника hole punching — оба одновременно шлют пакеты наружу, чтобы каждый NAT завёл запись для другого, подробно в части 9 — помогает не всегда, и тогда трафик идёт через сервер-посредник.
  • Hairpin. Обращение изнутри сети на свой же публичный адрес: пакет приходит на R1 с внутренней стороны и адресован R1 же. Не все домашние роутеры умеют развернуть его обратно внутрь.
  • Конец сквозной связности. Задумка интернета была в том, что любой адрес достижим с любого. NAT её отменил, и значительная часть современных протоколов — обход этого факта.

NAT часто путают с файрволом. Он действительно не пропускает внутрь ничего незапрошенного, но не по политике, а потому что не знает, куда. Собственно файрвол — отдельные правила, обычно на том же роутере.

В Linux NAT делает модуль conntrack, который и ведёт таблицу, а включается одним правилом:

$ sysctl -w net.ipv4.ip_forward=1
$ iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

«Всё, что уходит через eth1, подменять на адрес eth1». Таблицу видно так:

$ conntrack -L
tcp  6 431999 ESTABLISHED src=192.168.1.10 dst=203.0.113.10 sport=51234 dport=443 \
     src=203.0.113.10 dst=198.51.100.2 sport=443 dport=51234 [ASSURED] use=1

Первая половина строки — как пакет выглядит изнутри, вторая — как должен выглядеть ответ снаружи. 431999 — секунды до удаления записи.


Смотрим руками

Решение таблицы маршрутов для конкретного адреса, с интерфейсом, next hop и адресом отправителя, который будет подставлен:

$ ip route get 203.0.113.10
203.0.113.10 via 192.168.1.1 dev eth0 src 192.168.1.10 uid 1000

Добавить маршрут и удалить:

$ sudo ip route add 10.1.0.0/16 via 192.168.1.253
$ sudo ip route del 10.1.0.0/16

Превратить машину в роутер — включить пересылку (по умолчанию выключена, и машина молча выбрасывает чужие пакеты):

$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0

Увидеть маршрутизацию глазами пакета поможет traceroute — ему посвящён раздел в части 8, когда дойдём до ICMP, на котором он построен. А пока достаточно одного наблюдения в tcpdump -v: если захватить один и тот же пакет на входе и выходе роутера, в нём изменится TTL, контрольная сумма и оба MAC-адреса, а адреса IP — только если роутер делает NAT.


Что запомнить

  • Заголовок IPv4 — 20 байт: адреса, TTL, Protocol, длина, поля фрагментации, контрольная сумма только заголовка.
  • TTL — счётчик хопов против петель; каждый роутер уменьшает его и пересчитывает контрольную сумму.
  • Роутер ищет в таблице самый длинный подходящий префикс, при равенстве — меньшую метрику; подключённые сети, статические маршруты и маршрут по умолчанию — три вида строк. Каждый пакет — независимое решение, без состояния.
  • Пакет длиннее MTU режется на фрагменты с общим Identification, собирает их только получатель; современные стеки ставят DF и полагаются на ICMP.
  • NAT подменяет адрес и порт отправителя, ведёт таблицу трансляций и восстанавливает адрес в ответах; входящие соединения требуют проброса, протоколы с адресами внутри ломаются, сквозной связности нет.

Дальше — ARP: как хост, зная IP-адрес соседа, узнаёт его MAC-адрес, что лежит в кэше соседей и как долго, что такое gratuitous ARP и почему подменить чужой адрес в локальной сети так просто.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.