Анатомия сетевого пакета: как Linux видит сеть

Вводный курс в архитектуру сетевого стека Linux, объясняющий путь данных от электрического сигнала до системного вызова. Вы изучите структуру инкапсуляции через призму ядра и научитесь использовать утилиту ip для базового анализа интерфейсов.

Модель OSI сквозь призму ядра Linux: уровни абстракции и границы ответственности

Модель OSI сквозь призму ядра Linux: уровни абстракции и границы ответственности

Когда вы вводите в терминале команду curl google.com, ваш компьютер за доли секунды отправляет текстовый запрос, который превращается в электрические импульсы на сетевом кабеле или радиоволны Wi-Fi. Как простой текст трансформируется в физический сигнал? В операционных системах, таких как Linux, эта магия строго регламентирована и напоминает конвейер на заводе, где каждый цех выполняет только свою узкую задачу, не интересуясь работой соседей.

Чтобы понимать сети на глубоком уровне и уметь их диагностировать, нам нужно разобраться, как этот конвейер устроен логически и как он физически реализован внутри операционной системы.

Две реальности: академическая OSI и практический TCP/IP

В теории сетей безраздельно властвует модель OSI (Open Systems Interconnection). Это эталонная концепция из семи уровней, которую преподают в университетах. Она описывает идеальный мир, где сетевое взаимодействие разбито на мельчайшие логические шаги: от прикладного (L7) до физического (L1).

Однако ядро Linux (как и Windows, и macOS) не использует семиуровневую модель. Исторически интернет строился на базе стека протоколов TCP/IP, который оказался проще и прагматичнее. В нем всего четыре уровня.

Давайте посмотрим, как академические уровни OSI схлопываются в суровую реальность ядра Linux:

  1. Прикладной уровень (Application, L7-L5 в OSI). В Linux это единый уровень приложения. Здесь работают ваши браузеры, мессенджеры, базы данных и утилиты вроде curl. Они оперируют чистыми данными (HTTP-запросами, текстом писем), понятия не имея о том, как эти данные будут доставлены.
  2. Транспортный уровень (Transport, L4). Отвечает за связь между конкретными программами на разных компьютерах. Главные протоколы здесь — TCP (гарантированная доставка) и UDP (быстрая доставка без гарантий).
  3. Сетевой уровень (Internet, L3). Отвечает за глобальную маршрутизацию — как найти путь от вашего компьютера до серверов Google через весь мир. Главный протокол — IP (IPv4 или IPv6).
  4. Канальный и физический уровни (Network Access, L2-L1). Уровень локальной сети. Отвечает за передачу кадров между устройствами, подключенными к одному коммутатору или кабелю (Ethernet, Wi-Fi), и преобразование битов в электрические сигналы.

Главный принцип обеих моделей — абстракция. Сетевой уровень (IP) не знает, что именно он передает (картинку с котиком или банковскую транзакцию). Транспортный уровень (TCP) не знает, передаются ли данные по медному кабелю или по оптоволокну. Каждый уровень решает только свою задачу.

Граница миров: User Space и Kernel Space

Чтобы понять, как Linux видит сеть, недостаточно просто выучить уровни. Нужно наложить эти уровни на архитектуру самой операционной системы.

Память и процессы в Linux жестко разделены на две зоны: User Space (пространство пользователя) и Kernel Space (пространство ядра). Это сделано для безопасности — чтобы ошибка в браузере не привела к краху всей системы.

Сетевой стек TCP/IP (начиная с 4-го уровня и ниже) целиком реализован внутри ядра Linux.

Как же приложение из User Space передает данные в сеть? Через специальный программный интерфейс — сокет (socket). Сокет — это дверь между приложением и сетевым стеком ядра.

Процесс отправки данных выглядит так:

  1. Приложение (например, веб-сервер) формирует данные и пишет их в сокет. На этом работа приложения (L7) закончена.
  2. Данные пересекают границу и попадают в ядро Linux.
  3. Ядро берет данные и передает их модулю TCP (L4). Тот нарезает данные на куски и добавляет свой заголовок с портами. Происходит инкапсуляция — упаковка данных в конверт.
  4. TCP-сегмент передается модулю IP (L3). Тот добавляет свой заголовок с IP-адресами источника и назначения.
  5. IP-пакет спускается к драйверу сетевой карты (L2), который упаковывает его в Ethernet-кадр, добавляя MAC-адреса.
  6. Драйвер отдает команду аппаратному обеспечению (сетевой карте, L1) превратить кадр в электрический сигнал.

