Netzwerkprobleme in Linux simulieren

Hallo zusammen, ich bin Sascha, ich leite das Backend-Testing bei FunCorp. Wie viele andere Unternehmen haben wir eine serviceorientierte Architektur implementiert. Einerseits erleichtert das die Arbeit, da jeder Service einfacher einzeln getestet werden kann, aber andererseits entsteht die Notwendigkeit, die Interaktion zwischen den Services zu testen, was oft über das Netzwerk geschieht.

In diesem Artikel werde ich zwei Tools vorstellen, mit denen man grundlegende Szenarien überprüfen kann, die die Funktionsweise der Anwendung bei Problemen mit dem Netzwerk beschreiben.

Netzwerkprobleme in Linux simulieren

Netzwerkprobleme simulieren

In der Regel wird Software auf Testservern mit guter Internetverbindung getestet. Unter den harten Bedingungen der Produktion kann es jedoch nicht so reibungslos laufen, daher muss man manchmal Programme unter Bedingungen mit einer schlechten Verbindung überprüfen. In Linux hilft das Tool tc.

tc (Abkürzung für Traffic Control) ermöglicht die Steuerung des Netzwerks paketübertragung im System. Dieses Tool verfügt über umfangreiche Funktionen, über die man hier mehr lesen kann hier. Hier werde ich nur einige davon betrachten: uns interessiert das Traffic Scheduling, wofür wir qdisc, verwenden, und da wir ein instabiles Netzwerk emulieren müssen, werden wir den classless qdisc netem.

verwenden. Wir starten einen Echo-Server auf dem Server (ich habe dazu verwendet nmap-ncat):

ncat -l 127.0.0.1 12345 -k -c 'xargs -n1 -i echo "Response: {}"'

Um alle Zeitstempel bei jedem Schritt der Interaktion des Clients mit dem Server im Detail anzuzeigen, habe ich ein einfaches Skript in Python geschrieben, das eine Anfrage schickt Test an unseren Echo-Server.

Der Quellcode des Clients

#!/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

Lass uns ihn starten und den Verkehr an der Schnittstelle lo und Port 12345 ansehen:

[user@host ~]# python client.py
[Zeit vor Verbindung: 1578652979.44837]
[Zeit nach Verbindung, vor Versand: 1578652979.44889]
[Zeit nach Versand, vor Empfang: 1578652979.44894]
[Zeit nach Empfang, vor Schließen: 1578652979.45922]
[Zeit nach Schließen: 1578652979.45928]
[Gesamtdauer: 0.01091]
Antwort: Test

Verkehrsdump

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: Ausführliche Ausgabe unterdrückt, verwenden Sie -v oder -vv für die vollständige Protokolldekodierung
höre auf lo, Linktyp EN10MB (Ethernet), Erfassungsgröße 262144 Bytes
10:42:59.448601 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [S], seq 3383332866, win 43690, Optionen [mss 65495,sackOK,TS val 606325685 ecr 0,nop,wscale 7], Länge 0
10:42:59.448612 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [S.], seq 2584700178, ack 3383332867, win 43690, Optionen [mss 65495,sackOK,TS val 606325685 ecr 606325685,nop,wscale 7], Länge 0
10:42:59.448622 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [.], ack 1, win 342, Optionen [nop,nop,TS val 606325685 ecr 606325685], Länge 0
10:42:59.448923 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, Optionen [nop,nop,TS val 606325685 ecr 606325685], Länge 5
10:42:59.448930 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [.], ack 6, win 342, Optionen [nop,nop,TS val 606325685 ecr 606325685], Länge 0
10:42:59.459118 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [P.], seq 1:15, ack 6, win 342, Optionen [nop,nop,TS val 606325696 ecr 606325685], Länge 14
10:42:59.459213 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [.], ack 15, win 342, Optionen [nop,nop,TS val 606325696 ecr 606325696], Länge 0
10:42:59.459268 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [F.], seq 6, ack 15, win 342, Optionen [nop,nop,TS val 606325696 ecr 606325696], Länge 0
10:42:59.460184 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [F.], seq 15, ack 7, win 342, Optionen [nop,nop,TS val 606325697 ecr 606325696], Länge 0
10:42:59.460196 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [.], ack 16, win 342, Optionen [nop,nop,TS val 606325697 ecr 606325697], Länge 0

