SSH отключает переадресацию портов удаленно, вместо того, чтобы убивать процесс на сервере

Я делаю автоматический запрос удаленного SSH-порта с моего устройства каждые несколько минут:

ssh -y -f -N -R port:localhost:22 user@server \
-o ExitOnForwardFailure=yes -o ServerAliveInterval=60 \
-o StrictHostKeyChecking=no

Если переадресация портов прошла успешно, и я перезагружаю свое устройство и сбрасываю подключение к Интернету, я не могу подключиться к нему или выполнить переадресацию нового порта (это нормально и нормально, так как мой IP-адрес изменен и порт, кажется, занят). Мне нужно подождать около 1-2 часов, чтобы порт снова был свободен, или мне нужно убить процесс на моем сервере.

Я хотел бы отправить сценарий с моего устройства перед выключением, который бы освободил порт, чтобы после перезагрузки новый запрос на переадресацию порта был успешным.

Подводя итог: Как я могу удаленно отменить переадресацию портов с устройства, которое его запрашивало, вместо того, чтобы убивать процесс на сервере?

2 ответа

Решение

ТЛ; др

Используйте эти sshd параметры на сервере (в sshd_config файл):

ClientAliveCountMax 3
ClientAliveInterval 15

История на стороне клиента

Как я могу удаленно отменить переадресацию портов с устройства, которое его запросило, вместо того, чтобы убивать процесс на сервере?

Простой ответ: просто прекратить ssh на клиенте. Даже если вы принудительно уничтожите его, сервер должен быть уведомлен о том, что соединение разорвано (потому что ядро ​​выполняет эту работу).

... если уведомление когда-либо попадет на сервер, то есть. Я думаю, что это проблема. Сеть на вашем устройстве как-то падает до того ssh завершен, и нет никакого способа уведомить сервер, что это конкретное соединение SSH больше не.

Может быть, вы можете изменить дизайн на стороне клиента, чтобы обеспечить ssh завершается до того, как что-либо будет сделано с сетевым подключением. Я не знаю деталей вашей клиентской системы, поэтому я не скажу вам точно, что вы можете сделать и как. Свободные примеры: systemd единицы и их зависимости, если применимо; обертка над reboot и / или shutdown,


Серверная история

Я предполагаю, что сервер SSH является стандартным sshd, Конфиг по умолчанию (sshd_config) указывает

TCPKeepAlive yes

От man 5 sshd_config:

TCPKeepAlive

Указывает, должна ли система отправлять сообщения поддержки активности TCP другой стороне. Если они отправлены, смерть соединения или сбой одной из машин будут замечены должным образом. Однако это означает, что соединения прекратят свое существование, если маршрут временно не работает, и некоторые люди считают это раздражающим. С другой стороны, если сообщения поддержки активности TCP не отправляются, сеансы могут бесконечно зависать на сервере, оставляя "призрачных" пользователей и потребляя ресурсы сервера.

По умолчанию yes (для отправки сообщений поддержки активности TCP), и сервер заметит, если сеть выйдет из строя или произойдет сбой хоста клиента. Это позволяет избежать бесконечно зависающих сессий.

Этот механизм не специфичен для SSH. Объяснение:

В Linux параметры поддержки активности TCP:

tcp_keepalive_intvl
tcp_keepalive_probes
tcp_keepalive_time

Их значения по умолчанию:

tcp_keepalive_time = 7200 (в секундах)
tcp_keepalive_intvl = 75 (в секундах)
tcp_keepalive_probes = 9 (количество зондов)

Это означает, что процесс keepalive ожидает в течение двух часов (7200 секунд) активности сокета, прежде чем отправлять первый запрос keepalive, а затем отправлять его каждые 75 секунд. Если нет ACK Ответ получен девять раз подряд, соединение помечено как разорванное.

( Источник).

Это объясняет, почему вам "нужно подождать около 1-2 часов, чтобы порт снова был свободен".

Примеры того, как вы можете изменить эти значения (если у вас есть разрешения):

  • временно

    echo 300 > /proc/sys/net/ipv4/tcp_keepalive_time
    

    или же

    sysctl -w net.ipv4.tcp_keepalive_time=300
    
  • навсегда, редактируя /etc/sysctl.conf файл, добавьте:

    net.ipv4.tcp_keepalive_time=300
    

    затем вызвать sudo sysctl -p применить изменения.

Но это общесистемные настройки. В целом, любое изменение повлияет более чем на sshd, Вот почему лучше использовать SSH-специфичное решение. Опять из man 5 sshd_config:

ClientAliveCountMax

