Школа

← К программе

Модель TCP/IP и что происходит при открытии страницы

Урок курса «Курс подготовки DevOps-инженеров»

Хронометраж: занятие 60 минут — теория 30 минут, практика 30 минут. Домашняя работа: 1–2 часа.

Кратко о чём урок: Уровни и инкапсуляция, сквозной разбор одного HTTPS-запроса: DNS → TCP → TLS → HTTP → ответ. Каркас, к которому крепятся остальные уроки блока.


1. Что изучаем

После урока вы сможете:

  • назвать четыре уровня модели TCP/IP и сказать, чем они отличаются от семи уровней OSI;
  • показать инкапсуляцию на реальном пакете: где кончается один заголовок и начинается следующий;
  • посчитать, сколько данных влезает в пакет при заданном MTU, и объяснить, откуда берётся MSS;
  • разобрать один HTTPS-запрос по шагам: DNS → TCP → TLS → HTTP, с временем каждого шага;
  • по коду возврата curl определить, на каком уровне сломалось, не гадая.

2. Зачем это нужно и где применяется

Этот урок — каркас блока. Дальше каждый уровень разбирается отдельно, и без общей картины они рассыпаются на несвязанные факты.

«Сайт не работает» — и надо за минуту понять, кто виноват. Приложение, сеть, DNS, сертификат или фаервол. Уровни нужны именно для этого: они превращают «не работает» в «не работает вот на этом шаге», а дальше причина ищется в известном месте. Без модели остаётся перезапускать всё по очереди.

Половина сетевых проблем — не сетевые. Ответ «сервер не отвечает» может означать, что имя не разрешилось (уровень приложения), что порт закрыт (транспортный), что маршрута нет (межсетевой), или что сертификат просрочен. Все четыре выглядят одинаково для пользователя и по-разному чинятся.

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

Медленно не значит перегружено. В уроке измерен реальный HTTPS-запрос: DNS 59 мс, TCP 100 мс, TLS ещё 260 мс, первый байт ответа через 1,4 с. Три четверти времени ушло до того, как сервер начал отвечать. Если оптимизировать «медленный сервер», не разложив время по шагам, можно долго искать не там.

MTU — причина проблем, которые невозможно угадать. Соединение устанавливается, мелкие запросы проходят, большие зависают. Это про размер пакета и фрагментацию, и без понимания инкапсуляции такое не диагностируется вовсе.

3. Теория (30 минут)

3.1. Четыре уровня, а не семь

Модель OSI с семью уровнями знают все, кто готовился к собеседованию. Проблема в том, что интернет построен не по ней. Стек, который работает на ваших серверах, описан в RFC 1122, и там четыре уровня:

Уровень Название в RFC 1122 Что делает Примеры
4 Application прикладной протокол HTTP, DNS, SSH, SMTP
3 Transport доставка между процессами TCP, UDP
2 Internet доставка между хостами через сети IP, ICMP
1 Link доставка внутри одного сегмента Ethernet, Wi-Fi, PPP

Соответствие с OSI неточное, и RFC говорит об этом прямо: прикладной уровень интернет-стека «по существу объединяет функции двух верхних уровней OSI — Presentation и Application». Уровня «сеансового» в TCP/IP нет вовсе.

Важнее другое замечание из того же RFC: строгое послойное разделение — несовершенная модель, и реальные реализации регулярно нарушают границы уровней ради производительности и функциональности. Это не оговорка ради скромности, а предупреждение: TLS формально не уровень, ICMP живёт на межсетевом, но переносит сведения о транспортном, а сетевые карты считают контрольные суммы TCP за ядро. Модель — инструмент для рассуждения, а не карта территории.

Практический вывод, из-за которого мы вообще начинаем с уровней: у каждого уровня свой набор причин отказа и свой инструмент диагностики. К этому вернёмся в разделе 3.7.

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

Каждый уровень оборачивает данные соседа сверху в свой заголовок. Проверим на реальном пакете — это первый пакет TCP-соединения, поймано tcpdump:

$ tcpdump -i eth0 -c 1 -nn -t -e -v 'tcp port 80'
1a:84:6a:7c:ac:69 > b6:2d:34:68:d8:ed, ethertype IPv4 (0x0800), length 74:
    (tos 0x0, ttl 64, id 4048, offset 0, flags [DF], proto TCP (6), length 60)
    172.17.0.2.35848 > 172.66.147.243.80: Flags [S], seq 2169663509, win 65495,
    options [mss 65495,sackOK,TS val 1377023435 ecr 0,nop,wscale 7], length 0

