Когда приложение работает через прокси и что-то идёт не так, первое, что открывает инженер, это логи клиента. Там написано что-то вроде connection reset by peer, read timeout или просто EOF. Проблема в том, что эти строки почти ничего не говорят о причине. Кто сбросил соединение: прокси, целевой сервер или оператор связи между вами и прокси? Дошёл ли запрос вообще до прокси? Успел ли завершиться TLS-хендшейк? Библиотека HTTP-клиента этого не знает, а значит, не знаете и вы.

В этой статье мы спустимся на уровень ниже, к пакетам. Разберём, как снять дамп tcpdump на клиентской машине, что именно в этом дампе видно при работе через HTTP-прокси и SOCKS5, почему в HTTPS-туннеле вы увидите только CONNECT и зашифрованные записи TLS, как читать дамп в Wireshark и как по флагам RST, FIN, ретрансмитам и нулевому окну определить, на чьей стороне оборвалось соединение. Отдельно поговорим о расшифровке собственного трафика через SSLKEYLOGFILE и о том, как правильно подготовить дамп для обращения в поддержку прокси-сервиса, например Proxeon.

Важная оговорка: это не статья про mitmproxy. Там речь о перехвате и подмене HTTPS на уровне приложения с подставным сертификатом. Здесь мы работаем на пакетном уровне, ничего не подменяем и не вскрываем чужой трафик. Наша задача чисто диагностическая: понять, где рвётся цепочка клиент - прокси - целевой сервер.

Введение: когда логов клиента уже не хватает

Представьте типичную ситуацию. Скрипт на Python ходит через мобильный прокси, обрабатывает пару тысяч запросов в час, и примерно два процента из них заканчиваются ошибкой Connection aborted, RemoteDisconnected. Разработчик добавляет ретраи, ошибка не исчезает. Он пишет в поддержку прокси, там просят пример запроса и время. Поддержка смотрит свои логи и отвечает: у нас всё в порядке, соединение до целевого сервера устанавливалось. Кто прав?

Без дампа этот спор бесконечен. С дампом он заканчивается за пять минут: видно, что прокси ответил на CONNECT кодом 200, клиент отправил ClientHello, а через 180 миллисекунд от прокси прилетел RST. Это значит, что либо прокси не смог договориться с целевым сервером, либо сервер сам закрыл соединение. Дальше можно смотреть тайминги и уточнять. Главное, что разговор перешёл из области догадок в область фактов.

Логи HTTP-клиента работают на уровне приложения. Они видят результат, но не процесс. Дамп трафика показывает процесс: каждый пакет с точной отметкой времени, направлением, флагами и размером. Именно поэтому в серьёзной эксплуатации прокси-инфраструктуры умение снять и прочитать дамп считается базовым навыком, а не экзотикой.

Что вы получите из этой статьи

  • Понимание, какие данные физически проходят через сетевой интерфейс клиента при работе через прокси и что из них можно увидеть в открытом виде.
  • Готовые команды tcpdump для записи дампа с фильтрами, ротацией и ограничением размера.
  • Набор фильтров отображения Wireshark, которые можно копировать целиком.
  • Методику различения обрыва на своей стороне, на стороне прокси и на стороне целевого сервера.
  • Безопасный способ расшифровать свой собственный TLS-трафик для отладки.
  • Чеклист подготовки дампа для обращения в поддержку.

Основы: что вообще видно до прокси, внутри туннеля и после него

Начнём со схемы. При работе через прокси есть три участка пути, и на клиентской машине вы физически можете наблюдать только первый.

Три зоны наблюдения

  1. Зона A: клиент - прокси. Это единственное TCP-соединение, которое проходит через ваш сетевой интерфейс. Его видит tcpdump на вашей машине. Здесь видны: TCP-хендшейк с IP-адресом прокси, служебный протокол прокси (HTTP CONNECT или SOCKS5), а дальше либо открытый HTTP, либо зашифрованные записи TLS.
  2. Зона B: внутри туннеля. После того как прокси ответил 200 Connection established, все байты между вами и целевым сервером просто пробрасываются насквозь. Если целевой сервер работает по HTTPS, эти байты представляют собой TLS-записи. Вы видите их структуру (тип записи, длину, ClientHello с SNI, ServerHello, алерты), но не содержимое.
  3. Зона C: прокси - целевой сервер. Это отдельное TCP-соединение, которое прокси устанавливает от своего имени с своего внешнего адреса. На клиентской машине его нет вообще. Увидеть его можно только на самом прокси-сервере, а это инфраструктура провайдера. Всё, что вы узнаёте о зоне C, вы узнаёте косвенно: по кодам ответа на CONNECT, по задержкам и по тому, как прокси закрывает туннель.

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

HTTP-прокси и открытый HTTP

Самый прозрачный случай. Если целевой сайт работает по http без шифрования, клиент отправляет прокси запрос с абсолютным URI:

GET http://example.com/api/items HTTP/1.1
Host: example.com
Proxy-Authorization: Basic dXNlcjpwYXNz
User-Agent: my-client/1.0

В дампе видно всё: метод, путь, заголовки, тело, ответ прокси с телом от сервера. Обратите внимание на заголовок Proxy-Authorization. Это ваши учётные данные в base64, они лежат в дампе в открытом виде. Запомним этот факт, он пригодится, когда будем говорить о передаче дампа третьим лицам.

HTTP-прокси и HTTPS через CONNECT

Здесь начинается самое интересное. Для HTTPS клиент сначала просит прокси установить TCP-туннель до хоста и порта:

CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
Proxy-Authorization: Basic dXNlcjpwYXNz
Proxy-Connection: Keep-Alive

Прокси отвечает:

HTTP/1.1 200 Connection established

С этого момента прокси перестаёт разбирать HTTP. Он просто копирует байты из одного сокета в другой. Клиент начинает TLS-хендшейк напрямую с целевым сервером, и весь HTTP-трафик (GET, POST, заголовки, тела) оказывается внутри TLS. Вот почему в дампе HTTPS-туннеля виден только CONNECT: это последнее, что клиент говорит прокси в открытом виде. Дальше идут TLS-записи, которые прокси не может прочитать, и вы в дампе тоже.

Что при этом остаётся видимым внутри туннеля:

  • ClientHello: версии TLS, набор шифров, расширения и, как правило, SNI (имя хоста в открытом виде).
  • ServerHello: выбранная версия и шифр.
  • В TLS 1.2 - сертификат сервера в открытом виде. В TLS 1.3 сертификат уже зашифрован.
  • TLS Alert: тип алерта виден в TLS 1.2, в TLS 1.3 алерты после хендшейка зашифрованы, но сам факт записи типа Alert длиной 2 байта распознаётся.
  • Размеры и тайминги записей Application Data. По ним можно оценить, сколько данных пришло, прежде чем соединение оборвалось.

SOCKS5

