Імітуємо мережеві проблеми у Linux

Всім привіт, мене звуть Сашко, я керую тестуванням бекенду у FunCorp. У нас, як і у багатьох, реалізована сервіс-орієнтована архітектура. З одного боку, це полегшує роботу, т.к. кожен сервіс простіше тестувати окремо, але з іншого — виникає потреба тестувати взаємодію сервісів між собою, яка часто відбувається через мережу.

У цій статті я розповім про два утиліти, за допомогою яких можна перевірити базові сценарії, що описують роботу програми за наявності проблем із мережею.

Імітуємо мережеві проблеми у Linux

Імітуємо проблеми із мережею

Зазвичай програмне забезпечення тестується на тестових серверах з хорошим інтернет-каналом. У суворих умовах продакшена може бути не так гладко, тому іноді потрібно перевіряти програми в умовах поганого з'єднання. У Linux із завданням імітації таких умов допоможе утиліта tc.

tc (скор. від Traffic Control) дозволяє налаштовувати передачу мережевих пакетів у системі. Ця утиліта має великі можливості, почитати про них докладніше можна тут. Відразу я розгляну лише кілька з них: нас цікавить шедулінг трафіку, для чого ми використовуємо qdisc, а оскільки нам потрібно емулювати нестабільну мережу, то будемо використовувати classless qdisc netem.

Запустимо echo-сервер на сервері (я для цього використав nmap-ncat):

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

Для того, щоб детально вивести всі таймстемпи на кожному кроці взаємодії клієнта з сервером, я написав простий скрипт на Python, який надсилає запит Тест на наш echo-сервер.

Вихідний код клієнта

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

Запустимо його та подивимося на трафік на інтерфейсі lo та порту 12345:

[user@host ~]# python client.py
[time before connection: 1578652979.44837]
[time after connection, before sending: 1578652979.44889]
[time after sending, before receiving: 1578652979.44894]
[time after receiving, before closing: 1578652979.45922]
[time after closing: 1578652979.45928]
[total duration: 0.01091]
Response: Test

Дамп трафіку

[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
10:42:59.448601 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [S], seq 3383332866, win 43690, options [mss 65495,sackOK,TS val 606325685 ecr 0,nop,wscale 7], length 0
10:42:59.448612 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [S.], seq 2584700178, ack 3383332867, win 43690, options [mss 65495,sackOK,TS val 606325685 ecr 606325685,nop,wscale 7], length 0
10:42:59.448622 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 606325685 ecr 606325685], length 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, options [nop,nop,TS val 606325685 ecr 606325685], length 5
10:42:59.448930 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [.], ack 6, win 342, options [nop,nop,TS val 606325685 ecr 606325685], length 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, options [nop,nop,TS val 606325696 ecr 606325685], length 14
10:42:59.459213 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [.], ack 15, win 342, options [nop,nop,TS val 606325696 ecr 606325696], length 0
10:42:59.459268 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [F.], seq 6, ack 15, win 342, options [nop,nop,TS val 606325696 ecr 606325696], length 0
10:42:59.460184 IP 127.0.0.1.12345 > 127.0.0.1.54054: Flags [F.], seq 15, ack 7, win 342, options [nop,nop,TS val 606325697 ecr 606325696], length 0
10:42:59.460196 IP 127.0.0.1.54054 > 127.0.0.1.12345: Flags [.], ack 16, win 342, options [nop,nop,TS val 606325697 ecr 606325697], length 0

Все стандартно: тристороннє рукостискання, PSH/ACK і ACK у відповідь двічі – це обмін запитом та відповіддю між клієнтом та сервером, і двічі FIN/ACK та ACK – завершення з'єднання.

Затримка пакетів

Тепер встановимо затримку 500 мілісекунд:

tc qdisc add dev lo root netem delay 500ms

Запускаємо клієнт і бачимо, що тепер скрипт виконується 2 секунди:

[user@host ~]# ./client.py
[time before connection: 1578662612.71044]
[time after connection, before sending: 1578662613.71059]
[time after sending, before receiving: 1578662613.71065]
[time after receiving, before closing: 1578662614.72011]
[time after closing: 1578662614.72019]
[total duration: 2.00974]
Response: Test

Що ж у трафіку? Дивимося:

Дамп трафіку

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

Можна побачити, що у взаємодії між клієнтом та сервером з'явився очікуваний лаг у півсекунди. Набагато цікавіше поводиться система, якщо лаг буде більше: ядро ​​починає повторно надсилати деякі TCP-пакети. Змінимо затримку на 1 секунду і подивимося трафік (висновок клієнта я не показуватиму, там очікувані 4 секунди в total duration):

tc qdisc change dev lo root netem delay 1s

Дамп трафіку

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

Видно, що клієнт двічі надсилав SYN-пакет, а сервер двічі надсилав SYN/ACK.

