Learning Platform
Глоссарий Troubleshooting
Урок 17.02 · 22 мин
Начальный
pcaptcpdumpWiresharkTLSTCPAnalysis

Анализ pcap — что реально летит в проводе при curl https

Если первый урок capstone был про дизайн, то этот — про наблюдение. Прежде чем строить свою инфраструктуру, полезно один раз увидеть, что происходит, когда вы делаете обычный curl https://example.com. Какой DNS-запрос ушёл? Сколько пакетов потребовалось на TCP-handshake? Что внутри ClientHello? Где границы между TLS-record’ами?

Это упражнение — из тех, что разделяет инженеров до и после: до него сети ощущаются магией, после — набором конкретных, поддающихся анализу пакетов. Никакой магии нет: всё, что происходит, можно увидеть в tcpdump или Wireshark, разобрать по байтам и понять.

В этом уроке мы захватим один HTTPS-запрос с нуля, разберём четыре фазы (DNS -> TCP -> TLS -> HTTP), найдём SNI, проследим, какие пакеты потерялись или ретрансмитились. Команды одинаковые для macOS и Linux.


Подготовка — инструменты и привилегии

tcpdump требует root-прав, потому что захват трафика — это privileged операция (можно подслушать чужой трафик на shared-интерфейсе). На Linux вы запускаете через sudo tcpdump, на macOS — то же самое.

Сначала проверим, что инструменты установлены:

tcpdump --version
# tcpdump version 4.99.x

wireshark --version 2>/dev/null || tshark --version
# Wireshark 4.x.x или TShark

Если wireshark нет — ничего страшного, мы будем работать через CLI. Для визуального просмотра можно потом открыть .pcap в Wireshark.

Найдём правильный интерфейс. На macOS обычно en0 (Wi-Fi) или en1 (Ethernet), на Linux — eth0, ens33, wlan0:

# macOS
ifconfig | grep -E "^[a-z]" | head -5
# en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500

# Linux
ip -br addr show
# eth0    UP    192.168.1.42/24 ...

Дальше — запускаем захват и в другом терминале делаем запрос.


Захват — одна команда, один curl

В одном терминале запускаем tcpdump на нужный интерфейс с фильтром port 53 or port 443 (DNS + HTTPS), записывая в файл:

sudo tcpdump -i en0 -w trace.pcap '(port 53 or port 443) and host example.com'

Флаги:

  • -i en0 — интерфейс (замените на свой)
  • -w trace.pcap — запись в файл (без -w пишет в stdout в человекочитаемом виде)
  • 'port 53 or port 443' — BPF-фильтр: DNS + HTTPS
  • and host example.com — только трафик к/от example.com

В другом терминале:

curl https://example.com/ -o /dev/null -s

Вернёмся в первый терминал, нажмём Ctrl+C — tcpdump остановится. Должно быть от 10 до 30 пакетов.

Проверим, что файл записался:

ls -la trace.pcap
# -rw-r--r-- 1 root staff 8234 May 18 14:32 trace.pcap

sudo chown $(whoami) trace.pcap  # чтобы открывать без sudo

Теперь у нас есть pcap — бинарный файл с метаданными каждого пакета (timestamp, размер, флаги) и payload’ом.


Чтение pcap в человеческом виде

tcpdump -r trace.pcap -nn -tttt | head -30

Флаги:

  • -r trace.pcap — читать из файла, не из интерфейса
  • -nn — не резолвить ни IP в имена, ни порты в названия сервисов
  • -tttt — timestamps в человекочитаемом формате

Что мы увидим — разберём по фазам.

Четыре фазы curl https://example.com

Каждая фаза состоит из нескольких пакетов; вместе они занимают ~100-300 мс

Phase 1: DNSЗапрос к /etc/resolv.conf resolver'у. UDP port 53. Запрос A-record example.com. Ответ -- один или несколько IPv4. ~1-50 мс
resolve
Phase 2: TCP handshakeSYN -> SYN-ACK -> ACK. Три пакета. Устанавливают TCP-соединение. ~1 RTT. На локалхосте -- меньше мс, на дальнем CDN -- 50-200 мс
Phase 3: TLS handshakeClientHello (от нас, с SNI) -> ServerHello + Certificate + Finished (от сервера) -> Finished (от нас). В TLS 1.3 -- 1 RTT. SNI виден в plaintext
encrypted
Phase 4: HTTP requestGET / HTTP/1.1, headers. Зашифрован TLS. В pcap виден только размер -- payload не разобрать без ключей