SOCKS5 работает иначе, но идея та же. Клиент отправляет приветствие с методами аутентификации (байты 05 02 00 02), прокси выбирает метод, затем идёт аутентификация по имени и паролю (субпротокол с байтами 01, длина, имя, длина, пароль), затем команда CONNECT с типом адреса 03 (доменное имя) и портом. Прокси отвечает 05 00 при успехе или кодом ошибки: 01 общая ошибка, 03 сеть недоступна, 04 хост недоступен, 05 соединение отклонено, 06 TTL истёк. Эти коды ошибок - прямой перевод того, что прокси увидел в зоне C. Wireshark умеет декодировать SOCKS, если указать порт через Decode As.

Что не видно никогда

С клиентской машины вы не увидите: внешний IP прокси, с которого он ходит на целевой сервер (для мобильных прокси это адрес оператора), TCP-соединение прокси - сервер, DNS-запросы прокси. Если поддержка Proxeon говорит, что видит ошибку на стороне целевого сервера, она смотрит именно в зону C, недоступную вам. Ваша задача - принести дамп зоны A, чтобы сопоставить две картины по времени.

Глубокое погружение: почему прокси не может показать больше и как читать косвенные признаки

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

Туннель CONNECT как байтовая труба

После ответа 200 HTTP-прокси, по спецификации, обязан передавать байты в обе стороны без интерпретации до закрытия любой из сторон. Он не знает, что внутри TLS. Он не знает, какие HTTP-запросы вы отправляете. Он видит только два события: поток байтов и закрытие сокета. Когда целевой сервер закрывает соединение с прокси (отправляет FIN или RST), прокси обязан закрыть соединение с вами. Как именно он это сделает, зависит от реализации: одни прокси транслируют FIN как FIN и RST как RST, другие всё превращают в FIN, третьи при ошибке чтения из удалённого сокета отправляют клиенту RST.

Практическое следствие: RST от IP-адреса прокси не означает, что прокси виноват. Это означает, что туннель закрыт со стороны прокси, а причина может быть где угодно за ним. Чтобы понять причину, смотрим на контекст: что произошло перед RST, сколько времени прошло, дошёл ли ответ.

Разница между SYN на прокси и SYN на целевой сервер

Это самый частый источник путаницы. В дампе через прокси вы никогда не увидите SYN на порт 443 целевого сервера. Все SYN идут на IP и порт прокси. Если SYN на прокси не получает SYN-ACK и повторяется с интервалами 1, 2, 4, 8 секунд, проблема между вами и прокси: сеть, файрвол, неправильный адрес или порт, прокси не запущен. Целевой сервер тут вообще не при чём, вы до него даже не пытались достучаться в терминах вашего дампа.

А вот если TCP с прокси установлен, CONNECT отправлен, и ответ приходит через 20-30 секунд с кодом 504 Gateway Timeout или 502 Bad Gateway, это прокси не смог установить зону C. Тайминг здесь сам говорит за себя: ваш пакет CONNECT ушёл мгновенно, ответа не было долго, значит прокси ждал таймаут в своей попытке.

TLS 1.3, ECH и что меняется к 2026 году

Ландшафт медленно закрывается. TLS 1.3 доминирует, и в нём сертификат сервера зашифрован, поэтому проверить, какой сертификат вернул сервер, по дампу без ключей уже нельзя. Расширение ECH (Encrypted Client Hello) постепенно внедряется браузерами и крупными CDN, и там SNI тоже становится зашифрованным. Для диагностики через прокси это менее критично, потому что имя хоста всё равно видно в строке CONNECT, но привычный фильтр по SNI будет срабатывать реже.

Ещё одна тенденция - HTTP/3 поверх QUIC. Классический HTTP-прокси с CONNECT туннелирует только TCP. Если в дампе вы вдруг видите UDP-трафик на порт 443 напрямую к адресу целевого сервера, минуя прокси, это признак утечки: приложение пытается использовать QUIC напрямую, а не через прокси. Правильно настроенный клиент должен либо отключать QUIC при работе через прокси, либо использовать проксирование UDP через отдельные механизмы. Фильтр для быстрой проверки: udp.port == 443. Если дамп через прокси показывает такие пакеты, конфигурацию клиента нужно править.

Где ставить точку захвата

Обычно ответ один - на клиентской машине. Но есть нюансы. Если клиент работает в Docker-контейнере, tcpdump на хосте на интерфейсе docker0 или на интерфейсе моста покажет трафик до NAT, а на внешнем интерфейсе - после NAT, с другим адресом источника. Проще зайти в сетевое пространство контейнера: nsenter -t PID -n tcpdump ... или запустить контейнер с tcpdump через --net=container:имя. На виртуальных машинах в облаке обращайте внимание на offloading: пакеты в дампе могут выглядеть крупнее MTU, потому что сетевая карта склеивает сегменты. Это нормально и на анализ флагов не влияет.

tcpdump на практике: фильтры, запись в файл, ротация

tcpdump есть почти на любой Linux-машине и на macOS. На Windows аналогичную роль выполняет Wireshark с драйвером Npcap или консольный dumpcap. Разберём команды, которые покрывают 95 процентов задач диагностики прокси.

Базовый захват трафика к прокси

Допустим, адрес прокси 203.0.113.10, порт 8080. Записываем всё, что ходит между нами и прокси, в файл:

sudo tcpdump -i any -nn -s 0 -w proxy.pcap 'host 203.0.113.10 and port 8080'

Разбор ключей:

  • -i any - слушать все интерфейсы. Удобно, когда не знаете, через какой уходит трафик. Если знаете, лучше указать конкретный: -i eth0 или -i wlan0. На macOS -i any не поддерживается, указывайте en0.
  • -nn - не резолвить IP в имена и порты в имена сервисов. Резолвинг замедляет захват и добавляет лишние DNS-запросы в сеть.
  • -s 0 - захватывать пакет целиком. В современных версиях это значение по умолчанию, но явное указание не вредит.
  • -w proxy.pcap - писать в файл в формате pcap, а не выводить на экран. Только так дамп можно потом открыть в Wireshark.
  • Фильтр в одинарных кавычках - это BPF-фильтр захвата. Он отсекает всё лишнее ещё в ядре, поэтому файл будет содержать только нужное.

Фильтры по хосту и порту

Несколько прокси или пул портов:

sudo tcpdump -i eth0 -nn -w proxy.pcap 'host 203.0.113.10 and portrange 10000-10100'

Прокси по доменному имени (tcpdump зарезолвит его один раз при старте, что не всегда подходит для ротируемых адресов):

sudo tcpdump -i eth0 -nn -w proxy.pcap 'host proxy.example.net and port 8080'

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

sudo tcpdump -i eth0 -nn -w flags.pcap 'host 203.0.113.10 and tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0'

Только RST в обе стороны:

sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0'

Захват с исключением собственной SSH-сессии, чтобы не засорять дамп, когда снимаете его на удалённом сервере:

sudo tcpdump -i eth0 -nn -w proxy.pcap 'host 203.0.113.10 and not port 22'

Как не набрать гигабайты: ротация по размеру и времени

