web-dev-qa-db-ja.com

サーバー上のプロセスを強制終了する代わりに、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
Jacek

tl; dr

サーバーで次の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_(プローブの数)

これは、キープアライブプロセスがソケットアクティビティを2時間(7200秒)待ってから最初のキープアライブプローブを送信し、75秒ごとに再送信することを意味します。 ACK応答が9回連続して受信されない場合、接続は切断済みとしてマークされます。

ソース )。

これは、「ポートが再び空になるまで約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 …
_

これの代わりに、次のようなスクリプトを1回だけ実行できます(たとえば、crontabの_@reboot_経由)。最初に(サーバー上のsshdの)保存されたPIDを強制終了しようとし、次にトンネルを確立してそのsshdPIDを保存します。トンネルを確立できないか、最終的に終了した場合、スクリプトはしばらくスリープしてループします。

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

ノート:

  • これは迅速で汚いスクリプトであり、概念実証です。移植性は私の優先事項ではありませんでした。
  • わかりやすくするために、最初に使用したオプションの一部を省略しました。
  • スクリプトを2回実行すると、2つのインスタンスが互いのトンネルを何度も強制終了します。
3

転送は、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をキャンセルすることができます。

これで問題が解決するかどうかは不明です。ポートは通常、TIME_WAITなどをクリアするのに1〜2時間もかかりません...

1
thrig