web-dev-qa-db-ja.com

同じシェルによって複数の接続が開始されている場合、バックグラウンドで実行されているSSH接続が終了しない

どうやら、同じシェルが同じサーバーへの複数のssh接続を起動した場合、与えられたコマンドを実行した後、戻りませんが、ハング(Stopped (tty input))します。説明する:

_#!/bin/bash
ssh localhost sleep 2
echo "$$ DONE!"
_

上記のスクリプトをバックグラウンドで複数回実行すると、スクリプトが終了することはありません。

_$ for i in {1..3}; do foo.sh & done
[1] 28695
[2] 28696
[3] 28697
$                      ## Hit enter

[1]   Stopped                 foo.sh

[2]-  Stopped                 foo.sh

[3]+  Stopped                 foo.sh
$                      ## Hit enter again        
$ jobs -l
[1]  28695 Stopped (tty input)     foo.sh
[2]- 28696 Stopped (tty input)     foo.sh
[3]+ 28697 Stopped (tty input)     foo.sh
_

詳細

  • これは、コマンドを実行するためにPerlスクリプトでsshを実行していたためです。 Perlのsystem()呼び出しを使用してsshを起動した場合も同じ動作が発生します。
  • system()の代わりにPerlモジュールを使用する場合にも同じ問題が発生します。 _Net::SSH::Perl_、_Net:SSH2_、_Net::OpenSSH_を試しました。
  • 異なるシェルから複数のsshコマンドを実行する(複数のターミナルを開く)と、期待どおりに機能します。
  • Ssh接続のデバッグ情報に明らかに役立つものはありません。

    _OpenSSH_7.5p1, OpenSSL 1.1.0f  25 May 2017
    debug1: Reading configuration data /home/terdon/.ssh/config
    debug1: Reading configuration data /etc/ssh/ssh_config
    debug2: resolving "localhost" port 22
    debug2: ssh_connect_direct: needpriv 0
    debug1: Connecting to localhost [::1] port 22.
    debug1: Connection established.
    debug1: identity file /home/terdon/.ssh/id_rsa type 1
    debug1: key_load_public: No such file or directory
    debug1: identity file /home/terdon/.ssh/id_rsa-cert type -1
    debug1: key_load_public: No such file or directory
    debug1: identity file /home/terdon/.ssh/id_dsa type -1
    debug1: key_load_public: No such file or directory
    debug1: identity file /home/terdon/.ssh/id_dsa-cert type -1
    debug1: key_load_public: No such file or directory
    debug1: identity file /home/terdon/.ssh/id_ecdsa type -1
    debug1: key_load_public: No such file or directory
    debug1: identity file /home/terdon/.ssh/id_ecdsa-cert type -1
    debug1: key_load_public: No such file or directory
    debug1: identity file /home/terdon/.ssh/id_ed25519 type -1
    debug1: key_load_public: No such file or directory
    debug1: identity file /home/terdon/.ssh/id_ed25519-cert type -1
    debug1: Enabling compatibility mode for protocol 2.0
    debug1: Local version string SSH-2.0-OpenSSH_7.5
    debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5
    debug1: match: OpenSSH_7.5 pat OpenSSH* compat 0x04000000
    debug2: fd 3 setting O_NONBLOCK
    debug1: Authenticating to localhost:22 as 'terdon'
    debug3: hostkeys_foreach: reading file "/home/terdon/.ssh/known_hosts"
    debug3: record_hostkey: found key type ECDSA in file /home/terdon/.ssh/known_hosts:47
    debug3: load_hostkeys: loaded 1 keys from localhost
    debug3: order_hostkeyalgs: prefer hostkeyalgs: [email protected],[email protected],[email protected],ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521
    debug3: send packet: type 20
    debug1: SSH2_MSG_KEXINIT sent
    debug3: receive packet: type 20
    debug1: SSH2_MSG_KEXINIT received
    debug2: local client KEXINIT proposal
    debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,ext-info-c
    debug2: Host key algorithms: [email protected],[email protected],[email protected],ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,[email protected],[email protected],ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
    debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected],aes128-cbc,aes192-cbc,aes256-cbc
    debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected],aes128-cbc,aes192-cbc,aes256-cbc
    debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
    debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
    debug2: compression ctos: none,[email protected],zlib
    debug2: compression stoc: none,[email protected],zlib
    debug2: languages ctos: 
    debug2: languages stoc: 
    debug2: first_kex_follows 0 
    debug2: reserved 0 
    debug2: peer server KEXINIT proposal
    debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1
    debug2: Host key algorithms: ssh-rsa,rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256,ssh-ed25519
    debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected]
    debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected]
    debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
    debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
    debug2: compression ctos: none,[email protected]
    debug2: compression stoc: none,[email protected]
    debug2: languages ctos: 
    debug2: languages stoc: 
    debug2: first_kex_follows 0 
    debug2: reserved 0 
    debug1: kex: algorithm: curve25519-sha256
    debug1: kex: Host key algorithm: ecdsa-sha2-nistp256
    debug1: kex: server->client cipher: [email protected] MAC: <implicit> compression: none
    debug1: kex: client->server cipher: [email protected] MAC: <implicit> compression: none
    debug3: send packet: type 30
    debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
    debug3: receive packet: type 31
    debug1: Server Host key: ecdsa-sha2-nistp256 SHA256:uxhkh+gGPiCJQPaP024WXHth382h3BTs7QdGMokB9VM
    debug3: hostkeys_foreach: reading file "/home/terdon/.ssh/known_hosts"
    debug3: record_hostkey: found key type ECDSA in file /home/terdon/.ssh/known_hosts:47
    debug3: load_hostkeys: loaded 1 keys from localhost
    debug1: Host 'localhost' is known and matches the ECDSA Host key.
    debug1: Found key in /home/terdon/.ssh/known_hosts:47
    debug3: send packet: type 21
    debug2: set_newkeys: mode 1
    debug1: rekey after 134217728 blocks
    debug1: SSH2_MSG_NEWKEYS sent
    debug1: expecting SSH2_MSG_NEWKEYS
    debug3: receive packet: type 21
    debug1: SSH2_MSG_NEWKEYS received
    debug2: set_newkeys: mode 0
    debug1: rekey after 134217728 blocks
    debug2: key: /home/terdon/.ssh/id_rsa (0x555a5e4b5060)
    debug2: key: /home/terdon/.ssh/id_dsa ((nil))
    debug2: key: /home/terdon/.ssh/id_ecdsa ((nil))
    debug2: key: /home/terdon/.ssh/id_ed25519 ((nil))
    debug3: send packet: type 5
    debug3: receive packet: type 7
    debug1: SSH2_MSG_EXT_INFO received
    debug1: kex_input_ext_info: server-sig-algs=<ssh-ed25519,ssh-rsa,rsa-sha2-256,rsa-sha2-512,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521>
    debug3: receive packet: type 6
    debug2: service_accept: ssh-userauth
    debug1: SSH2_MSG_SERVICE_ACCEPT received
    debug3: send packet: type 50
    debug3: receive packet: type 51
    debug1: Authentications that can continue: publickey,password
    debug3: start over, passed a different list publickey,password
    debug3: preferred publickey,keyboard-interactive,password
    debug3: authmethod_lookup publickey
    debug3: remaining preferred: keyboard-interactive,password
    debug3: authmethod_is_enabled publickey
    debug1: Next authentication method: publickey
    debug1: Offering RSA public key: /home/terdon/.ssh/id_rsa
    debug3: send_pubkey_test
    debug3: send packet: type 50
    debug2: we sent a publickey packet, wait for reply
    debug3: receive packet: type 60
    debug1: Server accepts key: pkalg rsa-sha2-512 blen 279
    debug2: input_userauth_pk_ok: fp SHA256:OGvtyUIFJw426w/FK/RvIhsykeP8kIEAtAeZwYBIzok
    debug3: sign_and_send_pubkey: RSA SHA256:OGvtyUIFJw426w/FK/RvIhsykeP8kIEAtAeZwYBIzok
    debug3: send packet: type 50
    debug3: receive packet: type 52
    debug1: Authentication succeeded (publickey).
    Authenticated to localhost ([::1]:22).
    debug2: fd 6 setting O_NONBLOCK
    debug1: channel 0: new [client-session]
    debug3: ssh_session2_open: channel_new: 0
    debug2: channel 0: send open
    debug3: send packet: type 90
    debug1: Requesting [email protected]
    debug3: send packet: type 80
    debug1: Entering interactive session.
    debug1: pledge: network
    debug3: receive packet: type 80
    debug1: client_input_global_request: rtype [email protected] want_reply 0
    debug3: receive packet: type 91
    debug2: callback start
    debug2: fd 3 setting TCP_NODELAY
    debug3: ssh_packet_set_tos: set IPV6_TCLASS 0x08
    debug2: client_session2_setup: id 0
    debug1: Sending command: sleep 2
    debug2: channel 0: request exec confirm 1
    debug3: send packet: type 98
    debug2: callback done
    debug2: channel 0: open confirm rwindow 0 rmax 32768
    debug2: channel 0: rcvd adjust 2097152
    debug3: receive packet: type 99
    debug2: channel_input_status_confirm: type 99 id 0
    debug2: exec request accepted on channel 0
    _
  • これは私の_~/.ssh/config_の設定には依存しません。ファイルの名前を変更しても何も変わりません。

  • これは複数のマシンで発生します。更新されたUbuntuおよびArchディストリビューションを実行している4つまたは5つの異なるマシンを試しました。
  • コマンド(ダミーの例ではsleepですが、実際にはかなり複雑なもの)は正常に終了し、本来の動作を実行します。これは、実行しているコマンドに依存しません。sshの問題です。
  • これはそれらの中で最悪です:一貫性がありません。時々、インスタンスの1つが終了し、親スクリプトに制御を返します。しかし、必ずしもそうとは限らず、私が識別できたパターンはありません。
  • _~/.bashrc_の名前を変更しても違いはありません。また、Ubuntu(デフォルトのログインシェルdash)とArch(デフォルトのログインシェルbashshと呼ばれる)を実行しているマシンでこれを実行しました。
  • 興味深いことに、この問題は、いずれかのキーを押した場合にのみ発生します(たとえば Enter、ただし、いずれも機能しているようです)ループを起動した後、最初のスクリプトが終了する前。ターミナルをそのままにしておくと、期待通りに終了します。