Если ошибка редкая и воспроизводится раз в час, дамп должен крутиться долго. Без ротации диск закончится. Ротация по размеру, 100 МБ на файл, кольцо из 10 файлов:

sudo tcpdump -i eth0 -nn -w proxy-%Y%m%d-%H%M%S.pcap -C 100 -W 10 'host 203.0.113.10 and port 8080'

Здесь -C задаёт размер в миллионах байт, -W ограничивает количество файлов: одиннадцатый файл перезапишет первый. Ротация по времени, новый файл каждые 10 минут, хранить последние 24 файла (4 часа):

sudo tcpdump -i eth0 -nn -w proxy-%Y%m%d-%H%M%S.pcap -G 600 -W 24 'host 203.0.113.10 and port 8080'

Ключ -G указывает интервал в секундах, а шаблон времени в имени файла обязателен, иначе tcpdump будет перезаписывать один файл. Полезный ключ -Z пользователь сбрасывает привилегии после открытия интерфейса, а -U заставляет писать пакеты в файл сразу, без буферизации, что важно, если вы будете читать файл параллельно или боитесь потерять хвост при аварийном завершении.

Обрезка полезной нагрузки

Ещё один способ снизить объём - захватывать только заголовки. Для диагностики обрывов содержимое TLS-записей не нужно, достаточно первых 128 байт каждого пакета:

sudo tcpdump -i eth0 -nn -s 128 -w headers.pcap 'host 203.0.113.10'

Осторожно: при -s 128 строка CONNECT и заголовки могут быть обрезаны, а Wireshark пометит пакеты как truncated. Для полного анализа хендшейка TLS этого мало, ClientHello часто занимает 300-600 байт и более. Компромисс - -s 600.

Чтение дампа прямо в консоли

Wireshark не всегда под рукой, а быстро глянуть надо. Чтение файла с выводом TCP-флагов и абсолютных порядковых номеров:

tcpdump -nn -r proxy.pcap -S -tttt 'tcp[tcpflags] & (tcp-rst|tcp-fin) != 0'

Ключ -tttt печатает полную дату и время, -S показывает абсолютные sequence numbers, что помогает сопоставить с логами. Вывод содержимого пакетов в ASCII, чтобы увидеть строку CONNECT и ответ прокси:

tcpdump -nn -r proxy.pcap -A 'port 8080' | grep -E 'CONNECT|HTTP/1'

tshark как консольный Wireshark

Если Wireshark установлен на сервере, tshark даёт доступ к тем же диссекторам из консоли. Список всех CONNECT с кодами ответа:

tshark -r proxy.pcap -Y 'http.request.method == "CONNECT" or http.response.code' -T fields -e frame.time -e tcp.stream -e http.request.uri -e http.response.code

Список потоков, завершившихся RST, с указанием, кто его отправил:

tshark -r proxy.pcap -Y 'tcp.flags.reset == 1' -T fields -e frame.time_relative -e tcp.stream -e ip.src -e ip.dst

Wireshark: фильтры отображения, Follow TCP Stream, чтение TLS без расшифровки

Открыли pcap в Wireshark и увидели тысячи строк. С чего начать? С фильтров отображения. В отличие от BPF-фильтров захвата, они работают уже поверх записанного файла и понимают структуру протоколов.

Базовые фильтры для работы через прокси

Всё, что связано с прокси:

ip.addr == 203.0.113.10 && tcp.port == 8080

Все запросы CONNECT:

http.request.method == "CONNECT"

CONNECT к конкретному хосту:

http.request.method == "CONNECT" && http.host contains "api.example.com"

Ответы прокси, отличные от 200 (ошибки авторизации 407, недоступность 502, таймауты 504):

http.response.code >= 400 && tcp.port == 8080

Только ответы 407, признак неверных учётных данных или исчерпанного лимита:

http.response.code == 407

Фильтры TLS внутри туннеля

Wireshark умеет распознавать, что после CONNECT с ответом 200 начинается TLS, и подставляет TLS-диссектор автоматически. Если не распознал (бывает при обрезанных пакетах), кликните правой кнопкой на пакет, выберите Decode As и укажите TLS для этого порта.

Все ClientHello:

tls.handshake.type == 1

ClientHello с конкретным SNI:

tls.handshake.extensions_server_name contains "example.com"

ServerHello (если его нет после ClientHello - хендшейк не начался со стороны сервера):

tls.handshake.type == 2

TLS-алерты:

tls.alert_message

Поток, в котором есть ClientHello, но нет ServerHello, найти сложнее одним фильтром; удобнее через Statistics > Conversations, отсортировать по количеству пакетов и посмотреть на потоки с 5-7 пакетами.

Фильтры по проблемам TCP

Все пакеты с RST:

tcp.flags.reset == 1

RST, отправленные прокси в нашу сторону:

tcp.flags.reset == 1 && ip.src == 203.0.113.10

RST, отправленные нами:

tcp.flags.reset == 1 && ip.dst == 203.0.113.10

Ретрансмиты:

tcp.analysis.retransmission

Нулевое окно и его следствия:

tcp.analysis.zero_window || tcp.analysis.window_full

Все аномалии, которые Wireshark заметил сам (ретрансмиты, дубликаты ACK, потерянные сегменты, нулевое окно):

tcp.analysis.flags && !tcp.analysis.window_update

SYN без ответа, то есть повторные SYN:

tcp.flags.syn == 1 && tcp.flags.ack == 0 && tcp.analysis.retransmission

Большие паузы внутри потока, больше 5 секунд между пакетами:

tcp.time_delta > 5

Для этого фильтра нужно включить Edit > Preferences > Protocols > TCP > Calculate conversation timestamps.

Follow TCP Stream

Нашли подозрительный пакет, например RST. Кликаете правой кнопкой > Follow > TCP Stream. Wireshark покажет весь диалог этого соединения в виде текста: наш трафик одним цветом, трафик прокси другим. Для HTTPS через CONNECT вы увидите строку CONNECT, ответ 200 Connection established, а дальше нечитаемые байты TLS. Это нормально. Смотреть надо на объёмы: сколько байт ушло от нас (наш ClientHello и запросы), сколько пришло (ServerHello и ответ) и где всё закончилось.

Внизу окна Follow есть счётчики: столько-то байт клиент, столько-то сервер. Если от сервера пришло 0 байт после ответа 200, целевой сервер не ответил на ClientHello вообще. Если пришло около 100-4000 байт и оборвалось, хендшейк начался, но не завершился. Если пришли десятки килобайт и потом обрыв, проблема в середине передачи данных.

Полезная привычка: фильтруйте по номеру потока. После Follow в строке фильтра автоматически появится tcp.stream eq 42. Закройте окно Follow, и в основном списке останется только этот поток с флагами и таймингами. Это самый удобный вид для анализа обрыва.

Чтение TLS-хендшейка без расшифровки

