Cześć wszystkim, nazywam się Sasha, zarządzam testowaniem backendu w FunCorp. Mamy, jak wiele firm, wdrożoną architekturę opartą na usługach. Z jednej strony ułatwia to pracę, ponieważ każdą usługę łatwiej testować osobno, ale z drugiej strony pojawia się potrzeba testowania interakcji między usługami, które często odbywają się przez sieć.
W tym artykule opowiem o dwóch narzędziach, które umożliwiają sprawdzenie podstawowych scenariuszy opisujących działanie aplikacji w przypadku problemów z siecią.

Symulujemy problemy z siecią
Oprogramowanie zazwyczaj jest testowane na serwerach testowych z dobrym połączeniem internetowym. W trudnych warunkach produkcyjnych wszystko może wyglądać inaczej, dlatego czasami konieczne jest sprawdzenie programów w warunkach słabego połączenia. W systemie Linux w zadaniu symulacji takich warunków pomoże narzędzie tc.
tc (skrót od Traffic Control) pozwala na konfigurację przesyłania pakietów sieciowych w systemie. To narzędzie ma szerokie możliwości, o których można poczytać więcej . Zajmę się tylko kilkoma z nich: interesuje nas zarządzanie ruchem, do czego użyjemy qdisc, a ponieważ musimy emulować niestabilną sieć, użyjemy klasy qdisc .
Uruchomimy serwer echo na serwerze (użyłem do tego ):
ncat -l 127.0.0.1 12345 -k -c 'xargs -n1 -i echo "Response: {}"'
Aby szczegółowo wyświetlić wszystkie znaczniki czasu na każdym etapie interakcji klienta z serwerem, napisałem prosty skrypt w Pythonie, który wysyła zapytanie Test do naszego serwera echo.
Kod źródłowy klienta
#!/bin/python
import socket
import time
HOST = '127.0.0.1'
PORT = 12345
BUFFER_SIZE = 1024
MESSAGE = "Testn"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
t1 = time.time()
print "[time before connection: %.5f]" % t1
s.connect((HOST, PORT))
print "[time after connection, before sending: %.5f]" % time.time()
s.send(MESSAGE)
print "[time after sending, before receiving: %.5f]" % time.time()
data = s.recv(BUFFER_SIZE)
print "[time after receiving, before closing: %.5f]" % time.time()
s.close()
t2 = time.time()
print "[time after closing: %.5f]" % t2
print "[total duration: %.5f]" % (t2 - t1)
print data
Uruchomimy go i przyjrzymy się ruchowi na interfejsie lo i porcie 12345:
[user@host ~]# python client.py
[czas przed połączeniem: 1578652979.44837]
[czas po połączeniu, przed wysłaniem: 1578652979.44889]
[czas po wysłaniu, przed odebraniem: 1578652979.44894]
[czas po odebraniu, przed zamknięciem: 1578652979.45922]
[czas po zamknięciu: 1578652979.45928]
[całkowity czas trwania: 0.01091]
Odpowiedź: Test
Zrzut ruchu
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: wyjście szczegółowe tłumione, użyj -v lub -vv, aby uzyskać pełne dekodowanie protokołu
nasłuchując na lo, typ łącza EN10MB (Ethernet), rozmiar przechwytywania 262144 bajty
10:42:59.448601 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flagi [S], seq 3383332866, win 43690, opcje [mss 65495,sackOK,TS val 606325685 ecr 0,nop,wscale 7], długość 0
10:42:59.448612 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flagi [S.], seq 2584700178, ack 3383332867, win 43690, opcje [mss 65495,sackOK,TS val 606325685 ecr 606325685,nop,wscale 7], długość 0
10:42:59.448622 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flagi [.], ack 1, win 342, opcje [nop,nop,TS val 606325685 ecr 606325685], długość 0
10:42:59.448923 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flagi [P.], seq 1:6, ack 1, win 342, opcje [nop,nop,TS val 606325685 ecr 606325685], długość 5
10:42:59.448930 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flagi [.], ack 6, win 342, opcje [nop,nop,TS val 606325685 ecr 606325685], długość 0
10:42:59.459118 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flagi [P.], seq 1:15, ack 6, win 342, opcje [nop,nop,TS val 606325696 ecr 606325685], długość 14
10:42:59.459213 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flagi [.], ack 15, win 342, opcje [nop,nop,TS val 606325696 ecr 606325696], długość 0
10:42:59.459268 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flagi [F.], seq 6, ack 15, win 342, opcje [nop,nop,TS val 606325696 ecr 606325696], długość 0
10:42:59.460184 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flagi [F.], seq 15, ack 7, win 342, opcje [nop,nop,TS val 606325697 ecr 606325696], długość 0
10:42:59.460196 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flagi [.], ack 16, win 342, opcje [nop,nop,TS val 606325697 ecr 606325697], długość 0
Wszystko standardowo: trójstronna ręka, PSH/ACK i ACK w odpowiedzi dwa razy — to wymiana żądania i odpowiedzi pomiędzy klientem a serwerem, a dwa razy FIN/ACK i ACK — zakończenie połączenia.
Opóźnienie pakietów
Teraz ustawiamy opóźnienie 500 milisekund:
tc qdisc add dev lo root netem delay 500ms
Uruchomiamy klienta i widzimy, że skrypt teraz wykonuje się przez 2 sekundy:
[user@host ~]# ./client.py
[czas przed połączeniem: 1578662612.71044]
[czas po połączeniu, przed wysłaniem: 1578662613.71059]
[czas po wysłaniu, przed odebraniem: 1578662613.71065]
[czas po odebraniu, przed zamknięciem: 1578662614.72011]
[czas po zamknięciu: 1578662614.72019]
[całkowity czas trwania: 2.00974]
Odpowiedź: Test
Co w ruchu sieciowym? Przyglądamy się:
Zrzut ruchu
13:23:33.210520 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [S], seq 1720950927, win 43690, options [mss 65495,sackOK,TS val 615958947 ecr 0,nop,wscale 7], length 0
13:23:33.710554 IP 127.0.0.1.12345 > 127.0.0.1.58694: Flags [S.], seq 1801168125, ack 1720950928, win 43690, options [mss 65495,sackOK,TS val 615959447 ecr 615958947,nop,wscale 7], length 0
13:23:34.210590 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 615959947 ecr 615959447], length 0
13:23:34.210657 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 615959947 ecr 615959447], length 5
13:23:34.710680 IP 127.0.0.1.12345 > 127.0.0.1.58694: Flags [.], ack 6, win 342, options [nop,nop,TS val 615960447 ecr 615959947], length 0
13:23:34.719371 IP 127.0.0.1.12345 > 127.0.0.1.58694: Flags [P.], seq 1:15, ack 6, win 342, options [nop,nop,TS val 615960456 ecr 615959947], length 14
13:23:35.220106 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [.], ack 15, win 342, options [nop,nop,TS val 615960957 ecr 615960456], length 0
13:23:35.220188 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [F.], seq 6, ack 15, win 342, options [nop,nop,TS val 615960957 ecr 615960456], length 0
13:23:35.720994 IP 127.0.0.1.12345 > 127.0.0.1.58694: Flags [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 615961457 ecr 615960957], length 0
13:23:36.221025 IP 127.0.0.1.58694 > 127.0.0.1.12345: Flags [.], ack 16, win 342, options [nop,nop,TS val 615961957 ecr 615961457], length 0
Można zauważyć, że w interakcji między klientem a serwerem wystąpił oczekiwany opóźnienie wynoszące pół sekundy. Znacznie ciekawiej zachowuje się system, gdy opóźnienie jest większe: jądro zaczyna ponownie wysyłać niektóre pakiety TCP. Zmieńmy opóźnienie na 1 sekundę i sprawdźmy ruch (nie będę pokazywać wyjścia klienta, tam spodziewane 4 sekundy w total duration):
tc qdisc change dev lo root netem delay 1s
Zrzut ruchu
13:29:07.709981 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [S], seq 283338334, win 43690, options [mss 65495,sackOK,TS val 616292946 ecr 0,nop,wscale 7], length 0
13:29:08.710018 IP 127.0.0.1.12345 > 127.0.0.1.39306: Flags [S.], seq 3514208179, ack 283338335, win 43690, options [mss 65495,sackOK,TS val 616293946 ecr 616292946,nop,wscale 7], length 0
13:29:08.711094 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [S], seq 283338334, win 43690, options [mss 65495,sackOK,TS val 616293948 ecr 0,nop,wscale 7], length 0
13:29:09.710048 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 616294946 ecr 616293946], length 0
13:29:09.710152 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 616294947 ecr 616293946], length 5
13:29:09.711120 IP 127.0.0.1.12345 > 127.0.0.1.39306: Flags [S.], seq 3514208179, ack 283338335, win 43690, options [mss 65495,sackOK,TS val 616294948 ecr 616292946,nop,wscale 7], length 0
13:29:10.710173 IP 127.0.0.1.12345 > 127.0.0.1.39306: Flags [.], ack 6, win 342, options [nop,nop,TS val 616295947 ecr 616294947], length 0
13:29:10.711140 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 616295948 ecr 616293946], length 0
13:29:10.714782 IP 127.0.0.1.12345 > 127.0.0.1.39306: Flags [P.], seq 1:15, ack 6, win 342, options [nop,nop,TS val 616295951 ecr 616294947], length 14
13:29:11.714819 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [.], ack 15, win 342, options [nop,nop,TS val 616296951 ecr 616295951], length 0
13:29:11.714893 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [F.], seq 6, ack 15, win 342, options [nop,nop,TS val 616296951 ecr 616295951], length 0
13:29:12.715562 IP 127.0.0.1.12345 > 127.0.0.1.39306: Flags [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 616297952 ecr 616296951], length 0
13:29:13.715596 IP 127.0.0.1.39306 > 127.0.0.1.12345: Flags [.], ack 16, win 342, options [nop,nop,TS val 616298952 ecr 616297952], length 0
Widać, że klient dwukrotnie wysłał pakiet SYN, a serwer dwukrotnie wysłał SYN/ACK.
Oprócz stałej wartości, dla opóźnienia można ustawić odchylenie, funkcję rozkładu i korelację (z wartością dla poprzedniego pakietu). Można to zrobić w następujący sposób:
tc qdisc change dev lo root netem delay 500ms 400ms 50 distribution normal
Tutaj zdefiniowaliśmy opóźnienie w zakresie od 100 do 900 milisekund, a wartości będą dobierane zgodnie z rozkładem normalnym z 50-procentową korelacją z wartością opóźnienia dla poprzedniego pakietu.
Mogłeś zauważyć, że w pierwszej komendzie użyłem add, a potem change. Wartości tych komend są oczywiste, dlatego dodam tylko, że jest jeszcze del, która pozwala usunąć konfigurację.
Utrata pakietów
Spróbujmy teraz wymusić utratę pakietów. Jak pokazuje dokumentacja, można to zrobić na trzy sposoby: losowo tracić pakiety z określonym prawdopodobieństwem, używać do obliczeń łańcucha Markowa z 2, 3 lub 4 stanami lub zastosować model Elliota-Gilberta. W artykule omówię pierwszy (najprostszy i najbardziej oczywisty) sposób, a o innych można poczytać. .
Spowodujemy utratę 50% pakietów z korelacją 25%:
tc qdisc add dev lo root netem loss 50% 25%
Niestety, tcpdump nie będzie w stanie nam wizualnie pokazać utraty pakietów, będziemy jedynie przypuszczać, że rzeczywiście działa. A w tym pomoże nam zwiększony i niestabilny czas działania skryptu client.py (może zakończyć się w mgnieniu oka, a może zająć 20 sekund), a także zwiększona liczba retransmitowanych pakietów:
[user@host ~]# netstat -s | grep retransmited; sleep 10; netstat -s | grep retransmited
17147 segmentów retransmitowanych
17185 segmentów retransmitowanych
Dodawanie szumów do pakietów
Oprócz utraty pakietów, można symulować ich uszkodzenie: w losowej pozycji pakietu pojawi się szum. Spowodujemy uszkodzenie pakietów z 50-procentową szansą i bez korelacji:
tc qdisc change dev lo root netem corrupt 50%
Uruchamiamy skrypt klienta (nie ma tam nic ciekawego, ale trwał 2 sekundy), obserwujemy ruch:
Zrzut ruchu
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: szczegółowe dane wyciszone, użyj -v lub -vv dla pełnego dekodowania protokołów
słuchanie na lo, typ linku EN10MB (Ethernet), rozmiar przechwycenia 262144 bajtów
10:20:54.812434 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flagi [S], seq 2023663770, win 43690, opcje [mss 65495,sackOK,TS val 1037001049 ecr 0,nop,wscale 7], długość 0
10:20:54.812449 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flagi [S.], seq 2104268044, ack 2023663771, win 43690, opcje [mss 65495,sackOK,TS val 1037001049 ecr 1037001049,nop,wscale 7], długość 0
10:20:54.812458 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flagi [.], ack 1, win 342, opcje [nop,nop,TS val 1037001049 ecr 1037001049], długość 0
10:20:54.812509 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flagi [P.], seq 1:6, ack 1, win 342, opcje [nop,nop,TS val 1037001049 ecr 1037001049], długość 5
10:20:55.013093 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flagi [P.], seq 1:6, ack 1, win 342, opcje [nop,nop,TS val 1037001250 ecr 1037001049], długość 5
10:20:55.013122 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flagi [.], ack 6, win 342, opcje [nop,nop,TS val 1037001250 ecr 1037001250], długość 0
10:20:55.014681 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flagi [P.], seq 1:15, ack 6, win 342, opcje [nop,nop,TS val 1037001251 ecr 1037001250], długość 14
10:20:55.014745 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flagi [.], ack 15, win 340, opcje [nop,nop,TS val 1037001251 ecr 1037001251], długość 0
10:20:55.014823 IP 127.0.0.1.43666 > 127.0.0.5.12345: Flagi [F.], seq 2023663776, ack 2104268059, win 342, opcje [nop,nop,TS val 1037001251 ecr 1037001251], długość 0
10:20:55.214088 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flagi [P.], seq 1:15, ack 6, win 342, opcje [nop,unknown-65 0x0a3dcf62eb3d,[bad opt]
10:20:55.416087 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flagi [F.], seq 6, ack 15, win 342, opcje [nop,nop,TS val 1037001653 ecr 1037001251], długość 0
10:20:55.416804 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flagi [F.], seq 15, ack 7, win 342, opcje [nop,nop,TS val 1037001653 ecr 1037001653], długość 0
10:20:55.416818 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flagi [.], ack 16, win 343, opcje [nop,nop,TS val 1037001653 ecr 1037001653], długość 0
10:20:56.147086 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flagi [F.], seq 15, ack 7, win 342, opcje [nop,nop,TS val 1037002384 ecr 1037001653], długość 0
10:20:56.147101 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flagi [.], ack 16, win 342, opcje [nop,nop,TS val 1037002384 ecr 1037001653], długość 0
Widać, że niektóre pakiety zostały wysłane ponownie, a jeden z pakietów ma uszkodzone metadane: options [nop,unknown-65 0x0a3dcf62eb3d,[bad opt]>. Ale najważniejsze, że ostatecznie wszystko zadziałało poprawnie – TCP poradził sobie ze swoim zadaniem.
Duplikacja pakietów
Co jeszcze można zrobić za pomocą netem? Например, сымитировать ситуацию, обратную потере пакетов, — дубликацию пакетов. Эта команда также принимает 2 аргумента: вероятность и корреляцию.
tc qdisc change dev lo root netem duplicate 50% 25%
Zmienianie kolejności pakietów
Można pomieszać pakiety, i to na dwa sposoby.
W pierwszym przypadku część pakietów jest wysyłana od razu, reszta – z określonym opóźnieniem. Przykład z dokumentacji:
tc qdisc change dev lo root netem delay 10ms reorder 25% 50%
Z prawdopodobieństwem 25% (i korelacją 50%) pakiet zostanie wysłany od razu, pozostałe zostaną wysłane z opóźnieniem 10 milisekund.
Drugi sposób polega na tym, że każdy N-ty pakiet jest wysyłany natychmiast z określonym prawdopodobieństwem (i korelacją), a pozostałe – z określonym opóźnieniem. Przykład z dokumentacji:
tc qdisc change dev lo root netem delay 10ms reorder 25% 50% gap 5
Co piąty pakiet z prawdopodobieństwem 25% będzie wysyłany bez opóźnienia.
Zmiana przepustowości
Zwykle wszędzie wysyła się do , ale za pomocą netem można również zmienić przepustowość interfejsu:
tc qdisc change dev lo root netem rate 56kbit
Ta komenda sprawi, że podróże będą localhost tak samo męczące jak surfowanie po Internecie przez modem dial-up. Oprócz ustawienia bitrate'u, można również emulować model protokołu warstwy łącz nadawczych: ustawić nagłówek dla pakietu, rozmiar komórki i nagłówek dla komórki. Na przykład, w ten sposób można zasymulować i bitrate 56 kbit/s:
tc qdisc change dev lo root netem rate 56kbit 0 48 5
Symulacja timeoutu połączenia
Jeszcze jeden ważny punkt w planie testów przy odbiorze oprogramowania – timeouty. To istotne, ponieważ w systemach rozproszonych, po wyłączeniu jednej z usług, pozostałe powinny na czas wykonać fallback na inne lub zwrócić błąd klientowi, nigdy nie powinny po prostu zawieszać się, oczekując na odpowiedź lub zestawienie połączenia.
Jest kilka sposobów, aby to zrealizować: na przykład użyć mocka, który nie odpowiada, lub połączyć się z procesem za pomocą debuggera, w odpowiednim miejscu ustawić punkt przerwania i zatrzymać wykonanie procesu (to chyba najdziwniejszy sposób). Ale jednym z najbardziej oczywistych sposobów jest zablokowanie portów lub hostów. W tym pomoże nam .
Aby zademonstrować, będziemy blokować port 12345 i uruchamiać nasz skrypt klienta. Można zablokować pakiety wychodzące na ten port u nadawcy lub przychodzące na odbiorniku. W moich przykładach będą blokowane pakiety przychodzące (używamy łańcucha INPUT i opcji —dport). Takim pakietom można przypisać akcje DROP, REJECT lub REJECT z flagą TCP RST, można także skorzystać z ICMP host unreachable (de facto domyślne zachowanie to icmp-port-unreachable, a ponadto istnieje możliwość wysłania w odpowiedzi icmp-net-unreachable, icmp-proto-unreachable, icmp-net-prohibited i icmp-host-prohibited).
DROP
W przypadku reguły z DROP, pakiety po prostu "znikały".
iptables -A INPUT -p tcp --dport 12345 -j DROP
Uruchamiamy klienta i widzimy, że zawiesza się na etapie łączenia z serwerem. Sprawdzamy ruch:
Zrzut ruchu
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
08:28:20.213506 IP 127.0.0.1.32856 > 127.0.0.1.12345: Flags [S], seq 3019694933, win 43690, options [mss 65495,sackOK,TS val 1203046450 ecr 0,nop,wscale 7], length 0
08:28:21.215086 IP 127.0.0.1.32856 > 127.0.0.1.12345: Flags [S], seq 3019694933, win 43690, options [mss 65495,sackOK,TS val 1203047452 ecr 0,nop,wscale 7], length 0
08:28:23.219092 IP 127.0.0.1.32856 > 127.0.0.1.12345: Flags [S], seq 3019694933, win 43690, options [mss 65495,sackOK,TS val 1203049456 ecr 0,nop,wscale 7], length 0
08:28:27.227087 IP 127.0.0.1.32856 > 127.0.0.1.12345: Flags [S], seq 3019694933, win 43690, options [mss 65495,sackOK,TS val 1203053464 ecr 0,nop,wscale 7], length 0
08:28:35.235102 IP 127.0.0.1.32856 > 127.0.0.1.12345: Flags [S], seq 3019694933, win 43690, options [mss 65495,sackOK,TS val 1203061472 ecr 0,nop,wscale 7], length 0
Widać, że klient wysyła pakiety SYN z rosnącym czasem oczekiwania. Oto znaleźliśmy mały błąd w kliencie: należy użyć metody settimeout(), aby ograniczyć czas, przez jaki klient będzie próbował połączyć się z serwerem.
Od razu usuwamy regułę:
iptables -D INPUT -p tcp --dport 12345 -j DROPMożna usunąć od razu wszystkie reguły:
iptables -F
Jeśli używasz Dockera i musisz zablokować cały ruch idący do kontenera, można to zrobić w następujący sposób:
iptables -I DOCKER-USER -p tcp -d CONTAINER_IP -j DROP
REJECT
Teraz dodajmy analogiczną regułę, ale z REJECT:
iptables -A INPUT -p tcp --dport 12345 -j REJECT
Klient kończy działanie po sekundzie z błędem [Errno 111] Connection refused. Sprawdzamy ruch ICMP:
[user@host ~]# tcpdump -i lo -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
08:45:32.871414 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 tcp port 12345 unreachable, length 68
08:45:33.873097 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 tcp port 12345 unreachable, length 68
Widać, że klient dwa razy otrzymał port unreachable i po tym zakończył się błędem.
REJECT with tcp-reset
Spróbujemy dodać opcję —reject-with tcp-reset:
iptables -A INPUT -p tcp --dport 12345 -j REJECT --reject-with tcp-reset
W takim przypadku klient od razu wychodzi z błędem, ponieważ na pierwsze zapytanie otrzymał pakiet RST:
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: szczegółowe dane wyjścia stłumione, użyj -v lub -vv, aby zobaczyć pełny dekodowanie protokołu
nasłuchiwanie na lo, typ łącza EN10MB (Ethernet), rozmiar przechwytywania 262144 bajty
09:02:52.766175 IP 127.0.0.1.60658 > 127.0.0.1.12345: Flagi [S], seq 1889460883, win 43690, opcje [mss 65495,sackOK,TS val 1205119003 ecr 0,nop,wscale 7], długość 0
09:02:52.766184 IP 127.0.0.1.12345 > 127.0.0.1.60658: Flagi [R.], seq 0, ack 1889460884, win 0, długość 0
REJECT z icmp-host-unreachable
Spróbujmy jeszcze jedną wersję użycia REJECT:
iptables -A INPUT -p tcp --dport 12345 -j REJECT --reject-with icmp-host-unreachable
Klient kończy działanie po sekundzie z błędem [Errno 113] Brak trasy do hosta, w ruchu ICMP widzimy ICMP host 127.0.0.1 niedostępny.
Możesz także spróbować pozostałych parametrów REJECT, a ja zatrzymam się na tych 🙂
Symulujemy timeout requestu
Jeszcze jedna sytuacja — to gdy klient mógł połączyć się z serwerem, ale nie może wysłać mu zapytania. Jak filtrować pakiety, aby filtrowanie zaczęło się jakby nie od razu? Patrząc na ruch w jakiejkolwiek komunikacji między klientem a serwerem, można zauważyć, że przy nawiązywaniu połączenia używane są tylko flagi SYN i ACK, a w ostatnim pakiecie zapytania będzie flaga PSH. Ustawia się ona automatycznie, aby uniknąć buforowania. Można wykorzystać te informacje do stworzenia filtru: będzie on przepuszczał wszystkie pakiety, z wyjątkiem tych, które zawierają flagę PSH. W ten sposób połączenie zostanie nawiązane, a klient nie będzie mógł wysłać danych do serwera.
DROP
Dla DROP polecenie będzie wyglądać następująco:
iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j DROP
Uruchamiamy klienta i obserwujemy ruch:
Zrzut ruchu
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: szczegółowe dane wyjścia stłumione, użyj -v lub -vv, aby zobaczyć pełny dekodowanie protokołu
nasłuchiwanie na lo, typ łącza EN10MB (Ethernet), rozmiar przechwytywania 262144 bajty
10:02:47.549498 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flagi [S], seq 2166014137, win 43690, opcje [mss 65495,sackOK,TS val 1208713786 ecr 0,nop,wscale 7], długość 0
10:02:47.549510 IP 127.0.0.1.12345 > 127.0.0.1.49594: Flagi [S.], seq 2341799088, ack 2166014138, win 43690, opcje [mss 65495,sackOK,TS val 1208713786 ecr 1208713786,nop,wscale 7], długość 0
10:02:47.549520 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flagi [.], ack 1, win 342, opcje [nop,nop,TS val 1208713786 ecr 1208713786], długość 0
10:02:47.549568 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flagi [P.], seq 1:6, ack 1, win 342, opcje [nop,nop,TS val 1208713786 ecr 1208713786], długość 5
10:02:47.750084 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flagi [P.], seq 1:6, ack 1, win 342, opcje [nop,nop,TS val 1208713987 ecr 1208713786], długość 5
10:02:47.951088 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flagi [P.], seq 1:6, ack 1, win 342, opcje [nop,nop,TS val 1208714188 ecr 1208713786], długość 5
10:02:48.354089 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flagi [P.], seq 1:6, ack 1, win 342, opcje [nop,nop,TS val 1208714591 ecr 1208713786], długość 5
Widocznie połączenie zostało nawiązane, a klient nie może wysłać danych do serwera.
REJECT
W tej sytuacji zachowanie będzie takie samo: klient nie będzie mógł wysłać żądania, ale otrzyma ICMP 127.0.0.1 tcp port 12345 niedostępny i zwiększać czas między ponownym wysłaniem żądania w sposób wykładniczy. Komenda wygląda następująco:
iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j REJECT
REJECT with tcp-reset
Komenda wygląda następująco:
iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j REJECT --reject-with tcp-reset
Już wiemy, że podczas korzystania z —reject-with tcp-reset klient otrzyma w odpowiedzi pakiet RST, więc możemy przewidzieć zachowanie: otrzymanie pakietu RST przy nawiązanym połączeniu oznacza nieoczekiwane zamknięcie gniazda z drugiej strony, co oznacza, że klient powinien otrzymać Connection reset by peer. Uruchamiamy nasz skrypt i upewniamy się o tym. A oto jak będzie wyglądał ruch:
Zrzut ruchu
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: szczegółowe wyjście zostało ukryte, użyj -v lub -vv dla pełnego dekodowania protokołu
nasłuchiwanie na lo, typ łącza EN10MB (Ethernet), rozmiar przechwytywania 262144 bajtów
10:22:14.186269 IP 127.0.0.1.52536 > 127.0.0.1.12345: Flagi [S], seq 2615137531, win 43690, opcje [mss 65495,sackOK,TS val 1209880423 ecr 0,nop,wscale 7], długość 0
10:22:14.186284 IP 127.0.0.1.12345 > 127.0.0.1.52536: Flagi [S.], seq 3999904809, ack 2615137532, win 43690, opcje [mss 65495,sackOK,TS val 1209880423 ecr 1209880423,nop,wscale 7], długość 0
10:22:14.186293 IP 127.0.0.1.52536 > 127.0.0.1.12345: Flagi [.], ack 1, win 342, opcje [nop,nop,TS val 1209880423 ecr 1209880423], długość 0
10:22:14.186338 IP 127.0.0.1.52536 > 127.0.0.1.12345: Flagi [P.], seq 1:6, ack 1, win 342, opcje [nop,nop,TS val 1209880423 ecr 1209880423], długość 5
10:22:14.186344 IP 127.0.0.1.12345 > 127.0.0.1.52536: Flagi [R], seq 3999904810, win 0, długość 0
REJECT z icmp-host-unreachable
Myślę, że już wszystkim jest oczywiste, jak będzie wyglądać komenda 🙂 Zachowanie klienta w takim przypadku będzie nieco różnić się od tego, które miało miejsce przy prostym REJECT: klient nie będzie zwiększać czasu oczekiwania między próbami ponownego wysłania pakietu.
[user@host ~]# tcpdump -i lo -nn icmp
tcpdump: szczegółowe wyjście zostało ukryte, użyj -v lub -vv dla pełnego dekodowania protokołu
nasłuchiwanie na lo, typ łącza EN10MB (Ethernet), rozmiar przechwytywania 262144 bajtów
10:29:56.149202 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 niedostępny, długość 65
10:29:56.349107 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 niedostępny, długość 65
10:29:56.549117 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 niedostępny, długość 65
10:29:56.750125 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 niedostępny, długość 65
10:29:56.951130 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 niedostępny, długość 65
10:29:57.152107 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 niedostępny, długość 65
10:29:57.353115 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 niedostępny, długość 65
Wnioski
Nie ma potrzeby pisania mocka do sprawdzenia interakcji usługi z zawieszonym klientem lub serwerem, czasami wystarczy skorzystać z standardowych narzędzi dostępnych w systemie Linux.
Narzędzia omówione w artykule mają znacznie więcej możliwości, niż zostało opisane, więc możesz wymyślić swoje własne sposoby ich wykorzystania. Osobiście zawsze wystarcza mi to, co napisałem (w rzeczywistości nawet mniej). Jeśli korzystasz z tych lub podobnych narzędzi w testowaniu w swojej firmie, daj proszę znać, w jaki sposób. Jeśli nie, mam nadzieję, że Twoje oprogramowanie stanie się lepszej jakości, jeśli zdecydujesz się testować je w warunkach problemów z siecią w proponowany sposób.
Źródło: habr.com