どうしたの?これはsshのバグですか?設定する必要のあるオプションはありますか?同じシェルからSSH経由でコマンドを実行するスクリプトの複数のインスタンスを起動するにはどうすればよいですか?

6
terdon

フォアグラウンドプロセスと端末アクセス制御

何が起こっているのかを理解するには、端末の共有について少し知っておく必要があります。 2つのプログラムが同じ端末から同時に読み取ろうとするとどうなりますか?各入力バイトはランダムにプログラムの1つに送られます。 (カーネルのようにランダムではなく、RNGを使用して決定します。実際には予測できない場合のようにランダムです。)2つのプログラムがパイプ、または1つの場所から移動されるバイトのストリームである他のファイルタイプから読み取る場合も同じことが起こります。任意のバイトを複数回読み取ることができるバイト配列(通常のファイル、ブロックデバイス)ではなく、別のバイト(ソケット、文字デバイスなど)に。たとえば、ターミナルでシェルを実行し、ターミナルの名前を見つけて、catを実行します。

$ tty
/dev/pts/18
$ cat

次に、別の端末からcat /dev/pts/18を実行します。次に、ターミナルに入力します。ラインがcatプロセスの1つに行く場合と、他のプロセスに行く場合があります。端末がクックドモードの場合、行は全体としてディスパッチされます。端末をrawモードにすると、各バイトは個別にディスパッチされます。