Понимание этой границы критически важно для диагностики. Если у вас проблема на уровне приложения (неверный пароль к базе данных) — вы ищете ошибку в логах программы в User Space. Если пакеты теряются в пути или не маршрутизируются — вы обращаетесь к инструментам, которые опрашивают ядро Linux.

Инструментарий: как заглянуть в сетевой стек

Раз сетевой стек живет в ядре, нам нужны утилиты для общения с ним. В современном Linux главным "пультом управления" сетью является утилита ip из пакета iproute2.

Она позволяет посмотреть на сеть глазами ядра, спускаясь по уровням абстракции.

Взгляд на физический и канальный уровни (L1-L2)

Чтобы увидеть аппаратные интерфейсы и их MAC-адреса, используется команда: ip link

Вывод покажет все сетевые карты, известные ядру. Например: 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff

Здесь ядро сообщает нам: "У меня есть интерфейс eth0, он физически включен (state UP), и его MAC-адрес на канальном уровне — 52:54:00:12:34:56".

Взгляд на сетевой уровень (L3)

Чтобы узнать, какие IP-адреса (глобальные координаты) ядро назначило этим интерфейсам, используется команда: ip addr (или сокращенно ip a)

Вывод дополнится информацией третьего уровня: inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0

Теперь мы видим, что к интерфейсу eth0 привязан IP-адрес 192.168.1.10. Именно этот адрес ядро будет подставлять в IP-заголовок (L3) при отправке пакетов.

Итоги

Мы рассмотрели сеть как конвейер. Приложение работает с высокоуровневыми смыслами в безопасном User Space. Когда приходит время отправить данные, они передаются через сокет в Kernel Space. Там ядро Linux, следуя стеку TCP/IP, последовательно упаковывает данные в заголовки протоколов, пока они не превратятся в физический сигнал.

В следующих главах мы возьмем в руки микроскоп и подробно изучим каждый из этих конвертов: какие именно байты ядро пишет в заголовки канального (L2) и сетевого (L3) уровней, и как утилита ip помогает этим управлять.

Инкапсуляция данных: анатомия заголовков и структура сетевого кадра

Инкапсуляция данных: анатомия заголовков и структура сетевого кадра

Когда приложение записывает строку GET / HTTP/1.1 в сокет, ядро операционной системы получает просто массив сырых байт. Сетевая карта не понимает, что такое HTTP, куда отправлять этот текст и как проверить его целостность. Чтобы байты достигли адресата на другом конце планеты, ядро должно обернуть их в систему служебных конвертов. Этот процесс последовательной упаковки данных при движении сверху вниз по сетевому стеку называется инкапсуляцией.

Архитектура «матрёшки»: от полезной нагрузки к кадру

В пространстве ядра каждый уровень стека TCP/IP добавляет к исходным данным свой заголовок (header). Заголовок содержит инструкции для аналогичного уровня на принимающей стороне.

Процесс формирования итогового сообщения выглядит так:

  1. Данные приложения (Payload) спускаются на транспортный уровень (L4).
  2. Транспортный уровень добавляет свой заголовок (например, TCP), где указывает порты источника и назначения. Получается сегмент (segment).
  3. Сегмент спускается на сетевой уровень (L3). Здесь добавляется IP-заголовок с IP-адресами. Теперь это пакет (packet).
  4. Пакет передаётся на канальный уровень (L2), где оборачивается в Ethernet-заголовок с MAC-адресами и концевик (FCS) для проверки ошибок. Итоговая конструкция, готовая к отправке в кабель, называется кадром (frame).

Размер кадра ограничен физическими характеристиками сети. Максимальный размер полезной нагрузки, который может быть помещен в один кадр без фрагментации, называется MTU (Maximum Transmission Unit). Для стандартного Ethernet MTU=1500MTU = 1500 байт. Это означает, что сумма размеров IP-заголовка, TCP-заголовка и самих данных не должна превышать 1500 байт: SizeIP+SizeTCP+SizeData1500Size_{IP} + Size_{TCP} + Size_{Data} \leq 1500.