Alles standardmäßig: das dreifache Handshake, PSH/ACK und ACK als Antwort zweimal — das ist der Austausch von Anfrage und Antwort zwischen Client und Server, und zweimal FIN/ACK und ACK — das Beenden der Verbindung.

Paketverzögerung

Jetzt setzen wir eine Verzögerung von 500 Millisekunden fest:

tc qdisc add dev lo root netem delay 500ms

Wir starten den Client und sehen, dass das Skript jetzt 2 Sekunden lang läuft:

[user@host ~]# ./client.py
[Zeit vor der Verbindung: 1578662612.71044]
[Zeit nach der Verbindung, vor dem Senden: 1578662613.71059]
[Zeit nach dem Senden, vor dem Empfangen: 1578662613.71065]
[Zeit nach dem Empfangen, vor dem Schließen: 1578662614.72011]
[Zeit nach dem Schließen: 1578662614.72019]
[Gesamtdauer: 2.00974]
Antwort: Test

Was ist mit dem Verkehr? Wir schauen:

Verkehrsdump

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

Es ist zu erkennen, dass zwischen dem Client und dem Server die erwartete Verzögerung von einer halben Sekunde aufgetreten ist. Viel interessanter verhält sich das System, wenn die Verzögerung größer ist: Der Kernel beginnt, einige TCP-Pakete erneut zu senden. Lassen Sie uns die Verzögerung auf 1 Sekunde erhöhen und den Datenverkehr überprüfen (die Ausgabe des Clients werde ich nicht zeigen, da dort erwartete 4 Sekunden in der Gesamtdauer vorhanden sind):

tc qdisc change dev lo root netem delay 1s

Verkehrsdump

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

Es ist zu erkennen, dass der Client zweimal ein SYN-Paket gesendet hat, während der Server zweimal ein SYN/ACK-Paket gesendet hat.

Neben dem konstanten Wert kann auch eine Abweichung, eine Verteilungsfunktion und eine Korrelation (mit dem Wert des vorherigen Pakets) für die Verzögerung festgelegt werden. Dies geschieht wie folgt:

tc qdisc change dev lo root netem delay 500ms 400ms 50 distribution normal

Hier haben wir eine Verzögerung im Bereich von 100 bis 900 Millisekunden festgelegt, die Werte werden gemäß einer Normalverteilung ausgewählt und es wird eine 50-prozentige Korrelation mit dem Verzögerungswert des vorherigen Pakets bestehen.

Sie haben vielleicht bemerkt, dass ich im ersten Befehl add, und dann auf changeverwendet habe. Der Zweck dieser Befehle ist offensichtlich, daher füge ich nur hinzu, dass es auch delgibt, mit dem die Konfiguration entfernt werden kann.

Pakete verlieren

Versuchen wir jetzt, Paketverluste zu simulieren. Wie aus der Dokumentation hervorgeht, kann dies auf drei Arten geschehen: Pakete zufällig mit einer bestimmten Wahrscheinlichkeit verlieren, eine Markov-Kette mit 2, 3 oder 4 Zuständen zur Berechnung des Paketverlusts verwenden oder das Elliott-Gilbert-Modell nutzen. In diesem Artikel werde ich die erste (einfachste und offensichtlichste) Methode erläutern, während die anderen in separaten Artikeln behandelt werden können. hier.

Wir werden einen Verlust von 50 % der Pakete mit einer Korrelation von 25 % simulieren:

tc qdisc add dev lo root netem loss 50% 25%

Leider, tcpdump kann uns nicht direkt zeigen, wie viele Pakete verloren gehen; wir werden lediglich vermuten, dass es tatsächlich funktioniert. Und um das zu überprüfen, wird uns die erhöhte und instabile Ausführungszeit des Skripts helfen. client.py (möglicherweise wird es sofort ausgeführt, oder es kann bis zu 20 Sekunden dauern), sowie die steigende Anzahl an retransmitierten Paketen:

[user@host ~]# netstat -s | grep retransmited; sleep 10; netstat -s | grep retransmited
    17147 Segmente retransmited
    17185 Segmente retransmited

Störgeräusche in Pakete einfügen

