Как данные попадают не просто на сервер, а в нужную программу
ssncIP-адрес доставляет пакет до машины, но на ней работают десятки программ. Какой из них отдать данные, решает порт — число от 0 до 65535 в заголовке TCP или UDP. Пара «адрес и порт» называется сокетом, а соединение однозначно задаётся четвёркой: адрес и порт отправителя, адрес и порт получателя.
192.168.116.10:54159 ──────────▶ 192.168.116.34:22
клиент, временный порт сервер SSH, известный порт
| Диапазон | Название | Кто использует |
|---|---|---|
| 0–1023 | Системные (well-known) | Стандартные службы; открыть их для приёма может только root |
| 1024–49151 | Зарегистрированные | Приложения: PostgreSQL 5432, альтернативный HTTP 8080 |
| 49152–65535 (в Linux с 32768) | Временные (эфемерные) | Клиентская сторона: система выдаёт порт на время соединения |
$ grep -E '^(ssh|smtp|domain|http|ntp|https|postgresql)\b' /etc/services
ssh 22/tcp # SSH Remote Login Protocol
smtp 25/tcp mail
domain 53/tcp # Domain Name Server
domain 53/udp
http 80/tcp www # WorldWideWeb HTTP
ntp 123/udp # Network Time Protocol
https 443/tcp # http protocol over TLS/SSL
https 443/udp # HTTP/3
postgresql 5432/tcp postgres # PostgreSQL Database
Номер порта — договорённость, а не закон: SSH можно запустить на 2222, веб-сервер — на 8080. Клиент в этом случае должен знать нестандартный порт.
| UDP | TCP | |
|---|---|---|
| Соединение | Нет: каждая датаграмма сама по себе | Устанавливается перед обменом и закрывается после |
| Доставка | Не гарантирована: потеря молча | Гарантирована: подтверждения и повторная отправка |
| Порядок | Не гарантирован | Восстанавливается по номерам |
| Заголовок | 8 байт | 20–60 байт |
| Скорость реакции | Сразу | После рукопожатия, с регулировкой скорости |
| Где | DNS, NTP, DHCP, голос и видео, игры, QUIC (HTTP/3) | HTTP/1.1 и HTTP/2, SSH, почта, базы данных |
UDP выбирают, когда опоздавшие данные бесполезны (кадр видеозвонка), когда запрос-ответ укладывается в один пакет (DNS) или когда надёжность приложение обеспечивает само (QUIC). TCP — когда важно получить всё и по порядку: файл, страницу, транзакцию.
TCP-соединение начинается с обмена тремя сегментами. Захватим его с помощью tcpdump при подключении к SSH на этой же машине:
$ sudo tcpdump -i lo -nn 'tcp port 22'
IP 127.0.0.1.58100 > 127.0.0.1.22: Flags [S], seq 1056504289, win 65495, length 0
IP 127.0.0.1.22 > 127.0.0.1.58100: Flags [S.], seq 2580490338, ack 1056504290, win 65483, length 0
IP 127.0.0.1.58100 > 127.0.0.1.22: Flags [.], ack 1, win 512, length 0
IP 127.0.0.1.58100 > 127.0.0.1.22: Flags [F.], seq 1, ack 1, win 512, length 0
IP 127.0.0.1.22 > 127.0.0.1.58100: Flags [.], ack 2, win 512, length 0
IP 127.0.0.1.22 > 127.0.0.1.58100: Flags [P.], seq 1:42, ack 2, win 512, length 41: SSH: SSH-2.0-OpenSSH_10.0p2 Debian-7+deb13u4
Первые три строки — рукопожатие. Клиент здесь только проверял порт и сразу начал закрывать соединение ([F.]), а сервер подтвердил это и всё равно успел отправить приветствие с версией SSH — по одному соединению TCP данные идут в обе стороны независимо.
| Флаг в tcpdump | Название | Смысл |
|---|---|---|
[S] | SYN | Клиент: «хочу соединиться, мой начальный номер такой-то» |
[S.] | SYN + ACK | Сервер: «согласен, подтверждаю твой номер, мой такой-то» |
[.] | ACK | Клиент подтверждает — соединение установлено |
[P.] | PUSH + ACK | Данные: сервер SSH представился версией |
[F.] | FIN | «Я закончил передавать» — начало закрытия |
[R] | RST | Сброс: на этом порту никто не слушает или соединение оборвано |
Номера seq и ack — счётчики байтов: так TCP понимает, что дошло, а что нужно отправить повторно. win — окно: сколько данных получатель готов принять, не подтверждая. В Wireshark тот же обмен виден с расшифровкой каждого поля — откройте файл, записанный tcpdump -w capture.pcap.
[R] на [S] — порт закрыт, служба не запущена; нет ответа вовсе — пакеты отбрасывает межсетевой экран или узел недоступен; рукопожатие прошло, а данные не идут — проблема в приложении.$ ss -tan
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 200 127.0.0.1:5432 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
ESTAB 0 80 192.168.116.34:22 192.168.116.10:54159
TIME-WAIT 0 0 192.168.116.34:33744 146.75.122.132:80
TIME-WAIT 0 0 192.168.116.34:33756 146.75.122.132:80
| Состояние | Что значит |
|---|---|
LISTEN | Служба ждёт подключений на порту |
SYN-SENT | Клиент отправил SYN, ждёт ответа. Много таких — назначение недоступно или фильтруется |
ESTAB | Соединение установлено, идёт обмен |
TIME-WAIT | Соединение закрыто, сторона, закрывшая первой, ещё ~60 с помнит его, чтобы поздние пакеты не попали в новое соединение. Это нормально |
CLOSE-WAIT | Другая сторона закрыла, а наша программа — ещё нет. Копится — ошибка в приложении |
Recv-Q и Send-Q у установленного соединения — байты, ещё не прочитанные программой и ещё не подтверждённые другой стороной. Постоянно растущий Send-Q говорит, что получатель не успевает или сеть теряет пакеты. Полезные фильтры: ss -tn state established, ss -tn dst 192.168.116.10, ss -tlnp — кто слушает (модуль 08 курса Linux).
nc (netcat) открывает TCP- или UDP-соединение и передаёт по нему стандартный ввод. Это лучший способ проверить, доступен ли порт, без установки клиента нужной службы. В Debian ставьте вариант из пакета netcat-openbsd — примеры ниже для него.
$ nc -zv -w2 nevabit.ru 443 80 22 # -z: только проверить, -v: подробно
Connection to nevabit.ru (188.120.235.218) 443 port [tcp/https] succeeded!
Connection to nevabit.ru (188.120.235.218) 80 port [tcp/http] succeeded!
nc: connect to nevabit.ru (188.120.235.218) port 22 (tcp) timed out: Operation now in progress
$ nc -zv 127.0.0.1 23
nc: connect to 127.0.0.1 port 23 (tcp) failed: Connection refused
Разница между «refused» и «timed out» важна: отказ пришёл от узла — он доступен, но порт закрыт (ответ RST); тайм-аут — ответа нет совсем, пакеты отбрасываются по дороге или на узле межсетевым экраном. Порт 22 на сайте курса закрыт именно экраном.
Два терминала — и у вас простейший сервер и клиент:
# терминал 1: слушать порт 5001
$ nc -l 127.0.0.1 5001
данные от клиента
# терминал 2: подключиться и отправить
$ echo "данные от клиента" | nc -N 127.0.0.1 5001
Протокол HTTP можно «набрать руками» — это текст:
$ printf 'GET / HTTP/1.1\r\nHost: nevabit.ru\r\nConnection: close\r\n\r\n' | nc -w3 nevabit.ru 80
HTTP/1.1 301 Moved Permanently
Server: nginx
Content-Type: text/html
Content-Length: 162
Connection: close
echo test | nc -u -w1 127.0.0.1 5999 завершится с кодом 0, хотя порт никто не слушает. Подтверждений в UDP нет, поэтому доступность UDP-службы проверяют её собственным клиентом: DNS — dig, NTP — ntpdate -q.tcpdump показывает пакеты, проходящие через интерфейс. Нужен sudo. Главное — фильтр: без него на загруженном сервере вывод не прочитать.
$ sudo tcpdump -i ens18 -nn -c 4 'udp port 53' # 4 пакета DNS
IP 192.168.116.34.38947 > 192.168.116.1.53: 18255+ AAAA? nevabit.ru. (28)
IP 192.168.116.1.53 > 192.168.116.34.38947: 18255 0/1/0 (80)
IP 192.168.116.34.60184 > 192.168.116.1.53: 16902+ AAAA? nevabit.ru.localdomain. (40)
IP 192.168.116.1.53 > 192.168.116.34.60184: 16902 NXDomain 0/1/0 (115)
| Опция или фильтр | Значение |
|---|---|
-i ens18, -i any | Интерфейс, все интерфейсы |
-nn | Не переводить адреса и порты в имена — быстрее и точнее |
-c 10 | Остановиться после 10 пакетов |
-w file.pcap | Записать в файл для Wireshark |
host 10.0.0.5, port 443, tcp, icmp | Фильтры, объединяются and, or, not |
'tcp[tcpflags] & tcp-syn != 0' | Только пакеты с флагом SYN — попытки соединений |
Захват выше — наглядный пример UDP: запрос и ответ по одному пакету, без рукопожатия. Видно и то, как система, не найдя AAAA-запись, пробует дописать к имени домен поиска localdomain.
ss -tan: LISTEN, ESTAB, TIME-WAIT (норма), CLOSE-WAIT (ошибка приложения)nc -zv — проверка порта: «refused» и «timed out» означают разноеtcpdump -nn с фильтром — что на самом деле идёт по сети