Анатомия заголовков: как уровни общаются друг с другом

Чтобы понять, как ядро Linux обрабатывает сеть, нужно посмотреть на структуру конкретных заголовков. Они не просто хранят адреса — они обеспечивают механизм мультиплексирования, позволяя множеству протоколов работать поверх одного кабеля.

Канальный уровень: Ethernet II

Заголовок Ethernet предельно прост и занимает 14 байт. Его задача — доставить кадр от одной сетевой карты к другой в пределах одного физического сегмента (коммутатора).

  • Destination MAC (6 байт) — физический адрес получателя.
  • Source MAC (6 байт) — физический адрес отправителя.
  • EtherType (2 байта) — ключевое поле демультиплексирования.

Поле EtherType сообщает сетевой карте получателя, какому именно протоколу сетевого уровня нужно передать распакованную полезную нагрузку. Если EtherType равен 0x0800, ядро передаст данные обработчику IPv4. Если 0x0806 — обработчику ARP, а 0x86DD означает IPv6. Без этого поля ядро не знало бы, как парсить следующие байты.

Сетевой уровень: IPv4

Сняв Ethernet-заголовок, ядро смотрит в IP-заголовок (обычно 20 байт). Его задача — глобальная маршрутизация между разными сетями.

Помимо очевидных Source IP и Destination IP (по 4 байта каждый), критически важны два поля:

  • TTL (Time to Live) — счетчик жизни пакета. Выставляется отправителем (в Linux по умолчанию 64) и уменьшается на 1 каждым маршрутизатором на пути. Если TTL=0TTL = 0, пакет уничтожается. Это защищает интернет от вечно блуждающих пакетов в петлях маршрутизации.
  • Protocol (1 байт) — аналог EtherType, но для следующего уровня. Указывает, кому внутри ядра отдать данные дальше. Значение 6 означает TCP, 17 — UDP, 1 — ICMP (ping).

Управление памятью в Linux: структура sk_buff

С точки зрения теории, инкапсуляция — это просто добавление байт спереди. Но с точки зрения ядра ОС, копирование данных в памяти — невероятно дорогая операция. Если бы ядро создавало новый массив в памяти на каждом уровне стека, чтобы приклеить 20 байт заголовка к 1400 байтам данных, процессоры серверов тратили бы всё время на копирование памяти.

Linux решает эту проблему элегантно. Вся жизнь пакета в ядре вращается вокруг одной фундаментальной структуры данных — struct sk_buff (socket buffer).

Вместо того чтобы копировать данные, ядро выделяет один большой блок памяти с запасом места спереди (headroom) и сзади (tailroom). Управление инкапсуляцией сводится к перемещению четырёх указателей внутри этого блока:

  1. head — указывает на абсолютное начало выделенной памяти.
  2. data — указывает на начало текущих полезных данных.
  3. tail — указывает на конец текущих полезных данных.
  4. end — указывает на абсолютный конец выделенной памяти.

Когда приложение отправляет данные, они помещаются ближе к концу буфера, оставляя большой headroom. Когда пакет спускается на уровень TCP, ядро не двигает сами данные. Оно просто сдвигает указатель data «влево» (уменьшает адрес) на 20 байт и записывает TCP-заголовок в освободившееся пространство.

Механизм sk_buff реализует принцип Zero-Copy внутри сетевого стека. Заголовки «наращиваются» путем манипуляции указателями, а сами пользовательские данные остаются неподвижными в оперативной памяти с момента попадания в ядро и до передачи контроллеру сетевой карты (DMA).

Понимание sk_buff — ключ к диагностике производительности сети в Linux. Когда вы видите отброшенные пакеты (drops) на интерфейсе, часто это означает не проблему с кабелем, а то, что ядру не хватило памяти для выделения новых структур sk_buff при пиковой нагрузке.

Разобравшись с тем, как формируется кадр в памяти, мы подошли к границе между программным обеспечением и железом. Сформированный sk_buff готов покинуть оперативную память и превратиться в электрические сигналы.

Жизненный цикл пакета: от прерывания сетевой карты до сокета приложения

Жизненный цикл пакета: от прерывания сетевой карты до сокета приложения

