Компьютерные сети: карта трека и модель уровней
Эта статья — вход в трек. Её задача не пересказать четырнадцать последующих материалов, а поставить линзу, через которую они читаются: любой сетевой запрос — это последовательность независимых слоёв, каждый из которых решает ровно одну задачу, ничего не знает о соседях сверху и добавляет свою цену. Когда что-то ломается, вопрос всегда один: на каком уровне обрывается цепочка и чем это видно снаружи.
Обзорный материал про сети есть в базовом треке — «Основы сетей» и «Как работает веб». Здесь всё иначе: мы лезем в байты заголовков, читаем вывод ss, tcpdump и openssl s_client, разбираем, почему curl работает, а браузер — нет, и почему «у меня всё открывается» — не аргумент. Каждый протокол разбираем по одной схеме: зачем появился → какую проблему решил → чем за это платим.
1. Почему стек разбит на уровни
Представьте, что уровней нет. У вас есть N технологий передачи (Ethernet, Wi-Fi, оптика, 5G, спутник) и M приложений (браузер, почта, БД, видеозвонок). Без промежуточного слоя нужно N × M реализаций: «почта поверх Wi-Fi», «БД поверх спутника». Каждая новая технология требует переписать все приложения.
Решение — талия песочных часов. Посередине ставится один-единственный узкий протокол, который обязаны понимать все: IP. Сверху него — сколько угодно транспортов и приложений, снизу — сколько угодно физических сред. Число реализаций падает с N × M до N + M.
Формально это архитектурное правило: каждый уровень предоставляет сервис уровню выше и пользуется сервисом уровня ниже, общаясь со своим двойником на другой стороне по протоколу. Ethernet-драйвер вашей карты «разговаривает» с Ethernet-драйвером коммутатора; TCP в вашем ядре — с TCP на сервере; ни один из них не знает о существовании HTTP.
Вторая идея, определившая архитектуру Интернета, — end-to-end argument (Saltzer, Reed, Clark, 1984): функцию, корректность которой всё равно придётся проверять на концах, не надо реализовывать в середине сети. Проверка целостности данных нужна на уровне приложения (диск может испортить байты после приёма) — значит, сеть не обязана гарантировать доставку. Отсюда «глупая сеть, умные концы»: маршрутизаторы просто перекладывают пакеты, а надёжность строится в TCP на конечных машинах.
Цена такой архитектуры честно перечислена в «The Design Philosophy of the DARPA Internet Protocols» Дэвида Кларка (SIGCOMM, 1988): при выборе приоритетов выживаемость сети стояла первой, а учёт ресурсов и управляемость — последними. Мы до сих пор расплачиваемся: у IP нет встроенной аутентификации источника, нет гарантий задержки, нет биллинга. Всё это надстроено сверху — и потому дороже и сложнее, чем могло быть.
| Что даёт слоистость | Чем платим |
|---|---|
| Замена технологии на одном уровне не ломает остальные | Каждый уровень добавляет заголовок: 54 байта накладных на TCP/IP/Ethernet |
| Независимая разработка и стандартизация | Дублирование функций: контроль ошибок есть в Ethernet, IP, TCP и TLS одновременно |
| Отладка локализуется по уровням | Информация теряется на границах: TCP не знает, что потеря была из-за Wi-Fi, а не из-за перегрузки |
| Сеть переживает отказ любого узла | Сквозные оптимизации почти невозможны — отсюда QUIC, который схлопнул три уровня в один |
2. OSI и TCP/IP: что из этого правда
Семиуровневая модель OSI — учебная система координат, а не то, что работает в вашем ядре. Реальный стек описан в RFC 1122 и имеет четыре уровня. Знать OSI полезно ровно по одной причине: индустрия говорит на её языке — «L2-коммутатор», «L3-маршрутизация», «L4-балансировщик», «L7-прокси». Эти номера — из OSI.
| OSI | TCP/IP (RFC 1122) | Единица данных | Примеры | Где живёт код |
|---|---|---|---|---|
| 7 Приложение / 6 Представление / 5 Сеанс | Application | сообщение | HTTP, DNS, TLS, gRPC | ваш процесс, библиотеки |
| 4 Транспортный | Transport | сегмент / датаграмма | TCP, UDP, QUIC (в userspace) | ядро ОС |
| 3 Сетевой | Internet | пакет | IPv4, IPv6, ICMP, BGP | ядро + маршрутизаторы |
| 2 Канальный | Link | кадр | Ethernet, Wi-Fi, ARP, VLAN | драйвер + NIC |
| 1 Физический | Link | биты | 1000BASE-T, оптика, радио | железо |
Три места, где реальность не совпадает с картинкой:
- TLS не является отдельным уровнем OSI. Формально он «между 4 и 7», практически — библиотека, которая шифрует байты потока TCP. Отсюда путаница с «L5» и «L6», которых на практике нет.
- QUIC ломает модель сознательно. Он реализует транспорт (надёжность, окна, мультиплексирование) в пространстве пользователя поверх UDP и склеивает его с криптографией TLS 1.3. Это прямое следствие того, что менять TCP в ядрах миллиардов устройств невозможно — подробности в «UDP и QUIC».
- Средние узлы давно перестали быть «глупыми». NAT читает и переписывает L4-порты, файрволы смотрят в L7, CDN терминирует TLS. Каждое такое устройство — это нарушение end-to-end, за которое мы платим сложностью отладки: см. «NAT, фаерволы и VPN».
3. Инкапсуляция: что физически лежит в кадре
Когда приложение вызывает send(), данные не «уходят в сеть». Они последовательно оборачиваются заголовками, каждый уровень добавляет свой префикс. Это и есть инкапсуляция.
Арифметика, которую стоит помнить наизусть:
MTU Ethernet = 1500 B — максимальный размер IP-пакета
− заголовок IPv4 = 20 B (без опций)
− заголовок TCP = 20 B (без опций)
= MSS = 1460 B — сколько полезных байт влезет в сегмент
− опция timestamps = 12 B
= MSS на практике = 1448 B — именно это вы увидите в `ss -i`
Проверим на живой машине — вот срез одного установленного соединения:
$ ss -tin state established '( dport = :443 )' | head -4
ESTAB 0 0 198.51.100.24:37372 160.79.104.10:443
cubic wscale:13,12 rto:204 rtt:3.322/0.687 mss:1448 pmtu:1500 rcvmss:1448
advmss:1448 cwnd:65 ssthresh:65 bytes_sent:858895 bytes_acked:858896
bytes_received:34077 segs_out:715 segs_in:298 send 226658639bps
pacing_rate 271939200bps delivery_rate 148698808bps delivered:605
busy:141ms reordering:31 rcv_space:33087 minrtt:2.077 snd_wnd:352256
Здесь уже видна половина трека: mss:1448 — арифметика выше; pmtu:1500 — обнаруженный MTU пути; cubic — алгоритм управления перегрузкой; cwnd:65 и ssthresh:65 — окно перегрузки вышло из slow start; rtt:3.322/0.687 — сглаженный RTT и его вариация, из которых считается rto:204. Все эти поля мы разберём в «TCP».
Практический смысл MTU не в том, чтобы знать число. Он в том, что каждый туннель отнимает байты: VXLAN — 50, WireGuard — 60, PPPoE оставляет MTU 1492, IPv6 забирает 20 против IPv4. Если где-то по пути MTU меньше, а ICMP-сообщение «Fragmentation Needed» заблокировано файрволом (классика), получается PMTU-блэкхол: короткие ответы проходят, длинные молча теряются. Симптом выглядит абсурдно — «сайт открывается, но не грузится», «curl работает, а git clone виснет». Проверяется одной командой:
$ tracepath -n 1.1.1.1
1?: [LOCALHOST] pmtu 1500
1: 198.51.100.1 0.999ms
2: 139.60.160.77 0.860ms asymm 1
3: 173.205.41.153 3.853ms
4: 141.136.105.70 1.518ms asymm 5
5: 208.116.240.226 9.336ms asymm 3
6: 162.158.61.105 2.404ms asymm 5
7: no reply
pmtu 1500 в первой строке и отсутствие строк вида pmtu 1400 дальше означают, что путь однородный. asymm подсказывает, что обратный маршрут отличается от прямого — обычное дело в Интернете и частая причина «пинг идёт, а TCP не устанавливается».
4. Один запрос целиком: curl https://example.com
Разберём то, что происходит между нажатием Enter и первым байтом ответа. Начнём с фактов — вот реальный вывод с измерением фаз:
$ curl -sS -o /dev/null -w 'dns=%{time_namelookup} tcp=%{time_connect} \
tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total} \
ip=%{remote_ip} http=%{http_version}\n' https://example.com/
dns=0.001125 tcp=0.003359 tls=0.020957 ttfb=0.028004 total=0.028077
ip=104.20.23.154 http=2
Читается это так: DNS-ответ пришёл из кэша за 1,1 мс; TCP-рукопожатие заняло 2,2 мс (3,359 − 1,125); TLS — ещё 17,6 мс; сервер начал отдавать ответ через 7 мс после завершения TLS. Половина времени ушла на криптографию, и ни одного байта полезных данных за это время не передано. Это и есть цена соединения, из-за которой существуют keep-alive, session resumption, 0-RTT и HTTP/3.
Теперь то же самое в деталях протокола:
$ curl -v --http1.1 -o /dev/null https://example.com/ 2>&1 | head -40
* Host example.com:443 was resolved.
* IPv6: 2606:4700:10::ac42:93f3, 2606:4700:10::6814:179a
* IPv4: 104.20.23.154, 172.66.147.243
* Trying 104.20.23.154:443...
* Connected to example.com (104.20.23.154) port 443
* ALPN: curl offers http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / id-ecPublicKey
* ALPN: server accepted http/1.1
* Server certificate:
* subject: CN=example.com
* expire date: Aug 29 21:41:26 2026 GMT
* issuer: C=US; O=SSL Corporation; CN=Cloudflare TLS Issuing ECC CA 3
* SSL certificate verify ok.
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.5.0
> Accept: */*
Заметьте: Trying 104.20.23.154 — из четырёх полученных адресов (двух IPv6 и двух IPv4) выбран один. Порядок выбора определяет Happy Eyeballs — клиент пробует IPv6 и IPv4 почти одновременно и берёт то, что ответило первым. Это прямой ответ на вопрос «почему у одного пользователя сайт открывается, а у другого — нет»: у них разные адреса и разные пути.
Полная последовательность обменов:
Жизненный цикл того же соединения глазами приложения — с состояниями, в которых оно способно застрять:
5. Демультиплексирование: как ядро находит ваш сокет
Пакет пришёл на сетевую карту. Как ядро понимает, какому из тысяч процессов его отдать? По цепочке полей — каждый уровень читает своё поле и передаёт остаток выше:
наш или broadcast?"} B -- нет --> Z["Отбросить
если не promiscuous"] B -- да --> C{"EtherType"} C -- "0x0806" --> ARP["ARP: разрешение адресов
статья 01"] C -- "0x86DD" --> IP6["IPv6"] C -- "0x0800" --> IP4["IPv4"] IP4 --> D{"Контрольная сумма,
адрес назначения наш?"} D -- нет --> R["Маршрутизация
или отбросить"] D -- да --> E{"Поле Protocol"} E -- "1 ICMP" --> ICMP["ping, PMTU, unreachable"] E -- "6 TCP" --> TCP["Поиск сокета"] E -- "17 UDP" --> UDP["DNS, QUIC, VPN"] TCP --> F{"Четвёрка
src ip, src port,
dst ip, dst port"} F -- "точное совпадение" --> G["Установленное соединение"] F -- "только dst port" --> H["Слушающий сокет: SYN"] F -- "не найдено" --> RST["Ответить RST"] G --> I["Буфер приёма процесса"] H --> J["Очередь accept"]
Ключевой факт: соединение идентифицируется четвёркой (source IP, source port, destination IP, destination port), а не портом. Поэтому один сервер на порту 443 обслуживает миллионы соединений, а два разных процесса на клиенте могут одновременно ходить на один и тот же адрес — у них разные исходящие порты. Диапазон исходящих портов в Linux по умолчанию 32768–60999, около 28 тысяч значений:
$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999
Отсюда классический продовый предел: исходящих соединений к одному адресу и порту не может быть больше ~28 тысяч одновременно, а с учётом состояния TIME_WAIT длительностью 60 секунд — эффективно ещё меньше. Симптом — EADDRNOTAVAIL под нагрузкой. Лечится пулом соединений, а не увеличением диапазона.
6. Пять чисел, которые надо знать наизусть
Инженер, который помнит эти порядки величин, экономит себе часы отладки, потому что сразу видит, когда наблюдаемое время невозможно.
| Величина | Значение | Что из этого следует |
|---|---|---|
| Скорость света в волокне | ~200 000 км/с | каждые 100 км пути добавляют ~1 мс к RTT |
| RTT внутри дата-центра | 0,2–1 мс | 100 последовательных запросов к БД — уже 100 мс |
| RTT Москва — Франкфурт | ~30 мс | лишнее рукопожатие TLS 1.2 стоит 60 мс |
| RTT трансатлантика | ~90–120 мс | пять последовательных RTT — секунда до первого байта |
| Пакет в LTE/5G | 20–80 мс, скачками | мобильный клиент чувствует каждый лишний round-trip |
Никакая оптимизация кода не победит физику. Если сервер в Вирджинии, а пользователь в Новосибирске, минимальный RTT — около 160 мс, и это не изменить. Меняют другое: сокращают число round-trip’ов (HTTP/2 вместо 1.1, TLS 1.3 вместо 1.2, 0-RTT resumption) и сокращают расстояние (CDN, edge-вычисления). Отсюда весь смысл статей «CDN и edge» и «HTTP».
Второе важное число — произведение полосы на задержку (bandwidth-delay product). Канал 100 Мбит/с с RTT 100 мс держит «в полёте» 100 000 000 / 8 × 0,1 = 1,25 МБ данных. Если окно приёма TCP меньше — вы никогда не используете полосу целиком, сколько бы ни платили провайдеру. Именно поэтому существует масштабирование окна (wscale в выводе ss) и почему у «толстых длинных труб» такая скверная репутация.
7. Инструменты: чем щупают каждый уровень
Ниже — рабочий набор. Правило простое: инструмент должен быть на том же уровне, что и гипотеза. Проверять L7-проблему пингом бессмысленно.
| Уровень | Инструмент | Что показывает |
|---|---|---|
| L1/L2 | ip -s link, ethtool, ethtool -S |
ошибки CRC, коллизии, скорость линка, дропы на карте |
| L2 | ip neigh, arping, bridge fdb |
ARP-таблица, дубликаты IP, MAC за портом |
| L3 | ip route get, mtr, tracepath, ping |
какой маршрут выбран, где растёт задержка, PMTU |
| L4 | ss -tin, tcpdump, nstat, nc -vz |
состояние сокетов, ретрансмиты, окна, доступность порта |
| L4/L7 | openssl s_client, curl -v, mitmproxy |
цепочка сертификатов, ALPN, заголовки, редиректы |
| L7 | dig, curl -w, h2load, Wireshark |
резолвинг, тайминги фаз, поведение под нагрузкой |
Маршрут: mtr вместо traceroute
traceroute даёт один снимок, mtr — статистику. Разница принципиальна, потому что единичная потеря ничего не значит:
$ mtr -n -r -c 5 1.1.1.1
Start: 2026-07-16T09:09:48+0000
HOST: dev-01 Loss% Snt Last Avg Best Wrst StDev
1.|-- 198.51.100.1 0.0% 5 0.5 0.5 0.3 0.6 0.1
2.|-- 139.60.160.77 0.0% 5 11.6 2.7 0.4 11.6 5.0
3.|-- 173.205.41.153 0.0% 5 8.9 3.2 1.3 8.9 3.2
4.|-- 141.136.105.70 0.0% 5 10.4 3.3 1.5 10.4 4.0
5.|-- 208.116.240.226 60.0% 5 2.6 2.5 2.4 2.6 0.2
6.|-- 162.158.61.117 0.0% 5 15.1 5.9 2.5 15.1 5.3
7.|-- 1.1.1.1 0.0% 5 4.4 2.6 2.0 4.4 1.0
Здесь нет проблемы, хотя на пятом хопе 60 % потерь. Это самая частая ошибка чтения traceroute: промежуточные маршрутизаторы генерируют ICMP-ответы на низкоприоритетном управляющем процессоре и режут их лимитом. Потери на хопе N, которых нет на хопе N+1, — артефакт измерения, а не сбой. Реальная потеря — та, что сохраняется до последнего хопа включительно. Подробно — в «Диагностике сети».
DNS: dig +trace показывает всю иерархию
$ dig +trace example.com A
com. 172800 IN NS a.gtld-servers.net.
...
;; Received 836 bytes from 192.5.5.241#53(f.root-servers.net) in 3 ms
example.com. 172800 IN NS hera.ns.cloudflare.com.
example.com. 172800 IN NS elliott.ns.cloudflare.com.
;; Received 359 bytes from 192.54.112.30#53(h.gtld-servers.net) in 8 ms
example.com. 300 IN A 104.20.23.154
example.com. 300 IN A 172.66.147.243
;; Received 72 bytes from 108.162.192.162#53(hera.ns.cloudflare.com) in 4 ms
Три обращения: корень → сервер зоны .com → авторитетный сервер домена. +trace игнорирует кэш вашего резолвера и идёт по цепочке сам — поэтому это единственный честный способ проверить «а что реально настроено в зоне», когда вы только что поменяли запись и вам говорят «не применилось». TTL 300 в последней строке — ровно то, сколько минимум чужие резолверы будут отдавать старый ответ. Всё про это — в «DNS».
TLS: openssl s_client показывает цепочку доверия
$ echo | openssl s_client -connect example.com:443 -servername example.com
depth=3 C = US, O = SSL Corporation, CN = SSL.com TLS ECC Root CA 2022
depth=2 C = US, O = SSL Corporation, CN = SSL.com TLS Transit ECC CA R2
depth=1 C = US, O = SSL Corporation, CN = Cloudflare TLS Issuing ECC CA 3
depth=0 CN = example.com
verify return:1
---
Certificate chain
0 s:CN = example.com
i:C = US, O = SSL Corporation, CN = Cloudflare TLS Issuing ECC CA 3
v:NotBefore: May 31 21:39:12 2026 GMT; NotAfter: Aug 29 21:41:26 2026 GMT
1 s:C = US, O = SSL Corporation, CN = Cloudflare TLS Issuing ECC CA 3
i:C = US, O = SSL Corporation, CN = SSL.com TLS Transit ECC CA R2
2 s:C = US, O = SSL Corporation, CN = SSL.com TLS Transit ECC CA R2
i:C = US, O = SSL Corporation, CN = SSL.com TLS ECC Root CA 2022
Читается снизу вверх: корневой сертификат уже есть в вашем хранилище, каждый следующий подписан предыдущим. Опция -servername задаёт SNI — без неё сервер, обслуживающий сотню доменов на одном IP, отдаст сертификат по умолчанию, и вы получите «неправильный сертификат» там, где всё в порядке. Классический баг у старых Java-клиентов и curl-обёрток. Разбор — в «TLS и HTTPS» и в статье «Безопасность транспорта».
Пакеты: tcpdump — последняя инстанция
Когда логи приложения и метрики противоречат друг другу, спор решают байты на проводе. Вот реальный захват только «интересных» флагов:
$ sudo tcpdump -i any -n \
'tcp port 443 and (tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst)) != 0'
09:11:18.765636 Out IP 198.51.100.24.40800 > 172.66.147.243.443:
Flags [S], seq 30389069, win 42340,
options [mss 1460,sackOK,TS val 3712154096 ecr 0,nop,wscale 12], length 0
09:11:18.767622 In IP 172.66.147.243.443 > 198.51.100.24.40800:
Flags [S.], seq 613599368, ack 30389070, win 65535,
options [mss 1400,sackOK,TS val 334124251 ecr 3712154096,nop,wscale 13]
09:11:18.793430 Out IP 198.51.100.24.40800 > 172.66.147.243.443:
Flags [F.], seq 798, ack 5349, win 14
09:11:18.795260 In IP 172.66.147.243.443 > 198.51.100.24.40800:
Flags [F.], seq 5349, ack 798, win 16
Что здесь видно построчно:
Flags [S]— SYN, начало соединения. Клиент анонсируетmss 1460иwscale 12.Flags [S.]— SYN+ACK за 2 мс. Сервер отвечаетmss 1400— он за туннелем, и это меньшее значение станет рабочим MSS.ack 30389070=seqклиента + 1: подтверждён сам факт SYN, а не байты.Flags [F.]— FIN+ACK. Соединение прожило 30 мс и передало 798 байт от клиента и 5349 от сервера.
Полезные фильтры, которые стоит держать под рукой:
# только рукопожатия — кто вообще подключается
tcpdump -i any -n 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'
# кто нам шлёт RST — «connection reset by peer» с именами
tcpdump -i any -n 'tcp[tcpflags] & tcp-rst != 0'
# весь DNS, включая ответы
tcpdump -i any -n 'port 53' -A
# ICMP: unreachable и fragmentation needed — источник блэкхолов MTU
tcpdump -i any -n 'icmp[icmptype] != icmp-echo and icmp[icmptype] != icmp-echoreply'
# записать в файл для Wireshark, без обрезки пакетов
tcpdump -i any -n -s0 -w /tmp/capture.pcap 'host 10.0.3.7 and port 5432'
8. Что видно в Wireshark
tcpdump собирает, Wireshark анализирует. Разница в том, что Wireshark реконструирует состояние соединения и помечает аномалии сам. Вот что искать.
Нормальное рукопожатие — три пакета подряд с интервалом в один RTT: SYN → SYN, ACK → ACK. Фильтр tcp.flags.syn == 1. Если между SYN и SYN-ACK лежит 1 секунда, потом 2, потом 4 — это ретрансмиты SYN: первый пакет не дошёл, ядро повторяет с экспоненциальной задержкой. Симптом у пользователя — «сайт открывался ровно 3 секунды».
Потеря пакета выглядит не как пропуск, а как последовательность дубликатов ACK. Отправитель шлёт сегменты 1–5, теряется второй. Получатель, приняв 3, 4 и 5, каждый раз повторяет «жду второй» — три Dup ACK подряд. Отправитель, не дожидаясь таймаута, повторяет только второй сегмент. Это fast retransmit. В Wireshark вы увидите цепочку:
1 seq=1001 len=1448 [TCP segment of a reassembled PDU]
2 seq=2449 len=1448 ← этот пакет не дошёл, в захвате его нет
3 seq=3897 len=1448
4 ACK=2449 [TCP Dup ACK 1#1]
5 ACK=2449 [TCP Dup ACK 1#2]
6 ACK=2449 [TCP Dup ACK 1#3]
7 seq=2449 len=1448 [TCP Fast Retransmission]
8 ACK=6793 ← подтверждено всё разом
Если дубликатов ACK нет, а через 200–300 мс приходит повтор с пометкой [TCP Retransmission] — потерян был последний сегмент окна, дублировать ACK было нечем, сработал таймер RTO. Это дороже: пауза равна rto из ss вместо одного RTT.
Набор фильтров, покрывающий 90 % задач:
| Фильтр | Что ловит |
|---|---|
tcp.analysis.retransmission |
все повторы — базовый индикатор потерь |
tcp.analysis.duplicate_ack |
сигнал о дыре в потоке до срабатывания таймера |
tcp.analysis.zero_window |
получатель не успевает читать: проблема в приложении, а не в сети |
tcp.flags.reset == 1 |
кто и когда рвёт соединения |
tls.handshake.type == 1 |
ClientHello — видно SNI и предложенный ALPN даже без ключей |
http.time > 1 |
медленные ответы, если HTTP не зашифрован |
dns.time > 0.2 |
тормозящий резолвер |
Отдельная тонкость: TLS не мешает читать метаданные. SNI в ClientHello передаётся открытым текстом, размеры записей и тайминги видны всегда. Поэтому «зашифровано» не означает «непрозрачно» — Encrypted Client Hello решает эту задачу только частично.
9. Путь через прокси и CDN
Между вашим кодом и пользователем в проде почти никогда нет прямого соединения. Есть цепочка: CDN, WAF, балансировщик, service mesh sidecar. Каждое звено — точка терминации TLS, точка кэширования и точка потери контекста.
Реальные заголовки такой цепочки видно невооружённым глазом:
$ curl -sSI https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
HTTP/2 200
cf-cache-status: BYPASS
cache-control: no-transform, public, s-maxage=30672000, max-age=30672000, immutable
last-modified: Tue, 29 Aug 2023 04:36:11 GMT
vary: Accept-Encoding
x-cdnjs-cache: HIT
server: cloudflare
cf-ray: a22afeb49fc455d7-EWR
alt-svc: h3=":443"; ma=86400
Расшифровка по строкам — это готовый список тем трека:
HTTP/2— согласовано через ALPN внутри TLS, а не отдельным запросом («HTTP»).cf-cache-status: BYPASSрядом сx-cdnjs-cache: HIT— два независимых кэша, и они не согласованы. Классический источник «я сделал purge, а старый файл всё ещё отдаётся» («CDN и edge»).s-maxageдля прокси иmax-ageдля браузера различаются намеренно: общий кэш и приватный живут по разным правилам.vary: Accept-Encoding— ключ кэша включает заголовок запроса. ЗабытьVaryзначит отдать сжатый ответ клиенту, который сжатие не понимает.cf-ray: ...-EWR— идентификатор запроса и код аэропорта ближайшего PoP. Это ваш сквозной ключ корреляции между логами CDN и логами сервиса («Прокси и балансировка»).alt-svc: h3=":443"— «в следующий раз приходи по HTTP/3». Так браузер узнаёт про QUIC, не тратя лишний round-trip («UDP и QUIC»).
Три ловушки этой архитектуры, которые встречаются в проде постоянно:
remote_addrбольше не адрес клиента. Он равен адресу последнего прокси. Реальный клиент — вX-Forwarded-ForилиForwarded(RFC 7239). Доверять этому заголовку можно только от известных IP прокси — иначе любой клиент подделает себе адрес и обойдёт rate limit по IP.- Таймауты не согласованы. Если клиентский таймаут 5 с, у CDN 30 с, у балансировщика 60 с, а у сервиса 120 с — клиент отвалится, а вся цепочка продолжит работать, генерируя нагрузку, результат которой никому не нужен. Правило: таймаут снаружи должен быть больше внутреннего на каждом уровне, а суммарный бюджет — известен.
- Health check проверяет не то. Проверка
GET /на балансировщике покажет «жив», пока сервис не может достучаться до БД. Проверка должна быть на том же уровне, что и обещание сервиса.
10. Как выбирают транспорт
Логика чтения: чем выше точка, тем важнее, чтобы каждый байт дошёл; чем правее — тем дороже обходится каждый лишний round-trip. Голосовой звонок в правом нижнем углу: потерянные 20 мс звука никому не нужны через секунду, а вот задержка убивает разговор — поэтому RTP работает поверх UDP. Загрузка бэкапа в левом верхнем: потерять байт нельзя, а лишние 200 мс не заметит никто — идеальный TCP. Верхний правый угол — самый сложный случай, где TCP страдает от head-of-line blocking, и ради него был создан QUIC.
11. Как стек пришёл к нынешнему виду
Отдельного упоминания заслуживает 1986 год: сеть NSFNET пережила коллапс перегрузки — пропускная способность канала упала с 32 кбит/с до 40 бит/с, в тысячу раз. Причина — все отправители одновременно повторяли потерянные пакеты, усугубляя затор. Ответом стала работа Ван Джекобсона «Congestion Avoidance and Control» (1988), которая ввела slow start, AIMD и оценку RTO. Без неё Интернет в нынешнем виде не работал бы; детали — в «TCP».
12. Карта трека
Как читать трек в зависимости от задачи:
| Ваша задача | Порядок чтения |
|---|---|
| Разобраться системно, с нуля | подряд с 01 до 13 — материал накапливается |
| «Сервис отвечает медленно» | 03 TCP → 06 HTTP → 10 Прокси → 13 Диагностика |
| «Не резолвится / резолвится не туда» | 05 DNS → 11 CDN |
| «Ошибка сертификата» | 07 TLS → 10 Прокси |
| «Работает локально, не работает у клиента» | 12 NAT и VPN → 02 IP и маршрутизация |
| Проектирую межсервисное взаимодействие | 09 RPC и gRPC → 08 Реальное время → 04 QUIC |
| Настраиваю сеть в кластере | 01 Канальный → 02 IP → 12 NAT |
Смежные треки: «Операционные системы» — как сетевой стек устроен внутри ядра, сокеты и системные вызовы; «Распределённые системы» — что делать с тем фактом, что сеть ненадёжна; «Безопасность» — атаки на транспорт и их митигации; «DevOps» — как всё это наблюдать в проде.
13. Методика: как локализовать проблему за пять шагов
Главный навык сетевого инженера — не помнить флаги, а сужать область поиска вдвое каждым действием. Стандартный подход — снизу вверх по уровням, потому что верхний уровень не может работать при сломанном нижнем.
ошибка, таймаут,
медленно, иногда"} Q0 --> L2{"Линк поднят?
ip -br link"} L2 -- нет --> F2["Кабель, драйвер, MTU,
ip link set up"] L2 -- да --> L3{"Адрес и маршрут есть?
ip route get 1.1.1.1"} L3 -- нет --> F3["DHCP, шлюз,
конфликт маршрутов"] L3 -- да --> DNS{"Имя резолвится?
dig +short host"} DNS -- нет --> FD["resolv.conf, split-horizon,
TTL, search-домены"] DNS -- да --> L4{"Порт принимает?
nc -vz host port"} L4 -- "RST сразу" --> FR["Сервис не слушает
или слушает 127.0.0.1"] L4 -- "таймаут" --> FF["Файрвол, security group,
NetworkPolicy"] L4 -- ок --> TLS{"TLS согласуется?
openssl s_client"} TLS -- нет --> FT["SNI, срок, цепочка,
несовпадение версий"] TLS -- да --> L7{"Код ответа и тайминг?
curl -v -w"} L7 -- "4xx/5xx" --> F7["Проблема приложения,
смотрим логи по X-Request-Id"] L7 -- "медленно" --> PERF{"Где именно?
ttfb против total"} PERF -- "большой ttfb" --> FB["Сервер думает: БД, блокировки"] PERF -- "большой total" --> FC["Канал, окна, потери:
ss -i, tcpdump"]
Четыре различия, которые экономят больше всего времени:
- RST против таймаута. Мгновенный
Connection refusedозначает, что пакет дошёл и никто не слушает. Тишина до таймаута — пакет отбросили молча: файрвол, security group,NetworkPolicy. Это разные команды и разные люди. - Медленно у всех против медленно у некоторых. Первое — сервер или БД. Второе — путь: конкретный PoP, конкретный ISP, IPv6-маршрут, MTU у одного клиента.
- Постоянно против иногда. «Иногда» почти всегда означает, что за одним именем скрывается несколько бэкендов и один из них сломан. Проверяется перебором:
curl --resolve host:443:IPпо каждому адресу изdig. - Первый запрос медленный, остальные быстрые. Это стоимость установки соединения: DNS + TCP + TLS. Лечится пулом соединений и keep-alive, а не оптимизацией кода.
14. Домашняя лаборатория
Читать про сети бесполезно без возможности их сломать. Минимальная лаборатория на любом Linux, без виртуалок:
# два изолированных сетевых пространства имён, соединённых виртуальным кабелем
sudo ip netns add ns1
sudo ip netns add ns2
sudo ip link add veth1 type veth peer name veth2
sudo ip link set veth1 netns ns1
sudo ip link set veth2 netns ns2
sudo ip netns exec ns1 ip addr add 10.10.0.1/24 dev veth1
sudo ip netns exec ns2 ip addr add 10.10.0.2/24 dev veth2
sudo ip netns exec ns1 ip link set veth1 up
sudo ip netns exec ns2 ip link set veth2 up
# проверяем связность и смотрим ARP-обмен на «канале»
sudo ip netns exec ns1 ping -c2 10.10.0.2
sudo ip netns exec ns2 tcpdump -i veth2 -n arp
Теперь — самое ценное: воспроизведение плохой сети. Пакетный планировщик netem умеет добавлять задержку, джиттер, потери и переупорядочивание:
# 150 мс задержки с разбросом 30 мс — трансатлантика
sudo ip netns exec ns1 tc qdisc add dev veth1 root netem delay 150ms 30ms
# 2 % потерь — увидите fast retransmit своими глазами
sudo ip netns exec ns1 tc qdisc change dev veth1 root netem loss 2%
# узкий канал 1 Мбит/с с буфером — увидите bufferbloat
sudo ip netns exec ns1 tc qdisc change dev veth1 root \
netem rate 1mbit delay 50ms limit 1000
# вернуть как было
sudo ip netns exec ns1 tc qdisc del dev veth1 root
Прогоните под этими условиями curl и iperf3, снимите ss -tin до и после — вы увидите, как падает cwnd, растёт rto и появляются retrans. Это тот опыт, который невозможно получить чтением. Для наблюдения за счётчиками ядра пригодится nstat:
$ nstat -az | grep -E 'TcpRetransSegs|TcpExtTCPLostRetransmit|TcpExtTCPTimeouts'
TcpRetransSegs 1843
TcpExtTCPTimeouts 219
TcpExtTCPLostRetransmit 7
Растущий TcpRetransSegs на фоне стабильного трафика — прямое указание на потери; TCPTimeouts без DupAcks говорит о потерях в конце окна.
15. Десять заблуждений, которые дорого стоят
- «Пинг проходит — сеть работает». ICMP и TCP обрабатываются разными правилами и часто разными приоритетами. Работающий
pingне говорит ничего о доступности порта. - «Потери на промежуточном хопе в traceroute — авария». Нет: маршрутизаторы лимитируют генерацию ICMP. Считается только последний хоп.
- «DNS обновится сразу после смены записи». Обновится через TTL, а часть резолверов игнорирует TTL. Снижайте TTL до миграции, а не во время.
- «HTTPS в браузере значит, что трафик защищён до сервера». Он защищён до первой точки терминации TLS. Дальше — как настроено.
- «Соединений хватит, портов 65535». Исходящих — около 28 тысяч на пару адрес-порт, и
TIME_WAITдержит их ещё 60 секунд. - «Увеличим таймаут — станет надёжнее». Станет медленнее отказывать. Таймаут — это бюджет, а не гарантия; без бюджета ретраи умножают нагрузку.
- «Раз работает через
curl, значит работает».curlне выполняет JS, не хранит cookies по умолчанию, не использует HTTP/3 без флага и не следует CORS. Половина «странных» багов — здесь. - «MTU — это про производительность». MTU — это про то, доходит ли пакет вообще. Блэкхол PMTU выглядит как «маленькие запросы работают, большие — нет».
- «Балансировщик равномерно распределит нагрузку». Round-robin по соединениям при keep-alive распределяет соединения, а не запросы. Один долгоживущий клиент может нагрузить один бэкенд.
- «Сеть надёжна». Это первая из восьми ложных предпосылок распределённых вычислений Питера Дойча. Остальные семь — про задержку, полосу, безопасность, топологию, администратора, стоимость и однородность — ошибочны ровно так же.
16. Мини-итог
- Уровни существуют, чтобы N технологий и M приложений не превращались в N × M реализаций. Цена — накладные заголовки и потеря сквозного контекста.
- IP — «талия песочных часов»: единственный протокол, который обязаны понимать все. Всё остальное заменяемо.
- End-to-end argument объясняет, почему сеть «глупая», а надёжность живёт на концах — и почему всякий middlebox нарушает эту модель и усложняет отладку.
- Инкапсуляция — не абстракция, а конкретные 54 байта перед вашими данными; из них следует MSS 1448 и все истории про MTU.
- Соединение идентифицируется четвёркой, а не портом; отсюда лимит исходящих соединений и смысл пулов.
- Физика задаёт нижнюю границу задержки. Оптимизируют не скорость света, а число round-trip’ов и расстояние.
- Инструмент выбирают по уровню гипотезы:
ss— L4,dig— DNS,openssl s_client— TLS,tcpdump— арбитр в спорах. - Потеря пакета в захвате выглядит как три дубликата ACK и fast retransmit; отсутствие дубликатов и пауза в сотни миллисекунд — сработал RTO.
- В проде между вами и клиентом всегда цепочка прокси. Каждое звено имеет свой кэш, свой таймаут и свою терминацию TLS.
Источники
Книги, к которым трек возвращается постоянно:
- Kurose, Ross. Computer Networking: A Top-Down Approach — лучший учебник для построения общей картины, материалы курса открыты.
- Fall, Stevens. TCP/IP Illustrated, Volume 1 (2nd ed., 2011) — разбор протоколов по байтам, эталон точности.
- Ilya Grigorik. High Performance Browser Networking — доступна бесплатно на hpbn.co, обязательна для веб-разработчика.
- Peter Bailis et al. Systems Approach — systemsapproach.org, современное открытое издание классики Peterson & Davie.
Первоисточники:
- RFC 791 (IPv4), RFC 9293 (TCP, актуальная редакция вместо RFC 793), RFC 1122 (требования к хостам).
- RFC 9110 (семантика HTTP), RFC 9112 (HTTP/1.1), RFC 9113 (HTTP/2), RFC 9114 (HTTP/3).
- RFC 8446 (TLS 1.3), RFC 9000 (QUIC), RFC 1034 и RFC 1035 (DNS).
- Saltzer, Reed, Clark. End-to-End Arguments in System Design (1984).
- Clark. The Design Philosophy of the DARPA Internet Protocols (SIGCOMM, 1988).
- Jacobson. Congestion Avoidance and Control (SIGCOMM, 1988).
Практика: Wireshark User’s Guide и коллекция sample captures — учебные захваты почти всех протоколов; Cloudflare Learning Center — короткие корректные объяснения инфраструктурных тем; Julia Evans, «Networking! ACK!» — сжатые шпаргалки по отладке.
Что дальше
Канальный уровень: Ethernet, MAC, ARP, коммутаторы, VLAN — спустимся на самый низ и разберём, как кадр находит соседа в пределах одного сегмента: почему MAC-адрес не имеет ничего общего с маршрутизацией, как ARP превращает IP в MAC и почему это до сих пор незащищённый протокол, чем коммутатор отличается от хаба и что он держит в CAM-таблице, зачем нужны VLAN и как один физический провод превращается в десяток изолированных сетей.