Phase 1 — DNS query

В начале pcap должны быть два пакета UDP на порт 53:

2026-05-18 14:32:01.123456 IP 192.168.1.42.54321 > 192.168.1.1.53: 12345+ A? example.com. (29)
2026-05-18 14:32:01.156789 IP 192.168.1.1.53 > 192.168.1.42.54321: 12345 1/0/0 A 93.184.216.34 (45)

Разбираем первый пакет:

  • 192.168.1.42.54321 — наш хост, порт 54321 (эфемерный, выбран ядром)
  • > — направление (от/к)
  • 192.168.1.1.53 — наш router (или DNS-сервер из /etc/resolv.conf), порт 53
  • 12345+ — ID запроса 12345, флаг + означает рекурсивный
  • A? example.com. — спрашиваем A-record для example.com
  • (29) — 29 байт DNS-payload

Второй пакет — ответ:

  • 12345 1/0/0 — тот же ID (так клиент сматчит запрос-ответ), 1 answer + 0 authority + 0 additional
  • A 93.184.216.34 — ответ: A-record 93.184.216.34

Это базовый кусок, который вы должны узнать в любом DNS-трафике. На реальном Wi-Fi первый DNS-запрос может занять 5-50 мс, последующие — из кэша, мгновенно.

TIP

Если в pcap нет DNS-запросов, это нормально — значит, ответ был в кэше. Запустите sudo killall -HUP mDNSResponder на macOS или sudo resolvectl flush-caches на Linux, чтобы очистить DNS-кэш, и повторите. Тогда DNS-пакеты появятся.


Phase 2 — TCP handshake

После DNS curl открывает TCP-соединение на 93.184.216.34:443. Три пакета:

2026-05-18 14:32:01.200001 IP 192.168.1.42.50001 > 93.184.216.34.443: Flags [S], seq 1234567890, win 65535
2026-05-18 14:32:01.245678 IP 93.184.216.34.443 > 192.168.1.42.50001: Flags [S.], seq 987654321, ack 1234567891, win 65535
2026-05-18 14:32:01.245789 IP 192.168.1.42.50001 > 93.184.216.34.443: Flags [.], ack 987654322, win 65535

Это 3-way handshake:

TCP 3-way handshake -- три пакета, одно соединение
client
server
SYN, seq=XSYN-ACK, seq=Y, ack=X+1ACK, ack=Y+1

В tcpdump флаги в скобках:

  • [S] — SYN
  • [S.] — SYN + ACK (точка = ACK)
  • [.] — только ACK
  • [P.] — PSH + ACK (данные)
  • [F.] — FIN + ACK (закрытие)
  • [R] — RST (форсированный разрыв)

Между SYN и SYN-ACK — ~45 мс. Это round-trip time (RTT) до сервера. Для example.com (CDN на Fastly) обычно 20-100 мс. Если в pcap RTT 300+ мс — сервер далёкий или сеть с проблемами.


Phase 3 — TLS handshake

После TCP начинается TLS. В TLS 1.3 handshake занимает 1 RTT (1 round trip). Видим примерно такие пакеты:

2026-05-18 14:32:01.300000 IP 192.168.1.42.50001 > 93.184.216.34.443: Flags [P.], seq 1:518, ack 1, length 517
2026-05-18 14:32:01.350000 IP 93.184.216.34.443 > 192.168.1.42.50001: Flags [P.], seq 1:3500, ack 518, length 3499
2026-05-18 14:32:01.355000 IP 192.168.1.42.50001 > 93.184.216.34.443: Flags [P.], seq 518:582, ack 3500, length 64

Первый пакет (517 байт) — ClientHello. Это часть, которую можно посмотреть в plaintext, потому что шифрование ещё не установлено. Внутри:

  • TLS-версия (0x0303 для TLS 1.2 framing совместимости)
  • random (32 байта)
  • список cipher suites (что мы поддерживаем)
  • extensions: SNI, ALPN, supported_groups, и так далее

Где увидеть SNI

SNI (Server Name Indication) — это TLS-extension, в которой клиент сообщает серверу, к какому домену он подключается. Это нужно, потому что один IP может хостить много доменов под разными сертификатами (так работает CDN).

Чтобы извлечь SNI из pcap, удобнее всего tshark:

tshark -r trace.pcap -Y 'tls.handshake.type == 1' \
    -T fields -e tls.handshake.extensions_server_name
# example.com