Neben dem Paketverlust können wir auch deren Beschädigung simulieren: an einer zufälligen Position des Pakets wird ein Störgeräusch erscheinen. Wir werden die Beschädigung von Paketen mit einer Wahrscheinlichkeit von 50 % und ohne Korrelation durchführen:

tc qdisc change dev lo root netem corrupt 50%

Wir starten das Clientskript (da ist nichts Interessantes, aber es lief 2 Sekunden) und schauen den Datenverkehr an:

Verkehrsdump

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: ausführliche Ausgabe unterdrückt, verwenden Sie -v oder -vv für die vollständige Protokollanalyse
höre auf lo, Linktyp EN10MB (Ethernet), Erfassungsgröße 262144 Bytes
10:20:54.812434 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [S], seq 2023663770, win 43690, options [mss 65495,sackOK,TS val 1037001049 ecr 0,nop,wscale 7], length 0
10:20:54.812449 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [S.], seq 2104268044, ack 2023663771, win 43690, options [mss 65495,sackOK,TS val 1037001049 ecr 1037001049,nop,wscale 7], length 0
10:20:54.812458 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 1037001049 ecr 1037001049], length 0
10:20:54.812509 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 1037001049 ecr 1037001049], length 5
10:20:55.013093 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 1037001250 ecr 1037001049], length 5
10:20:55.013122 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [.], ack 6, win 342, options [nop,nop,TS val 1037001250 ecr 1037001250], length 0
10:20:55.014681 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [P.], seq 1:15, ack 6, win 342, options [nop,nop,TS val 1037001251 ecr 1037001250], length 14
10:20:55.014745 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [.], ack 15, win 340, options [nop,nop,TS val 1037001251 ecr 1037001251], length 0
10:20:55.014823 IP 127.0.0.1.43666 > 127.0.0.5.12345: Flags [F.], seq 2023663776, ack 2104268059, win 342, options [nop,nop,TS val 1037001251 ecr 1037001251], length 0
10:20:55.214088 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [P.], seq 1:15, ack 6, win 342, options [nop,unknown-65 0x0a3dcf62eb3d,[bad opt]>
10:20:55.416087 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [F.], seq 6, ack 15, win 342, options [nop,nop,TS val 1037001653 ecr 1037001251], length 0
10:20:55.416804 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 1037001653 ecr 1037001653], length 0
10:20:55.416818 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [.], ack 16, win 343, options [nop,nop,TS val 1037001653 ecr 1037001653], length 0
10:20:56.147086 IP 127.0.0.1.12345 > 127.0.0.1.43666: Flags [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 1037002384 ecr 1037001653], length 0
10:20:56.147101 IP 127.0.0.1.43666 > 127.0.0.1.12345: Flags [.], ack 16, win 342, options [nop,nop,TS val 1037002384 ecr 1037001653], length 0