Разберём три строки — это три уровня, сверху вниз по выводу и снизу вверх по стеку.

Первая строка — Link (Ethernet). Два MAC-адреса и ethertype IPv4 (0x0800) — метка, говорящая, что внутри лежит IP-пакет. length 74 — размер всего кадра.

Вторая строка — Internet (IP). ttl 64 (сколько маршрутизаторов пакет может пройти), proto TCP (6) — метка следующего уровня, length 60 — размер IP-датаграммы вместе с её заголовком, flags [DF] — «не фрагментировать».

Третья строка — Transport (TCP). Порты 35848 > 80, флаг [S] (SYN — начало соединения), номер последовательности, размер окна и опции. length 0 — данных нет вовсе.

Теперь арифметика, из которой всё становится ясно:

кадр Ethernet          74 байта
IP-датаграмма          60 байт      ← разница 14 байт = заголовок Ethernet
IP-заголовок           20 байт      (минимальный, без опций)
TCP-заголовок          40 байт      (20 базовых + 20 опций: mss, sackOK, TS, wscale)
полезные данные         0 байт

74 байта передано, чтобы сообщить «хочу открыть соединение». Ни одного байта данных.

3.3. MTU и MSS: сколько данных влезает в пакет

MTU (maximum transmission unit) — максимальный размер IP-датаграммы, который проходит через интерфейс без фрагментации. Для Ethernet это 1500 байт:

$ ip -o link show | grep -o 'mtu [0-9]*'
mtu 1500

MSS (maximum segment size) — сколько в этой датаграмме остаётся на данные TCP:

MSS = MTU - заголовок IP - заголовок TCP = 1500 - 20 - 20 = 1460

Это то число, которым хосты обмениваются в опции mss при установке соединения — вы видели его в захвате выше. Дальше каждая сторона не отправляет сегменты больше объявленного другой стороной.

Почему это важно на практике: если где-то по пути MTU меньше (туннель VPN, инкапсуляция в облаке), большой пакет с флагом DF не пройдёт и не будет фрагментирован. Маршрутизатор должен прислать ICMP «Fragmentation Needed», хост уменьшит размер — это называется Path MTU Discovery. Когда ICMP заблокирован фаерволом, механизм ломается, и получается характерная картина: соединение устанавливается, короткие запросы работают, а большие ответы зависают. Диагностировать это, не понимая инкапсуляции, практически невозможно — а зная, проверяется одной командой (ping -M do -s <размер>, урок 05.13).

Важная оговорка про стенд. В контейнере на macOS и Windows MTU виртуального интерфейса не 1500. Проверено — там mtu 65535, и MSS в захвате был 65495 вместо 1460:

$ ip -o link show eth0 | grep -o 'mtu [0-9]*'
mtu 65535

Это не ошибка, а свойство виртуального сетевого стека Docker Desktop. Настоящие 1500 вы увидите на физической машине, на ВМ и на VPS — там же и надо проверять всё, что касается MTU. На macOS-хосте, например, ifconfig en0 показывает ровно mtu 1500.

3.4. Один HTTPS-запрос по шагам

Теперь пройдём весь путь от curl https://… до ответа. Времена — реальные, снятые curl:

$ curl -s -o /dev/null -w '
  DNS (namelookup)    %{time_namelookup} с
  TCP (connect)       %{time_connect} с
  TLS (appconnect)    %{time_appconnect} с
  первый байт         %{time_starttransfer} с
  всего               %{time_total} с
  ' https://yandex.cloud/ru/docs

  DNS (namelookup)    0.059341 с
  TCP (connect)       0.159418 с
  TLS (appconnect)    0.418915 с
  первый байт         1.427461 с
  всего               3.850603 с

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

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

Шаг Как считается Время Что происходит
DNS namelookup 59 мс имя → адрес
TCP connect - namelookup 100 мс три рукопожатия
TLS appconnect - connect 260 мс согласование шифра, проверка сертификата
Ожидание сервера starttransfer - appconnect 1008 мс сервер думает
Передача тела total - starttransfer 2423 мс 790 КБ по сети

Из таблицы сразу видно, куда ушло время, и это не «медленная сеть»: соединение установилось за 420 мс, а потом секунду думал сервер и две с половиной секунды качалось тело ответа.

DNS кешируется, и это видно. Тот же запрос повторно:

прогон 1: dns=0.002812 connect=0.103877 tls=0.289760
прогон 2: dns=0.001698 connect=0.146708 tls=0.321872

