Четвёртый из семи уровней. Адреса и имена уже пройдены – данные знают, куда ехать. Теперь про то, КАК они едут: TCP с гарантией доставки против UDP без гарантий, трёхстороннее рукопожатие, состояния соединения и два самых частых диагноза на дежурстве, которые новички путают – "refused" и "timeout".
"Connection refused" и "Connection timeout" – две самых частых ошибки в DevOps. Они означают совершенно разные вещи, и различие – в TCP."
Откуда это пошло
1974 – Vint Cerf и Bob Kahn публикуют TCP (Transmission Control Protocol). Проблема: IP-пакеты могут теряться, дублироваться, приходить не в том порядке. TCP решает: надёжная, упорядоченная доставка поверх ненадёжной сети.
1980 – UDP (User Datagram Protocol, RFC 768). Иногда надёжность не нужна, а скорость – критична. DNS-запрос: один вопрос – один ответ, зачем рукопожатие? Видеозвонок: лучше пропустить кадр, чем ждать повторной передачи.
Аналогия: TCP – заказное письмо с уведомлением о вручении. UDP – открытка без обратного адреса.
TCP vs UDP – фундаментальное различие
| TCP | UDP | |
|---|---|---|
| Соединение | Да (рукопожатие, handshake) | Нет (без установления, fire-and-forget) |
| Надёжность | Гарантированная доставка | Нет гарантий |
| Порядок | Гарантирован | Не гарантирован |
| Контроль потока | Да (flow control, congestion) | Нет |
| Накладные расходы (overhead) | Высокий (20+ байт заголовок) | Низкий (8 байт) |
| Примеры | HTTP, SSH, SMTP, PostgreSQL | DNS, NTP, DHCP, видео, игры |
| Для DevOps | 95% трафика | DNS-запросы, мониторинг (StatsD) |
Трёхстороннее рукопожатие (TCP handshake)
Клиент (curl) Сервер (nginx:8080)
│ │
│──── SYN (seq=100) ──────────────────→│ 1. "Привет, хочу подключиться"
│ │
│←─── SYN-ACK (seq=300, ack=101) ─────│ 2. "ОК, я тоже готов"
│ │
│──── ACK (seq=101, ack=301) ──────────→│ 3. "Подтверждаю, начинаем"
│ │
│ ═══ СОЕДИНЕНИЕ УСТАНОВЛЕНО ═══│
│ │
│──── DATA: "GET /health HTTP/1.1" ───→│ 4. Отправка данных
│ │
Что происходит при ошибках
"Connection refused" (мгновенная ошибка):
│──── SYN ──────────────→│
│←─── RST ──────────────│ Сервер АКТИВНО отказывает:
│ │ - Порт не слушается
│ │ - файрвол с REJECT
"Connection timeout" (ожидание 30+ секунд):
│──── SYN ──────────────→│ ... тишина ...
│──── SYN (retry 1) ───→│ ... тишина ...
│──── SYN (retry 2) ───→│ ... тишина ...
│──── TIMEOUT ──────────│ Пакет пропал:
│ │ - файрвол с DROP (не REJECT)
│ │ - Хост выключен
│ │ - Нет маршрута
20/80 для диагностики (troubleshooting):
- Connection refused → сервис не запущен ИЛИ слушает на другом порту/интерфейсе
- Connection timeout → файрвол DROP, хост недоступен, неправильный IP/порт
Порты – адреса для приложений
IP-адрес = адрес компьютера (как адрес дома)
Порт = номер квартиры (какое приложение)
Полный адрес: 10.0.0.5:8080
└─IP────┘ └порт┘
Порты, которые стоит знать наизусть
Строго говоря, "системные" порты (well-known) – это только 0–1023 (ниже: SSH, DNS, HTTP, HTTPS). Остальные – зарегистрированные пользовательские, но в DevOps встречаются постоянно:
| Порт | Протокол | Сервис |
|---|---|---|
| 22 | TCP | SSH |
| 53 | TCP/UDP | DNS |
| 80 | TCP | HTTP |
| 443 | TCP | HTTPS |
| 5432 | TCP | PostgreSQL |
| 6379 | TCP | Redis |
| 9090 | TCP | Prometheus |
| 3000 | TCP | Grafana |
| 8080 | TCP | HTTP (альтернативный) |
| 9000 | TCP | ClickHouse (native) |
| 8123 | TCP | ClickHouse (HTTP) |
| 2379 | TCP | etcd (K8s) |
| 6443 | TCP | K8s API server |
| 10250 | TCP | kubelet |
Эфемерные порты (ephemeral, 32768–60999)
# Клиент использует случайный порт:
curl http://api:8080
# В TCP-пакете:
# src_port = 54321 (эфемерный – случайный)
# dst_port = 8080 (системный – целевой)
# Диапазон эфемерных портов:
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768 60999
Сокет – и почему ss так называется
ss расшифровывается как socket statistics. А что такое сокет – ещё помнишь? В сетях сокет – это конечная точка соединения, пара IP:порт.
Слушающий сокет (listening) один: 0.0.0.0:8080. А вот установленное соединение задаётся уже четвёркой (4-tuple):
(IP:порт клиента) ↔ (IP:порт сервера)
10.0.0.10:54321 ↔ 10.0.0.5:8080
Отсюда то, что обычно и спрашивают:
- Почему nginx на одном порту 8080 держит тысячи соединений? Порт сервера один, но каждый клиент приходит со своим
src IP:порт– каждая четвёрка уникальна. - Почему на клиенте кончаются порты (
Cannot assign requested address)? При массе исходящих к одномуdstуникальным остаётся толькоsrc port, а их всего ~28000. - Колонки
Local Address:PortиPeer Address:Portвss -tnp(ниже) – это и есть две половины четвёрки.
И не путать – сокетов много:
- Unix-сокет (Unix domain socket) – не
IP:порт, а файл:/run/postgresql/.s.PGSQL.5432,/run/docker.sock. Тот же PostgreSQL слушает и сетевой сокетIP:5432, и локальный файл-сокет (ss -xпокажет их). - Процессорный сокет (CPU socket из
lscpu) – это железо, разъём под процессор. - Вызов
socket()из Berkeley API – программистская абстракция создания сокета; под капот приложения нам не надо.
ss – современная замена netstat
# Все слушающие TCP-порты:
ss -tlnp
# State Local Address:Port Process
# LISTEN 0.0.0.0:22 sshd
# LISTEN 0.0.0.0:8080 nginx
# LISTEN 127.0.0.1:5432 postgres ← только localhost!
# LISTEN [::]:80 nginx ← IPv4 + IPv6
# Расшифровка флагов:
# -t = TCP
# -l = LISTEN (слушающие)
# -n = числовые порты (не резолвить имена)
# -p = показать процесс (нужен root)
# Все активные TCP-соединения:
ss -tnp
# State Local Address:Port Peer Address:Port Process
# ESTAB 10.0.0.5:8080 10.0.0.10:54321 nginx
# TIME-WAIT 10.0.0.5:8080 10.0.0.11:54322
# UDP:
ss -ulnp
# LISTEN 0.0.0.0:53 coredns
# Статистика:
ss -s
# TCP: 45 (estab 12, closed 5, timewait 8)
# Фильтр по порту:
ss -tnp dst :8080
ss -tnp src :22
netstat vs ss:
netstat– пакетnet-tools(устаревший, deprecated).ss– пакетiproute2(современный, быстрее). На собесе используйss.
Состояния TCP-соединения
┌─────────┐
│ CLOSED │
└────┬─────┘
SYN sent │ SYN received
┌────▼─────┐
│ SYN_SENT │ (клиент ждёт SYN-ACK)
└────┬─────┘
SYN-ACK │
┌────▼──────┐
│ESTABLISHED │ ← рабочее состояние
└────┬──────┘
FIN │
┌────▼──────┐
│ FIN_WAIT │ (закрытие инициировано)
└────┬──────┘
│
┌────▼──────┐
│ TIME_WAIT │ ← ожидание 2×MSL (60 сек)
└────┬──────┘
│
┌────▼─────┐
│ CLOSED │
└──────────┘
TIME_WAIT – частая проблема
# Много TIME_WAIT = нормально для высоконагруженных серверов
ss -tn state time-wait | wc -l
# 1500 ← если < 10000 – ОК
# Проблема: если > 28000 (лимит ephemeral портов)
# → "Cannot assign requested address"
# Решение (в /etc/sysctl.conf):
net.ipv4.tcp_tw_reuse = 1 # переиспользовать TIME_WAIT сокеты
net.core.somaxconn = 65535 # увеличить очередь соединений (backlog)
tcpdump – "рентген" для сетевого трафика
# Весь трафик на интерфейсе eth0:
sudo tcpdump -i eth0
# Только TCP на порт 8080:
sudo tcpdump -i eth0 tcp port 8080
# TCP-рукопожатие (SYN, SYN-ACK, ACK):
sudo tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' -c 10
# DNS-запросы:
sudo tcpdump -i eth0 udp port 53
# HTTP-запросы (ASCII):
sudo tcpdump -i eth0 -A tcp port 80
# Сохранить в файл для анализа в Wireshark:
sudo tcpdump -i eth0 -w capture.pcap tcp port 8080
# Фильтр по IP:
sudo tcpdump -i eth0 host 10.0.0.5
sudo tcpdump -i eth0 src 10.0.0.5 and dst port 8080
Чтение вывода tcpdump
10:05:23.456789 IP 10.0.0.5.54321 > 10.0.0.10.8080: Flags [S], seq 12345
│ │ └── src:port ┘ └── dst:port ┘ └─ SYN ┘ └── seq ┘
│ └── протокол (IP)
└── timestamp
Flags:
[S] = SYN (начало соединения)
[S.] = SYN-ACK (ответ на SYN)
[.] = ACK
[P.] = PUSH-ACK (данные)
[F.] = FIN-ACK (закрытие)
[R.] = RST (сброс – "connection refused")
Практика: "Почему не подключается?"
Чеклист диагностики (от L1 к L7)
# 1. Сеть доступна? (L3)
ping 10.0.0.10
# Если timeout → проблема маршрутизации или firewall
# 2. Порт открыт? (L4)
ss -tlnp | grep 8080
# Если пусто → сервис не слушает
# 3. TCP-соединение устанавливается?
# Быстрая проверка:
nc -zv 10.0.0.10 8080
# Connection to 10.0.0.10 8080 port [tcp/*] succeeded! ← ОК
# nc: connect to 10.0.0.10 port 8080: Connection refused ← порт закрыт
# nc: connect to 10.0.0.10 port 8080: Connection timed out ← firewall DROP
# 4. Что на проводе?
sudo tcpdump -i eth0 host 10.0.0.10 and port 8080 -c 5
# 5. Приложение отвечает? (L7)
curl -v http://10.0.0.10:8080/health
Подвохи для собеса
Подвох 1: "Чем "Connection refused" отличается от "Connection timeout"?"
Ответ через TCP:
Connection refused:
Клиент отправил SYN → получил RST (reset)
Значит: пакет ДОШЁЛ до хоста, но порт НЕ слушает
Время: мгновенно (< 1мс)
Причины: сервис не запущен, слушает другой порт, firewall REJECT
Connection timeout:
Клиент отправил SYN → тишина → retry → тишина → timeout
Значит: пакет НЕ ДОШЁЛ или ответ потерялся
Время: 30–120 секунд (зависит от tcp_syn_retries)
Причины: firewall DROP, хост выключен, неправильный IP, нет маршрута
На собесе: "Refused – RST пришёл, хост жив, но порт закрыт. Timeout – ответа нет, пакет не дошёл или заблокирован. Разница принципиальна: refused = проблема на хосте (сервис), timeout = проблема в сети (маршрутизация/firewall)."
Подвох 2: "Зачем нужен трёхстороннее рукопожатие? Почему не двустороннее?"
Ответ:
Двустороннее: SYN → SYN-ACK. Проблема: клиент отправил SYN, но не получил SYN-ACK (потерялся). Клиент думает, что нет соединения. Сервер думает, что есть. Рассинхронизация.
Трёхстороннее: SYN → SYN-ACK → ACK. Обе стороны подтвердили, что обе стороны получили подтверждение.
Двустороннее (сломанное):
Клиент → SYN → Сервер ✓ Сервер знает о клиенте
Клиент ← SYN-ACK ← Сервер ✓ Клиент знает о сервере
Но сервер НЕ знает, получил ли клиент SYN-ACK!
Трёхстороннее (рабочее):
Клиент → SYN → Сервер ✓ Сервер знает о клиенте
Клиент ← SYN-ACK ← Сервер ✓ Клиент знает о сервере
Клиент → ACK → Сервер ✓ Сервер знает, что клиент получил
На собесе: "Трёхстороннее рукопожатие гарантирует, что обе стороны синхронизировали начальные порядковые номера (sequence numbers) и обе подтвердили готовность. Без третьего ACK – полуоткрытые соединения (half-open) и утечка ресурсов на сервере (атака SYN-флуд эксплуатирует именно это)."
Подвох 3: "Когда UDP лучше TCP?"
Ответ:
- DNS – один запрос, один ответ, < 512 байт. TCP overhead (handshake) больше самих данных
- Мониторинг (StatsD, syslog) – потеря одной метрики некритична, а задержка TCP – критична
- Видео/аудио – лучше пропустить кадр, чем ждать повторной передачи (TCP retransmit = задержка)
- Игры – латентность > надёжность
- QUIC/HTTP3 – Google построил надёжный протокол ПОВЕРХ UDP (вместо TCP), потому что TCP-рукопожатие = лишний круговой обмен (RTT)
На собесе: "UDP выбирают, когда потеря отдельных пакетов допустима, а задержка – нет. Или когда обмен запрос-ответ (request-response) укладывается в один пакет (DNS). Современный тренд – QUIC: надёжность TCP + скорость UDP, потому что рукопожатие совмещено с TLS-рукопожатием (handshake)."
Код-челлендж
Задача: на любой Linux-машине выполни диагностику и ответь:
- Какие порты слушает nginx? (
ss -tlnp) - Сколько активных TCP-соединений к PostgreSQL (порт 5432)?
- Перехвати DNS-запрос с помощью tcpdump при выполнении
dig google.com - Проверь, доступен ли порт 6443 (K8s API) на master-ноде
Решение
# 1. Порты nginx:
ss -tlnp | grep nginx
# LISTEN 0.0.0.0:80 nginx
# LISTEN 0.0.0.0:443 nginx
# 2. Активные соединения к PostgreSQL (на сервере БД порт 5432 – локальный):
ss -tnp src :5432 | grep ESTAB | wc -l
# С клиента, который сам ходит в БД, наоборот: dst :5432
# 3. Перехват DNS:
# Терминал 1:
sudo tcpdump -i eth0 udp port 53 -c 4
# Терминал 2:
dig google.com
# Увидишь: запрос (Query) и ответ (Response)
# 4. K8s API порт:
nc -zv <master-ip> 6443 -w 3
# succeeded! → доступен
# Connection timed out → firewall блокирует
Дальше → Уровень 4
Ты понимаешь TCP: рукопожатие, порты, состояния, умеешь диагностировать ss и tcpdump. Ты знаешь, ПОЧЕМУ "Connection refused" отличается от "timeout".
Но TCP – транспорт. Данные, которые по нему передаются – это HTTP: запросы, ответы, заголовки, статус-коды. А с 2015 года почти весь HTTP – зашифрован TLS (HTTPS). Когда ты видишь 502 Bad Gateway или ERR_CERT_DATE_INVALID – это проблемы на уровне HTTP/TLS.
→ Уровень 4: HTTP и TLS – протокол, на котором всё держится