Нагрузка на современную сеть колоссальна. Канал в 10 Гбит/с при стандартном размере кадра передает около 830 000 пакетов каждую секунду. Если бы центральный процессор (CPU) отвлекался на каждое физическое прибытие пакета, система бы мгновенно зависла, тратя 100% ресурсов только на переключение контекста. Чтобы выжить в таких условиях, ядро Linux и сетевые адаптеры используют сложную хореографию передачи ответственности.

В прошлой главе мы разобрали, как пакет упаковывается в структуру sk_buff и обрастает заголовками. Теперь мы проследим обратный путь: как электрический сигнал в кабеле превращается в данные, готовые для чтения вашим кодом.

DMA и Ring Buffer: доставка без участия CPU

Когда сетевая карта (NIC) принимает электрические сигналы и собирает из них кадр Ethernet, первой задачей становится передача этих данных в оперативную память (RAM).

Исторически процессор сам копировал данные из буфера сетевой карты в память. Сегодня это делает аппаратный механизм DMA (Direct Memory Access). Сетевая карта пишет данные напрямую в RAM, минуя CPU.

Для координации этого процесса используется Ring Buffer (кольцевой буфер) — структура данных в оперативной памяти, состоящая из дескрипторов.

  • Дескриптор — это небольшая запись, содержащая указатель на область памяти, куда NIC должна положить следующий пакет.
  • Буфер называется кольцевым, потому что когда сетевая карта доходит до последнего дескриптора, она возвращается к первому.

Процесс выглядит так:

  1. Драйвер сетевой карты при загрузке выделяет память под Ring Buffer (Rx Ring для приема) и подготавливает пустые структуры sk_buff.
  2. NIC принимает кадр из кабеля.
  3. NIC через DMA копирует кадр в память по адресу, указанному в текущем дескрипторе Ring Buffer.
  4. NIC помечает дескриптор как «готовый» (пакет принят).

На этом этапе данные уже находятся в оперативной памяти, но ядро Linux (и тем более приложение) еще не знает об их существовании.

NAPI: как Linux спасается от шторма прерываний

Чтобы сообщить процессору о прибытии данных, сетевая карта генерирует аппаратное прерывание — Hard IRQ. Процессор бросает текущие дела и выполняет короткий обработчик прерывания.

Если генерировать Hard IRQ на каждый из 830 000 пакетов в секунду, возникнет interrupt storm (шторм прерываний). Система впадет в состояние livelock: процессор будет только обрабатывать прерывания, не успевая передавать сами пакеты приложениям.

Для решения этой проблемы в Linux был внедрен механизм NAPI (New API). Его суть — гибридный подход: прерывания при низкой нагрузке и опрос (polling) при высокой.

Алгоритм NAPI работает следующим образом:

  1. Первый пакет: NIC генерирует Hard IRQ.
  2. Отключение прерываний: Обработчик Hard IRQ мгновенно отключает дальнейшие аппаратные прерывания от этой сетевой карты.
  3. Планирование SoftIRQ: Обработчик ставит в очередь программное прерывание NET_RX_SOFTIRQ и завершает работу. Процессор возвращается к нормальной жизни.
  4. Поллинг (опрос): В фоновом режиме ядро запускает обработчик SoftIRQ. Он начинает циклично забирать пакеты из Ring Buffer (процесс опроса).
  5. Возврат к прерываниям: Когда Ring Buffer пустеет (или исчерпан лимит времени/пакетов, так называемый budget), ядро снова включает аппаратные прерывания.

Подъем по стеку: от сырых байтов к сокету

Внутри обработчика NET_RX_SOFTIRQ происходит магия сетевого стека. Данные из Ring Buffer окончательно оформляются в структуру sk_buff (с которой мы знакомились ранее), и начинается процесс деинкапсуляции. Пакет передается от уровня к уровню через цепочку функций ядра.

Канальный уровень (L2)

Функция eth_type_trans() анализирует Ethernet-заголовок. Ее главная задача — прочитать поле EtherType. Если там указано 0x0800, ядро понимает, что внутри лежит IPv4, обрезает Ethernet-заголовок (сдвигая указатель data в sk_buff) и передает пакет на сетевой уровень.

Сетевой уровень (L3)