59 мс превратились в 2 мс — ответ пришёл из кеша. А TLS каждый раз согласуется заново, потому что это новое соединение.

Теперь то же самое в пакетах. Начало соединения — те самые три рукопожатия:

$ tcpdump -i eth0 -nn -t 'tcp port 443'
IP 172.17.0.2.54172 > 172.66.147.243.443: Flags [S],  seq 750929090          ← SYN
IP 172.66.147.243.443 > 172.17.0.2.54172: Flags [S.], seq 1515896469, ack …  ← SYN+ACK
IP 172.17.0.2.54172 > 172.66.147.243.443: Flags [.],  ack 1                  ← ACK
IP 172.17.0.2.54172 > 172.66.147.243.443: Flags [P.], seq 1:1572, length 1571 ← ClientHello (TLS)
IP 172.66.147.243.443 > 172.17.0.2.54172: Flags [P.], seq 1:1349, length 1348 ← ServerHello

Первые три пакета не несут данных — это установка соединения. Четвёртый, 1571 байт, уже TLS: клиент предлагает версии, шифры и сообщает имя сайта. Всего на один запрос к example.com в захвате оказалось 28 пакетов.

Что согласовали, видно так:

$ echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep New
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384

А DNS-запрос выглядит совсем скромно — один пакет туда, один обратно, по UDP на порт 53:

IP 172.17.0.2.42358 > 192.168.65.7.53: 43839+ [1au] A? example.com. (52)
IP 192.168.65.7.53 > 172.17.0.2.42358: 43839 2/0/0 A 172.66.147.243, A 104.20.23.154 (83)

52 байта вопроса, 83 байта ответа с двумя адресами. Обратите внимание: DNS — это прикладной протокол, работающий поверх UDP. Он не «отдельный уровень», хотя в разборе стоит первым.

3.5. TCP и UDP: два способа доставки на одном уровне

На транспортном уровне живут два протокола, и разница между ними определяет поведение всего, что поверх них работает.

TCP устанавливает соединение, нумерует байты, подтверждает доставку и восстанавливает порядок. Цена — три пакета до передачи первого байта данных (те самые рукопожатия) и состояние, которое обе стороны держат в памяти.

UDP просто отправляет датаграмму. Ни соединения, ни подтверждений, ни порядка. Цена отсутствует, гарантий тоже.

Разница хорошо видна в числах. Полный DNS-запрос по UDP — два пакета, вопрос и ответ:

IP 172.17.0.2.42358 > 192.168.65.7.53: 43839+ [1au] A? example.com. (52)
IP 192.168.65.7.53 > 172.17.0.2.42358: 43839 2/0/0 A 172.66.147.243, A 104.20.23.154 (83)

У TCP только на установку соединения уходит три пакета, и лишь четвёртый несёт данные. Для запроса, который весит 52 байта и должен выполниться за миллисекунды, это неоправданно — поэтому DNS по умолчанию работает по UDP.

Но не всегда. Если ответ не влезает в датаграмму, сервер ставит флаг «усечено» (tc), и клиент повторяет запрос по TCP. Проверено на записи, которая гарантированно большая:

$ dig DNSKEY .
;; Truncated, retrying in TCP mode.
;; flags: qr rd ra; QUERY: 1, ANSWER: 3

$ dig +ignore +bufsize=512 DNSKEY .        # запретили повтор по TCP
;; flags: qr tc rd ra; QUERY: 1, ANSWER: 1
                ^ вот он, флаг усечения

Отсюда практическое следствие, из-за которого это стоит знать: фаервол, открывший DNS только по UDP, ломает часть запросов — те, ответы на которые не поместились. Симптом получается издевательский: большинство имён разрешается, а некоторые нет.

Ещё одна деталь транспортного уровня, которая пригодится в каждом следующем уроке, — откуда берутся исходящие порты. Сервер слушает известный порт (80, 443, 22), а клиент получает случайный из «эфемерного» диапазона:

$ cat /proc/sys/net/ipv4/ip_local_port_range
32768	60999

Все клиентские порты из захватов этого урока — 35848, 54172, 42358 — из этого диапазона. Именно пара «адрес и порт клиента» + «адрес и порт сервера» однозначно определяет соединение: поэтому с одного хоста можно открыть много соединений к одному и тому же серверу, и они не перепутаются. А ещё поэтому диапазон исчерпаем: около 28 тысяч одновременных исходящих соединений к одному адресату — предел, с которым сталкиваются нагруженные прокси.

3.6. Кто с кем разговаривает: адреса на каждом уровне