Раскройте ClientHello в дереве пакета. Что смотреть:

  • Version и supported_versions - предлагает ли клиент TLS 1.3. Если сервер требует 1.3, а клиент предлагает только 1.2, сервер закроет соединение алертом protocol_version или просто FIN.
  • server_name - совпадает ли SNI с хостом в CONNECT. Расхождение бывает при неправильной настройке клиента и приводит к ошибкам сертификата.
  • Cipher Suites - список шифров. Слишком короткий или устаревший список - причина алерта handshake_failure.
  • ALPN - предлагает ли клиент h2. Если сервер выбрал h2, а клиент внутри ожидал HTTP/1.1, приложение может падать с непонятными ошибками.

В ServerHello смотрите выбранную версию и шифр. В TLS 1.2 дальше идёт Certificate в открытом виде, можно проверить имя и срок действия. В TLS 1.3 после ServerHello почти всё зашифровано, и следующее, что читается, это тип записи. Application Data значит, что хендшейк завершился успешно и пошли данные. Alert длиной 2 байта сразу после ServerHello или вместо него значит, что что-то не так: в TLS 1.3 вы не увидите код алерта без ключей, но сам факт уже информативен.

В Statistics > Conversations > TCP удобно оценить общую картину: сколько потоков, сколько байт в каждую сторону, длительность. Потоки с длительностью 0.05 секунды и 6 пакетами - это, скорее всего, соединения, сброшенные сразу после хендшейка.

Диагностика по дампу: RST, FIN, retransmission, zero window

Теперь главное. Как по дампу зоны A понять, где именно порвалось? Разберём каждый сигнал и его интерпретацию в контексте прокси.

Матрица направлений

Первый вопрос всегда один: кто отправил закрывающий пакет? В дампе это поле ip.src. Есть только два варианта - наш адрес или адрес прокси.

  • RST или FIN от нас - закрыло соединение наше приложение, наша ОС или что-то на нашем хосте. Прокси и целевой сервер тут не участвовали. Типичные причины: таймаут в HTTP-клиенте, аварийное завершение процесса, исчерпание файловых дескрипторов, работа локального файрвола или антивируса.
  • RST или FIN от прокси - туннель закрыт со стороны прокси. Причина либо в самом прокси (лимиты, политика, таймаут простоя), либо транслирована от целевого сервера, либо от сети между прокси и сервером. Различаем по контексту и таймингам.
  • Ни RST, ни FIN, только ретрансмиты - пакеты теряются на пути между нами и прокси. Ни одна сторона не закрывала соединение, оно просто умерло от потерь.

RST после CONNECT без ответа 200

Картина: TCP установлен, мы отправили CONNECT, прокси ответил RST без HTTP-ответа. Такое поведение обычно означает, что прокси отказал на уровне политики или не смог разобрать запрос. Причины: недопустимый порт назначения, лимит одновременных соединений, некорректный формат запроса. Здесь проблема в зоне A или в самом прокси. Целевой сервер не при чём.

Ответ 502, 503 или 504 на CONNECT

Картина: CONNECT ушёл, через N секунд пришёл HTTP-ответ с кодом 5xx. Смотрим на N. Если ответ пришёл за время порядка RTT до прокси, прокси отказал сразу - возможно, DNS-имя не резолвится на его стороне или адрес недоступен мгновенно (ICMP unreachable). Если N около 10-30 секунд, прокси ждал таймаут при подключении к целевому серверу: сервер не отвечает на SYN. В обоих случаях это зона C, и ваш дамп доказывает, что вы всё сделали правильно, а прокси честно сообщил о невозможности.

FIN или RST сразу после ClientHello

Картина: CONNECT - 200 - наш ClientHello - через одну RTT до прокси плюс что-то приходит FIN или RST от прокси. Здесь два кандидата: целевой сервер отверг соединение на этапе TLS (не понравился SNI, версия, отсутствие клиентского сертификата) или защитная система сервера сбросила соединение по признакам ClientHello. Ключевой признак - время. Если между нашим ClientHello и обрывом прошло время, заметно большее, чем RTT до прокси, значит прокси успел отправить ClientHello дальше и получить реакцию. Это не прокси решил закрыть, это транслированная реакция целевого сервера.

Сравните с RTT до прокси, который вы измеряете по TCP-хендшейку: время между SYN и SYN-ACK. Если RTT до прокси 40 мс, а обрыв после ClientHello пришёл через 200 мс, разница в 160 мс - это примерно RTT прокси - сервер туда-обратно. Картина полностью консистентна с отказом сервера.

Обрыв после ServerHello или после части данных

Хендшейк начался, сервер ответил, пошли данные, и на середине FIN или RST. Если это FIN и от прокси, и перед ним последняя запись Application Data имеет логичный размер, возможно, сервер просто закрыл соединение после ответа (Connection: close внутри TLS), а клиент неправильно это интерпретировал. Если RST посреди потока данных без предшествующего снижения темпа, это либо принудительное закрытие на сервере, либо прокси прервал туннель по лимиту трафика или времени жизни сессии. Для мобильных прокси с ротацией IP по времени второе очень вероятно: обрыв происходит ровно в момент смены IP. Проверьте, совпадает ли время обрыва с интервалом ротации в настройках вашего прокси.

Ретрансмиты и их направление

Wireshark помечает пакет как retransmission, когда видит повторную передачу тех же sequence numbers. Смотрим, кто ретрансмитит:

  • Ретрансмитим мы - наши пакеты не подтверждаются прокси. Либо они теряются на пути к прокси, либо теряются подтверждения на обратном пути. В обоих случаях проблема в сети зоны A.
  • Ретрансмитит прокси - его пакеты не подтверждаются нами. Мы их не получаем или наши ACK не доходят. Тоже зона A, но с уклоном во входящее направление.
  • Ретрансмиты SYN - отдельный случай: прокси недоступен на уровне TCP, соединение не устанавливается вовсе.

Важно: потери в зоне C вы в виде ретрансмитов не увидите никогда. Прокси сам разбирается с целевым сервером. Единственный след потерь в зоне C - паузы: прокси передаёт нам данные неравномерно, с провалами, хотя ретрансмитов в дампе нет. Фильтр tcp.time_delta > 1 для пакетов от прокси покажет такие паузы.

Zero Window

TCP-окно нулевого размера означает, что получатель не успевает вычитывать данные из буфера сокета. Если нулевое окно объявляем мы, наше приложение не читает ответ достаточно быстро: занят поток, блокировка, медленная обработка. Прокси в этом случае ждёт, потом отправляет Zero Window Probe, и, если приложение так и не начало читать, через несколько десятков секунд может закрыть соединение. Клиент увидит обрыв и обвинит прокси, хотя причина в клиенте.

Если нулевое окно объявляет прокси, значит, он не успевает передать наши данные дальше - целевой сервер медленно принимает. Это косвенный признак проблем в зоне C, встречается при загрузке крупных файлов через прокси на медленный сервер.

Дубликаты ACK и SACK

