Иногда возникает необходимость прибить какое-то TCP-соединение. Часто для решения этой задачи с помощью lsof или netstat вычисляют процесс, который это соединение обслуживает и что-то с этим процессом делают (например, kill -9). Но вот для ситуации когда процесс найти не получается (например, он завершился аварийно и не закрыл после себя соединение) или прибивать процесс нельзя, уже задачка становиться не совсем тривиальной.
Тут может пригодится perl-утилитка под названием killcx, которая должна помочь и в случае когда TCP-соединение пребывает в состоянии TIME_WAIT. Работает примерно так:
# ./killcx 10.11.12.13:44034 killcx v1.0.3 - (c)2009-2011 Jerome Bruandet - http://killcx.sourceforge.net/ [PARENT] checking connection with [10.11.12.13:44034] [PARENT] found connection with [10.10.10.10:22] (ESTABLISHED) [PARENT] forking child [PARENT] sending spoofed SYN to [10.10.10.10:22] with bogus SeqNum [CHILD] interface not defined, will use [eth0] [CHILD] setting up filter to sniff ACK on [eth0] for 5 seconds f[CHILD] hooked ACK from [10.10.10.10:22] [CHILD] found AckNum [1240626855] and SeqNum [301466544] [CHILD] sending spoofed RST to [10.10.10.10:22] with SeqNum [1240626855] [CHILD] sending RST to remote host as well with SeqNum [301466544] [CHILD] all done, sending USR1 signal to parent [19312] and exiting [PARENT] received child signal, checking results... => success : connection has been closed !
В качестве аргумента ей нужно передать IP-адрес и порт удаленной стороны TCP-соединения. В этом примере я подключился по SSH с клиента 10.11.12.13 на сервер с адресом 10.10.10.10. Команда выполнялась на сервере 10.10.10.10.
Для работы утилита требует наличия следующих perl-модулей:
* Net::RawIP (для создания spoofed packets, CPAN-ом он у меня просто так ставиться не захотел, жалуясь на тесты, пришлось сделать force install Net::RawIP)
* Net::Pcap (для перехвата TCP-пакетов).
* NetPacket::Ethernet (для декодирования TCP/IP-пакетов).
Также может потребоваться предварительная установка пакета libpcap-devel.
Также с похожей функциональностью есть утилита cutter. Но она работает только в случае, если запускается на промежуточном между клиентом и сервером роутере.
Попытка оборвать соединения с сервисом, запущенном на порту 62616 сервера 10.10.10.10, с клиента 10.20.20.20:
[root@avz /tmp/cutter-1.04]# ./cutter 10.10.10.10 62616 10.20.20.20 Error: The matching connection terminates on THIS computer. Note: cutter can only cut connections running over the router or firewall on which it is run. It cannot cut connections that terminate locally. So: you should run cutter on the firewall/router, not on the client or server machine.
Что, в принципе, было ожидаемо. На роутере я ее пока запускать не пробовал.
Ещё есть третий вариант принудительного завершения TCP-соединения - использование утилиты tcpkill. Её преимущество в том, что ее намного проще установить (по сравнению с killcx) – достаточно просто установить пакет dsniff, в состав которого она входит. Какое именно соединение обрывать ей нужно указать с помощью BPF-выражения, формат которых знаком каждому, кто пользовался tcpdump-ом.
Пример использования:
[root@srv ~]# lsof -i -nP | grep ssh sshd 1805 root 3u IPv4 14341 0t0 TCP *:22 (LISTEN) sshd 1805 root 4u IPv6 14345 0t0 TCP *:22 (LISTEN) sshd 9832 root 3r IPv4 15780961 0t0 TCP 192.168.1.1:5224->192.168.1.51:34749 (ESTABLISHED) sshd 9835 avz 3u IPv4 15780961 0t0 TCP 192.168.1.1:5224->192.168.1.51:34749 (ESTABLISHED) [root@srv ~]# tcpkill -i eth1.2 host 192.168.1.51 and port 34749 tcpkill: listening on eth1.2 [host 192.168.1.51 and port 34749] Write failed: Broken pipe
Здесь я подключился по SSH с хоста 192.168.1.51 на сервер 192.168.1.1 и запустил сначала команду lsof чтобы узнать порт на стороне клиента (34749). Затем передал его в качестве аргумента tcpkill-у и соединение сразу было разорвано. Следует учитывать, что tcpkill сработает только для соединения, по которому передаются хоть какие-то данные. В противном случае он просто будет висеть в ожидании следующей порции трафика.
Еще один, пожалуй, самый изящный вариант с использованием отладчика gdb. Опять поключаемся по SSH с 192.168.1.51 на 192.168.1.1 и что-то там запускаем для красоты, например такой цикл:
[user@avz ~]$ ssh 192.168.1.1 Last login: Sat Aug 26 23:13:00 2017 from 192.168.1.51 [user@srv ~]$ for i in {1..100} ; do date ; sleep 1 ; done Sat Aug 26 23:25:35 EEST 2017 Sat Aug 26 23:25:36 EEST 2017 Sat Aug 26 23:25:37 EEST 2017 Sat Aug 26 23:25:38 EEST 2017 Sat Aug 26 23:25:39 EEST 2017
В соседней консоли смотрим информацию о только что установленном соединении:
[root@avz]# lsof -i -nP | grep ssh | grep 192.168.1.1:22 ssh 19543 avz 3u IPv4 830283369 0t0 TCP 192.168.1.51:37527->192.168.1.1:22 (ESTABLISHED)
Получаем PID (19543) и номер файлового дескриптора (3, см. 4-ое поле). Далее запускаем gdb и командой close говорим ему закрыть файловый дескриптор с номером 3:
[root@avz]# gdb -p 19543 (gdb) call close(3) $1 = 0 (gdb) quit A debugging session is active. Inferior 1 [process 19543] will be detached. Quit anyway? (y or n) y Detaching from program: /usr/bin/ssh, process 19543
Возвращаемся в первую консоль и видим там, что соединение было разорвано:
Sat Aug 26 23:25:40 EEST 2017 Sat Aug 26 23:25:41 EEST 2017 Sat Aug 26 23:25:42 EEST 2017 Sat Aug 26 23:25:43 EEST 2017 Sat Aug 26 23:25:44 EEST 2017 Sat Aug 26 23:25:45 EEST 2017 Sat Aug 26 23:25:46 EEST 2017 Sat Aug 26 23:25:47 EEST 2017 Sat Aug 26 23:25:48 EEST 2017 Write failed: Bad file descriptor [user@avz ~]$
Также есть другой вариант избавиться от TIME-WAIT-ов, из разряда "из пушки по воробьям" – просто рестартануть сеть (service network restart или что там в Вашем дистрибутиве аналогичное).
А вот чтобы не доводить вообще до появления TIME-WAIT-ов или снизить их количество, может помочь установка следующих опций ядра:
net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_tw_reuse = 1
При чем для net.ipv4.tcp_tw_recycle следует помнить, что ее включение может поломать работу клиентов за NAT-ом и в общем случае включать ее не рекомендуется.
А вот для закрытия соединений в состоянии CLOSE_WAIT может пригодится вот эта штука: https://github.com/rghose/kill-close-wait-connections или свежие версии штатной утилиты ss из пакета iproute:
ss --tcp state CLOSE-WAIT '( dport = 22 or dst 1.1.1.1 )' --kill
В стремлении избавляться от TIME-WAIT-ов важно не переусердствовать: http://developerweb.net/viewtopic.php?id=2941
Там намекают, что их не зря придумали. Выждать таймаут от 1 до 4 минут (длительность пребывания соединения в состоянии TIME-WAIT) нужно для того, чтобы где-то задержавшийся в пути блуждающий сегмент-дубликат не смог создать новую инкарнацию уже закрытого ранее соединения.