Одна из главных путаниц новичков — какой адрес на каком уровне и что меняется по пути.

Уровень Адрес Меняется по пути?
Link MAC (1a:84:6a:7c:ac:69) да, на каждом маршрутизаторе
Internet IP (172.17.0.2) нет (кроме NAT, урок 05.08)
Transport порт (35848, 443) нет (кроме NAT)
Application имя (example.com) нет

MAC-адрес в захваченном кадре — это адрес соседа по сегменту, а не сервера на другом конце мира. Пакет до 172.66.147.243 уходит на MAC вашего шлюза, тот перепишет MAC-адреса и отправит дальше. IP-адреса при этом остаются те же — на этом и держится маршрутизация (урок 05.04).

3.7. Локализация: на каком уровне сломалось

Теперь ради чего всё это. У curl есть коды возврата, и они указывают на уровень. Проверено на воспроизводимых случаях:

Код Сообщение Уровень Что проверять дальше
0 — всё прошло —
6 Could not resolve host Application (DNS) dig, /etc/resolv.conf (урок 05.06)
7 Failed to connect to … port Transport / Internet порт, фаервол, маршрут (05.09, 05.04)
28 Connection timed out Internet или приложение молчит маршрут, фильтрация, загрузка сервера
52 Empty reply from server Application соединение есть, ответа нет — смотреть логи сервиса
60 SSL certificate problem TLS срок, цепочка, имя в сертификате (05.10)

Разница между 7 и 28 тонкая и важная: код 7 означает активный отказ — ядро на другой стороне ответило RST, порт закрыт. Код 28 — тишина: пакеты ушли и ничего не вернулось, что обычно значит фильтрацию или неверный маршрут. Первое чинится на сервере, второе — в сети между вами.

Проверить это без внешних сайтов можно на своём сертификате, и результат воспроизводим:

$ openssl req -x509 -newkey rsa:2048 -keyout k.pem -out c.pem -days 1 -nodes -subj '/CN=lab.local'
$ openssl s_server -cert c.pem -key k.pem -accept 8443 -quiet &

$ curl -s -o /dev/null https://127.0.0.1:8443/ ; echo $?
60
$ curl -sS -o /dev/null https://127.0.0.1:8443/
curl: (60) SSL certificate problem: self-signed certificate

$ curl -sS -o /dev/null --cacert c.pem https://127.0.0.1:8443/
curl: (60) SSL: certificate subject name 'lab.local' does not match target hostname '127.0.0.1'

$ curl -s -o /dev/null --cacert c.pem --resolve lab.local:8443:127.0.0.1 https://lab.local:8443/ ; echo $?
52

Последний случай особенно поучителен: код сменился с 60 на 52. Это значит, что TLS сошёлся — и сертификату доверяем, и имя совпало, — а вот ответа приложения нет, потому что s_server не HTTP-сервер. Ошибка «поднялась» на уровень выше, и искать её надо уже в приложении.

Правило, к которому это сводится: идти снизу вверх и остановиться на первом уровне, где ломается. Порядок проверок — ip a и ip r (есть ли адрес и маршрут) → ping (доходит ли до хоста) → ss -tlnp или nc -vz (открыт ли порт) → curl -v (отвечает ли приложение). Подробно этот порядок разбирается в уроке 05.13, а готовый скрипт лежит в примерах к 02.10.

3.8. Примеры и варианты реализации

Одна задача — «разобрать один HTTPS-запрос по уровням и увидеть каждый своими глазами».

Среда Чем смотреть Ограничения
Локальный стенд (контейнер) tcpdump, curl -w, dig, openssl s_client MTU виртуальный (65535, не 1500), ping может быть недоступен
VPS / bare-metal то же плюс настоящий MTU и Path MTU Discovery нужен root для tcpdump
Yandex Cloud то же на ВМ; плюс зеркалирование трафика для захвата без доступа к хосту захват внутри ВМ ничем не отличается от VPS

Локальный стенд. Всё, что показано в этом уроке, снято в контейнере — образ с сетевыми утилитами собирается так:

docker run --rm -it --cap-add=NET_ADMIN --cap-add=NET_RAW debian:13 bash
apt-get update -qq && apt-get install -y -qq iproute2 tcpdump dnsutils curl netcat-openbsd

Ставить пакеты руками не обязательно: образ с готовой оснасткой описан в examples/docker/ — соберите course-net:13 и запустите docker run --rm -it course-net:13 bash.