それは厄介です。確かに、1つのプログラムが端末を取得し、他のプログラムは取得しないことを決定するメカニズムが必要です。さて、あります!通常のケースでトリガーされますが、上記で設定したシナリオではトリガーされません。 cat /dev/pts/18/dev/pts/18から開始されていないため、このシナリオは異常です。この端末内で起動されなかったプログラムから端末にアクセスするのは珍しいことです。通常の場合、ターミナルでシェルを実行し、そのシェルからプログラムを実行します。その場合のルールは、フォアグラウンドのプログラムはターミナルを取得し、バックグラウンドのプログラムは取得しないことです。これは 端末アクセス制御 として知られています。それが機能する方法は次のとおりです。

  • 各プロセスには 制御端末 があります(または、通常は端末である開いているファイル記述子がないため、ありません)。
  • プロセスがその制御端末にアクセスしようとしたときに、プロセスがフォアグラウンドにない場合、カーネルはそれをブロックします。 (条件が適用されます。他の端末へのアクセスは規制されていません。)
  • シェルは、誰がフォアグラウンドプロセスであるかを決定します。 (実際には、フォアグラウンドプロセスグループです。) tcsetpgrp を呼び出して、誰がフォアグラウンドにいる必要があるかをカーネルに通知します。

これは通常のケースで機能します。シェルでプログラムを実行すると、そのプログラムがフォアグラウンドプロセスになります。 (&を使用して)バックグラウンドでプログラムを実行すると、プログラムがフォアグラウンドになりません。シェルがプロンプトを表示しているとき、シェルはそれ自体をフォアグラウンドに置きます。中断されたジョブをfgで再開すると、ジョブはフォアグラウンドになります。 bgでは、そうではありません。

