tcpdump i Wireshark przy pracy przez proxy: zrzut pakietów i diagnoza zerwań
Spis treści
- Wprowadzenie: kiedy logów klienta już nie wystarcza
- Podstawy: co właściwie widać przed proxy, wewnątrz tunelu i po nim
- Głębokie zanurzenie: dlaczego proxy nie może pokazać więcej i jak czytać pośrednie przesłanki
- Tcpdump w praktyce: filtry, zapis do pliku, rotacja
- Wireshark: filtry wyświetlania, follow tcp stream, czytanie tls bez deszyfrowania
- Diagnostyka po zrzucie: rst, fin, retransmisje, zero window
- Deszyfrowanie własnego ruchu przez sslkeylogfile
- Typowe obrazy: timeout połączenia, zerwanie w środku odpowiedzi, straty w sieci mobilnej
- Typowe błędy przy robieniu i czytaniu zrzutu
- Narzędzia i zasoby
- Przypadki i wyniki
- Checklista robienia zrzutu do zgłoszenia w supportcie
- Faq
- Zakończenie
Kiedy aplikacja pracuje przez proxy i coś idzie nie tak, pierwsze, co otwiera inżynier, to logi klienta. Widnieje tam coś w rodzaju connection reset by peer, read timeout albo po prostu EOF. Problem w tym, że te komunikaty prawie nic nie mówią o przyczynie. Kto zresetował połączenie: proxy, serwer docelowy czy operator między tobą a proxy? Czy żądanie w ogóle dotarło do proxy? Czy TLS handshake zdążył się zakończyć? Biblioteka klienta HTTP tego nie wie, więc ty też nie.
W tym artykule zejdziemy poziom niżej, do pakietów. Omówimy, jak zrobić zrzut tcpdump na maszynie klienta, co właściwie widać w takim zrzucie przy pracy przez proxy HTTP i SOCKS5, dlaczego w tunelu HTTPS zobaczysz tylko CONNECT i zaszyfrowane rekordy TLS, jak czytać zrzut w Wiresharku i jak po flagach RST, FIN, retransmisjach i zerowym oknie ustalić, po której stronie zerwało się połączenie. Osobno porozmawiamy o deszyfrowaniu własnego ruchu przez SSLKEYLOGFILE i o tym, jak poprawnie przygotować zrzut do zgłoszenia w supportcie serwisu proxy, na przykład Proxeon.
Ważne zastrzeżenie: to nie jest artykuł o mitmproxy. Tam mowa o przechwytywaniu i podmianie HTTPS na poziomie aplikacji z podłożonym certyfikatem. Tutaj pracujemy na poziomie pakietów, niczego nie podmieniamy i nie wnikamy w cudzy ruch. Nasze zadanie jest czysto diagnostyczne: zrozumieć, gdzie urywa się łańcuch klient - proxy - serwer docelowy.
Wprowadzenie: kiedy logów klienta już nie wystarcza
Wyobraź sobie typową sytuację. Skrypt w Pythonie chodzi przez proxy mobilne, obsługuje kilka tysięcy żądań na godzinę i mniej więcej dwa procent kończy się błędem Connection aborted, RemoteDisconnected. Programista dodaje retry, błąd nie znika. Pisze do supportu proxy, tam proszą o przykład żądania i czas. Support patrzy w swoje logi i odpowiada: u nas wszystko w porządku, połączenie do serwera docelowego było nawiązywane. Kto ma rację?
Bez zrzutu ten spór jest nieskończony. Ze zrzutem kończy się w pięć minut: widać, że proxy odpowiedziało na CONNECT kodem 200, klient wysłał ClientHello, a po 180 milisekundach od proxy przyleciał RST. To znaczy, że albo proxy nie dogadało się z serwerem docelowym, albo serwer sam zamknął połączenie. Dalej można patrzeć na czasy i doprecyzowywać. Najważniejsze, że rozmowa przeniosła się z dziedziny domysłów do dziedziny faktów.
Logi klienta HTTP działają na poziomie aplikacji. Widzą wynik, ale nie proces. Zrzut ruchu pokazuje proces: każdy pakiet z dokładnym znacznikiem czasu, kierunkiem, flagami i rozmiarem. Właśnie dlatego w poważnej eksploatacji infrastruktury proxy umiejętność zrobienia i przeczytania zrzutu jest podstawą, a nie egzotyką.
Co z tego artykułu wyniesiesz
- Zrozumienie, jakie dane fizycznie przechodzą przez interfejs sieciowy klienta przy pracy przez proxy i co z nich widać w otwartej postaci.
- Gotowe polecenia tcpdump do zapisu zrzutu z filtrami, rotacją i limitem rozmiaru.
- Zestaw filtrów wyświetlania Wiresharka, które można kopiować w całości.
- Metodykę odróżniania zerwania po swojej stronie, po stronie proxy i po stronie serwera docelowego.
- Bezpieczny sposób odszyfrowania własnego ruchu TLS do debugowania.
- Checklistę przygotowania zrzutu do zgłoszenia w supportcie.
Podstawy: co właściwie widać przed proxy, wewnątrz tunelu i po nim
Zacznijmy od schematu. Przy pracy przez proxy są trzy odcinki drogi, a na maszynie klienta fizycznie możesz obserwować tylko pierwszy.
Trzy strefy obserwacji
- Strefa A: klient - proxy. To jedyne połączenie TCP, które przechodzi przez twój interfejs sieciowy. Widzi je tcpdump na twojej maszynie. Widać tu: TCP handshake z adresem IP proxy, protokół pomocniczy proxy (HTTP CONNECT lub SOCKS5), a dalej albo otwarte HTTP, albo zaszyfrowane rekordy TLS.
- Strefa B: wewnątrz tunelu. Po tym, jak proxy odpowiedziało 200 Connection established, wszystkie bajty między tobą a serwerem docelowym są po prostu przepuszczane na wskroś. Jeśli serwer docelowy pracuje po HTTPS, te bajty to rekordy TLS. Widzisz ich strukturę (typ rekordu, długość, ClientHello z SNI, ServerHello, alerty), ale nie zawartość.
- Strefa C: proxy - serwer docelowy. To osobne połączenie TCP, które proxy ustanawia od swojego imienia i ze swojego zewnętrznego adresu. Na maszynie klienta go nie ma w ogóle. Można je zobaczyć tylko na samym serwerze proxy, a to infrastruktura dostawcy. Wszystko, co wiesz o strefie C, wiesz pośrednio: po kodach odpowiedzi na CONNECT, po opóźnieniach i po tym, jak proxy zamyka tunel.
To kluczowa myśl całego artykułu. Zrzut na kliencie nie pokazuje serwera docelowego bezpośrednio. Pokazuje jednak zachowanie proxy, a proxy, jako dobry przekaźnik, przekłada zachowanie serwera docelowego na formę, którą da się zinterpretować. Naszym zadaniem jest nauczyć się czytać to tłumaczenie.
Proxy HTTP i otwarte HTTP
Najbardziej przejrzysty przypadek. Jeśli strona docelowa pracuje po http bez szyfrowania, klient wysyła do proxy żądanie z absolutnym URI:
GET http://example.com/api/items HTTP/1.1
Host: example.com
Proxy-Authorization: Basic dXNlcjpwYXNz
User-Agent: my-client/1.0W zrzucie widać wszystko: metodę, ścieżkę, nagłówki, ciało, odpowiedź proxy z ciałem od serwera. Zwróć uwagę na nagłówek Proxy-Authorization. To twoje dane logowania w base64, leżą w zrzucie w otwartej postaci. Zapamiętajmy ten fakt, przyda się, gdy będziemy mówić o przekazywaniu zrzutu osobom trzecim.
Proxy HTTP i HTTPS przez CONNECT
Tu zaczyna się najciekawsze. Dla HTTPS klient najpierw prosi proxy o ustanowienie tunelu TCP do hosta i portu:
CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
Proxy-Authorization: Basic dXNlcjpwYXNz
Proxy-Connection: Keep-AliveProxy odpowiada:
HTTP/1.1 200 Connection establishedOd tego momentu proxy przestaje parsować HTTP. Po prostu kopiuje bajty z jednego gniazda do drugiego. Klient rozpoczyna TLS handshake bezpośrednio z serwerem docelowym, a cały ruch HTTP (GET, POST, nagłówki, ciała) ląduje wewnątrz TLS. Dlatego w zrzucie tunelu HTTPS widać tylko CONNECT: to ostatnie, co klient mówi proxy w otwartej postaci. Dalej idą rekordy TLS, których proxy nie może odczytać, a ty w zrzucie też nie.
Co przy tym pozostaje widoczne wewnątrz tunelu:
- ClientHello: wersje TLS, zestaw szyfrów, rozszerzenia i z reguły SNI (nazwa hosta w otwartej postaci).
- ServerHello: wybrana wersja i szyfr.
- W TLS 1.2 - certyfikat serwera w otwartej postaci. W TLS 1.3 certyfikat jest już zaszyfrowany.
- TLS Alert: typ alertu widać w TLS 1.2, w TLS 1.3 alerty po handshake są zaszyfrowane, ale sam fakt rekordu typu Alert o długości 2 bajtów daje się rozpoznać.
- Rozmiary i czasy rekordów Application Data. Po nich można oszacować, ile danych przyszło, zanim połączenie zostało zerwane.
SOCKS5
SOCKS5 działa inaczej, ale idea jest ta sama. Klient wysyła powitanie z metodami uwierzytelniania (bajty 05 02 00 02), proxy wybiera metodę, potem idzie uwierzytelnienie po nazwie użytkownika i haśle (podprotokół z bajtami 01, długość, nazwa, długość, hasło), następnie komenda CONNECT z typem adresu 03 (nazwa domenowa) i portem. Proxy odpowiada 05 00 przy sukcesie lub kodem błędu: 01 błąd ogólny, 03 sieć niedostępna, 04 host niedostępny, 05 połączenie odrzucone, 06 TTL wygasł. Te kody błędów to bezpośrednie tłumaczenie tego, co proxy zobaczyło w strefie C. Wireshark umie dekodować SOCKS, jeśli wskażesz port przez Decode As.
Czego nigdy nie widać
Z maszyny klienta nie zobaczysz: zewnętrznego IP proxy, z którego chodzi do serwera docelowego (dla proxy mobilnych to adres operatora), połączenia TCP proxy - serwer, zapytań DNS proxy. Jeśli support Proxeon mówi, że widzi błąd po stronie serwera docelowego, patrzy właśnie w strefę C, niedostępną dla ciebie. Twoim zadaniem jest przynieść zrzut strefy A, żeby zestawić oba obrazy po czasie.
Głębokie zanurzenie: dlaczego proxy nie może pokazać więcej i jak czytać pośrednie przesłanki
Warto zrozumieć, dlaczego obraz wygląda tak, jak wygląda, i co z tego wynika dla diagnostyki.
Tunel CONNECT jako rura na bajty
Po odpowiedzi 200 proxy HTTP, zgodnie ze specyfikacją, ma obowiązek przekazywać bajty w obie strony bez interpretacji, aż do zamknięcia przez którąkolwiek ze stron. Nie wie, że w środku jest TLS. Nie wie, jakie żądania HTTP wysyłasz. Widzi tylko dwa zdarzenia: strumień bajtów i zamknięcie gniazda. Kiedy serwer docelowy zamyka połączenie z proxy (wysyła FIN albo RST), proxy ma obowiązek zamknąć połączenie z tobą. Jak dokładnie to zrobi, zależy od implementacji: jedne proxy przekazują FIN jako FIN i RST jako RST, inne wszystko zamieniają na FIN, jeszcze inne przy błędzie odczytu ze zdalnego gniazda wysyłają do klienta RST.
Praktyczna konsekwencja: RST od adresu IP proxy nie oznacza, że proxy jest winne. Oznacza, że tunel został zamknięty po stronie proxy, a przyczyna może być wszędzie za nim. Żeby ją poznać, patrzymy na kontekst: co było przed RST, ile czasu minęło, czy dotarła odpowiedź.
Różnica między SYN na proxy i SYN na serwer docelowy
To najczęstsze źródło zamieszania. W zrzucie przez proxy nigdy nie zobaczysz SYN na port 443 serwera docelowego. Wszystkie SYN idą na IP i port proxy. Jeśli SYN na proxy nie dostaje SYN-ACK i powtarza się w interwałach 1, 2, 4, 8 sekund, problem jest między tobą a proxy: sieć, firewall, zły adres lub port, proxy nie działa. Serwer docelowy nie ma tu nic do rzeczy, nawet nie próbowałeś do niego dotrzeć w kategoriach twojego zrzutu.
Jeśli natomiast TCP z proxy jest ustanowiony, CONNECT wysłany, a odpowiedź przychodzi po 20-30 sekundach z kodem 504 Gateway Timeout lub 502 Bad Gateway, to proxy nie mogło ustanowić strefy C. Czasy same mówią za siebie: twój pakiet CONNECT poszedł natychmiast, odpowiedzi długo nie było, więc proxy czekało na timeout w swojej próbie.
TLS 1.3, ECH i co się zmienia w kierunku 2026
Krajobraz powoli się zamyka. TLS 1.3 dominuje, a w nim certyfikat serwera jest zaszyfrowany, więc sprawdzenie, jaki certyfikat zwrócił serwer, po samym zrzucie bez kluczy jest już niemożliwe. Rozszerzenie ECH (Encrypted Client Hello) stopniowo wdrażają przeglądarki i duże CDN-y, a tam SNI też staje się zaszyfrowane. Dla diagnostyki przez proxy to mniej krytyczne, bo nazwa hosta i tak jest widoczna w linii CONNECT, ale przyzwyczajony filtr po SNI będzie się uruchamiał rzadziej.
Jeszcze jeden trend - HTTP/3 na QUIC. Klasyczne proxy HTTP z CONNECT tuneluje tylko TCP. Jeśli w zrzucie nagle widzisz ruch UDP na port 443 bezpośrednio do adresu serwera docelowego, omijając proxy, to oznaka wycieku: aplikacja próbuje używać QUIC bezpośrednio, a nie przez proxy. Poprawnie skonfigurowany klient powinien albo wyłączać QUIC przy pracy przez proxy, albo używać proxy UDP przez osobne mechanizmy. Filtr do szybkiego sprawdzenia: udp.port == 443. Jeśli zrzut przez proxy pokazuje takie pakiety, konfigurację klienta trzeba poprawić.
Gdzie umieścić punkt przechwytywania
Zwykle odpowiedź jest jedna - na maszynie klienta. Ale są niuanse. Jeśli klient działa w kontenerze Dockera, tcpdump na hoście na interfejsie docker0 albo na interfejsie mostu pokaże ruch przed NAT, a na interfejsie zewnętrznym - po NAT, z innym adresem źródłowym. Prościej wejść w przestrzeń sieciową kontenera: nsenter -t PID -n tcpdump ... albo uruchomić kontener z tcpdump przez --net=container:nazwa. Na maszynach wirtualnych w chmurze zwróć uwagę na offloading: pakiety w zrzucie mogą wyglądać na większe niż MTU, bo karta sieciowa skleja segmenty. To normalne i nie wpływa na analizę flag.
tcpdump w praktyce: filtry, zapis do pliku, rotacja
tcpdump jest prawie na każdej maszynie z Linuksem i na macOS. W Windowsie analogiczną rolę pełni Wireshark z driverem Npcap albo konsolowy dumpcap. Przeanalizujmy polecenia, które pokrywają 95 procent zadań diagnostyki proxy.
Podstawowe przechwytywanie ruchu do proxy
Zalóżmy, że adres proxy to 203.0.113.10, port 8080. Zapisujemy wszystko, co chodzi między nami a proxy, do pliku:
sudo tcpdump -i any -nn -s 0 -w proxy.pcap 'host 203.0.113.10 and port 8080'Rozbiór kluczy:
- -i any - słuchaj wszystkich interfejsów. Wygodne, gdy nie wiesz, którym wychodzi ruch. Jeśli wiesz, lepiej wskazać konkretny: -i eth0 albo -i wlan0. Na macOS -i any nie jest obsługiwane, wskazuj en0.
- -nn - nie rozwiązuj IP na nazwy i portów na nazwy usług. Rozwiązywanie zwalnia przechwytywanie i dodaje zbędne zapytania DNS do sieci.
- -s 0 - przechwytuj pakiet w całości. W nowoczesnych wersjach to wartość domyślna, ale jawne wskazanie nie zaszkodzi.
- -w proxy.pcap - pisz do pliku w formacie pcap, a nie na ekran. Tylko tak zrzut można potem otworzyć w Wiresharku.
- Filtr w apostrofach - to filtr przechwytywania BPF. Odcina wszystko zbędne jeszcze w jądrze, więc plik będzie zawierał tylko to, co trzeba.
Filtry po hoście i porcie
Kilka proxy albo pula portów:
sudo tcpdump -i eth0 -nn -w proxy.pcap 'host 203.0.113.10 and portrange 10000-10100'Proxy po nazwie domenowej (tcpdump rozwiąże ją raz przy starcie, co nie zawsze pasuje do rotowanych adresów):
sudo tcpdump -i eth0 -nn -w proxy.pcap 'host proxy.example.net and port 8080'Przechwytywanie tylko pakietów pomocniczych bez danych, żeby patrzeć na handshake i zerwania przy dużym wolumenie ruchu:
sudo tcpdump -i eth0 -nn -w flags.pcap 'host 203.0.113.10 and tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0'Tylko RST w obie strony:
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0'Przechwytywanie z wykluczeniem własnej sesji SSH, żeby nie zaśmiecać zrzutu, gdy robisz go na zdalnym serwerze:
sudo tcpdump -i eth0 -nn -w proxy.pcap 'host 203.0.113.10 and not port 22'Jak nie nazbierać gigabajtów: rotacja po rozmiarze i czasie
Jeśli błąd jest rzadki i pojawia się raz na godzinę, zrzut musi się kręcić długo. Bez rotacji dysk się skończy. Rotacja po rozmiarze, 100 MB na plik, pierścień z 10 plików:
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'Tutaj -C ustawia rozmiar w milionach bajtów, -W ogranicza liczbę plików: jedenasty plik nadpisze pierwszy. Rotacja po czasie, nowy plik co 10 minut, zachowaj ostatnie 24 pliki (4 godziny):
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'Klucz -G wskazuje interwał w sekundach, a szablon czasu w nazwie pliku jest obowiązkowy, inaczej tcpdump będzie nadpisywał jeden plik. Przydatny klucz -Z użytkownik zrzuca uprawnienia po otwarciu interfejsu, a -U wymusza zapis pakietów do pliku od razu, bez buforowania, co jest ważne, jeśli będziesz czytać plik równolegle albo boisz się stracić ogon przy awaryjnym zakończeniu.
Obcinanie ładunku
Jeszcze jeden sposób na zmniejszenie objętości - przechwytywać tylko nagłówki. Do diagnostyki zerwań zawartość rekordów TLS nie jest potrzebna, wystarczy pierwszych 128 bajtów każdego pakietu:
sudo tcpdump -i eth0 -nn -s 128 -w headers.pcap 'host 203.0.113.10'Ostrożnie: przy -s 128 linia CONNECT i nagłówki mogą być obcięte, a Wireshark oznaczy pakiety jako truncated. Do pełnej analizy handshake TLS to za mało, ClientHello często zajmuje 300-600 bajtów i więcej. Kompromis - -s 600.
Czytanie zrzutu prosto w konsoli
Wireshark nie zawsze jest pod ręką, a szybko rzucić okiem trzeba. Czytanie pliku z wypisaniem flag TCP i absolutnych numerów sekwencji:
tcpdump -nn -r proxy.pcap -S -tttt 'tcp[tcpflags] & (tcp-rst|tcp-fin) != 0'Klucz -tttt wypisuje pełną datę i czas, -S pokazuje absolutne numery sekwencji, co pomaga zestawić z logami. Wypisanie zawartości pakietów w ASCII, żeby zobaczyć linię CONNECT i odpowiedź proxy:
tcpdump -nn -r proxy.pcap -A 'port 8080' | grep -E 'CONNECT|HTTP/1'tshark jako konsolowy Wireshark
Jeśli Wireshark jest zainstalowany na serwerze, tshark daje dostęp do tych samych dyssektorów z konsoli. Lista wszystkich CONNECT z kodami odpowiedzi:
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.codeLista strumieni zakończonych RST, ze wskazaniem, kto go wysłał:
tshark -r proxy.pcap -Y 'tcp.flags.reset == 1' -T fields -e frame.time_relative -e tcp.stream -e ip.src -e ip.dstWireshark: filtry wyświetlania, Follow TCP Stream, czytanie TLS bez deszyfrowania
Otworzyłeś pcap w Wiresharku i zobaczyłeś tysiące linii. Od czego zacząć? Od filtrów wyświetlania. W odróżnieniu od filtrów przechwytywania BPF działają już na zapisanym pliku i rozumieją strukturę protokołów.
Podstawowe filtry do pracy przez proxy
Wszystko, co związane z proxy:
ip.addr == 203.0.113.10 && tcp.port == 8080Wszystkie żądania CONNECT:
http.request.method == "CONNECT"CONNECT do konkretnego hosta:
http.request.method == "CONNECT" && http.host contains "api.example.com"Odpowiedzi proxy inne niż 200 (błędy autoryzacji 407, niedostępność 502, timeouty 504):
http.response.code >= 400 && tcp.port == 8080Tylko odpowiedzi 407, oznaka błędnych danych logowania albo wyczerpanego limitu:
http.response.code == 407Filtry TLS wewnątrz tunelu
Wireshark umie rozpoznać, że po CONNECT z odpowiedzią 200 zaczyna się TLS, i automatycznie podstawia dyssektor TLS. Jeśli nie rozpoznał (zdarza się przy obciętych pakietach), kliknij prawym przyciskiem na pakiet, wybierz Decode As i wskaż TLS dla tego portu.
Wszystkie ClientHello:
tls.handshake.type == 1ClientHello z konkretnym SNI:
tls.handshake.extensions_server_name contains "example.com"ServerHello (jeśli go nie ma po ClientHello - handshake nie zaczął się od strony serwera):
tls.handshake.type == 2Alerty TLS:
tls.alert_messageStrumień, w którym jest ClientHello, ale nie ma ServerHello, trudniej znaleźć jednym filtrem; wygodniej przez Statistics > Conversations, posortować po liczbie pakietów i popatrzeć na strumienie z 5-7 pakietami.
Filtry po problemach TCP
Wszystkie pakiety z RST:
tcp.flags.reset == 1RST wysłane przez proxy w naszą stronę:
tcp.flags.reset == 1 && ip.src == 203.0.113.10RST wysłane przez nas:
tcp.flags.reset == 1 && ip.dst == 203.0.113.10Retransmisje:
tcp.analysis.retransmissionZerowe okno i jego skutki:
tcp.analysis.zero_window || tcp.analysis.window_fullWszystkie anomalie, które Wireshark sam zauważył (retransmisje, duplikaty ACK, utracone segmenty, zerowe okno):
tcp.analysis.flags && !tcp.analysis.window_updateSYN bez odpowiedzi, czyli powtórzone SYN:
tcp.flags.syn == 1 && tcp.flags.ack == 0 && tcp.analysis.retransmissionDuże pauzy wewnątrz strumienia, więcej niż 5 sekund między pakietami:
tcp.time_delta > 5Dla tego filtra trzeba włączyć Edit > Preferences > Protocols > TCP > Calculate conversation timestamps.
Follow TCP Stream
Znalazłeś podejrzany pakiet, na przykład RST. Klikasz prawym przyciskiem > Follow > TCP Stream. Wireshark pokaże cały dialog tego połączenia w formie tekstu: nasz ruch jednym kolorem, ruch proxy drugim. Dla HTTPS przez CONNECT zobaczysz linię CONNECT, odpowiedź 200 Connection established, a dalej nieczytelne bajty TLS. To normalne. Patrzeć trzeba na objętości: ile bajtów poszło od nas (nasz ClientHello i żądania), ile przyszło (ServerHello i odpowiedź) i gdzie wszystko się skończyło.
Na dole okna Follow są liczniki: tyle a tyle bajtów klient, tyle a tyle serwer. Jeśli od serwera przyszło 0 bajtów po odpowiedzi 200, serwer docelowy w ogóle nie odpowiedział na ClientHello. Jeśli przyszło około 100-4000 bajtów i się urwało, handshake się zaczął, ale się nie zakończył. Jeśli przyszły dziesiątki kilobajtów i potem zerwanie, problem jest w środku transmisji danych.
Przydatny nawyk: filtruj po numerze strumienia. Po Follow w linii filtra automatycznie pojawi się tcp.stream eq 42. Zamknij okno Follow, a w głównej liście zostanie tylko ten strumień z flagami i czasami. To najwygodniejszy widok do analizy zerwania.
Czytanie TLS handshake bez deszyfrowania
Rozwiń ClientHello w drzewie pakietu. Na co patrzeć:
- Version i supported_versions - czy klient proponuje TLS 1.3. Jeśli serwer wymaga 1.3, a klient proponuje tylko 1.2, serwer zamknie połączenie alertem protocol_version albo po prostu FIN.
- server_name - czy SNI zgadza się z hostem w CONNECT. Rozbieżność zdarza się przy złej konfiguracji klienta i prowadzi do błędów certyfikatu.
- Cipher Suites - lista szyfrów. Zbyt krótka albo przestarzała lista - przyczyna alertu handshake_failure.
- ALPN - czy klient proponuje h2. Jeśli serwer wybrał h2, a klient w środku oczekiwał HTTP/1.1, aplikacja może padać z niezrozumiałymi błędami.
W ServerHello patrz na wybraną wersję i szyfr. W TLS 1.2 dalej idzie Certificate w otwartej postaci, można sprawdzić nazwę i termin ważności. W TLS 1.3 po ServerHello prawie wszystko jest zaszyfrowane, a następne, co się czyta, to typ rekordu. Application Data znaczy, że handshake zakończył się sukcesem i poszły dane. Alert o długości 2 bajtów zaraz po ServerHello albo zamiast niego znaczy, że coś jest nie tak: w TLS 1.3 kodu alertu bez kluczy nie zobaczysz, ale sam fakt jest już informacyjny.
W Statistics > Conversations > TCP wygodnie ocenić ogólny obraz: ile strumieni, ile bajtów w każdą stronę, jak długo trwają. Strumienie o długości 0.05 sekundy i 6 pakietach to najpewniej połączenia zresetowane zaraz po handshake.
Diagnostyka po zrzucie: RST, FIN, retransmisje, zero window
Teraz najważniejsze. Jak po zrzucie strefy A ustalić, gdzie dokładnie się urwało? Rozbierzmy każdy sygnał i jego interpretację w kontekście proxy.
Macierz kierunków
Pierwsze pytanie zawsze jedno: kto wysłał pakiet zamykający? W zrzucie to pole ip.src. Są tylko dwie możliwości - nasz adres albo adres proxy.
- RST albo FIN od nas - połączenie zamknęła nasza aplikacja, nasz system albo coś na naszym hoście. Proxy i serwer docelowy nie brali w tym udziału. Typowe przyczyny: timeout w kliencie HTTP, awaryjne zakończenie procesu, wyczerpanie deskryptorów plików, działanie lokalnego firewalla albo antywirusa.
- RST albo FIN od proxy - tunel zamknięty po stronie proxy. Przyczyna albo w samym proxy (limity, polityka, timeout bezczynności), albo przetłumaczona od serwera docelowego, albo od sieci między proxy a serwerem. Rozróżniamy po kontekście i czasach.
- Ani RST, ani FIN, tylko retransmisje - pakiety giną na drodze między nami a proxy. Żadna strona nie zamykała połączenia, ono po prostu umarło od strat.
RST po CONNECT bez odpowiedzi 200
Obraz: TCP ustanowiony, wysłaliśmy CONNECT, proxy odpowiedziało RST bez odpowiedzi HTTP. Takie zachowanie zwykle oznacza, że proxy odmówiło na poziomie polityki albo nie mogło sparsować żądania. Przyczyny: niedozwolony port docelowy, limit równoczesnych połączeń, niepoprawny format żądania. Tu problem jest w strefie A albo w samym proxy. Serwer docelowy nie ma nic do rzeczy.
Odpowiedź 502, 503 albo 504 na CONNECT
Obraz: CONNECT poszedł, po N sekundach przyszła odpowiedź HTTP z kodem 5xx. Patrzymy na N. Jeśli odpowiedź przyszła w czasie rzędu RTT do proxy, proxy odmówiło od razu - być może nazwa DNS nie rozwiązuje się po jego stronie albo adres jest natychmiast niedostępny (ICMP unreachable). Jeśli N wynosi około 10-30 sekund, proxy czekało na timeout przy łączeniu z serwerem docelowym: serwer nie odpowiada na SYN. W obu przypadkach to strefa C, a twój zrzut dowodzi, że zrobiłeś wszystko poprawnie, a proxy uczciwie poinformowało o niemożności.
FIN albo RST zaraz po ClientHello
Obraz: CONNECT - 200 - nasz ClientHello - po jednym RTT do proxy plus coś przychodzi FIN albo RST od proxy. Tu jest dwóch kandydatów: serwer docelowy odrzucił połączenie na etapie TLS (nie spodobało mu się SNI, wersja, brak certyfikatu klienta) albo system ochronny serwera zresetował połączenie po cechach ClientHello. Kluczowa przesłanka to czas. Jeśli między naszym ClientHello a zerwaniem minęło wyraźnie więcej niż RTT do proxy, znaczy, że proxy zdążyło wysłać ClientHello dalej i dostać reakcję. To nie proxy zdecydowało o zamknięciu, to przetłumaczona reakcja serwera docelowego.
Porównaj z RTT do proxy, które mierzysz po TCP handshake: czas między SYN i SYN-ACK. Jeśli RTT do proxy to 40 ms, a zerwanie po ClientHello przyszło po 200 ms, różnica 160 ms to w przybliżeniu RTT proxy - serwer w obie strony. Obraz jest w pełni spójny z odmową serwera.
Zerwanie po ServerHello albo po części danych
Handshake się zaczął, serwer odpowiedział, poszły dane, i w połowie FIN albo RST. Jeśli to FIN i od proxy, a przed nim ostatni rekord Application Data ma logiczny rozmiar, być może serwer po prostu zamknął połączenie po odpowiedzi (Connection: close wewnątrz TLS), a klient błędnie to zinterpretował. Jeśli RST pośrodku strumienia danych bez wcześniejszego spadku tempa, to albo wymuszone zamknięcie na serwerze, albo proxy przerwało tunel po limicie ruchu albo czasie życia sesji. Dla proxy mobilnych z rotacją IP po czasie to drugie bardzo prawdopodobne: zerwanie następuje dokładnie w momencie zmiany IP. Sprawdź, czy czas zerwania zgadza się z interwałem rotacji w ustawieniach twojego proxy.
Retransmisje i ich kierunek
Wireshark oznacza pakiet jako retransmission, gdy widzi ponowną transmisję tych samych numerów sekwencji. Patrzymy, kto retransmituje:
- Retransmitujemy my - nasze pakiety nie są potwierdzane przez proxy. Albo giną na drodze do proxy, albo giną potwierdzenia na drodze powrotnej. W obu przypadkach problem jest w sieci strefy A.
- Retransmituje proxy - jego pakiety nie są potwierdzane przez nas. Nie dostajemy ich albo nasze ACK nie docierają. Też strefa A, ale z naciskiem na kierunek przychodzący.
- Retransmisje SYN - osobny przypadek: proxy jest nieosiągalne na poziomie TCP, połączenie w ogóle się nie ustanawia.
Ważne: strat w strefie C nie zobaczysz nigdy jako retransmisji. Proxy sam radzi sobie z serwerem docelowym. Jedyny ślad strat w strefie C to pauzy: proxy przekazuje nam dane nierównomiernie, z przerwami, choć retransmisji w zrzucie nie ma. Filtr tcp.time_delta > 1 dla pakietów od proxy pokaże takie pauzy.
Zero Window
Okno TCP o rozmiarze zero oznacza, że odbiorca nie nadąża z odczytem danych z bufora gniazda. Jeśli zerowe okno ogłaszamy my, nasza aplikacja nie czyta odpowiedzi wystarczająco szybko: zajęty wątek, blokada, wolne przetwarzanie. Proxy w takim przypadku czeka, potem wysyła Zero Window Probe, a jeśli aplikacja nadal nie zaczęła czytać, po kilkudziesięciu sekundach może zamknąć połączenie. Klient zobaczy zerwanie i obwini proxy, choć przyczyna jest po stronie klienta.
Jeśli zerowe okno ogłasza proxy, znaczy, że nie nadąża przekazywać naszych danych dalej - serwer docelowy przyjmuje wolno. To pośrednia oznaka problemów w strefie C, zdarza się przy pobieraniu dużych plików przez proxy z wolnego serwera.
Duplikaty ACK i SACK
Duplicate ACK od proxy to sygnał, że otrzymało pakiet nie po kolei, coś z naszego zginęło. Wiele duplikatów ACK i następujące po nich retransmisje to klasyczny obraz strat w sieci mobilnej albo Wi-Fi po naszej stronie. Duplikaty ACK od nas - zginęły pakiety od proxy. Opcja SACK w handshake pozwala TCP skuteczniej się odbudowywać, ale fakt strat i tak jest na wierzchu.
Heurystyka TTL
Zaawansowany trik. Popatrz na pole IP TTL w normalnych pakietach od proxy i w pakiecie RST. Jeśli TTL w RST różni się o kilka jednostek, RST wygenerował nie samo proxy, ale węzeł pośredni na drodze: firewall, load balancer albo system filtrowania operatora. To nie jest absolutny dowód, ale silna wskazówka, że warto sprawdzić drogę między tobą a proxy, a nie obwiniać proxy albo serwer docelowy.
Tabela zbiorcza interpretacji
- SYN powtarzany, SYN-ACK brak: proxy nieosiągalne albo sieć do niego. Strefa A.
- SYN - RST: port proxy zamknięty albo filtrowany. Strefa A.
- CONNECT - 407: błędna autoryzacja albo limit konta. Proxy.
- CONNECT - 502/504 szybko: proxy nie mogło zacząć łączenia z serwerem. Strefa C, raczej DNS albo trasa.
- CONNECT - 504 po 20-30 sekundach: serwer nie odpowiada na SYN proxy. Strefa C.
- 200 - ClientHello - RST/FIN po czasie większym niż RTT: serwer odrzucił TLS. Strefa C.
- 200 - ClientHello - cisza - zerwanie po timeoucie klienta: serwer przyjmuje TCP, ale nie odpowiada na TLS. Strefa C albo blokujący filtr za proxy.
- Dane idą - RST od proxy w stałym momencie: limit albo rotacja proxy. Proxy.
- Retransmisje i duplikaty ACK bez RST: straty w sieci między nami a proxy. Strefa A.
- Zero Window od nas: nasza aplikacja nie czyta. Klient.
- RST od nas po długiej ciszy: nasz timeout. Klient.
Deszyfrowanie własnego ruchu przez SSLKEYLOGFILE
Czasem flag nie wystarcza i trzeba zobaczyć, co dokładnie zwrócił serwer wewnątrz TLS: kod odpowiedzi, nagłówki, ciało błędu. Do tego nie potrzeba mitmproxy ani podłożonego certyfikatu. Wystarczy, żeby twój własny klient zapisywał klucze sesyjne do pliku, a Wireshark użył ich do odszyfrowania. To działa tylko dla twojego klienta i twoich połączeń: klucze ma tylko ta strona, która brała udział w handshake. Odszyfrowanie cudzego ruchu albo ruchu, który proxy prowadzi z serwerem w strefie C, tą metodą jest niemożliwe, i tak powinno być.
Jak włączyć w różnych klientach
Przeglądarki na bazie Chromium i Firefox czytają zmienną środowiskową SSLKEYLOGFILE i zapisują tam klucze w formacie NSS Key Log:
export SSLKEYLOGFILE=/home/user/tls-keys.log
google-chrome --proxy-server=http://203.0.113.10:8080curl zbudowany z OpenSSL też czyta tę zmienną:
export SSLKEYLOGFILE=/home/user/tls-keys.log
curl -v -x http://user:pass@203.0.113.10:8080 https://api.example.com/healthNode.js z kluczem wiersza poleceń:
node --tls-keylog=/home/user/tls-keys.log app.jsPython nie czyta zmiennej automatycznie, ale od wersji 3.8 w SSLContext jest atrybut keylog_filename. Dla requests przez adapter:
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 przez pole KeyLogWriter w 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,
}Podłączenie kluczy w Wiresharku
Dwa sposoby. Pierwszy: Edit > Preferences > Protocols > TLS > (Pre)-Master-Secret log filename, wskaż ścieżkę do pliku z kluczami. Wireshark odszyfruje wszystkie strumienie, dla których znajdzie dopasowanie po Client Random. Drugi sposób jest pewniejszy do przekazywania pliku kolegom: wbudować klucze bezpośrednio w pcapng:
editcap --inject-secrets tls,/home/user/tls-keys.log proxy.pcap proxy-with-keys.pcapngPo tym w Wiresharku wewnątrz tunelu CONNECT pojawią się odszyfrowane żądania i odpowiedzi HTTP/1.1 albo HTTP/2. Filtr dla odszyfrowanego HTTP/2:
http2.header.name == ":status"Dla odszyfrowanego HTTP/1.1 wewnątrz tunelu działają zwykłe http.response.code i http.request.uri.
Co to daje dla diagnostyki przez proxy
Deszyfrowanie zdejmuje ostatnią niepewność. Widzisz, że serwer odpowiedział 429 z nagłówkiem Retry-After i potem zamknął połączenie, albo że zwrócił 200, ale ciało urwało się na 40 procentach, albo że żądanie poszło, a odpowiedzi nie było w ogóle. Z takim obrazem zgłoszenie w supportcie proxy staje się konkretne: albo problem jest wyraźnie w serwerze, albo wyraźnie w tunelu.
Zasady bezpieczeństwa
- Plik z kluczami pozwala odszyfrować zapisane sesje w całości, włącznie z ciasteczkami i tokenami. Traktuj go jak hasło i usuwaj po analizie.
- Nie przekazuj pliku z kluczami razem ze zrzutem do supportu proxy ani nikomu innemu. Supportowi do analizy zerwań klucze nie są potrzebne, wystarczą mu flagi i czasy.
- Jeśli naprawdę trzeba pokazać odszyfrowaną treść, rób to na testowym koncie w serwisie docelowym i z testowymi danymi logowania proxy.
- Nie zostawiaj SSLKEYLOGFILE włączonego na produkcji. Zmienna środowiskowa, która przypadkiem trafi do konfiguracji serwisu, będzie latami zapisywać klucze na dysk.
Typowe obrazy: timeout połączenia, zerwanie w środku odpowiedzi, straty w sieci mobilnej
Zbierzmy omówione przesłanki w rozpoznawalne scenariusze. Każdy opisany tak, jak wygląda w liście pakietów Wiresharka po filtrze tcp.stream eq N.
Obraz 1: timeout połączenia z proxy
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)Ani jednego pakietu od proxy. Wykładnicze interwały 1, 2, 4, 8 sekund to standardowy retransmission backoff jądra Linuksa. Diagnoza: proxy nieosiągalne z twojego punktu. Sprawdź adres, port, firewall, routing, a także czy nie zmienił się adres proxy. Jeśli to proxy mobilne Proxeon z dedykowanym portem, upewnij się, że port z twojego panelu klienta zgadza się z tym w konfiguracji klienta.
Obraz 2: timeout połączenia proxy z serwerem docelowym
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 do proxy 41 ms, proxy potwierdziło odbiór CONNECT, a potem milczało 30 sekund i zwróciło 504. Diagnoza: serwer docelowy nie odpowiada proxy na próbę połączenia. Możliwe przyczyny - serwer leży, port zamknięty dla puli adresowej proxy, problemy sieciowe na trasie proxy - serwer. Twój klient i twoja sieć nie mają z tym nic wspólnego.
Obraz 3: serwer odrzuca 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]Zerwanie po 164 ms po ClientHello przy RTT do proxy 45 ms. Różnica około 120 ms to czas, w którym proxy przekazało ClientHello serwerowi i dostało zamknięcie. Diagnoza: serwer przyjął TCP, ale zamknął połączenie na etapie TLS. Sprawdź parametry handshake: wersje, szyfry, SNI, ALPN. Jeśli ServerHello nie ma i nie ma alertu, serwer zamknął połączenie cicho, co często robią systemy ochronne przy nietypowym ClientHello.
Obraz 4: zerwanie w środku odpowiedzi
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]Handshake przeszedł, około 500 KB danych przyszło, i RST bez zwolnienia, bez retransmisji, bez zero window. Patrz na czas absolutny. Jeśli zgadza się z momentem rotacji IP w proxy mobilnym albo z wygaśnięciem limitu czasu trwania sesji, przyczyna jest w proxy, a rozwiązanie - wyrównać interwał rotacji z czasem trwania twoich żądań albo użyć trybu bez rotacji podczas długich pobrań. Jeśli zbieżności nie ma, prawdopodobne zerwanie po stronie serwera albo CDN. Tu pomaga deszyfrowanie przez SSLKEYLOGFILE: jeśli w środku widać nagłówek Content-Length, a ciało przyszło nie w całości, serwer przerwał transmisję.
Obraz 5: straty w sieci mobilnej po stronie klienta
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)Duplikaty ACK od proxy, za nimi retransmisje od nas z rosnącymi interwałami. Ani RST, ani FIN. Diagnoza: straty na drodze klient - proxy. Jeśli klient sam jest podłączony przez sieć komórkową albo Wi-Fi, to oczekiwane w momentach przełączania między stacjami bazowymi. Rozwiązanie - zwiększyć timeouty klienta, włączyć TCP keepalive, nie trzymać długich bezczynnych połączeń, bo NAT operatorów kasuje wpisy o idle-połączeniach, zwykle po 30-300 sekundach, i następny pakiet leci w pustkę. Filtr do oceny skali strat:
tcp.analysis.retransmission && ip.dst == 203.0.113.10Policz procent takich pakietów w stosunku do ogólnej liczby przez Statistics > Capture File Properties. Jeden-dwa procent - do zniesienia dla sieci mobilnej, więcej niż pięć - szukaj problemu w warunkach radiowych albo w sprzęcie.
Obraz 6: nasz własny timeout
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]Żądanie poszło, proxy potwierdziło, odpowiedź nie przyszła, i dokładnie po 10 sekundach FIN wysłaliśmy my. To nasz read timeout. Błąd w logu klienta będzie wyglądał jak zerwanie, ale po zrzucie widać: połączenie zamknęliśmy sami, bo serwer myślał dłużej, niż jesteśmy gotowi czekać. Rozwiązanie - albo zwiększyć timeout, albo zająć się tym, dlaczego serwer jest wolny, ale proxy tu po prostu uczciwie czekało razem z nami.
Typowe błędy przy robieniu i czytaniu zrzutu
Wymieńmy to, na czym regularnie potykają się nawet doświadczeni inżynierowie.
- Robienie zrzutu bez filtra przechwytywania na obciążonej maszynie. W minutę nazbiera się gigabajt, a szukanie w nim potrzebnych 200 pakietów jest niewygodne. Zawsze filtruj po hoście proxy.
- Filtrowanie po adresie serwera docelowego. Przez proxy takich pakietów na twojej maszynie nie ma. Filtr zwróci pustkę, a człowiek dojdzie do wniosku, że ruch nie idzie. Idzie, ale do proxy.
- Mylenie filtra przechwytywania i filtra wyświetlania. Składnia BPF w tcpdump (host, port, tcp[tcpflags]) i składnia Wiresharka (ip.addr, tcp.port, tcp.flags.reset) są różne. Filtr Wiresharka w tcpdump wywoła błąd składni i odwrotnie.
- Patrzenie na RST od proxy i od razu obwinianie proxy. RST od adresu proxy to zamknięcie tunelu, a nie przyznanie się do winy. Patrz na czasy i na to, co było przed RST.
- Ignorowanie RTT. Bez zmierzonego opóźnienia do proxy nie da się odróżnić natychmiastowej odmowy proxy od przetłumaczonej odmowy serwera.
- Robienie zrzutu z -s 64 i potem próba czytania CONNECT. Nagłówki będą obcięte. Do diagnostyki proxy potrzebne jest pełne przechwytywanie albo przynajmniej -s 600.
- Brak synchronizacji czasu. Jeśli zegar na maszynie ze zrzutem spóźnia się o minutę, zestawienie zrzutu z logami supportu się nie uda. Włącz NTP i podawaj w zgłoszeniu UTC.
- Wysyłanie zrzutu z danymi logowania proxy. Nagłówek Proxy-Authorization w otwartym żądaniu HTTP do proxy zawiera login i hasło. Albo rób zrzut z testowymi danymi, albo zmień hasło po wysłaniu.
- Wysyłanie pliku z kluczami razem ze zrzutem. To ujawnia całą zawartość sesji. Supportowi klucze do analizy zerwań nie są potrzebne.
- Trzymanie jednego długiego zrzutu bez rotacji. Dysk zapełni się w najmniej odpowiednim momencie, a tcpdump padnie razem z potrzebnymi pakietami.
- Brak sprawdzenia wycieku QUIC. UDP na 443 bezpośrednio - oznaka, że część ruchu idzie obok proxy, a diagnostyka po tunelu TCP nic nie pokaże.
- Zapominanie o segmentation offload. Pakiety o rozmiarze 60 KB w zrzucie na maszynie wirtualnej nie oznaczają błędnego MTU. To jądro oddaje tcpdumpowi jeszcze nie pocięte segmenty.
Narzędzia i zasoby
Minimalny zestaw do pracy opisaną metodyką.
Przechwytywanie
- tcpdump - standard na Linuksie i macOS. Zainstalowany prawie wszędzie, wymaga roota albo capability CAP_NET_RAW.
- dumpcap - konsolowy przechwytywacz z zestawu Wiresharka, obsługuje te same klucze rotacji (-b filesize, -b files) i format pcapng.
- Wireshark z Npcap - dla Windowsa. Można przechwytywać prosto z GUI z filtrem przechwytywania w tej samej składni BPF.
- tshark - konsolowa analiza z dyssektorów Wiresharka, wygodna na serwerach bez grafiki i do automatyzacji.
Analiza
- Wireshark - główne narzędzie. Follow TCP Stream, Expert Info, Statistics > Conversations, IO Graph do wizualizacji pauz i skoków retransmisji.
- editcap - cięcie i filtrowanie pcap, wbudowywanie kluczy TLS w pcapng.
- mergecap - sklejanie plików rotacji w jeden do analizy.
- capinfos - szybkie podsumowanie pliku: czas trwania, liczba pakietów, rozmiar.
Klienci ze wsparciem debugowania
- curl z kluczami -v i --trace-time dubluje obraz zrzutu na poziomie aplikacji i obsługuje SSLKEYLOGFILE.
- openssl s_client -proxy host:port pozwala ręcznie wykonać CONNECT i TLS handshake przez proxy i zobaczyć odpowiedź serwera bez klienta HTTP.
Przydatne jednolinijkowce
Sklejanie plików rotacji:
mergecap -w all.pcapng proxy-*.pcapWycięcie ze zrzutu tylko jednego strumienia do wysłania w supportcie:
tshark -r all.pcapng -Y 'tcp.stream eq 42' -w stream42.pcapngSzybka statystyka po strumieniach z RST:
tshark -r all.pcapng -q -z conv,tcp | head -40Ręczne sprawdzenie CONNECT przez openssl:
openssl s_client -proxy 203.0.113.10:8080 -connect api.example.com:443 -servername api.example.com -briefPrzypadki i wyniki
Przypadek 1: dwa procent zerwań, winny okazał się klient
Serwis zbierania cen pracował przez pulę proxy mobilnych, około 40 tysięcy żądań na dobę. Mniej więcej 2,3 procenta żądań padało z RemoteDisconnected. Zespół był pewien, że winne są proxy. Zrobili zrzut z rotacją po 100 MB na dobę, odfiltrowali tcp.flags.reset == 1 i popatrzyli na ip.src. W 91 procentach przypadków RST wysyłał sam klient. Rozbiór strumieni pokazał: przed RST była cisza dokładnie 5 sekund po wysłaniu żądania, potem RST od klienta. W kodzie stał read timeout 5 sekund, a serwer docelowy w godzinach szczytu odpowiadał w 6-8 sekund. Zwiększenie timeoutu do 15 sekund zmniejszyło błędy do 0,3 procenta. Pozostałe 0,3 procenta to były prawdziwe FIN od proxy po 160-200 ms po ClientHello: serwer okresowo odrzucał połączenia od części puli adresowej. Te dane przekazano supportowi, tam potwierdzono obraz na podstawie swoich logów strefy C.
Przypadek 2: zerwania dużych pobrań dokładnie co 10 minut
Klient ściągał archiwa po 300-800 MB przez proxy mobilne i skarżył się na zerwania w środku. Zrzut pokazał RST od proxy w momentach oddalonych od siebie dokładnie o 600 sekund, co do sekundy, niezależnie od początku pobierania. Przyczyna okazała się w rotacji IP po harmonogramie co 10 minut: przy zmianie adresu proxy zamyka aktywne tunele. Rozwiązanie - przełączyć port na rotację na żądanie i uruchamiać zmianę adresu między pobraniami. Zerwania zniknęły całkowicie, wystarczyły dwie godziny na diagnostykę zamiast tygodni korespondencji.
Przypadek 3: serwer leży, a wydaje się, że winne proxy
Rano wszystkie żądania do jednego API zaczęły zwracać błąd połączenia. Logi klienta: Connection aborted. Pierwsza reakcja - proxy padło. Zrzut z jednej minuty pokazał: TCP z proxy ustanawia się w 38 ms, CONNECT wychodzi, a po 30 sekundach przychodzi 504 i FIN. Jednocześnie żądanie do innego hosta przez to samo proxy przechodziło w 300 ms. Diagnoza: serwer docelowy nie przyjmuje połączeń od proxy. Po 40 minutach strona statusu serwisu docelowego potwierdziła incydent po ich stronie. Zrzut oszczędził poranek i uchronił przed fałszywym ticketem w supportcie proxy.
Przypadek 4: straty w Wi-Fi wyglądały jak problem proxy
Programista testował integrację z laptopa przez proxy mobilne Proxeon i dostawał sporadyczne timeouty. Zrzut pokazał retransmisje od klienta o częstości 6 procent i duplikaty ACK od proxy, przy czym ani jednego RST od proxy. Podłączenie laptopa kablem zmniejszyło retransmisje do zera, timeouty zniknęły. Problem był w przeciążonym punkcie dostępu w biurze.
Checklista robienia zrzutu do zgłoszenia w supportcie
Jeśli zamierzasz wysłać zrzut do supportu serwisu proxy, oto kolejność działań, która oszczędzi czas obu stronom.
Przygotowanie
- Zsynchronizuj zegar na maszynie przez NTP. Sprawdź poleceniem timedatectl albo date -u.
- Jeśli to możliwe, załóż osobne testowe dane logowania proxy na czas diagnostyki albo zaplanuj zmianę hasła po wysłaniu zrzutu.
- Zapisz wersję klienta, biblioteki HTTP, system, sposób podłączenia do internetu (kabel, Wi-Fi, sieć komórkowa).
- Zapisz adres i port proxy, host docelowy, oczekiwane i faktyczne zachowanie.
Przechwytywanie
- Uruchom tcpdump z filtrem po hoście i porcie proxy, z rotacją, pełnym rozmiarem pakietu:
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'- Odtwórz problem. Jeśli powtarza się rzadko, zostaw przechwytywanie na potrzebny czas; 48 plików po 5 minut pokryje 4 godziny.
- Równolegle z przechwytywaniem zrób kontrolne żądanie przez curl z -v i --trace-time, zapisz wynik. Da dokładne powiązanie czasu z pakietami.
- Zapisz dokładny czas (UTC) każdego wystąpienia błędu z logów klienta.
- Zatrzymaj tcpdump przez Ctrl+C. Upewnij się, że pliki nie są puste: capinfos proxy-*.pcap.
Obróbka
- Sklej pliki: mergecap -w all.pcapng proxy-*.pcap.
- Znajdź problemowe strumienie filtrem tcp.flags.reset == 1 albo po czasie z logów.
- Wytnij tylko potrzebne strumienie: tshark -r all.pcapng -Y 'tcp.stream in {12 47 93}' -w report.pcapng. Supportowi nie są potrzebne godziny normalnego ruchu.
- Sprawdź, że w wyciętym zrzucie nie ma nic zbędnego: cudzych połączeń, ruchu innych serwisów.
- Pliku z kluczami TLS nie załączaj.
Treść zgłoszenia
- Czas wystąpienia w UTC z dokładnością do sekundy.
- Adres i port proxy, host docelowy.
- Zwięzła interpretacja: co widzisz w zrzucie (na przykład 200 na CONNECT, potem FIN od proxy po 170 ms po ClientHello, RTT do proxy 45 ms).
- Numery klatek albo strumieni w załączonym pliku.
- Wynik curl -v ze znacznikami czasu.
- Co już sprawdzono i wykluczono: inny host przez to samo proxy, ten sam host przez inne połączenie.
Takie zgłoszenie support obsługuje za jednym podejściem. Wystarczy mu zestawić twoje znaczniki czasu z logami strefy C i potwierdzić albo zaprzeczyć wersji.
FAQ
Dlaczego w zrzucie nie ma adresu IP serwera docelowego, przecież się do niego zwracam?
Bo zwracasz się do niego nie bezpośrednio, a przez proxy. Twój klient ustanawia połączenie TCP z adresem proxy i prosi je, żeby połączyło się z hostem docelowym. Połączenie proxy - serwer istnieje tylko po stronie proxy. W zrzucie nazwa hosta docelowego jest widoczna w linii CONNECT i w SNI wewnątrz ClientHello, ale pakietów na jego IP z twojej maszyny nie ma i być nie może.
Czy po zrzucie można poznać, jakiego zewnętrznego IP użyło proxy do zwrócenia się do serwera?
Nie. Ta informacja należy do strefy C. Jedyny sposób - zapytać przez proxy o serwis zwracający adres klienta albo sprawdzić w panelu dostawcy, jaki adres był aktywny w tym momencie.
Proxy wysłało RST. Znaczy, problem jest w proxy?
Niekoniecznie. RST od adresu proxy oznacza, że tunel został zamknięty po stronie proxy, a przyczyna może być w serwerze docelowym albo w sieci za proxy. Patrz na czas: jeśli RST przyszedł po czasie wyraźnie przekraczającym RTT do proxy od twojego ostatniego pakietu, najprawdopodobniej proxy przekazało reakcję serwera. Jeśli RST przyszedł w stałym momencie albo po dokładnym interwale, prawdopodobna jest polityka samego proxy, na przykład rotacja albo limit.
Jak zmierzyć RTT do proxy po zrzucie?
Różnica czasu między SYN od ciebie i SYN-ACK od proxy na początku dowolnego połączenia. W Wiresharku można włączyć Statistics > TCP Stream Graphs > Round Trip Time dla strumienia albo użyć pola tcp.analysis.ack_rtt jako kolumny.
Wireshark nie pokazuje TLS wewnątrz tunelu, tylko dane TCP. Co robić?
Kliknij prawym przyciskiem na pakiet po odpowiedzi 200 Connection established, wybierz Decode As, w kolumnie Current wybierz TLS dla portu TCP proxy. Upewnij się też, że pakiety nie są obcięte (-s 0 przy przechwytywaniu) i że dyssektor HTTP jest ustawiony na port proxy, jeśli jest niestandardowy: Edit > Preferences > Protocols > HTTP > TCP ports.
Czym to podejście różni się od mitmproxy?
mitmproxy pracuje na poziomie aplikacji: terminuje TLS z podłożonym certyfikatem, czyta i może zmieniać żądania HTTP. Do tego klient musi ufać jego certyfikatowi głównemu. tcpdump i Wireshark pracują na poziomie pakietów i niczego nie podmieniają: widzisz prawdziwe pakiety, prawdziwe flagi i czasy, włącznie z błędami TCP, które mitmproxy by ukryło, bo samo by je obsłużyło. Do diagnostyki zerwań poziom pakietów jest uczciwszy. Do przeglądania zawartości żądań prościej mitmproxy, ale w razie potrzeby zawartość swojego ruchu można zobaczyć też w Wiresharku przez SSLKEYLOGFILE bez podmiany certyfikatów.
Czy można odszyfrować w Wiresharku ruch, który proxy prowadzi z serwerem?
Nie. Nie masz ani pakietów tego połączenia, ani kluczy do niego. SSLKEYLOGFILE daje klucze tylko od sesji twojego klienta, a wewnątrz tunelu CONNECT to właśnie twoja sesja z serwerem, więc ją odszyfrować można. Ale osobnego TLS między proxy a serwerem w przypadku CONNECT nie ma: proxy po prostu przepuszcza twoje bajty.
Jak poznać, że klient wycieka obok proxy?
Zrób zrzut bez filtra po proxy, a z filtrem po całym twoim interfejsie, i popatrz na wychodzące połączenia na porty 80 i 443 do adresów innych niż proxy, a także na UDP 443 (QUIC) i zapytania DNS do zewnętrznych resolverów z nazwami hostów docelowych. Filtr Wiresharka: (tcp.port == 443 || udp.port == 443) && !(ip.addr == 203.0.113.10). Każde trafienie - wyciek konfiguracji.
Jak duży powinien być zrzut dla supportu?
Im mniejszy, tym lepiej. Jeden wycięty problemowy strumień zajmuje od 5 do 500 KB. Plik większy niż 50 MB support będzie analizować dłużej, a połowa tej objętości okaże się normalnym ruchem. Wycinaj potrzebne strumienie przez tshark albo File > Export Specified Packets w Wiresharku.
Co robić, jeśli tcpdump na maszynie wirtualnej pokazuje pakiety większe niż MTU?
To generic segmentation offload: jądro przekazuje do tcpdump duże segmenty, zanim karta sieciowa je pokroi. Na analizę flag, czasów i zerwań to nie wpływa. Jeśli przeszkadza, wyłącz offload na czas przechwytywania poleceniem ethtool -K eth0 gso off tso off gro off, ale pamiętaj, że obniży to wydajność sieci.
Zakończenie
Zrzut ruchu przy pracy przez proxy to nie podglądanie zawartości ani magia. To uczciwy zapis tego, co naprawdę działo się na łączu: z dokładnością do milisekundy i do każdej flagi. Logi klienta mówią, że połączenie się zerwało. Zrzut mówi, kto to zrobił, kiedy dokładnie i co było przed tym.
Podsumujmy metodykę. Na maszynie klienta widzisz tylko połączenie do proxy: TCP handshake, CONNECT albo dialog SOCKS i zaszyfrowane rekordy TLS wewnątrz tunelu. Serwer docelowy bezpośrednio nie jest widoczny, ale jego zachowanie jest tłumaczone przez proxy poprzez kody odpowiedzi na CONNECT, czasy i sposób zamknięcia tunelu. RST albo FIN od twojego adresu - twój problem albo twój timeout. Retransmisje i duplikaty ACK bez zamknięcia - straty na drodze do proxy. Zamknięcie od proxy po czasie wielokrotnie przekraczającym RTT do niego - przetłumaczona reakcja serwera. Zamknięcie w stałych momentach - polityka proxy. Zero Window od ciebie - twoja aplikacja nie czyta.
Praktyczne kroki na dziś: wrzuć do zakładek polecenia tcpdump z rotacją i filtry Wiresharka z tego artykułu; sprawdź, czy zegary na maszynach, na których pracują twoi klienci, są zsynchronizowane; upewnij się, że w twoim kliencie nie ma wycieku QUIC obok proxy; załóż testowe dane logowania do diagnostyki, żeby nie świecić produkcyjnych w zrzutach. I następnym razem, gdy logi powiedzą connection reset by peer, nie zgaduj. Zrób zrzut, otwórz strumień, popatrz na kierunek i czas. Po pięciu minutach będziesz wiedział, do kogo pisać: do siebie, do supportu proxy czy do właściciela serwera docelowego.