NET_RAW нужен для tcpdump: без него захват не запустится. Что нельзя проверить здесь: настоящий MTU 1500 и всё, что с ним связано, — фрагментацию, Path MTU Discovery, поведение при уменьшенном MTU в туннеле. В контейнере на macOS и Windows интерфейс виртуальный, и mtu 65535 сбивает все расчёты. Эти опыты — на ВМ.

VPS / bare-metal. Основная среда для второй половины блока. Здесь настоящий Ethernet с MTU 1500, реальные соседи в сегменте (для ARP из урока 05.02) и живой шлюз. Захват требует прав:

sudo tcpdump -i eth0 -nn -c 20 'tcp port 443'

Полезная привычка с первого дня: снимать в файл и разбирать потом, а не читать на бегу.

sudo tcpdump -i eth0 -w /tmp/cap.pcap 'tcp port 443'     # снять
tcpdump -r /tmp/cap.pcap -nn -t -e -v | head -20         # разобрать (уже без root)

Файл .pcap открывается в Wireshark на своей машине — для разбора TLS-рукопожатия это заметно удобнее консоли.

Yandex Cloud. На виртуальной машине всё то же, что на VPS, — отличий для этого урока нет. Что облако добавляет: возможность захватить трафик, не заходя на машину. У Virtual Private Cloud есть зеркалирование трафика (traffic mirroring): копия пакетов отправляется на отдельную ВМ-коллектор. Это нужно, когда доступа к продовой машине нет или когда захват на ней недопустим по нагрузке.

Честно про непроверенное: конкретные команды yc для настройки зеркалирования в этом уроке не приводятся — проверить их в стенде курса я не мог. Смотрите документацию VPC; сам разбор захваченного трафика от способа получения не зависит.

Что выбрать. Для этого урока — контейнер: он поднимается за секунды, и всё, кроме MTU, в нём видно честно. Как только речь пойдёт про MTU, ARP, маршруты между сетями и фаервол (уроки 05.02–05.04, 05.09), нужна ВМ: контейнер там показывает не то, что происходит на настоящей машине. Правило простое — как только в опыте участвуют два узла или размер пакета, берите ВМ.

4. Практика на занятии (30 минут)

Что делаем. Разбираем один HTTPS-запрос на четыре уровня и учимся определять по коду возврата curl, где сломалось. Работаем в контейнере — всё, кроме MTU, видно честно.

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

Стенд поднимается так (образ с утилитами пригодится на весь блок):

docker run --rm -it --cap-add=NET_ADMIN --cap-add=NET_RAW debian:13 bash
apt-get update -qq && apt-get install -y -qq \
    iproute2 iputils-ping tcpdump dnsutils curl netcat-openbsd

Весь разбор целиком делает examples/05.01-tcp-ip-model/request-walkthrough.sh — он снимает тайминги, считает длительности шагов, повторяет запрос и разбирает захват. Открывайте после того, как пройдёте шаги руками: смысл в том, чтобы понимать, что именно скрипт делает.

Шаг 1 (6 минут). Разложить время запроса по шагам.

  • Что ввести:
curl -s -o /dev/null -w '
  DNS      %{time_namelookup}
  TCP      %{time_connect}
  TLS      %{time_appconnect}
  1-й байт %{time_starttransfer}
  всего    %{time_total}
  код HTTP %{http_code}  версия %{http_version}  сервер %{remote_ip}
' https://example.com/

# повторите дважды и сравните DNS
for i in 1 2; do
    curl -s -o /dev/null -w "прогон $i: dns=%{time_namelookup} tls=%{time_appconnect}\n" \
        https://example.com/
done
  • Зачем: увидеть, из каких шагов складывается запрос и сколько занял каждый из них.
  • Ожидаемый результат: пять нарастающих значений времени (DNS < TCP < TLS < 1-й байт < всего) и код HTTP 200; во втором прогоне DNS заметно меньше (ответ из кеша), а TLS примерно тот же.
  • Если не так: все времена нулевые или запрос завис — из контейнера нет выхода в интернет, проверьте curl -sI https://example.com/. DNS во втором прогоне не уменьшился — имя резолвится мимо кеша (см. урок 05.06).
  • Ответьте: значения накопительные или это длительности шагов? Сколько занял именно TLS? Почему DNS во втором прогоне быстрее, а TLS — нет?

Шаг 2 (8 минут). Увидеть инкапсуляцию на своём пакете.

  • Что ввести:
tcpdump -i eth0 -c 1 -nn -t -e -v 'tcp port 80' &
sleep 1; curl -s -o /dev/null http://example.com/; sleep 2
  • Зачем: поймать один пакет и найти в нём три уровня — Ethernet, IP и TCP — со своими заголовками.
  • Ожидаемый результат: одна запись tcpdump с MAC-адресами и ethertype IPv4, строкой IP (ttl, proto TCP, length) и строкой TCP (порты, Flags [S]). Кадр 74 байта, IP-датаграмма 60 байт, данных 0.
  • Если не так: You don't have permission to capture — забыт --cap-add=NET_RAW. Захват пуст — трафик пошёл не через eth0 (проверьте имя интерфейса ip -o link) или curl не успел (увеличьте sleep).
  • В выводе найдите и выпишите:
    • MAC-адреса и ethertype — это какой уровень?
    • ttl, proto, length в скобках — какой?
    • порты и Flags [S] — какой?
    • размер кадра и размер IP-датаграммы. Чему равна разница и почему именно столько?
  • Ответьте: сколько байт передано и сколько из них полезных данных.

Шаг 3 (8 минут). Три рукопожатия и начало TLS.

  • Что ввести:
tcpdump -i eth0 -nn -t 'tcp port 443' > /tmp/tls.txt &
sleep 1; curl -s -o /dev/null https://example.com/; sleep 3
head -7 /tmp/tls.txt
wc -l < /tmp/tls.txt
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep New
  • Зачем: увидеть установку TCP-соединения (три пакета без данных) и первый пакет с данными — ClientHello.
  • Ожидаемый результат: первые три пакета с флагами [S], [S.], [.] и length 0; четвёртый пакет [P.] уже с данными (ClientHello); строка New, TLSv1.3, Cipher is … от openssl.
  • Если не так: пусто в /tmp/tls.txt — соединение шло не через eth0 или curl не успел до sleep. grep New ничего не вернул — сервер не завершил рукопожатие (проверьте выход в интернет).
  • Ответьте: какие флаги у первых трёх пакетов и почему их именно три? В каком пакете впервые появляются данные и что это за данные? Сколько всего пакетов ушло на один запрос?

Шаг 4 (8 минут). Определить уровень поломки по коду возврата.

  • Что ввести: сначала — на своём сертификате, без внешних сайтов:
cd /tmp
openssl req -x509 -newkey rsa:2048 -keyout k.pem -out c.pem -days 1 -nodes -subj '/CN=lab.local'
openssl s_server -cert c.pem -key k.pem -accept 8443 -quiet &
sleep 1

curl -s -o /dev/null https://127.0.0.1:8443/ ; echo "код: $?"
curl -sS -o /dev/null https://127.0.0.1:8443/ 2>&1 | head -1
curl -sS -o /dev/null --cacert c.pem https://127.0.0.1:8443/ 2>&1 | head -1
curl -s -o /dev/null --cacert c.pem --resolve lab.local:8443:127.0.0.1 https://lab.local:8443/
echo "код с верным именем и своим УЦ: $?"

Затем — остальные уровни:

curl -s -o /dev/null --max-time 6 http://no-such-host-abc123.invalid/ ; echo "имя: $?"
curl -s -o /dev/null --max-time 6 http://127.0.0.1:9999/            ; echo "порт закрыт: $?"
curl -s -o /dev/null --max-time 4 http://192.0.2.1/                 ; echo "нет ответа: $?"
  • Зачем: получить разные коды возврата curl и связать каждый с уровнем, на котором сломалось.
  • Ожидаемый результат: коды 6 (DNS), 7 (отказ), 60 (сертификат), 52 (TLS прошёл, приложение молчит); последний блок про сертификат даёт смену 60 на 52 при переходе к верному имени.
  • Если не так: код 52 не появился — s_server не запустился, порт 8443 занят (посмотрите ss -tlnp). Коды в блоке про сертификат не меняются с 60 на 52 — проверьте, что --resolve и --cacert заданы верно.
  • Ответьте на главный вопрос: почему в последнем случае из блока с сертификатом код сменился с 60 на 52 и что это говорит о том, где искать причину?

Проверка результата. Вы разложили время запроса по пяти шагам и объяснили, что значения накопительные; нашли в одном пакете три уровня и посчитали, что 14 байт разницы — это Ethernet-заголовок; увидели три рукопожатия и первый пакет с данными; получили четыре разных кода curl и для каждого назвали уровень, на котором сломалось.

5. Домашняя работа (1–2 часа)

