phpseclib: setTimeout doesn't work with the sleep command

Hello, thank you for the wonderful library and all the work you do with phpseclib.

I am curious about the following case.

When I’m setting timeout for some command like ‘ping’ it works as expected.

When I’m setting timeout and call ‘sleep’, command waits the exact number of seconds passed to sleep, instead of respecting the timeout set to SSH2.

The code:

<?php
use phpseclib\Crypt\RSA;
use phpseclib\Net\SSH2;

$key = new RSA();
$key->loadKey(file_get_contents('private-key.txt'));

$ssh = new SSH2('127.0.0.1');
if (!$ssh->login('username', $key)) {
    exit('Login Failed');
}

$ssh->setTimeout(3);

$start = microtime(true);
$ssh->exec('ping google.com');
$elapsed = microtime(true) - $start;

if ($ssh->isTimeout()) {
   echo sprintf('Command timed out in %01.2f seconds', $elapsed);
} else {
   echo sprintf('Command completed in %01.2f seconds', $elapsed);
}

The code above returns as expected:

Command timed out in 3.12 seconds

However:

<?php
use phpseclib\Crypt\RSA;
use phpseclib\Net\SSH2;

$key = new RSA();
$key->loadKey(file_get_contents('private-key.txt'));

$ssh = new SSH2('127.0.0.1');
if (!$ssh->login('username', $key)) {
    exit('Login Failed');
}

$ssh->setTimeout(3);

$start = microtime(true);
$ssh->exec('sleep 10');
$elapsed = microtime(true) - $start;

if ($ssh->isTimeout()) {
   echo sprintf('Command timed out in %01.2f seconds', $elapsed);
} else {
   echo sprintf('Command completed in %01.2f seconds', $elapsed);
}

I am expecting the command to time out after 3 seconds. But the second code runs for 10 seconds instead of 3 and outputs

Command timed out in 10.11 seconds

What am I missing here?

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Comments: 15 (11 by maintainers)

Commits related to this issue

Most upvoted comments

I’m able to reproduce the issue.

From the logs:

-> NET_SSH2_MSG_CHANNEL_REQUEST (since last: 0.0004, network: 0.0001s)
00000000  00:00:00:00:00:00:00:04:65:78:65:63:01:00:00:00  ........exec....
00000010  08:73:6c:65:65:70:20:31:30                       .sleep 10

<- NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST (since last: 0.1306, network: 0.0001s)
00000000  00:00:00:01:00:20:00:00                          ..... ..

<- NET_SSH2_MSG_CHANNEL_SUCCESS (since last: 0.0006, network: 0.0001s)
00000000  00:00:00:01                                      ....

-> NET_SSH2_MSG_CHANNEL_EOF (since last: 2.7624, network: 0.0003s)
00000000  00:00:00:00                                      ....

-> NET_SSH2_MSG_CHANNEL_CLOSE (since last: 0.0007, network: 0.0001s)
00000000  00:00:00:00                                      ....

<- NET_SSH2_MSG_CHANNEL_REQUEST (since last: 7.2396, network: 0.0002s)
00000000  00:00:00:01:00:00:00:0b:65:78:69:74:2d:73:74:61  ........exit-sta
00000010  74:75:73:00:00:00:00:00                          tus.....

<- NET_SSH2_MSG_CHANNEL_CLOSE (since last: 0.0007, network: 0.0001s)
00000000  00:00:00:01                                      ....

Per that phpseclib does try to close the channel after ~3s. The problem is that the server isn’t acknowledging the close request until ~7s after it was sent (ie. after which ~10s has cumulatively passed).

Unfortunately, I’m not really sure what can be done about this. phpseclib could be updated so that it doesn’t wait for the close request to be acknowledged by the server but then what? If you just want to issue that one command then it’s not a problem but if you do want to issue other commands then it becomes more complicated.

In phpseclib 4.0 I want to rewrite SSH2 so that it supports multiple channels. At that point phpseclib wouldn’t need to wait for the close confirmation - every exec would be on it’s own unique channel. But that’s not an option for phpseclib 1.0 - 3.0. For these we’d just need to wait for that close confirmation before the the next command could be ran.

Meh. I’m sure that’s not the answer you were hoping to hear 😦

Just in case, the following way of setting a timeout will also work perfectly with sleep:

$ssh->exec('timeout 5 sleep 10');

As you can see, it relies on having the timeout utility from GNU coreutils available on the remote system, so YMMV. AFAIK it is available on most modern linux distributions.

No worries, thank you for at least looking into the issue. I guess I’ll wait for the future versions then - good luck with the releases!