Крім константного значення, для затримки можна задавати відхилення, функцію розподілу та кореляцію (зі значенням попереднього пакета). Робиться це так:

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

Тут ми задали затримку в проміжку від 100 до 900 мілісекунд, значення підбиратимуться відповідно до нормального розподілу і буде 50-відсоткова кореляція зі значенням затримки для попереднього пакета.

Ви могли помітити, що у першій команді я використав додавати, а потім зміна. Значення цих команд очевидне, тому додам лише, що ще є Дель, яким можна видалити конфігурацію.

Втрата пакетів

Спробуємо зробити втрату пакетів. Як видно з документації, це можна здійснити аж трьома способами: втрачати пакети рандомно з якоюсь ймовірністю, використовувати для обчислення втрати пакета ланцюг Маркова з 2, 3 або 4 станів або використовувати модель Еліота-Гілберта. У статті я розгляну перший (найпростіший і очевидніший) спосіб, а про інші можна почитати тут.

Зробимо втрату 50% пакетів із кореляцією 25%:

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

На жаль, TCPDOMP не зможе нам наочно показати втрату пакетів, будемо лише припускати, що вона справді працює. А переконатися в цьому нам допоможе нестабільний час роботи скрипту, що збільшився. client.py (може виконатися моментально, а може і за 20 секунд), а також кількість retransmitted-пакетів, що збільшилася:

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

Додавання шуму до пакетів

Окрім втрати пакетів, можна імітувати їх пошкодження: у рандомній позиції пакета з'явиться шум. Зробимо пошкодження пакетів з 50-відсотковою ймовірністю та без кореляції:

tc qdisc change dev lo root netem corrupt 50%

Запускаємо скрипт клієнта (там нічого цікавого, але виконувався він 2 секунди), дивимося трафік:

Дамп трафіку

[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
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

Видно, деякі пакети відправлялися повторно і є один пакет з битими метаданими: options [nop,unknown-65 0x0a3dcf62eb3d,[bad opt]>. Але головне, що зрештою все відпрацювало коректно — TCP впорався зі своїм завданням.

Дублювання пакетів

Що ще можна робити за допомогою netem? Наприклад, зімітувати ситуацію, обернену до втрати пакетів, — дублікацію пакетів. Ця команда також приймає 2 аргументи: ймовірність та кореляцію.

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

Зміна порядку пакетів

Можна перемішати пакети, причому двома способами.

У першому частина пакетів посилається відразу, решта — із заданою затримкою. Приклад із документації:

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

З ймовірністю 25% (і кореляцією 50%) пакет вирушить відразу, решта вирушить із затримкою 10 мілісекунд.

Другий спосіб - це коли кожен N-й пакет відсилається моментально із заданою ймовірністю (і кореляцією), а решта - із заданою затримкою. Приклад із документації:

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

Кожен п'ятий пакет із ймовірністю 25% буде відправлений без затримки.

Зміна пропускної спроможності

Зазвичай скрізь відсилаються до TBF, але за допомогою netem також можна змінити пропускну здатність інтерфейсу:

tc qdisc change dev lo root netem rate 56kbit

Ця команда зробить походи по локальний такими ж болісними, як серфінг в інтернеті через dial-up-модем. Крім установки бітрейту, можна також емулювати модель протоколу канального рівня: задати оверхід для пакета, розмір комірки і оверхід для комірки. Наприклад, так можна зімітувати Банкомат та бітрейт 56 кбіт/сек.:

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

Імітуємо connection timeout

Ще один важливий пункт у тест-плані при прийманні програмного забезпечення - таймаути. Це важливо, тому що в розподілених системах при відключенні одного з сервісів інші повинні вчасно сфолбечитися на інші або повернути помилку клієнту, при цьому вони в жодному разі не повинні просто зависати, чекаючи на відповідь або встановлення з'єднання.

Є кілька способів зробити це: наприклад, використовувати мок, який нічого не відповідає, або підключитися до процесу за допомогою дебаггера, в потрібному місці поставити breakpoint і зупинити виконання процесу (це, мабуть, найбільш збочений спосіб). Але один із найочевидніших — це фаєрволлити порти чи хости. Із цим нам допоможе Iptables.

Для демонстрації фаєрволлити порт 12345 і запускати наш скрипт клієнта. Можна надіслати вихідні пакети на порт відправника або вхідні на приймачі. У моїх прикладах фаєрволлиться вхідні пакети (використовуємо chain INPUT та опцію -dport). Таким пакетам можна робити DROP, REJECT чи REJECT з TCP прапором RST, можна з ICMP host unreachable (насправді дефолтне поведінка — це icmp-port-unreachable, а ще є можливість надіслати у відповідь icmp-net-unreachable, icmp-proto-unreachable, icmp-net-prohibited и icmp-host-prohibited).

DROP

За наявності правила з DROP пакети просто «зникатимуть».

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

Запускаємо клієнта і бачимо, що він зависає на етапі підключення до сервера. Дивимося трафік:
Дамп трафіку

[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

Видно, що клієнт посилає SYN-пакети з таймаутом, що збільшується по експоненті. Ось ми і знайшли невеликий баг у клієнті: потрібно використовувати метод settimeout(), щоб обмежити час, за який клієнт намагатиметься підключатися до сервера.

Відразу видаляємо правило:

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

Можна видалити одразу всі правила:

iptables -F

Якщо ви використовуєте Docker і вам потрібно зафаєрволлити весь трафік, що йде на контейнер, то зробити це можна так:

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

ОТХОДИТИ

Тепер додамо аналогічне правило, але з REJECT:

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

Клієнт завершується за секунду з помилкою [Errno 111] Connection refused. Дивимося трафік 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

Видно, що клієнт двічі отримав port unreachable і після цього завершився помилково.

REJECT with tcp-reset

Спробуємо додати опцію -reject-with tcp-reset:

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

У цьому випадку клієнт відразу виходить з помилкою, тому що на перший запит отримав RST пакет:

[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
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], length 0
09:02:52.766184 IP 127.0.0.1.12345 > 127.0.0.1.60658: Flags [R.], seq 0, ack 1889460884, win 0, length 0

REJECT with icmp-host-unreachable

Спробуємо ще один варіант використання REJECT:

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

Клієнт завершується за секунду з помилкою [Errno 113] No route to host, в ICMP трафіку бачимо ICMP host 127.0.0.1 unreachable.

Можете також спробувати інші параметри REJECT, а я зупинюся на цих 🙂

Імітуємо request timeout

Ще одна ситуація - це коли клієнт зміг підключитися до сервера, але не може надіслати йому запит. Як відфільтрувати пакети, щоб фільтрація почалася не відразу? Якщо подивитися на трафік будь-якого спілкування між клієнтом та сервером, то можна помітити, що при встановленні з'єднання використовуються лише прапори SYN та ACK, а ось при обміні даними в останньому пакеті запиту буде прапор PSH. Він встановлюється автоматично, щоб уникнути буферизації. Можна використовувати цю інформацію для створення фільтра: він пропускатиме всі пакети, крім тих, що містять прапор PSH. Таким чином, з'єднання встановлюватиметься, а ось надіслати дані серверу клієнт не зможе.

DROP

Для DROP команда буде виглядати так:

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

Запускаємо клієнт і дивимося трафік:

Дамп трафіку

[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
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], length 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], length 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], length 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], length 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], length 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], length 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], length 5