Задание. Разобрать по уровням запрос к своему сервису и научиться локализовать поломку.

  1. Карточка разбора запроса. Возьмите три разных адреса: сайт в вашем регионе, сайт на другом континенте и сервис на своей ВМ (из блока 02). Для каждого снимите тайминги curl -w и заполните карточку examples/05.01-tcp-ip-model/request-card.md: время каждого шага (не накопительное, а длительность), доля от общего, и вывод — что здесь узкое место.
  2. Сравнение и объяснение. Сведите три замера в одну таблицу и объясните расхождения: где сказалось расстояние, где — работа сервера, где — размер ответа. Отдельно ответьте, почему time_appconnect у сайта на другом континенте больше, чем у местного, даже если сервер быстрый.
  3. Разбор пакета своими руками. Снимите захват начала соединения (tcpdump -c 5) и заполните examples/05.01-tcp-ip-model/packet-layers.md: для каждого поля укажите уровень, значение и что оно означает. Обязательно посчитайте разницу между размером кадра и размером IP-датаграммы и объясните её.
  4. Эксперимент про накопительные тайминги. Проверьте гипотезу: «time_appconnect включает время TCP-соединения». Оформите карточкой эксперимента из урока 01.07: как проверяли, какие числа получили, каким расчётом подтвердили.
  5. Таблица локализации. Воспроизведите пять разных отказов и заполните examples/05.01-tcp-ip-model/diagnosis-table.md: код curl, сообщение, уровень, чем проверять дальше. Минимум: имя не разрешается, порт закрыт, нет ответа, проблема сертификата, пустой ответ. Сертификатные случаи делайте на своём сертификате (как в практике), а не на внешних сайтах — так воспроизводимо.
  6. MTU на настоящей машине. На ВМ из блока 02 (не в контейнере) посмотрите MTU интерфейса и MSS в захвате. Посчитайте MSS из MTU и сверьте с тем, что объявляет ядро. Объясните, почему в контейнере эти числа другие. Заполняйте examples/05.01-tcp-ip-model/mtu-dns-worksheet.md.
  7. DNS по UDP и TCP. Покажите обычный запрос (UDP) и запрос с +tcp. Затем получите флаг усечения tc (подсказка в разделе 3.5) и объясните, что произойдёт, если фаервол разрешает DNS только по UDP. Раздел 3 того же шаблона mtu-dns-worksheet.md.
  8. Свой порядок диагностики. Составьте последовательность из шести проверок для ответа на «сервис недоступен», от нижнего уровня к верхнему (последний раздел examples/05.01-tcp-ip-model/diagnosis-table.md). Против каждой напишите, какой уровень она закрывает и какой класс причин отсекает. Только те команды, которые выполняли.

Критерии приёмки.

  • Карточка разбора заполнена на три адреса; в каждой указаны длительности шагов, а не накопительные значения.
  • В сводной таблице объяснены расхождения между тремя замерами, включая влияние расстояния на time_appconnect.
  • В разборе пакета для каждого поля указан уровень; посчитана разница между кадром и IP-датаграммой и объяснено, из чего она состоит.
  • Карточка эксперимента про накопительные тайминги содержит расчёт, а не только числа.
  • Таблица локализации заполнена минимум на пять случаев с фактическими кодами curl.
  • Сертификатные случаи воспроизведены на своём сертификате, приложены команды.
  • MTU и MSS сняты на ВМ; расчёт MTU - 40 сверен с объявленным mss; объяснено, почему в контейнере иначе.
  • Показаны DNS по UDP и по TCP, получен флаг tc, объяснено следствие для фаервола.
  • Порядок диагностики из шести проверок: у каждой указан уровень и отсекаемый класс причин.

Файлы для работы: blocks/05-networking/examples/05.01-tcp-ip-model/ — примеры к этому уроку в репозитории курса.

6. Частые ошибки и подводные камни

Считать тайминги curl длительностями шагов. Симптом: сумма «шагов» получается больше общего времени, выводы неверные. Причина: значения накопительные — time_appconnect включает и DNS, и TCP. Что делать: вычитать предыдущее значение.

Мерить MTU в контейнере. Симптом: mtu 65535 и mss 65495 вместо 1500 и 1460, расчёты не сходятся. Причина: виртуальный интерфейс Docker Desktop. Что делать: всё про MTU — на ВМ или физической машине.

Захват через -i any. Симптом: размеры кадров не сходятся с арифметикой, вместо MAC-адресов странные поля. Причина: any использует псевдоинтерфейс с другим форматом заголовка (Linux cooked capture). Что делать: указывать конкретный интерфейс — -i eth0.