Устанавливает количество живых сообщений клиента (см. Ниже), которые могут быть отправлены без sshd(8) получать любые сообщения обратно от клиента. Если этот порог достигнут, пока клиент отправляет живые сообщения, sshd отключит клиента, завершив сеанс. Важно отметить, что использование живых сообщений клиента сильно отличается от TCPKeepAlive, […] Механизм работы клиента полезен, когда клиент или сервер зависят от того, когда соединение стало неактивным.

Значением по умолчанию является 3, Если ClientAliveInterval (см. ниже) установлен на 15, а также ClientAliveCountMax по умолчанию, не отвечающие клиенты SSH будут отключены примерно через 45 секунд. Эта опция применяется только к версии протокола 2.

ClientAliveInterval

Устанавливает интервал ожидания в секундах, после которого, если данные не были получены от клиента, sshd(8) отправит сообщение через зашифрованный канал для запроса ответа от клиента. По умолчанию 0, указывая, что эти сообщения не будут отправлены клиенту. Эта опция применяется только к версии протокола 2.

Если только вы можете перенастроить sshd на сервере это, на мой взгляд, самый элегантный способ. Пусть sshd_config содержат строки вроде:

ClientAliveCountMax 3
ClientAliveInterval 15

Заметки:

  • -o ServerAliveInterval=60 вы используете похожий вариант для ssh; это позволяет клиенту обнаруживать разрыв соединения. Это не влияет на сервер, хотя.
  • вы можете рассмотреть autossh на клиенте.

Вернуться к клиенту

Предположим, вы не можете перенастроить ни сервер, ни клиент. Я знаю, что ты сказал

вместо того, чтобы убивать процесс на сервере

но в этом случае убийство может быть лучшим вариантом. Как sshd вилки обслуживают определенные соединения, достаточно просто убить нужный процесс, не влияя на остальные. Чтобы инициировать убийство с клиента, вы должны изменить способ вызова ssh, Вы сказали:

каждые несколько минут:

ssh -f …

Вместо этого вы можете запустить скрипт, похожий на следующий, только один раз (например, через @reboot в хронтаб). Он пытается убить сохраненный PID (из sshd на сервере) сначала установите туннель и сохраните его sshd PID. Если туннель не может быть установлен или в конце концов завершен, сценарий некоторое время спит и зацикливается.

#!/bin/sh
port=12345
address=user@server
while :; do
  ssh $address '[ -f ~/.tunnel.pid ] && kill `cat ~/.tunnel.pid` && rm ~/.tunnel.pid'
  ssh -o ExitOnForwardFailure=yes -R ${port}:localhost:22 $address 'echo $PPID > ~/.tunnel.pid; exec sleep infinity'
  sleep 60
done

Заметки:

  • Это быстрый и грязный сценарий, подтверждение концепции; мобильность не была моим приоритетом.
  • Для ясности я опустил некоторые опции, которые вы изначально использовали.
  • Если вы запустите скрипт дважды, два экземпляра будут убивать туннели друг друга снова и снова.

Переадресация может быть отменена с помощью ssh экранирующий символ (~ в начале строки по умолчанию) или ~? за помощью

Supported escape sequences:
 ~.   - terminate connection (and any multiplexed sessions)
 ~B   - send a BREAK to the remote system
 ~C   - open a command line
 ~R   - request rekey
 ~V/v - decrease/increase verbosity (LogLevel)
 ~^Z  - suspend ssh
 ~#   - list forwarded connections
 ~&   - background ssh (when waiting for connections to terminate)
 ~?   - this message
 ~~   - send the escape character by typing it twice
(Note that escapes are only recognized immediately after newline.)

а также ~# будет перечислять соединения, но не так, чтобы их можно было убить с помощью команды

The following connections are open:
  #3 client-session (t4 r0 i0/0 o0/0 fd 8/9 cc -1)

который можно найти через ~C а потом help

ssh> help
Commands:
      -L[bind_address:]port:host:hostport    Request local forward
      -R[bind_address:]port:host:hostport    Request remote forward
      -D[bind_address:]port                  Request dynamic forward
      -KL[bind_address:]port                 Cancel local forward
      -KR[bind_address:]port                 Cancel remote forward
      -KD[bind_address:]port                 Cancel dynamic forward

так что в теории ваш может быть отменен через ~C-KL22 открыть командную строку и отменить LocalForward по номеру порта.

Решит ли это вашу проблему - неясно; порты обычно занимают намного меньше чем 1-2 часа, чтобы очистить TIME_WAIT или такой...

Другие вопросы по тегам