Расшифровка флагов:

  • -Y 'tls.handshake.type == 1' — фильтр на ClientHello (type 1)
  • -T fields -e ... — вывести только указанное поле

SNI приходит в plaintext. Это означает, что любой посредник на пути (provider, корпоративный proxy, MITM-злоумышленник) знает, к какому домену вы идёте, хотя сам трафик зашифрован. Это известная privacy-проблема, и поэтому TLS 1.3 разрабатывает Encrypted ClientHello (ECH) — но он пока в draft и поддерживается только Cloudflare + Firefox.

WARNING

Если вы анализируете пакет от незнакомого хоста и видите SNI — значит он использует обычный TLS, без ECH. По SNI можно определить домен даже если IP принадлежит CDN (один IP = много доменов). Это используется и в обе стороны: для аналитики, для блокировки, для firewall’ов.

Server response — ServerHello + Certificate

Второй TLS-пакет (3499 байт в нашем примере) большой, потому что содержит certificate chain — 1-3 сертификата по 1-2 КБ каждый. Также там ServerHello (выбран cipher suite, ключи Диффи-Хеллмана) и Finished.

Чтобы посмотреть сертификат:

tshark -r trace.pcap -Y 'tls.handshake.type == 11' \
    -T fields -e x509sat.printableString
# Fastly, Inc.
# *.example.com
# CN=Fastly Inc., O=Fastly, ...

tls.handshake.type == 11 — это Certificate handshake. Если в pcap его не видно, значит сервер использует TLS resumption: клиент подключался раньше, есть session ticket, certificate не пересылается. Это нормально.


Phase 4 — HTTP request (зашифрован)

После TLS handshake идут пакеты с данными. В tcpdump они выглядят как обычные TCP-сегменты с PSH-флагом:

2026-05-18 14:32:01.360000 IP 192.168.1.42.50001 > 93.184.216.34.443: Flags [P.], seq 582:660, ack 3500, length 78
2026-05-18 14:32:01.410000 IP 93.184.216.34.443 > 192.168.1.42.50001: Flags [P.], seq 3500:5000, ack 660, length 1500

Первый пакет (78 байт) — наш GET / HTTP/1.1\r\nHost: example.com\r\n\r\n, зашифрованный. Без приватного ключа сервера расшифровать payload нельзя. Но можно видеть:

  • Размер (~80 байт) — типичный для маленького GET без cookies.
  • Time-to-first-byte (TTFB) — разница между отправкой запроса и первым ответом. 50 мс в нашем примере. На медленном сервере было бы 200-500 мс.
  • Размер ответа (1500 байт первого пакета + последующие, в сумме ~1300-1400 байт payload).

Чтобы реально увидеть содержимое HTTP, есть два пути:

  1. Запустить с SSLKEYLOGFILE, который выгрузит ключи сессии в файл, и Wireshark расшифрует pcap.
  2. Использовать curl --trace-ascii или mitmproxy, которые видят HTTP до шифрования.

Первый путь:

export SSLKEYLOGFILE=$HOME/sslkeys.log
curl https://example.com/ -o /dev/null -s
# Wireshark: Preferences -> Protocols -> TLS -> Pre-Master-Secret log -> $HOME/sslkeys.log

В Wireshark теперь pcap покажет расшифрованный HTTP. Полезно для дебага, но в продакшене никогда не делайте этого — ключи сессии могут компрометировать всю историю запросов.


Анализ ретрансмиссий и потерь

В реальной сети не всё идеально. Пакеты теряются, ретрансмитятся, прибывают не по порядку. tcpdump покажет это в флагах:

tcpdump -r trace.pcap -nn | grep -i retransmission

Если в нашем pcap есть retransmission — это значит TCP-стек обнаружил потерю (через duplicate ACKs или timeout) и переслал данные. Не критично, но если retransmissions много — сеть перегружена.

Также полезно посмотреть статистику:

tshark -r trace.pcap -q -z io,stat,0

Это покажет общее число пакетов, размер payload, длительность сессии.

Для глубокого анализа: открыть pcap в Wireshark, Analyze -> Expert Information. Wireshark подсветит проблемы: out-of-order packets, retransmissions, zero windows, large delays.


Что искать — чек-лист

Когда вы анализируете pcap своего HTTPS-запроса, вы должны увидеть:

Чек-лист анализа pcap -- что должно быть

Если чего-то нет -- это сигнал к копанию