Ждать RST на lo, глядя на eth0. Симптом: захват пуст, хотя соединение точно отклонено. Причина: трафик к 127.0.0.1 идёт через loopback. Что делать: -i lo для локальных проверок.

Путать «порт закрыт» и «нет ответа». Симптом: чинят фаервол, когда не запущен сервис, или наоборот. Причина: оба выглядят как «не подключается». Что делать: смотреть код curl — 7 означает активный отказ (RST, порт закрыт), 28 — тишину (фильтрация или маршрут).

Считать MAC-адрес в захвате адресом сервера. Симптом: попытки искать по этому MAC чужое оборудование. Причина: на канальном уровне адресуется только сосед по сегменту — обычно шлюз. Что делать: помнить, что MAC меняется на каждом маршрутизаторе, а IP — нет.

Открывать DNS только по UDP. Симптом: большинство имён разрешается, отдельные — нет. Причина: ответ не поместился в датаграмму, сервер поставил флаг tc, клиент повторил по TCP — и упёрся в фаервол. Что делать: разрешать 53/udp и 53/tcp.

Искать «медленную сеть», не разложив время. Симптом: оптимизируют канал, а причина в сервере или в размере ответа. Причина: «долго» измерено целиком. Что делать: curl -w и разбор по шагам до любых выводов.

Верить, что семь уровней OSI — это про интернет. Симптом: попытки найти сеансовый уровень в TCP/IP и споры, к какому уровню отнести TLS. Причина: интернет описан RFC 1122 с четырьмя уровнями, а сама модель, по признанию RFC, несовершенна. Что делать: пользоваться моделью как инструментом рассуждения, а не искать в ней истину.

Забыть --cap-add=NET_RAW для контейнера. Симптом: tcpdump: eth0: You don't have permission to capture on that device. Причина: захват требует отдельной привилегии. Что делать: добавить --cap-add=NET_RAW (и NET_ADMIN, если будете менять настройки сети).

7. Проверка усвоения

  1. Сколько уровней в модели, по которой построен интернет, и как они называются?
  2. Почему в TCP/IP нет сеансового уровня, а TLS трудно отнести к какому-то одному?
  3. В захвате кадр 74 байта, а IP-датаграмма 60. Из чего состоит разница и почему именно столько?
  4. Откуда берётся число 1460 и что произойдёт, если по пути окажется интерфейс с MTU 1400?
  5. Значения time_connect и time_appconnect — это длительности шагов или что-то другое? Как получить время самого TLS?
  6. Почему при повторном запросе DNS быстрее, а TLS — нет?
  7. Почему первые три пакета TCP-соединения не несут данных и что в четвёртом?
  8. Чем код возврата curl 7 отличается от 28 по смыслу и что чинить в каждом случае?
  9. Что означает смена кода с 60 на 52 при переходе к правильному имени сертификата?
  10. Почему DNS работает по UDP, но иногда переключается на TCP, и чем это опасно при настройке фаервола?
  11. MAC-адрес в захваченном кадре — чей это адрес, если сервер находится на другом континенте?
  12. Почему в контейнере на macOS mtu 65535, и что из измерений этого урока там проверять нельзя?

8. Материалы

  • RFC 1122 «Requirements for Internet Hosts — Communication Layers» — первоисточник по четырёхуровневой модели. Читать целиком не нужно: раздел 1.1.3 «Internet Protocol Suite» — это две страницы, где перечислены уровни и прямо сказано, что строгое послойное деление несовершенно.
  • RFC 9293 «Transmission Control Protocol (TCP)» — действующая спецификация TCP (2022 год, заменила RFC 793). Нужна, когда надо точно ответить про флаги, состояния и установку соединения.
  • man tcpdump — раздел про выражения фильтрации и флаги -e, -v, -w. Первое, к чему возвращаешься при разборе захвата.
  • man curl, раздел --write-out — полный список переменных %{time_*}. Там же видно, какие значения накопительные.
  • Wireshark — для разбора .pcap глазами, особенно TLS-рукопожатия. Захват снимается на сервере через tcpdump -w, а открывается на своей машине.
  • Beej's Guide to Network Concepts — бесплатная книга, которая объясняет уровни и инкапсуляцию без формализма стандартов; полезна, если после этого урока захочется закрепить картину.

← Предыдущий: 04.08. Регулярные задачи: cron, systemd timers и наблюдение за ними · Блок 05 · Следующий: 05.02. Канальный уровень: Ethernet, MAC, ARP, коммутация, VLAN →

Материалы для практики

Скрипты, шпаргалки и заготовки к заданиям урока. Текст урока — на этой странице.