Duplicate ACK от прокси - сигнал, что он получил пакет вне порядка, что-то из нашего пропало. Много дубликатов ACK и последующие ретрансмиты - классическая картина потерь в мобильной или Wi-Fi сети на нашей стороне. Дубликаты ACK от нас - пропали пакеты от прокси. Наличие опции SACK в хендшейке позволяет TCP восстанавливаться эффективнее, но факт потерь всё равно на виду.

Эвристика TTL

Продвинутый приём. Посмотрите на поле IP TTL в нормальных пакетах от прокси и в пакете RST. Если TTL в RST отличается на несколько единиц, RST сгенерирован не самим прокси, а промежуточным узлом на пути: файрволом, балансировщиком или системой фильтрации оператора. Это не абсолютное доказательство, но сильный намёк, что стоит проверить путь между вами и прокси, а не обвинять прокси или целевой сервер.

Сводная таблица интерпретаций

  • SYN повторяется, SYN-ACK нет: прокси недоступен или сеть до него. Зона A.
  • SYN - RST: порт прокси закрыт или фильтруется. Зона A.
  • CONNECT - 407: неверная авторизация или лимит аккаунта. Прокси.
  • CONNECT - 502/504 быстро: прокси не смог начать подключение к серверу. Зона C, скорее DNS или маршрут.
  • CONNECT - 504 через 20-30 секунд: сервер не отвечает на SYN прокси. Зона C.
  • 200 - ClientHello - RST/FIN через время больше RTT: сервер отверг TLS. Зона C.
  • 200 - ClientHello - тишина - обрыв через таймаут клиента: сервер принимает TCP, но не отвечает на TLS. Зона C или блокирующий фильтр за прокси.
  • Данные идут - RST от прокси в фиксированный момент: лимит или ротация прокси. Прокси.
  • Ретрансмиты и дубликаты ACK без RST: потери в сети между нами и прокси. Зона A.
  • Zero Window от нас: наше приложение не читает. Клиент.
  • RST от нас после долгой тишины: наш таймаут. Клиент.

Расшифровка своего трафика через SSLKEYLOGFILE

Иногда флагов недостаточно и нужно увидеть, что именно вернул сервер внутри TLS: код ответа, заголовки, тело ошибки. Для этого не нужен mitmproxy и подставной сертификат. Достаточно, чтобы ваш собственный клиент записывал сессионные ключи в файл, а Wireshark использовал их для расшифровки. Это работает только для вашего клиента и ваших соединений: ключи есть только у той стороны, которая участвовала в хендшейке. Расшифровать чужой трафик или трафик, который прокси ведёт с сервером в зоне C, таким способом невозможно, и это правильно.

Как включить в разных клиентах

Браузеры на базе Chromium и Firefox читают переменную окружения SSLKEYLOGFILE и пишут туда ключи в формате NSS Key Log:

export SSLKEYLOGFILE=/home/user/tls-keys.log
google-chrome --proxy-server=http://203.0.113.10:8080

curl, собранный с OpenSSL, тоже читает эту переменную:

export SSLKEYLOGFILE=/home/user/tls-keys.log
curl -v -x http://user:pass@203.0.113.10:8080 https://api.example.com/health

Node.js с ключом командной строки:

node --tls-keylog=/home/user/tls-keys.log app.js

Python не читает переменную автоматически, но начиная с версии 3.8 в SSLContext есть атрибут keylog_filename. Для requests через адаптер:

import os, ssl, requests
from requests.adapters import HTTPAdapter

class KeyLogAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
ctx = ssl.create_default_context()
ctx.keylog_filename = os.environ.get("SSLKEYLOGFILE")
kwargs["ssl_context"] = ctx
return super().init_poolmanager(*args, **kwargs)

s = requests.Session()
s.mount("https://", KeyLogAdapter())
s.proxies = {"https": "http://user:pass@203.0.113.10:8080"}
r = s.get("https://api.example.com/health", timeout=30)
print(r.status_code)

Go через поле KeyLogWriter в tls.Config:

f, _ := os.OpenFile(os.Getenv("SSLKEYLOGFILE"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
tlsCfg := &tls.Config{KeyLogWriter: f}
tr := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: tlsCfg,
}

Подключение ключей в Wireshark

Два способа. Первый: Edit > Preferences > Protocols > TLS > (Pre)-Master-Secret log filename, указать путь к файлу ключей. Wireshark расшифрует все потоки, для которых найдёт совпадение по Client Random. Второй способ надёжнее для передачи файла коллегам: встроить ключи прямо в pcapng:

editcap --inject-secrets tls,/home/user/tls-keys.log proxy.pcap proxy-with-keys.pcapng

После этого в Wireshark внутри туннеля CONNECT появятся расшифрованные HTTP/1.1 или HTTP/2 запросы и ответы. Фильтр для расшифрованного HTTP/2:

http2.header.name == ":status"

Для расшифрованного HTTP/1.1 внутри туннеля работают обычные http.response.code и http.request.uri.

Что это даёт для диагностики через прокси

Расшифровка снимает последнюю неопределённость. Вы видите, что сервер ответил 429 с заголовком Retry-After и потом закрыл соединение, или что он вернул 200, но тело оборвалось на 40 процентах, или что запрос ушёл, а ответа не было вовсе. С такой картиной обращение в поддержку прокси становится предметным: либо проблема явно в сервере, либо явно в туннеле.

Правила безопасности

  • Файл ключей позволяет расшифровать записанные сессии полностью, включая куки и токены. Храните его как пароль и удаляйте после анализа.
  • Не передавайте файл ключей вместе с дампом в поддержку прокси или кому-либо ещё. Поддержке для анализа обрывов ключи не нужны, ей достаточно флагов и таймингов.
  • Если всё же нужно показать расшифрованное содержимое, делайте это с тестовым аккаунтом на целевом сервисе и тестовыми учётными данными прокси.
  • Не оставляйте SSLKEYLOGFILE включённым в продакшене. Переменная окружения, случайно попавшая в конфиг сервиса, будет годами писать ключи на диск.

Типичные картины: таймаут коннекта, обрыв в середине ответа, потери в мобильной сети

Соберём разобранные признаки в узнаваемые сценарии. Каждый описан так, как он выглядит в списке пакетов Wireshark после фильтра tcp.stream eq N.

Картина 1: таймаут подключения к прокси

0.000 client -> proxy [SYN]
1.002 client -> proxy [SYN] (TCP Retransmission)
3.006 client -> proxy [SYN] (TCP Retransmission)
7.010 client -> proxy [SYN] (TCP Retransmission)
15.02 client -> proxy [SYN] (TCP Retransmission)

Ни одного пакета от прокси. Экспоненциальные интервалы 1, 2, 4, 8 секунд - стандартный retransmission backoff ядра Linux. Диагноз: прокси недоступен с вашей точки. Проверяйте адрес, порт, файрвол, маршрутизацию, а также не изменился ли адрес прокси. Если это мобильный прокси Proxeon с выделенным портом, убедитесь, что порт из вашего личного кабинета совпадает с тем, что в конфиге клиента.

Картина 2: таймаут подключения прокси к целевому серверу

0.000 client -> proxy [SYN]
0.041 proxy -> client [SYN, ACK]
0.041 client -> proxy [ACK]
0.042 client -> proxy CONNECT api.example.com:443 HTTP/1.1
0.083 proxy -> client [ACK]
30.10 proxy -> client HTTP/1.1 504 Gateway Timeout
30.10 proxy -> client [FIN, ACK]

RTT до прокси 41 мс, прокси подтвердил получение CONNECT, а потом 30 секунд молчал и вернул 504. Диагноз: целевой сервер не отвечает прокси на попытку подключения. Возможные причины - сервер лежит, порт закрыт для адресного пула прокси, сетевые проблемы на маршруте прокси - сервер. Ваш клиент и ваша сеть не при чём.

Картина 3: сервер отвергает TLS

0.000 client -> proxy CONNECT api.example.com:443 HTTP/1.1
0.045 proxy -> client HTTP/1.1 200 Connection established
0.046 client -> proxy TLSv1.3 Client Hello (SNI=api.example.com)
0.210 proxy -> client [FIN, ACK]
0.210 client -> proxy [FIN, ACK]

Обрыв через 164 мс после ClientHello при RTT до прокси 45 мс. Разница около 120 мс - это время, за которое прокси переслал ClientHello серверу и получил закрытие. Диагноз: сервер принял TCP, но закрыл соединение на этапе TLS. Проверяйте параметры хендшейка: версии, шифры, SNI, ALPN. Если ServerHello отсутствует и нет алерта, сервер закрыл соединение молча, что часто делают защитные системы при нетипичном ClientHello.

Картина 4: обрыв в середине ответа

0.000 client -> proxy CONNECT cdn.example.com:443 HTTP/1.1
0.044 proxy -> client HTTP/1.1 200 Connection established
0.045 client -> proxy Client Hello
0.190 proxy -> client Server Hello, Change Cipher Spec, Application Data
0.195 client -> proxy Application Data (request)
0.350 proxy -> client Application Data (1460 bytes)
... ещё 340 пакетов данных
4.812 proxy -> client Application Data (1460 bytes)
4.813 proxy -> client [RST, ACK]

Хендшейк прошёл, около 500 КБ данных пришло, и RST без замедления, без ретрансмитов, без zero window. Смотрим на абсолютное время. Если оно совпадает с моментом ротации IP на мобильном прокси или с истечением лимита длительности сессии, причина в прокси, и решение - выровнять интервал ротации с длительностью ваших запросов или использовать режим без ротации во время долгих загрузок. Если совпадения нет, вероятен обрыв на стороне сервера или CDN. Здесь помогает расшифровка через SSLKEYLOGFILE: если внутри виден заголовок Content-Length и тело пришло не полностью, сервер оборвал передачу.

Картина 5: потери в мобильной сети на стороне клиента

0.000 client -> proxy Application Data (1460 bytes)
0.001 client -> proxy Application Data (1460 bytes)
0.002 client -> proxy Application Data (1460 bytes)
0.180 proxy -> client [ACK] (Dup ACK 1)
0.181 proxy -> client [ACK] (Dup ACK 2)
0.182 proxy -> client [ACK] (Dup ACK 3)
0.183 client -> proxy Application Data (TCP Fast Retransmission)
0.420 client -> proxy Application Data (TCP Retransmission)
0.900 client -> proxy Application Data (TCP Retransmission)

Дубликаты ACK от прокси, за ними ретрансмиты от нас с растущими интервалами. Ни RST, ни FIN. Диагноз: потери на пути клиент - прокси. Если клиент сам подключён через сотовую сеть или Wi-Fi, это ожидаемо в моменты переключения между базовыми станциями. Решение - увеличить таймауты клиента, включить TCP keepalive, не держать долгие простаивающие соединения, потому что NAT операторов сбрасывает записи об idle-соединениях, обычно через 30-300 секунд, и следующий пакет уходит в пустоту. Фильтр для оценки масштаба потерь:

tcp.analysis.retransmission && ip.dst == 203.0.113.10

Посчитайте процент таких пакетов от общего числа через Statistics > Capture File Properties. Один-два процента - терпимо для мобильной сети, больше пяти - ищите проблему в радиоусловиях или в оборудовании.

Картина 6: наш собственный таймаут

0.000 client -> proxy CONNECT api.example.com:443 HTTP/1.1
0.044 proxy -> client HTTP/1.1 200 Connection established
0.045 client -> proxy Client Hello
0.200 proxy -> client Server Hello ... Application Data
0.205 client -> proxy Application Data (request)
0.250 proxy -> client [ACK]
10.21 client -> proxy [FIN, ACK]
10.25 proxy -> client [FIN, ACK]

Запрос ушёл, прокси подтвердил, ответ не пришёл, и ровно через 10 секунд FIN отправили мы. Это наш read timeout. Ошибка в логе клиента будет выглядеть как обрыв, но по дампу видно: соединение закрыли мы сами, потому что сервер думал дольше, чем мы готовы ждать. Решение - либо увеличивать таймаут, либо разбираться, почему сервер медленный, но прокси здесь просто честно ждал вместе с нами.

Типичные ошибки при снятии и чтении дампа

Перечислим то, на чём регулярно спотыкаются даже опытные инженеры.

  • Снимать дамп без фильтра захвата на нагруженной машине. За минуту набегает гигабайт, а нужные 200 пакетов в нём искать неудобно. Всегда фильтруйте по хосту прокси.
  • Фильтровать по адресу целевого сервера. Через прокси таких пакетов на вашей машине нет. Фильтр вернёт пустоту, и человек решает, что трафик не идёт. Идёт, но к прокси.
  • Путать фильтр захвата и фильтр отображения. Синтаксис BPF в tcpdump (host, port, tcp[tcpflags]) и синтаксис Wireshark (ip.addr, tcp.port, tcp.flags.reset) разные. Фильтр Wireshark в tcpdump вызовет ошибку синтаксиса, и наоборот.
  • Смотреть на RST от прокси и сразу винить прокси. RST от адреса прокси - это закрытие туннеля, а не признание вины. Смотрите на тайминги и на то, что было до RST.
  • Игнорировать RTT. Без измеренной задержки до прокси невозможно отличить мгновенный отказ прокси от транслированного отказа сервера.
  • Снимать дамп с -s 64 и потом пытаться прочитать CONNECT. Заголовки будут обрезаны. Для диагностики прокси нужен полный захват или хотя бы -s 600.
  • Не синхронизировать время. Если часы на машине с дампом отстают на минуту, сопоставить дамп с логами поддержки не получится. Включите NTP и указывайте в обращении UTC.
  • Отправлять дамп с учётными данными прокси. Заголовок Proxy-Authorization в открытом HTTP-запросе к прокси содержит логин и пароль. Либо снимайте дамп с тестовыми данными, либо меняйте пароль после отправки.
  • Отправлять файл ключей вместе с дампом. Это раскрывает всё содержимое сессий. Поддержке ключи для анализа обрывов не нужны.
  • Держать один долгий дамп без ротации. Диск заполнится в самый неподходящий момент, и tcpdump упадёт вместе с нужными пакетами.
  • Не проверять utечку QUIC. UDP на 443 напрямую - признак того, что часть трафика идёт мимо прокси, и диагностика по TCP-туннелю ничего не покажет.
  • Забывать про segmentation offload. Пакеты размером 60 КБ в дампе на виртуальной машине не означают неправильный MTU. Это ядро отдаёт tcpdump ещё не разрезанные сегменты.

Инструменты и ресурсы

Минимальный набор для работы описанной методики.

Захват

  • tcpdump - стандарт на Linux и macOS. Установлен почти везде, требует root или capability CAP_NET_RAW.
  • dumpcap - консольный захватчик из состава Wireshark, поддерживает те же ключи ротации (-b filesize, -b files) и формат pcapng.
  • Wireshark с Npcap - для Windows. Можно захватывать прямо из GUI с фильтром захвата в том же BPF-синтаксисе.
  • tshark - консольный анализ с диссекторами Wireshark, удобен на серверах без графики и для автоматизации.

Анализ

  • Wireshark - главный инструмент. Follow TCP Stream, Expert Info, Statistics > Conversations, IO Graph для визуализации пауз и всплесков ретрансмитов.
  • editcap - нарезка и фильтрация pcap, внедрение ключей TLS в pcapng.
  • mergecap - склейка файлов ротации в один для анализа.
  • capinfos - быстрая сводка по файлу: длительность, количество пакетов, размер.

Клиенты с поддержкой отладки

  • curl с ключами -v и --trace-time дублирует картину дампа на уровне приложения и поддерживает SSLKEYLOGFILE.
  • openssl s_client -proxy host:port позволяет вручную выполнить CONNECT и TLS-хендшейк через прокси и увидеть ответ сервера без HTTP-клиента.

Полезные однострочники

Склеить файлы ротации:

mergecap -w all.pcapng proxy-*.pcap

Вырезать из дампа только один поток для отправки в поддержку:

tshark -r all.pcapng -Y 'tcp.stream eq 42' -w stream42.pcapng

Быстрая статистика по потокам с RST:

tshark -r all.pcapng -q -z conv,tcp | head -40

Ручная проверка CONNECT через openssl:

openssl s_client -proxy 203.0.113.10:8080 -connect api.example.com:443 -servername api.example.com -brief

Кейсы и результаты

Кейс 1: два процента обрывов, виноват оказался клиент

Сервис сбора цен работал через пул мобильных прокси, около 40 тысяч запросов в сутки. Примерно 2,3 процента запросов падали с RemoteDisconnected. Команда была уверена, что виноваты прокси. Сняли дамп с ротацией по 100 МБ на сутки, отфильтровали tcp.flags.reset == 1 и посмотрели на ip.src. В 91 проценте случаев RST отправлял сам клиент. Разбор потоков показал: перед RST шла тишина ровно 5 секунд после отправки запроса, затем RST от клиента. В коде стоял read timeout 5 секунд, а целевой сервер в пиковые часы отвечал за 6-8 секунд. Увеличение таймаута до 15 секунд снизило ошибки до 0,3 процента. Оставшиеся 0,3 процента были реальными FIN от прокси через 160-200 мс после ClientHello: сервер периодически отвергал соединения от части адресного пула. Эти данные передали в поддержку, там подтвердили картину по своим логам зоны C.

Кейс 2: обрывы больших загрузок ровно каждые 10 минут

Клиент скачивал архивы по 300-800 МБ через мобильный прокси и жаловался на обрывы в середине. Дамп показал RST от прокси в моменты, отстоящие друг от друга ровно на 600 секунд с точностью до секунды, независимо от начала загрузки. Причина оказалась в настроенной ротации IP по расписанию каждые 10 минут: при смене адреса прокси закрывает активные туннели. Решение - переключить порт на ротацию по запросу и запускать смену адреса между загрузками. Обрывы исчезли полностью, потребовалось два часа на диагностику вместо недель переписки.

Кейс 3: сервер лежит, а кажется, что виноват прокси

Утром все запросы к одному API стали возвращать ошибку соединения. Логи клиента: Connection aborted. Первая реакция - прокси упал. Дамп за одну минуту показал: TCP с прокси устанавливается за 38 мс, CONNECT уходит, а через 30 секунд приходит 504 и FIN. Одновременно запрос к другому хосту через тот же прокси проходил за 300 мс. Диагноз: целевой сервер не принимает соединения от прокси. Через 40 минут статус-страница целевого сервиса подтвердила инцидент на их стороне. Дамп сэкономил утро и избавил от ложного тикета в поддержку прокси.

Кейс 4: потери в Wi-Fi выглядели как проблема прокси

Разработчик тестировал интеграцию с ноутбука через мобильный прокси Proxeon и получал спорадические таймауты. Дамп показал ретрансмиты от клиента с частотой 6 процентов и дубликаты ACK от прокси, при этом ни одного RST от прокси. Подключение ноутбука кабелем снизило ретрансмиты до нуля, таймауты пропали. Проблема была в перегруженной точке доступа офиса.

Чеклист снятия дампа для обращения в поддержку

Если вы собираетесь отправлять дамп в поддержку прокси-сервиса, вот порядок действий, который сэкономит время обеим сторонам.

Подготовка

  1. Синхронизируйте часы на машине через NTP. Проверьте командой timedatectl или date -u.
  2. Если возможно, заведите отдельные тестовые учётные данные прокси на время диагностики или запланируйте смену пароля после отправки дампа.
  3. Зафиксируйте версию клиента, библиотеки HTTP, ОС, способ подключения к интернету (кабель, Wi-Fi, сотовая сеть).
  4. Запишите адрес и порт прокси, целевой хост, ожидаемое и фактическое поведение.

Захват

  1. Запустите tcpdump с фильтром по хосту и порту прокси, с ротацией, полным размером пакета:
sudo tcpdump -i eth0 -nn -s 0 -U -w proxy-%Y%m%d-%H%M%S.pcap -G 300 -W 48 'host 203.0.113.10 and port 8080'
  1. Воспроизведите проблему. Если воспроизводится редко, оставьте захват на нужное время; 48 файлов по 5 минут покроют 4 часа.
  2. Одновременно с захватом сделайте контрольный запрос через curl с -v и --trace-time, сохраните вывод. Он даст точную привязку времени к пакетам.
  3. Зафиксируйте точное время (UTC) каждого проявления ошибки из логов клиента.
  4. Остановите tcpdump сочетанием Ctrl+C. Убедитесь, что файлы не пустые: capinfos proxy-*.pcap.

Обработка

  1. Склейте файлы: mergecap -w all.pcapng proxy-*.pcap.
  2. Найдите проблемные потоки фильтром tcp.flags.reset == 1 или по времени из логов.
  3. Вырежьте только нужные потоки: tshark -r all.pcapng -Y 'tcp.stream in {12 47 93}' -w report.pcapng. Поддержке не нужны часы нормального трафика.
  4. Проверьте, что в вырезанном дампе нет ничего лишнего: чужих соединений, трафика других сервисов.
  5. Файл ключей TLS не прикладывайте.

Текст обращения

  • Время проявления в UTC с точностью до секунды.
  • Адрес и порт прокси, целевой хост.
  • Краткая интерпретация: что вы видите в дампе (например, 200 на CONNECT, затем FIN от прокси через 170 мс после ClientHello, RTT до прокси 45 мс).
  • Номера кадров или потоков в приложенном файле.
  • Вывод curl -v с отметками времени.
  • Что уже проверили и исключили: другой хост через тот же прокси, тот же хост через другое подключение.

Такое обращение поддержка обрабатывает за один заход. Ей достаточно сопоставить ваши отметки времени с логами зоны C и подтвердить или опровергнуть версию.

FAQ

Почему в дампе нет IP-адреса целевого сервера, я же к нему обращаюсь?

Потому что вы обращаетесь к нему не напрямую, а через прокси. Ваш клиент устанавливает TCP-соединение с адресом прокси и просит его подключиться к целевому хосту. Соединение прокси - сервер существует только на стороне прокси. В дампе имя целевого хоста видно в строке CONNECT и в SNI внутри ClientHello, но пакетов на его IP с вашей машины нет и быть не может.

Можно ли по дампу понять, какой внешний IP использовал прокси для обращения к серверу?

Нет. Эта информация относится к зоне C. Единственный способ - запросить через прокси сервис, возвращающий адрес клиента, или посмотреть в личном кабинете провайдера, какой адрес был активен в этот момент.

Прокси отправил RST. Значит, проблема в прокси?

Не обязательно. RST от адреса прокси означает, что туннель закрыт со стороны прокси, а причина может быть в целевом сервере или в сети за прокси. Смотрите тайминг: если RST пришёл через время, заметно превышающее RTT до прокси после вашего последнего пакета, скорее всего прокси транслировал реакцию сервера. Если RST пришёл в фиксированный момент времени или после точного интервала, вероятна политика самого прокси, например ротация или лимит.

Как измерить RTT до прокси по дампу?

Разница во времени между SYN от вас и SYN-ACK от прокси в начале любого соединения. В Wireshark можно включить Statistics > TCP Stream Graphs > Round Trip Time для потока или использовать поле tcp.analysis.ack_rtt в качестве колонки.

Wireshark не показывает TLS внутри туннеля, только TCP-данные. Что делать?

Кликните правой кнопкой на пакет после ответа 200 Connection established, выберите Decode As, в колонке Current выберите TLS для TCP-порта прокси. Также убедитесь, что пакеты не обрезаны (-s 0 при захвате) и что HTTP-диссектор настроен на порт прокси, если он нестандартный: Edit > Preferences > Protocols > HTTP > TCP ports.

Чем этот подход отличается от mitmproxy?

mitmproxy работает на уровне приложения: он терминирует TLS с подставным сертификатом, читает и может изменять HTTP-запросы. Для этого клиент должен доверять его корневому сертификату. tcpdump и Wireshark работают на пакетном уровне и ничего не подменяют: вы видите реальные пакеты, реальные флаги и тайминги, включая ошибки TCP, которые mitmproxy скрыл бы, потому что сам бы их обработал. Для диагностики обрывов пакетный уровень честнее. Для просмотра содержимого запросов проще mitmproxy, но при желании содержимое своего трафика можно увидеть и в Wireshark через SSLKEYLOGFILE без подмены сертификатов.

Можно ли расшифровать в Wireshark трафик, который прокси ведёт с сервером?

Нет. У вас нет ни пакетов этого соединения, ни ключей от него. SSLKEYLOGFILE даёт ключи только от сессий вашего клиента, а внутри туннеля CONNECT это как раз ваша сессия с сервером, поэтому её расшифровать можно. Но отдельного TLS между прокси и сервером в случае CONNECT нет: прокси просто пробрасывает ваши байты.

Как понять, что клиент утекает мимо прокси?

Снимите дамп без фильтра по прокси, а с фильтром по вашему интерфейсу целиком, и посмотрите на исходящие соединения на порты 80 и 443 к адресам, отличным от прокси, а также на UDP 443 (QUIC) и DNS-запросы к внешним резолверам с именами целевых хостов. Фильтр Wireshark: (tcp.port == 443 || udp.port == 443) && !(ip.addr == 203.0.113.10). Любые совпадения - утечка конфигурации.

Насколько большим должен быть дамп для поддержки?

Чем меньше, тем лучше. Один вырезанный проблемный поток занимает от 5 до 500 КБ. Файл больше 50 МБ поддержка будет анализировать дольше, а половина этого объёма окажется нормальным трафиком. Вырезайте нужные потоки через tshark или File > Export Specified Packets в Wireshark.

Что делать, если tcpdump на виртуальной машине показывает пакеты размером больше MTU?

Это generic segmentation offload: ядро передаёт в tcpdump крупные сегменты до того, как сетевая карта их разрежет. На анализ флагов, таймингов и обрывов это не влияет. Если мешает, отключите offload на время захвата командой ethtool -K eth0 gso off tso off gro off, но помните, что это снизит производительность сети.

Заключение

Дамп трафика при работе через прокси - это не про подглядывание за содержимым и не про магию. Это про честную запись того, что реально происходило на проводе: с точностью до миллисекунды и до каждого флага. Логи клиента говорят, что соединение оборвалось. Дамп говорит, кто это сделал, когда именно и что было до этого.

Резюмируем методику. На клиентской машине вы видите только соединение до прокси: TCP-хендшейк, CONNECT или SOCKS-диалог и зашифрованные TLS-записи внутри туннеля. Целевой сервер напрямую не виден, но его поведение транслируется прокси через коды ответа на CONNECT, тайминги и способ закрытия туннеля. RST или FIN от вашего адреса - ваша проблема или ваш таймаут. Ретрансмиты и дубликаты ACK без закрытия - потери на пути до прокси. Закрытие от прокси через время, кратно превышающее RTT до него, - транслированная реакция сервера. Закрытие в фиксированные моменты - политика прокси. Zero Window от вас - ваше приложение не читает.

Практические шаги на сегодня: положите в закладки команды tcpdump с ротацией и фильтры Wireshark из этой статьи; проверьте, синхронизированы ли часы на машинах, где работают ваши клиенты; убедитесь, что в вашем клиенте нет утечки QUIC мимо прокси; заведите тестовые учётные данные для диагностики, чтобы не светить рабочие в дампах. И в следующий раз, когда логи скажут connection reset by peer, не гадайте. Снимите дамп, откройте поток, посмотрите на направление и время. Через пять минут вы будете знать, кому писать: себе, в поддержку прокси или владельцу целевого сервера.