DNS query + responseUDP :53. Запрос A или AAAA, ответ с IP. Если нет -- ответ из кэша или DoH/DoT (DNS over HTTPS/TLS)
TCP 3-way handshakeSYN, SYN-ACK, ACK. RTT между SYN и SYN-ACK = latency до сервера. Если SYN без ответа -- firewall блокирует
TLS ClientHello с SNItls.handshake.type==1. SNI = домен в plaintext. Если SNI отсутствует -- старый TLS или ECH
Certificate chaintls.handshake.type==11. 1-3 сертификата. Если нет -- TLS resumption. Если cert невалидный -- TLS alert + RST

После handshake — зашифрованные данные. Структура: PSH+ACK с TCP payload >40 байт.

В конце — FIN или RST. Если запрос завершён нормально, обе стороны шлют FIN, потом ACK — это graceful close. Если соединение оборвалось (timeout, network glitch) — RST.


Попробуй сам

Захвати свой собственный pcap по схеме выше. Открой его в tshark, найди следующее:

# 1. Сколько DNS-запросов в pcap?
tshark -r trace.pcap -Y 'dns.qry.name' -T fields -e dns.qry.name | sort -u

# 2. Какой SNI в ClientHello?
tshark -r trace.pcap -Y 'tls.handshake.type==1' -T fields -e tls.handshake.extensions_server_name

# 3. Сколько байт занял TLS handshake (от SYN до первого PSH с данными)?
tshark -r trace.pcap -q -z conv,tcp

# 4. Сколько пакетов всего? Каков их совокупный размер?
tshark -r trace.pcap -q -z io,stat,0

# 5. Есть ли retransmissions?
tshark -r trace.pcap -Y 'tcp.analysis.retransmission'

Если в pcap слишком много пакетов (background-трафик от других процессов), уточни фильтр при захвате:

sudo tcpdump -i en0 -w trace2.pcap 'host 93.184.216.34'
# (использовать тот же IP, что выдал dig example.com)

И сравни: один и тот же curl можно запустить, изменив curl —tls-max 1.2, и увидеть разницу в TLS handshake — 2 RTT вместо 1.


Сравнение HTTP/1.1 vs HTTP/2 vs HTTP/3

Если вы делаете curl --http2 https://example.com или curl --http3 https://example.com, pcap будет отличаться:

  • HTTP/1.1 — то, что мы разбирали. TLS over TCP, в один поток.
  • HTTP/2 — то же самое поверх TLS, но frames вместо текста, multiplexing. В pcap не видно разницы на уровне пакетов (всё зашифровано), но размер response может быть меньше из-за HPACK-сжатия заголовков.
  • HTTP/3 — QUIC, UDP! Никакого TCP handshake. В pcap: только UDP пакеты на :443. Initial QUIC packet содержит ClientHello внутри.

Захватите три pcap для одного и того же URL с разными --http версиями. Сравните количество пакетов, время handshake, тип transport. Это упражнение примерно за час даёт интуицию того, как современный web эволюционирует.


HTTP/2 frames и HTTP/3 QUIC: что видит pcap и что происходит внутри
Проверка знанийKnowledge check
Вы захватили pcap от curl https://api.partner.com/. В нём нет ни одного DNS-пакета, но TCP handshake и TLS handshake идут нормально, ответ получен. Junior спрашивает: «Это значит, curl не делал DNS-запрос?». Назовите наиболее вероятную причину отсутствия DNS-пакетов в pcap.
ОтветAnswer

Итог

В этом уроке мы научились снимать «магию» с HTTPS:

  • Захват — sudo tcpdump -i <iface> -w trace.pcap 'port 53 or port 443'.
  • Чтение — tcpdump -r trace.pcap -nn -tttt или tshark для структурированного вывода.
  • Четыре фазы — DNS, TCP handshake, TLS handshake, HTTP request.
  • SNI в ClientHello — доступен в plaintext, виден через tshark -Y 'tls.handshake.type==1'.
  • Certificate chain — виден в server response, tls.handshake.type==11.
  • Зашифрованный HTTP — можно расшифровать через SSLKEYLOGFILE + Wireshark, только в dev.
  • Retransmissions и проблемы — видны через tcp.analysis.retransmission фильтр.

В следующем уроке — собираем HTTPS-сервер на Python с self-signed cert. Это маленький ssl.SSLContext и http.server, но с правильно сделанным сертификатом, который curl будет принимать без --insecure.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Вы захватили pcap от curl https://example.com и видите ClientHello размером 517 байт. Внутри TLS-extension есть SNI с значением 'example.com'. Какое утверждение об SNI верно?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4