Бачимо, що з'єднання встановлено, і клієнт не може надіслати дані серверу.

ОТХОДИТИ

У цьому випадку поведінка буде такою ж: клієнт не зможе надіслати запит, але отримуватиме ICMP 127.0.0.1 tcp port 12345 unreachable і збільшувати час між перенаправленням запиту по експоненті. Команда виглядає так:

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

REJECT with tcp-reset

Команда виглядає так:

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

Ми вже знаємо, що при використанні -reject-with tcp-reset клієнт отримає у відповідь RST-пакет, тому можна передбачити поведінку: отримання RST-пакету при встановленому з'єднанні означає непередбачене закриття сокета з іншого боку, отже, клієнт має отримати Connection reset by peer. Запускаємо наш скрипт і засвідчуємо це. А ось так виглядатиме трафік:

Дамп трафіку

[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
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], length 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], length 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], length 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], length 5
10:22:14.186344 IP 127.0.0.1.12345 > 127.0.0.1.52536: Flags [R], seq 3999904810, win 0, length 0

REJECT with icmp-host-unreachable

Думаю, вже всім очевидно, як виглядатиме команда 🙂 Поведінка клієнта в такому разі трохи відрізнятиметься від тієї, яка була з простим REJECT: клієнт не збільшуватиме таймату між спробами перенаправити пакет.

[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
10:29:56.149202 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:56.349107 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:56.549117 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:56.750125 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:56.951130 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:57.152107 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65
10:29:57.353115 IP 127.0.0.1 > 127.0.0.1: ICMP host 127.0.0.1 unreachable, length 65

Висновок

Не обов'язково писати мок для перевірки взаємодії сервісу із клієнтом або сервером, що завис, іноді достатньо використовувати стандартні утиліти, які є в Linux.

Розглянуті в статті утиліти мають ще більшу кількість можливостей, ніж було описано, тому ви можете придумати якісь свої варіанти їх використання. Особисто мені завжди вистачає те, про що я написав (насправді навіть менше). Якщо ви використовуєте ці або подібні утиліти у тестуванні у своїй компанії, напишіть, будь ласка, як саме. Якщо ж ні, то сподіваюся, ваше ПЗ стане якіснішим, якщо ви вирішите перевіряти його в умовах проблем із мережею запропонованими способами.

Джерело: habr.com

Додати коментар або відгук