Здесь в дело вступает функция ip_rcv(). На этом этапе ядро:

  • Проверяет контрольную сумму IP-заголовка.
  • Проверяет поле TTL (если оно равно 1, пакет отбрасывается, а отправителю уходит ICMP-сообщение Time Exceeded).
  • Принимает решение о маршрутизации. Если Destination IP совпадает с адресом нашего сервера, пакет предназначен нам.
  • Читает поле Protocol (например, 6 для TCP) и передает пакет на транспортный уровень.

Транспортный уровень (L4)

Функция tcp_v4_rcv() (или аналогичная для UDP) анализирует порты отправителя и получателя. Ядро ищет в таблице активных соединений сокет, который слушает указанный Destination Port.

Как только нужный сокет найден, ядро помещает sk_buff в очередь приема сокета (Receive Queue). На этом работа сетевого стека в контексте прерывания (SoftIRQ) завершена.

Встреча с User Space

Пакет лежит в очереди сокета в пространстве ядра (Kernel Space). Приложение (например, веб-сервер Nginx) работает в пространстве пользователя (User Space) и ничего не знает о прерываниях, Ring Buffer или sk_buff.

Приложение периодически вызывает системный вызов recv() или read(). Когда происходит этот вызов:

  1. Ядро проверяет очередь приема нужного сокета.
  2. Если там есть данные, ядро копирует полезную нагрузку (payload) из sk_buff в буфер, выделенный приложением в User Space.
  3. Структура sk_buff уничтожается, а память освобождается.

Этот момент — граница миров. Сетевой пакет прекратил свое существование как сетевая сущность и стал просто массивом байтов в памяти вашей программы. Понимание этого пути дает ключ к диагностике: потеря пакета может произойти на уровне Ring Buffer (не хватило места), на уровне L3 (ошибка маршрутизации) или на уровне сокета (приложение слишком медленно читает данные, и Receive Queue переполняется).

Инструментарий администратора: управление сетевыми объектами через утилиту ip

Инструментарий администратора: управление сетевыми объектами через утилиту ip

Если вы попытаетесь настроить сеть в современном дистрибутиве Linux с помощью привычных команд ifconfig или route, вы, скорее всего, получите ошибку «command not found». Эти утилиты официально признаны устаревшими (deprecated) более двадцати лет назад. На смену им пришел пакет iproute2 и его главная команда — ip. Это не просто смена синтаксиса, это фундаментальное изменение того, как пространство пользователя (User Space) общается с сетевым стеком ядра (Kernel Space).

В предыдущих главах мы разобрали, как сетевая карта перекладывает пакет в оперативную память и как ядро формирует структуру sk_buff. Теперь мы посмотрим на сеть глазами администратора: как именно мы указываем ядру, куда отправлять эти sk_buff и через какие интерфейсы.

Netlink: язык общения с ядром

Старые утилиты вроде ifconfig использовали системный вызов ioctl() (input/output control) для изменения сетевых настроек. Это был синхронный и неповоротливый механизм: утилита отправляла запрос, блокировалась и ждала ответа. Если в ядре что-то менялось (например, отключался кабель), утилита об этом не узнавала, пока не спрашивала снова.

Утилита ip работает совершенно иначе. Она использует Netlink — специальное семейство сокетов (AF_NETLINK), разработанное специально для двунаправленной связи между ядром и процессами в User Space.

Netlink работает как асинхронная шина сообщений. Утилита ip может не только отправлять команды (например, «добавь IP-адрес»), но и «слушать» события ядра в реальном времени (команда ip monitor).

Благодаря Netlink, утилита ip представляет собой единый пульт управления всеми сетевыми объектами. Синтаксис всегда строится по одной логике: ip [ОБЪЕКТ] [КОМАНДА].

Управление L2: объекты link

Объект link в терминологии iproute2 — это сетевой интерфейс (L2 по модели OSI). Команды этого семейства управляют физическими и виртуальными портами.

Мы уже использовали ip link show для просмотра интерфейсов. Но ip link set позволяет изменять их состояние. Вспомним структуру кадра из второй главы: размер полезной нагрузки ограничен параметром MTU. Если мы хотим изменить MTU или подменить MAC-адрес, мы обращаемся к L2-объекту:

# Выключить интерфейс (перевести в состояние DOWN)
ip link set eth0 down