バックグラウンドプロセスが端末から読み取ろうとすると、カーネルはそれにSIGTTIN信号を送信します。シグナルのデフォルトのアクションは、プロセスを一時停止することです(SIGSTOPなど)。プロセスの親は、waitpidフラグを指定して WSTOPPED を呼び出すことにより、これを知ることができます。子プロセスがそれを一時停止するシグナルを受信すると、親のwaitpid呼び出しが返され、シグナルが何であったかを親に知らせます。これは、シェルが「停止(tty入力)」を出力する方法です。これは、このジョブがSIGTTINのために中断されていることを示しています。

プロセスは中断されているため、再開または終了されるまで何も起こりません(プロセスがシグナルハンドラーを設定している場合、プロセスが中断されているため、プロセスは実行されないため、プロセスがキャッチしないシグナルを使用します)。 SIGCONTを送信することでプロセスを再開できますが、プロセスが端末から読み取っている場合は何も達成されず、すぐに別のSIGTTINを受け取ります。 fgを使用してプロセスを再開すると、プロセスはフォアグラウンドに移動するため、読み取りは成功します。

これで、バックグラウンドでcatを実行するとどうなるかがわかりました。

$ cat &
$ 
[1] + Stopped (tty input)        cat
$ 

SSHの場合

次に、SSHで同じことをしましょう。

$ ssh localhost sleep 999999 &
$ 
$ 
$ 
[1] + Stopped (tty input)        ssh localhost sleep 999999
$ 

押す Enter シェル(フォアグラウンドにある)に行くこともあれば、SSHプロセス(その時点でSIGTTINによって停止される)に行くこともあります。どうして? sshが端末から読み取っていた場合、すぐにSIGTTINを受信するはずですが、そうでない場合は、なぜSIGTTINを受信するのでしょうか。