Es ist offensichtlich, dass einige Pakete wiederholt versendet wurden und es gibt ein Paket mit beschädigten Metadaten: options [nop,unknown-65 0x0a3dcf62eb3d,[bad opt]>. Aber das Wichtigste ist, dass letztendlich alles korrekt funktioniert hat - TCP hat seine Aufgabe erfüllt.

Paketduplikation

Was kann man noch mit netem? Например, сымитировать ситуацию, обратную потере пакетов, — дубликацию пакетов. Эта команда также принимает 2 аргумента: вероятность и корреляцию.

tc qdisc change dev lo root netem duplicate 50% 25%

Änderung der Paketreihenfolge

Pakete können gemischt werden, und zwar auf zwei Arten.

Beim ersten werden einige Pakete sofort gesendet, während die anderen mit einer bestimmten Verzögerung gesendet werden. Ein Beispiel aus der Dokumentation:

tc qdisc change dev lo root netem delay 10ms reorder 25% 50%

Mit einer Wahrscheinlichkeit von 25% (und einer Korrelation von 50%) wird das Paket sofort gesendet, die anderen werden mit einer Verzögerung von 10 Millisekunden gesendet.

Die zweite Methode besteht darin, dass jedes N-te Paket sofort mit einer festgelegten Wahrscheinlichkeit (und Korrelation) gesendet wird, während die anderen mit einer festgelegten Verzögerung gesendet werden. Ein Beispiel aus der Dokumentation:

tc qdisc change dev lo root netem delay 10ms reorder 25% 50% gap 5

Jedes fünfte Paket hat mit einer Wahrscheinlichkeit von 25% keine Verzögerung beim Senden.

Änderung der Bandbreite

Normalerweise wird an TBF, aber mit Hilfe von netem kann auch die Bandbreite des Interfaces geändert werden:

tc qdisc change dev lo root netem rate 56kbit

Dieser Befehl macht die Reisen über localhost genauso mühsam wie das Surfen im Internet über ein Dial-up-Modem. Neben der Festlegung der Bitrate kann auch das Modell des Protokolls auf der Sicherungsebene emuliert werden: Überhead für das Paket, Zellgröße und Überhead für die Zelle festgelegt werden. Zum Beispiel so kann man ATM und eine Bitrate von 56 kbit/sec simulieren:

tc qdisc change dev lo root netem rate 56kbit 0 48 5

Simulieren eines Verbindungszeitüberschreitungs

Ein weiterer wichtiger Punkt im Testplan bei der Softwareabnahme sind Zeitüberschreitungen. Das ist wichtig, weil in verteilten Systemen, wenn einer der Dienste ausfällt, die anderen rechtzeitig auf andere zurückfallen oder dem Kunden einen Fehler zurückgeben sollten, und sie dürfen auf keinen Fall einfach hängenbleiben und auf eine Antwort oder die Herstellung einer Verbindung warten.

Es gibt mehrere Möglichkeiten, dies zu tun: Beispielsweise kann ein Mock verwendet werden, der nicht antwortet, oder man kann sich mit einem Debugger in den Prozess einklinken, an der gewünschten Stelle einen Haltepunkt setzen und die Ausführung des Prozesses anhalten (das ist wahrscheinlich die absurdeste Methode). Aber eine der offensichtlichsten ist es, Ports oder Hosts zu blockieren. Dabei hilft uns iptables.

Um eine Demonstration zu zeigen, werden wir den Port 12345 durch eine Firewall schützen und unser Client-Skript ausführen. Man kann ausgehende Pakete an diesen Port beim Sender oder eingehende beim Empfänger firewallen. In meinen Beispielen werden eingehende Pakete gefirewallt (wir benutzen die Chain INPUT und die Option —dport). Solchen Paketen kann man DROP, REJECT oder mit TCP-Flag RST REJECT zuweisen, man kann auch mit ICMP host unreachable reagieren (tatsächlich ist das Standardverhalten icmp-port-unreachable, außerdem gibt es die Möglichkeit, mit folgendem zu antworten icmp-net-unreachable, icmp-proto-unreachable, icmp-net-prohibited und icmp-host-prohibited).

DROP

Wenn eine Regel mit DROP vorhanden ist, verschwinden die Pakete einfach.

iptables -A INPUT -p tcp --dport 12345 -j DROP

Wir starten den Client und sehen, dass er während des Verbindungsaufbaus zum Server hängt. Wir schauen uns den Verkehr an:
Verkehrsdump

[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

Es ist zu sehen, dass der Client SYN-Pakete mit exponentiell ansteigendem Timeout sendet. Jetzt haben wir einen kleinen Bug im Client gefunden: Es sollte die Methode settimeout(), verwendet werden, um die Zeit zu begrenzen, in der der Client versucht, sich mit dem Server zu verbinden.

Sofort entfernen wir die Regel:

iptables -D INPUT -p tcp --dport 12345 -j DROP

Man kann auch alle Regeln auf einmal entfernen:

iptables -F

Wenn Sie Docker verwenden und den gesamten Verkehr, der an den Container geht, firewallen möchten, können Sie dies wie folgt tun:

iptables -I DOCKER-USER -p tcp -d CONTAINER_IP -j DROP

REJECT

Jetzt fügen wir eine ähnliche Regel hinzu, aber mit REJECT:

iptables -A INPUT -p tcp --dport 12345 -j REJECT

Der Client beendet sich nach einer Sekunde mit dem Fehler [Errno 111] Connection refused. Wir schauen uns den ICMP-Verkehr an:

[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

Es ist zu erkennen, dass der Client zweimal erhalten hat port unreachable und sich danach mit einem Fehler beendet hat.

REJECT with tcp-reset

Wir versuchen, die Option hinzuzufügen —reject-with tcp-reset:

iptables -A INPUT -p tcp --dport 12345 -j REJECT --reject-with tcp-reset

In diesem Fall tritt der Client sofort mit einem Fehler aus, da er beim ersten Request ein RST-Paket erhalten hat:

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: ausführliche Ausgabe unterdrückt, verwenden Sie -v oder -vv für vollständige Protokolldecodierung
höre auf lo, linktyp EN10MB (Ethernet), Aufnahmengröße 262144 Bytes
09:02:52.766175 IP 127.0.0.1.60658 > 127.0.0.1.12345: Flags [S], seq 1889460883, win 43690, Optionen [mss 65495,sackOK,TS val 1205119003 ecr 0,nop,wscale 7], Länge 0
09:02:52.766184 IP 127.0.0.1.12345 > 127.0.0.1.60658: Flags [R.], seq 0, ack 1889460884, win 0, Länge 0

REJECT mit icmp-host-unreachable

Lassen Sie uns noch eine weitere Variante der Verwendung von REJECT ausprobieren:

iptables -A INPUT -p tcp --dport 12345 -j REJECT --reject-with icmp-host-unreachable

Der Client beendet sich nach einer Sekunde mit dem Fehler [Errno 113] Keine Route zum Host, im ICMP-Verkehr sehen wir ICMP-Host 127.0.0.1 unerreichbar.

Sie können auch die anderen Parameter von REJECT ausprobieren, aber ich werde bei diesen bleiben 🙂

Wir simulieren einen Request-Timeout

Eine weitere Situation ist, wenn der Client sich mit dem Server verbinden konnte, aber ihm keine Anfrage senden kann. Wie filtert man Pakete, sodass die Filterung scheinbar nicht sofort beginnt? Wenn wir den Verkehr bei jeder Kommunikation zwischen dem Client und dem Server betrachten, können wir sehen, dass beim Verbindungsaufbau nur die Flags SYN und ACK verwendet werden, während im letzten Datenpaket der Anfrage das Flag PSH vorhanden sein wird. Dieses wird automatisch gesetzt, um das Pufferung zu vermeiden. Wir können diese Information nutzen, um einen Filter zu erstellen: Er lässt alle Pakete durch, außer denen, die das PSH-Flag enthalten. So wird die Verbindung hergestellt, aber der Client kann keine Daten an den Server senden.

DROP

Für DROP sieht der Befehl wie folgt aus:

iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j DROP

Wir starten den Client und sehen uns den Verkehr an:

Verkehrsdump

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: ausführliche Ausgabe unterdrückt, verwenden Sie -v oder -vv für vollständige Protokolldecodierung
höre auf lo, linktyp EN10MB (Ethernet), Aufnahmengröße 262144 Bytes
10:02:47.549498 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [S], seq 2166014137, win 43690, Optionen [mss 65495,sackOK,TS val 1208713786 ecr 0,nop,wscale 7], Länge 0
10:02:47.549510 IP 127.0.0.1.12345 > 127.0.0.1.49594: Flags [S.], seq 2341799088, ack 2166014138, win 43690, Optionen [mss 65495,sackOK,TS val 1208713786 ecr 1208713786,nop,wscale 7], Länge 0
10:02:47.549520 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [.], ack 1, win 342, Optionen [nop,nop,TS val 1208713786 ecr 1208713786], Länge 0
10:02:47.549568 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, Optionen [nop,nop,TS val 1208713786 ecr 1208713786], Länge 5
10:02:47.750084 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, Optionen [nop,nop,TS val 1208713987 ecr 1208713786], Länge 5
10:02:47.951088 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, Optionen [nop,nop,TS val 1208714188 ecr 1208713786], Länge 5
10:02:48.354089 IP 127.0.0.1.49594 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, Optionen [nop,nop,TS val 1208714591 ecr 1208713786], Länge 5

Wir sehen, dass die Verbindung hergestellt ist und der Client keine Daten an den Server senden kann.

REJECT

In diesem Fall wird das Verhalten dasselbe sein: Der Client kann keine Anfrage senden, erhält jedoch ICMP 127.0.0.1 tcp port 12345 unerreichbar und erhöht die Zeit zwischen den Wiederholungsanfragen exponentiell. Der Befehl sieht so aus:

iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j REJECT

REJECT with tcp-reset

Der Befehl sieht wie folgt aus:

iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j REJECT --reject-with tcp-reset

Wir wissen bereits, dass bei der Verwendung —reject-with tcp-reset der Client als Antwort ein RST-Paket erhält, daher können wir das Verhalten vorhersagen: Der Empfang eines RST-Pakets bei einer etablierten Verbindung deutet auf das unerwartete Schließen des Sockets von der anderen Seite hin, was bedeutet, dass der Client erhalten sollte Connection reset by peer. Lassen Sie uns unser Skript ausführen und dies überprüfen. So wird der Datenverkehr aussehen:

Verkehrsdump

[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: ausführliche Ausgabe unterdrückt, verwenden Sie -v oder -vv für vollständige Protokolldecodierung
lauschen auf lo, link-type EN10MB (Ethernet), Erfassungsgröße 262144 Bytes
10:22:14.186269 IP 127.0.0.1.52536 > 127.0.0.1.12345: Flags [S], seq 2615137531, win 43690, options [mss 65495,sackOK,TS val 1209880423 ecr 0,nop,wscale 7], Länge 0
10:22:14.186284 IP 127.0.0.1.12345 > 127.0.0.1.52536: Flags [S.], seq 3999904809, ack 2615137532, win 43690, options [mss 65495,sackOK,TS val 1209880423 ecr 1209880423,nop,wscale 7], Länge 0
10:22:14.186293 IP 127.0.0.1.52536 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 1209880423 ecr 1209880423], Länge 0
10:22:14.186338 IP 127.0.0.1.52536 > 127.0.0.1.12345: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 1209880423 ecr 1209880423], Länge 5
10:22:14.186344 IP 127.0.0.1.12345 > 127.0.0.1.52536: Flags [R], seq 3999904810, win 0, Länge 0

REJECT mit icmp-host-unreachable

Ich denke, es ist jetzt allen klar, wie der Befehl aussehen wird 🙂 Das Verhalten des Clients wird in diesem Fall etwas anders sein als bei einem einfachen REJECT: Der Client wird die Zeitüberschreitung zwischen den Wiederholungsversuchen nicht erhöhen.

[user@host ~]# tcpdump -i lo -nn icmp
tcpdump: ausführliche Ausgabe unterdrückt, verwenden Sie -v oder -vv für vollständige Protokolldecodierung
lauschen auf lo, link-type EN10MB (Ethernet), Erfassungsgröße 262144 Bytes
10:29:56.149202 IP 127.0.0.1 > 127.0.0.1: ICMP-Host 127.0.0.1 unerreichbar, Länge 65
10:29:56.349107 IP 127.0.0.1 > 127.0.0.1: ICMP-Host 127.0.0.1 unerreichbar, Länge 65
10:29:56.549117 IP 127.0.0.1 > 127.0.0.1: ICMP-Host 127.0.0.1 unerreichbar, Länge 65
10:29:56.750125 IP 127.0.0.1 > 127.0.0.1: ICMP-Host 127.0.0.1 unerreichbar, Länge 65
10:29:56.951130 IP 127.0.0.1 > 127.0.0.1: ICMP-Host 127.0.0.1 unerreichbar, Länge 65
10:29:57.152107 IP 127.0.0.1 > 127.0.0.1: ICMP-Host 127.0.0.1 unerreichbar, Länge 65
10:29:57.353115 IP 127.0.0.1 > 127.0.0.1: ICMP-Host 127.0.0.1 unerreichbar, Länge 65

Ausgabe

Es ist nicht unbedingt erforderlich, einen Mock für die Überprüfung der Interaktion des Dienstes mit einem festhängenden Client oder Server zu schreiben; manchmal reicht es aus, die Standardwerkzeuge zu verwenden, die in Linux verfügbar sind.

Die in dem Artikel behandelten Tools verfügen über noch mehr Funktionen, als beschrieben, sodass Sie Ihre eigenen Nutzungsmöglichkeiten erfinden können. Mir persönlich reicht immer das, was ich geschrieben habe (tatsächlich sogar weniger). Wenn Sie diese oder ähnliche Tools in den Tests Ihres Unternehmens verwenden, teilen Sie uns bitte mit, wie genau. Wenn nicht, hoffe ich, dass Ihre Software besser wird, wenn Sie sie mit den vorgeschlagenen Methoden unter Netzwerkproblemen testen.

Quelle: habr.com

60GB SSD 8Gb DDR4