# Изменить MAC-адрес
ip link set eth0 address 00:11:22:33:44:55

# Увеличить MTU для Jumbo Frames (если поддерживает драйвер и NIC)
ip link set eth0 mtu 9000

# Включить интерфейс обратно
ip link set eth0 up

Когда вы выполняете ip link set eth0 up, через Netlink-сокет в ядро уходит сообщение. Ядро обновляет свои внутренние структуры, инициализирует Ring Buffer для этого интерфейса и разрешает драйверу обрабатывать прерывания.

Управление L3: объекты addr

Объект addr управляет IP-адресами (L3). Одно из главных отличий ip от старого ifconfig заключается в том, что Linux изначально поддерживает множество IP-адресов на одном интерфейсе без создания виртуальных алиасов (вроде eth0:1).

Добавление и удаление адресов выглядит так:

# Добавить IP-адрес с маской подсети /24
ip addr add 192.168.1.50/24 dev eth0

# Удалить IP-адрес
ip addr del 192.168.1.50/24 dev eth0

Маска /24 (или 255.255.255.0255.255.255.0) сообщает ядру, что первые 24 бита адреса — это сеть, а оставшиеся 3224=832 - 24 = 8 бит — это хосты. На основе этой маски ядро автоматически понимает, какие адреса находятся в локальном сегменте, и создает для них прямые маршруты.

Мост между L2 и L3: объекты neigh

Представьте, что приложение хочет отправить пакет на IP-адрес 192.168.1.100, который находится в той же локальной сети. Ядро сформировало IP-заголовок (L3), но чтобы отправить кадр в Ethernet, ему нужен MAC-адрес (L2) получателя.

Связыванием L3 и L2 занимается протокол ARP (Address Resolution Protocol). Ядро хранит результаты работы ARP в кэше соседей (Neighbor cache). Управлять им можно через объект neigh:

Команда Описание
ip neigh show Показать текущую таблицу соответствия IP -> MAC
ip neigh flush dev eth0 Очистить кэш ARP для конкретного интерфейса
ip neigh add 192.168.1.100 lladdr 00:aa:bb:cc:dd:11 dev eth0 Добавить статическую запись (полезно для защиты от ARP-спуфинга)

Если в таблице ip neigh напротив IP-адреса стоит статус REACHABLE, значит ядро знает MAC-адрес и пакет будет отправлен немедленно. Если статус INCOMPLETE, ядро приостановит отправку sk_buff и пошлет широковещательный ARP-запрос, ожидая ответа от владельца IP.

Маршрутизация: объекты route

Если IP-адрес назначения не принадлежит локальной сети (маска не совпадает), ядро должно передать пакет маршрутизатору (шлюзу). Выбор пути — задача таблицы маршрутизации, управляемой через объект route.

Просмотр таблицы:

ip route show

Типичный вывод выглядит так:

default via 192.168.1.1 dev eth0 proto dhcp metric 100
10.0.0.0/8 via 192.168.1.254 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.50

Как ядро читает эту таблицу? Оно использует алгоритм Longest Prefix Match (LPM) — поиск по самому длинному совпадению префикса (маски).

Правила LPM просты:

  1. Ядро берет IP-адрес назначения из пакета.
  2. Сравнивает его со всеми строками в таблице маршрутизации.
  3. Отбрасывает те маршруты, куда IP не попадает.
  4. Из оставшихся выбирает тот, у которого маска самая большая (самая специфичная).

Маршрут default (или 0.0.0.0/0) имеет длину префикса 0. Это означает, что под него попадает абсолютно любой IP-адрес, но он имеет самый низкий приоритет. Пакет уйдет в default via 192.168.1.1 только в том случае, если ни одно другое, более точное правило (например, /8 или /24) не подошло.

Утилита ip позволяет администратору не просто настраивать интерфейсы, а напрямую манипулировать структурами в памяти ядра, определяя судьбу каждого sk_buff. Понимание этих механизмов — ключ к диагностике. Если пакет не доходит до цели, проблема всегда кроется на одном из уровней: выключен link, неверный addr, отсутствует MAC в neigh или нет пути в route. В следующей главе мы спустимся на транспортный уровень и посмотрим, как поверх этой маршрутизации строится надежная доставка TCP-сегментов.