何が起こっているのかというと、SSHプロセスが select システムコールを呼び出して、関心のあるファイルのいずれかで入力が利用可能になる時期を知る(または出力ファイルがより多くのデータを受信する準備ができているかどうか) )。入力ソースには、少なくとも端末とネットワークソケットが含まれます。 readとは異なり、selectはバックグラウンドプロセスで禁止されておらず、sshselectを呼び出してもSIGTTINを受け取りません。 selectの目的は、何も中断することなく、データが利用可能かどうかを確認することです。理想的にはselectはシステム状態をまったく変更しませんが、実際にはこれは完全には真実ではありません。 selectがSSHプロセスに、入力が端末ファイル記述子で利用可能であることを通知する場合、プロセスが後でreadを呼び出すと、カーネルは入力の送信をコミットする必要があります。 (そうでなく、プロセスがreadを呼び出した場合、この時点で使用可能な入力がない可能性があるため、selectからの戻り値は嘘でした。)したがって、カーネルは、入力をSSHプロセスにルーティングすることを決定し、selectシステムコールが戻るまでに決定します。次に、SSHはreadを呼び出し、その時点でカーネルは、バックグラウンドプロセスが端末から読み取ろうとしたことを確認し、SIGTTINでそれを中断します。

同じサーバーに複数の接続を起動する必要がないことに注意してください。 1つで十分です。複数の接続は、問題が発生する可能性を高めるだけです。

解決策:ターミナルから読み取らない

ターミナルから読み取るためにSSHセッションが必要な場合は、フォアグラウンドで実行します。

端末からの読み取りにSSHセッションが必要ない場合は、その入力が端末からのものでないことを確認してください。これを行うには2つの方法があります。

  • 入力をリダイレクトできます。

    ssh … </dev/null
    
  • -nまたは-fで端末接続を転送しないようにSSHに指示できます。 (-n</dev/nullと同等です。-fを使用すると、SSH自体が端末から読み取ることができます(パスワードの読み取りなど)が、コマンド自体では端末が開かれません。)

    ssh -n …
    

端末とSSHの間の切断はクライアントで発生する必要があることに注意してください。サーバーで実行されているsleepプロセスが端末から読み取ることはありませんが、SSHはそれを知る方法がありません。クライアントが標準入力で入力を受け取った場合、それをサーバーに転送する必要があります。これにより、アプリケーションがデータを読み取ることを決定した場合に備えて、データをバッファーで使用できるようになります(アプリケーションがselectを呼び出す場合は、データが利用可能であることが通知されます)。

マニュアルページでヘルプを見つけることができます:

 -n      Redirects stdin from /dev/null (actually, prevents reading from stdin).  This must be used when ssh is run in the
         background.  A common trick is to use this to run X11 programs on a remote machine.  For example, ssh -n
         shadows.cs.hut.fi emacs & will start an emacs on shadows.cs.hut.fi, and the X11 connection will be automatically
         forwarded over an encrypted channel.  The ssh program will be put in the background.  (This does not work if ssh
         needs to ask for a password or passphrase; see also the -f option.)

それでも問題が解決しない場合は、気まぐれで-T(疑似tty割り当てを無効にする)を試してみます。

4
Ulrich Schwarz

どうやら、同じシェルが同じサーバーへの複数のssh接続を起動した場合、与えられたコマンドを実行した後、戻りませんが、永遠にハングします(Stopped(tty input))。

これは、TTYへの同時アクセスの一般的な動作です。プロセス全体はすでにバックグラウンドで処理されており、出力を書き込もうとすると、TTYへのアクセスが許可されず、シグナル(SIGTTOU)を受信します。これは、bashプロセスによってキャッチされないため、デフォルトのアクションが実行されます(Stop)。

その他の回答 で説明されている-nオプション、またはIO)を一部のファイルにリダイレクトすると、役に立ちます。他に説明するものがあるかどうかはわかりません。 、ただし、その場合は明確にしてください。

1
Jakuje