Bonjour à tous, je m'appelle Sasha, je dirige les tests de backend chez FunCorp. Nous avons, comme beaucoup d'autres, mis en place une architecture orientée services. D'un côté, cela simplifie le travail, car chaque service est plus facile à tester individuellement, mais de l'autre, il devient nécessaire de tester l'interaction entre les services, qui se fait souvent par le réseau.
Dans cet article, je vais parler de deux outils qui permettent de tester des scénarios de base décrivant le fonctionnement d'une application en cas de problèmes de réseau.

Simulons des problèmes de réseau
En général, les logiciels sont testés sur des serveurs de test avec une bonne connexion Internet. Dans les conditions difficiles de la production, tout peut ne pas se passer aussi simplement, donc parfois il est nécessaire de tester les programmes dans des conditions de mauvaise connexion. Sous Linux, l'utilitaire qui aide à simuler ces conditions est tc.
tc (abréviation de Traffic Control) permet de configurer le transfert de paquets réseau dans le système. Cet utilitaire possède de nombreuses fonctionnalités, que vous pouvez lire en détail . Ici, je vais couvrir seulement quelques-unes d'entre elles : nous nous intéressons à la planification du trafic, pour quoi nous utilisons qdisc, et puisque nous devons émuler un réseau instable, nous utiliserons le qdisc sans classe .
Lançons un serveur echo sur le serveur (j'ai utilisé pour cela ):
ncat -l 127.0.0.1 12345 -k -c 'xargs -n1 -i echo "Response: {}"'
Pour afficher en détail tous les timestamps à chaque étape de l'interaction entre le client et le serveur, j'ai écrit un simple script en Python qui envoie une requête Test à notre serveur echo.
Code source du client
#!/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
Lançons-le et observons le trafic sur l'interface lo et le port 12345 :
[user@host ~]# python client.py
[temps avant la connexion : 1578652979.44837]
[temps après la connexion, avant l'envoi : 1578652979.44889]
[temps après l'envoi, avant la réception : 1578652979.44894]
[temps après la réception, avant la fermeture : 1578652979.45922]
[temps après la fermeture : 1578652979.45928]
[durée totale : 0.01091]
Response : Test
Dump de trafic
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump : sortie détaillée supprimée, utilisez -v ou -vv pour un décodage complet des protocoles
écoute sur lo, type de lien EN10MB (Ethernet), taille de capture 262144 octets
10:42:59.448601 IP 127.0.0.1.54054 > 127.0.0.1.12345 : Drapeaux [S], seq 3383332866, win 43690, options [mss 65495,sackOK,TS val 606325685 ecr 0,nop,wscale 7], longueur 0
10:42:59.448612 IP 127.0.0.1.12345 > 127.0.0.1.54054 : Drapeaux [S.], seq 2584700178, ack 3383332867, win 43690, options [mss 65495,sackOK,TS val 606325685 ecr 606325685,nop,wscale 7], longueur 0
10:42:59.448622 IP 127.0.0.1.54054 > 127.0.0.1.12345 : Drapeaux [.], ack 1, win 342, options [nop,nop,TS val 606325685 ecr 606325685], longueur 0
10:42:59.448923 IP 127.0.0.1.54054 > 127.0.0.1.12345 : Drapeaux [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 606325685 ecr 606325685], longueur 5
10:42:59.448930 IP 127.0.0.1.12345 > 127.0.0.1.54054 : Drapeaux [.], ack 6, win 342, options [nop,nop,TS val 606325685 ecr 606325685], longueur 0
10:42:59.459118 IP 127.0.0.1.12345 > 127.0.0.1.54054 : Drapeaux [P.], seq 1:15, ack 6, win 342, options [nop,nop,TS val 606325696 ecr 606325685], longueur 14
10:42:59.459213 IP 127.0.0.1.54054 > 127.0.0.1.12345 : Drapeaux [.], ack 15, win 342, options [nop,nop,TS val 606325696 ecr 606325696], longueur 0
10:42:59.459268 IP 127.0.0.1.54054 > 127.0.0.1.12345 : Drapeaux [F.], seq 6, ack 15, win 342, options [nop,nop,TS val 606325696 ecr 606325696], longueur 0
10:42:59.460184 IP 127.0.0.1.12345 > 127.0.0.1.54054 : Drapeaux [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 606325697 ecr 606325696], longueur 0
10:42:59.460196 IP 127.0.0.1.54054 > 127.0.0.1.12345 : Drapeaux [.], ack 16, win 342, options [nop,nop,TS val 606325697 ecr 606325697], longueur 0
Tout est standard : un échange de poignée de main à trois voies, PSH/ACK et ACK en réponse deux fois — c'est un échange de demande et de réponse entre le client et le serveur, et deux fois FIN/ACK et ACK — pour terminer la connexion.
Délai des paquets
Maintenant, fixons un délai de 500 millisecondes :
tc qdisc add dev lo root netem delay 500ms
Nous lançons le client et voyons que le script s'exécute maintenant pendant 2 secondes :
[user@host ~]# ./client.py
[temps avant la connexion : 1578662612.71044]
[temps après la connexion, avant l'envoi : 1578662613.71059]
[temps après l'envoi, avant la réception : 1578662613.71065]
[temps après la réception, avant la fermeture : 1578662614.72011]
[temps après la fermeture : 1578662614.72019]
[durée totale : 2.00974]
Réponse : Test
Alors, qu'en est-il du trafic ? Regardons :
Dump de trafic
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
Nous pouvons voir qu'une latence d'une demi-seconde est apparue dans l'interaction entre le client et le serveur. Ce qui est beaucoup plus intéressant, c'est le comportement du système si la latence augmente : le noyau commence à renvoyer certains paquets TCP. Changeons le délai à 1 seconde et observons le trafic (je ne montrerai pas la sortie du client, il y a 4 secondes dans la durée totale) :
tc qdisc change dev lo root netem delay 1s
Dump de trafic
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
Il est clair que le client a envoyé un paquet SYN deux fois, et le serveur a également envoyé un SYN/ACK deux fois.
En plus de la valeur constante, vous pouvez définir un écart, une fonction de distribution et une corrélation (avec la valeur pour le paquet précédent) pour le délai.
tc qdisc change dev lo root netem delay 500ms 400ms 50 distribution normal
Ici, nous avons établi un délai compris entre 100 et 900 millisecondes, les valeurs seront déterminées selon une distribution normale avec une corrélation de 50 % avec le délai du paquet précédent.
Vous avez peut-être remarqué que dans la première commande, j'ai utilisé add, et ensuite change. La signification de ces commandes est évidente, il convient donc d'ajouter que nous avons également del, qui peut être utilisé pour supprimer la configuration.
Perte de paquets
Essayons maintenant de simuler la perte de paquets. Comme indiqué dans la documentation, cela peut être réalisé de trois manières : perdre des paquets de manière aléatoire avec une certaine probabilité, utiliser une chaîne de Markov de 2, 3 ou 4 états pour calculer la perte de paquets, ou utiliser le modèle d'Elliot-Gilbert. Dans cet article, je traiterai de la première méthode (la plus simple et évidente), et vous pourrez en lire d'autres. .
Provoquons une perte de 50 % des paquets avec une corrélation de 25 % :
tc qdisc add dev lo root netem loss 50% 25%
Malheureusement, tcpdump ne pourra pas nous montrer visuellement la perte de paquets, nous ne ferons que supposer qu'elle fonctionne vraiment. Et pour nous en assurer, nous serons aidés par l'augmentation et l'instabilité du temps d'exécution du script client.py (peut s'exécuter instantanément, ou prendre jusqu'à 20 secondes), ainsi que l'augmentation du nombre de paquets retransmis :
[user@host ~]# netstat -s | grep retransmited; sleep 10; netstat -s | grep retransmited
17147 segments retransmited
17185 segments retransmited
Ajout de bruit aux paquets
En plus de la perte de paquets, nous pouvons simuler leur corruption : un bruit apparaîtra à une position aléatoire du paquet. Provoquons la corruption des paquets avec une probabilité de 50 % et sans corrélation :
tc qdisc change dev lo root netem corrupt 50%
Lançons le script client (rien d’intéressant, mais il s'est exécuté pendant 2 secondes), observons le trafic :
Dump de trafic
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: sortie verbeuse supprimée, utilisez -v ou -vv pour un décodage complet du protocole
écoute sur lo, type de lien EN10MB (Ethernet), taille de capture 262144 octets
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.1.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
Il est évident que certains paquets ont été renvoyés et qu'il y a un paquet avec des métadonnées corrompues : options [nop,unknown-65 0x0a3dcf62eb3d,[bad opt]>. Mais le principal, c'est qu'au final tout a fonctionné correctement — le TCP a fait son travail.
Duplication de paquets
Que peut-on faire d'autre avec netem? Например, сымитировать ситуацию, обратную потере пакетов, — дубликацию пакетов. Эта команда также принимает 2 аргумента: вероятность и корреляцию.
tc qdisc change dev lo root netem duplicate 50% 25%
Modification de l'ordre des paquets
On peut mélanger les paquets de deux manières.
La première consiste à envoyer une partie des paquets immédiatement, les autres avec un délai spécifié. Exemple de la documentation :
tc qdisc change dev lo root netem delay 10ms reorder 25% 50%
Avec une probabilité de 25 % (et une corrélation de 50 %), le paquet sera envoyé immédiatement, les autres seront envoyés avec un délai de 10 millisecondes.
La deuxième méthode consiste à envoyer chaque N-ième paquet immédiatement avec une probabilité (et une corrélation) spécifiées, tandis que les autres seront envoyés avec un délai spécifié. Exemple de la documentation :
tc qdisc change dev lo root netem delay 10ms reorder 25% 50% gap 5
Chaque cinquième paquet avec une probabilité de 25 % sera envoyé sans délai.
Modification de la bande passante
Généralement, on utilise , mais on peut également utiliser netem pour modifier la bande passante de l'interface :
tc qdisc change dev lo root netem rate 56kbit
Cette commande rendra les trajets par localhost aussi pénibles que de surfer sur Internet avec un modem dial-up. En plus de définir le débit, il est aussi possible d'émuler un modèle de protocole de couche de liaison : définir le surcoût pour le paquet, la taille de la cellule et le surcoût pour la cellule. Par exemple, on peut ainsi simuler et un débit de 56 kbit/s :
tc qdisc change dev lo root netem rate 56kbit 0 48 5
Simulation de délai de connexion
Un autre point important dans le plan de test lors de l'acceptation du logiciel est les délais d'attente. Cela est important, car dans les systèmes distribués, lorsque l'un des services est déconnecté, les autres doivent rapidement basculer vers d'autres ou renvoyer une erreur au client, sans jamais se bloquer en attendant une réponse ou l'établissement d'une connexion.
Il existe plusieurs moyens d'y parvenir : par exemple, utiliser un mock qui ne répond pas, ou se connecter au processus à l'aide d'un débogueur, positionner un point d'arrêt au bon endroit et arrêter l'exécution du processus (c'est probablement la méthode la plus tordue). Mais l'un des moyens les plus évidents consiste à bloquer les ports ou les hôtes. Cela nous aidera. .
Pour la démonstration, nous allons bloquer le port 12345 et exécuter notre script client. On peut bloquer les paquets sortants sur ce port à l’expéditeur ou entrants sur le récepteur. Dans mes exemples, nous allons bloquer les paquets entrants (en utilisant la chaîne INPUT et l’option —dport). Ces paquets peuvent être DROPPÉS, REJETÉS ou REJETÉS avec le drapeau TCP RST, ou avec ICMP hôte injoignable (en fait, le comportement par défaut est icmp-port-unreachable, et il y a aussi la possibilité d’envoyer en réponse icmp-net-unreachable, icmp-proto-unreachable, icmp-net-prohibited et icmp-host-prohibited).
DROP
Lorsqu'il y a une règle DROP, les paquets disparaissent simplement.
iptables -A INPUT -p tcp --dport 12345 -j DROP
Nous lançons le client et voyons qu'il se fige à l'étape de connexion au serveur. Regardons le trafic :
Dump de trafic
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump: sortie détaillée supprimée, utilisez -v ou -vv pour un décodage complet des protocoles
écoute sur lo, type de lien EN10MB (Ethernet), taille de capture 262144 octets
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], longueur 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], longueur 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], longueur 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], longueur 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], longueur 0
On voit que le client envoie des paquets SYN avec un timeout exponentiel croissant. Voilà, nous avons trouvé un petit bug dans le client : il faut utiliser la méthode settimeout(), pour limiter le temps pendant lequel le client essaiera de se connecter au serveur.
Nous supprimons immédiatement la règle :
iptables -D INPUT -p tcp --dport 12345 -j DROPOn peut supprimer toutes les règles en même temps :
iptables -F
Si vous utilisez Docker et que vous devez bloquer tout le trafic entrant vers un conteneur, vous pouvez le faire de la manière suivante :
iptables -I DOCKER-USER -p tcp -d CONTAINER_IP -j DROP
REJECT
Ajoutons maintenant une règle similaire, mais avec REJECT :
iptables -A INPUT -p tcp --dport 12345 -j REJECT
Le client se termine après une seconde avec l'erreur [Errno 111] Connection refused. Regardons le trafic ICMP :
[user@host ~]# tcpdump -i lo -nn icmp
tcpdump: sortie détaillée supprimée, utilisez -v ou -vv pour un décodage complet des protocoles
écoute sur lo, type de lien EN10MB (Ethernet), taille de capture 262144 octets
08:45:32.871414 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 tcp port 12345 unreachable, longueur 68
08:45:33.873097 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 tcp port 12345 unreachable, longueur 68
On voit que le client a reçu deux fois port unreachable et après cela a terminé avec une erreur.
REJECT avec tcp-reset
Essayons d’ajouter l'option —reject-with tcp-reset:
iptables -A INPUT -p tcp --dport 12345 -j REJECT --reject-with tcp-reset
Dans ce cas, le client sort immédiatement avec une erreur, car il a reçu un paquet RST dès la première requête :
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump : sortie détaillée supprimée, utilisez -v ou -vv pour le décodage complet du protocole
écoute sur lo, type de lien EN10MB (Ethernet), taille de capture 262144 octets
09:02:52.766175 IP 127.0.0.1.60658 > 127.0.0.1.12345 : Flags [S], seq 1889460883, win 43690, options [mss 65495,sackOK,TS val 1205119003 ecr 0,nop,wscale 7], longueur 0
09:02:52.766184 IP 127.0.0.1.12345 > 127.0.0.1.60658 : Flags [R.], seq 0, ack 1889460884, win 0, longueur 0
REJET avec icmp-host-unreachable
Essayons une autre variante d'utilisation de REJET :
iptables -A INPUT -p tcp --dport 12345 -j REJECT --reject-with icmp-host-unreachable
Le client se termine après une seconde avec l'erreur [Errno 113] Pas de route vers l'hôte, dans le trafic ICMP nous voyons Hôte ICMP 127.0.0.1 inaccessible.
Vous pouvez également essayer d'autres paramètres de REJET, mais je vais m'arrêter ici 🙂
Imitons un délai d'attente de requête
Une autre situation est lorsque le client a pu se connecter au serveur, mais ne peut pas envoyer de requête. Comment filtrer les paquets pour que le filtrage ne commence pas immédiatement ? Si l'on observe le trafic de toute communication entre le client et le serveur, on peut remarquer que lors de l'établissement de la connexion, seuls les flags SYN et ACK sont utilisés, tandis que dans le dernier paquet de requête, il y aura le flag PSH. Cela est défini automatiquement pour éviter le buffering. On peut utiliser cette information pour créer un filtre : il autorisera tous les paquets, sauf ceux contenant le flag PSH. Ainsi, la connexion sera établie, mais le client ne pourra pas envoyer de données au serveur.
DROP
Pour DROP, la commande sera la suivante :
iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j DROP
Nous lançons le client et observons le trafic :
Dump de trafic
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump : sortie détaillée supprimée, utilisez -v ou -vv pour le décodage complet du protocole
écoute sur lo, type de lien EN10MB (Ethernet), taille de capture 262144 octets
10:02:47.549498 IP 127.0.0.1.49594 > 127.0.0.1.12345 : Flags [S], seq 2166014137, win 43690, options [mss 65495,sackOK,TS val 1208713786 ecr 0,nop,wscale 7], longueur 0
10:02:47.549510 IP 127.0.0.1.12345 > 127.0.0.1.49594 : Flags [S.], seq 2341799088, ack 2166014138, win 43690, options [mss 65495,sackOK,TS val 1208713786 ecr 1208713786,nop,wscale 7], longueur 0
10:02:47.549520 IP 127.0.0.1.49594 > 127.0.0.1.12345 : Flags [.], ack 1, win 342, options [nop,nop,TS val 1208713786 ecr 1208713786], longueur 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, options [nop,nop,TS val 1208713786 ecr 1208713786], longueur 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, options [nop,nop,TS val 1208713987 ecr 1208713786], longueur 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, options [nop,nop,TS val 1208714188 ecr 1208713786], longueur 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, options [nop,nop,TS val 1208714591 ecr 1208713786], longueur 5
Nous constatons que la connexion est établie et que le client ne peut pas envoyer de données au serveur.
REJECT
Dans ce cas, le comportement sera le même : le client ne pourra pas envoyer de requête, mais il recevra ICMP 127.0.0.1 port tcp 12345 inaccessible et augmentera le temps entre les tentatives de réenvoi de la requête de manière exponentielle. La commande est comme suit :
iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j REJECT
REJECT avec tcp-reset
La commande est la suivante :
iptables -A INPUT -p tcp --tcp-flags PSH PSH --dport 12345 -j REJECT --reject-with tcp-reset
Nous savons déjà qu'en utilisant —reject-with tcp-reset le client recevra un paquet RST en réponse, il est donc possible de prédire le comportement : recevoir un paquet RST lors d'une connexion établie signifie que le socket a été fermé de manière inattendue de l'autre côté, donc le client doit recevoir Connection reset by peer. Exécutons notre script et en assurons-nous. Voici à quoi ressemblera le trafic :
Dump de trafic
[user@host ~]# tcpdump -i lo -nn port 12345
tcpdump : sortie détaillée supprimée, utilisez -v ou -vv pour un décodage complet du protocole
écoute sur lo, type de lien EN10MB (Ethernet), taille de capture 262144 octets
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], longueur 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], longueur 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], longueur 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], longueur 5
10:22:14.186344 IP 127.0.0.1.12345 > 127.0.0.1.52536 : Flags [R], seq 3999904810, win 0, longueur 0
REJET avec icmp-host-unreachable
Je pense que tout le monde voit déjà à quoi la commande ressemblera 🙂 Le comportement du client dans ce cas sera légèrement différent de celui observé avec un simple REJECT : le client ne va pas augmenter le délai entre les tentatives de réenvoi du paquet.
[user@host ~]# tcpdump -i lo -nn icmp
tcpdump : sortie détaillée supprimée, utilisez -v ou -vv pour un décodage complet du protocole
écoute sur lo, type de lien EN10MB (Ethernet), taille de capture 262144 octets
10:29:56.149202 IP 127.0.0.1 > 127.0.0.1 : ICMP hôte 127.0.0.1 inaccessible, longueur 65
10:29:56.349107 IP 127.0.0.1 > 127.0.0.1 : ICMP hôte 127.0.0.1 inaccessible, longueur 65
10:29:56.549117 IP 127.0.0.1 > 127.0.0.1 : ICMP hôte 127.0.0.1 inaccessible, longueur 65
10:29:56.750125 IP 127.0.0.1 > 127.0.0.1 : ICMP hôte 127.0.0.1 inaccessible, longueur 65
10:29:56.951130 IP 127.0.0.1 > 127.0.0.1 : ICMP hôte 127.0.0.1 inaccessible, longueur 65
10:29:57.152107 IP 127.0.0.1 > 127.0.0.1 : ICMP hôte 127.0.0.1 inaccessible, longueur 65
10:29:57.353115 IP 127.0.0.1 > 127.0.0.1 : ICMP hôte 127.0.0.1 inaccessible, longueur 65
Sortie
Il n'est pas obligatoire d'écrire un mock pour tester l'interaction du service avec un client ou un serveur bloqué, parfois il suffit d'utiliser les utilitaires standards disponibles sous Linux.
Les utilitaires présentés dans l'article offrent encore plus de fonctionnalités que celles décrites, vous pouvez donc imaginer vos propres façons de les utiliser. Personnellement, je trouve toujours suffisant ce que j'ai écrit (en réalité, même moins). Si vous utilisez ces utilitaires ou des outils similaires pour des tests dans votre entreprise, merci d'expliquer comment. Sinon, j'espère que votre logiciel deviendra de meilleure qualité si vous décidez de le tester dans des conditions de réseau problématiques avec les méthodes proposées.
Source : habr.com
