Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSH connections in non-blocking mode fail with invalid parameter #209

Open
Sebbo94BY opened this issue Aug 21, 2023 · 0 comments
Open

SSH connections in non-blocking mode fail with invalid parameter #209

Sebbo94BY opened this issue Aug 21, 2023 · 0 comments
Labels
bug Confirmed as a valid bug and requires fix.

Comments

@Sebbo94BY
Copy link
Collaborator

Sebbo94BY commented Aug 21, 2023

Summary

When you connect using the TS3PHPFramework via the classic "raw" way to your TeamSpeak server in the non-blocking mode, so that you can use it as bot, everything works as expected. When you simply change the port and ssh=0 to ssh=1, the PHP script starts failing within less than 5 minutes. At least in my test scenario.

Environment

  • TS3PHPFramework 1.2.1 (dev: 83a2ffaace6073ca2589881a764625f2993996bc)
  • PHP 8.2.7
  • PECL ssh2 1.4

How to reproduce

  1. Clone this repository
  2. Checkout the dev branch
  3. Run composer install
  4. Add a new test.php script in the root folder with the following content:
<?php
// load framework files
require_once("vendor/autoload.php");

use PlanetTeamSpeak\TeamSpeak3Framework\Adapter\ServerQuery;
use PlanetTeamSpeak\TeamSpeak3Framework\Exception\TeamSpeak3Exception;
use PlanetTeamSpeak\TeamSpeak3Framework\Helper\Signal;
use PlanetTeamSpeak\TeamSpeak3Framework\Node\Server;
use PlanetTeamSpeak\TeamSpeak3Framework\TeamSpeak3;

class exceptionDebugging
{
    private Server $ts3_VirtualServer;

    function onWaitTimeout(int $idle_seconds, ServerQuery $serverquery)
    {
        // For debugging purposes
        // Print every 30 seconds the current idle time of the bot connection.
        if ($idle_seconds % 30 == 0) {
            echo "No reply from the server for $idle_seconds seconds.\n";
        }

        // If the timestamp on the last query is more than 300 seconds (5 minutes) in the past, send 'keepalive'
        // 'keepalive' command is just server query command 'clientupdate' which does nothing without properties. So nothing changes.
        if ($serverquery->getQueryLastTimestamp() < time() - 260) {
            echo 'Sending keep-alive.\n';
            $serverquery->request('clientupdate');
        }

        // Get data every minute
        if ($idle_seconds % 60 == 0) {
            // Resetting lists
            echo "Resetting lists...\n";
            $this->ts3_VirtualServer->clientListReset();
            $this->ts3_VirtualServer->serverGroupListReset();

            // Get servergroup client info
            echo "Getting client list...\n";
            $this->ts3_VirtualServer->clientList(['client_type' => 0]);

            echo "Getting servergroup list...\n";
            $servergrouplist = $this->ts3_VirtualServer->serverGroupList(['type' => 1]);

            echo "Getting servergroup client lists...\n";
            $servergroup_clientlist = [];
            foreach ($servergrouplist as $servergroup) {
                $servergroup_clientlist[$servergroup->sgid] = count($this->ts3_VirtualServer->serverGroupClientList($servergroup->sgid));
            }

            // Get virtualserver info
            echo "Getting info...\n";
            $this->ts3_VirtualServer->getInfo(true, true);

            echo "Getting connection info...\n";
            $this->ts3_VirtualServer->connectionInfo();
        }
    }

    function main()
    {
        echo "Connecting...\n";

        try {
            // Connect to the specified server, authenticate and spawn an object for the virtual server
            $this->ts3_VirtualServer = TeamSpeak3::factory("serverquery://serveradmin:password@localhost:10011/?server_port=9987&ssh=0&blocking=0#no_query_clients");
        } catch(TeamSpeak3Exception $e) {
            // Print the error message returned by the server
            die("Error " . $e->getCode() . ": " . $e->getMessage());
        }

        // Register for server events
        echo "Register for server events...\n";
        $this->ts3_VirtualServer->notifyRegister('server');

        // Register a callback for serverqueryWaitTimeout events
        echo "Subscribing to `serverqueryWaitTimeout`...\n";
        Signal::getInstance()->subscribe('serverqueryWaitTimeout', $this->onWaitTimeout(...));

        echo "Waiting for events...\n";
        while (true) {
            $this->ts3_VirtualServer->getAdapter()->wait();
        }
    }
}

$testing = new exceptionDebugging;
$testing->main();

When I run it with the TCP port 10011 (classic "raw") and the option ssh=0, everything works as expected - no issues:

$ time php test.php 
Connecting...
Register for server events...
Subscribing to `serverqueryWaitTimeout`...
Waiting for events...
No reply from the server for 30 seconds.
No reply from the server for 30 seconds.
No reply from the server for 60 seconds.
...
Resetting lists...
Getting client list...
Getting servergroup list...
Getting servergroup client lists...
Getting info...
Getting connection info...
No reply from the server for 30 seconds.
No reply from the server for 60 seconds.
Resetting lists...
Getting client list...
Getting servergroup list...
Getting servergroup client lists...
Getting info...
Getting connection info...
No reply from the server for 30 seconds.

real    1180m10.935s
user    0m0.311s
sys     0m0.671s

As you can see: The script was running without any issues for over 19.5 hours (1180 minutes).

However, when you change the TCP port from 10011 to 10022 (encrypted SSH) and the option from ssh=0 to ssh=1 it fails for my TeamSpeak server mostly within 5 minutes:

$ time php test.php
Connecting...
Register for server events...
Subscribing to `serverqueryWaitTimeout`...
Waiting for events...
No reply from the server for 30 seconds.
No reply from the server for 60 seconds.
Resetting lists...
Getting client list...
Getting servergroup list...
Getting servergroup client lists...
PHP Fatal error:  Uncaught PlanetTeamSpeak\TeamSpeak3Framework\Exception\ServerQueryException: invalid parameter. ident 'cldbid' does not exist in node '{"_identifier":"+GPk+IrgHZUQ7Vu6aY0mGgtseNw="}' in /home/Sebbo94BY/ts3phpframework/src/Adapter/ServerQuery/Reply.php:206
Stack trace:
#0 /home/Sebbo94BY/ts3phpframework/src/Node/Server.php(1286): PlanetTeamSpeak\TeamSpeak3Framework\Adapter\ServerQuery\Reply->toAssocArray('cldbid')
#1 /home/Sebbo94BY/ts3phpframework/test.php(47): PlanetTeamSpeak\TeamSpeak3Framework\Node\Server->serverGroupClientList(147)
#2 [internal function]: exceptionDebugging->onWaitTimeout(60, Object(PlanetTeamSpeak\TeamSpeak3Framework\Adapter\ServerQuery))
#3 /home/Sebbo94BY/ts3phpframework/src/Helper/Signal/Handler.php(77): call_user_func_array(Object(Closure), Array)
#4 /home/Sebbo94BY/ts3phpframework/src/Helper/Signal.php(75): PlanetTeamSpeak\TeamSpeak3Framework\Helper\Signal\Handler->call(Array)
#5 /home/Sebbo94BY/ts3phpframework/src/Transport/Transport.php(271): PlanetTeamSpeak\TeamSpeak3Framework\Helper\Signal->emit('serverqueryWait...', Array, Object(PlanetTeamSpeak\TeamSpeak3Framework\Adapter\ServerQuery))
#6 /home/Sebbo94BY/ts3phpframework/src/Transport/TCP.php(152): PlanetTeamSpeak\TeamSpeak3Framework\Transport\Transport->waitForReadyRead()#7 /home/Sebbo94BY/ts3phpframework/src/Adapter/ServerQuery.php(177): PlanetTeamSpeak\TeamSpeak3Framework\Transport\TCP->readLine()
#8 /home/Sebbo94BY/ts3phpframework/test.php(81): PlanetTeamSpeak\TeamSpeak3Framework\Adapter\ServerQuery->wait()
#9 /home/Sebbo94BY/ts3phpframework/test.php(87): exceptionDebugging->main()
#10 {main}
  thrown in /home/Sebbo94BY/ts3phpframework/src/Adapter/ServerQuery/Reply.php on line 206

real    1m8.178s
user    0m0.156s
sys     0m0.052s

This issue is always reproducible for me.

Current behaviour

The bot works as expected via the classic "raw" connection.

The bot fails with the exception invalid parameter when it tries to get any list via a SSH connection.

Expected Behaviour

The bot should be properly able to get any list via a SSH connection as it does with the classic "raw" connection.

Additionals notes

  • This exception occurs for all *List() functions of the TS3PHPFramework
  • Sometimes it works and returns the lists as expected, but most of the time it fails. Maybe an issue with the TCP buffer or so?
  • I've already tested in a temporary written PHPUnit test, if longer (e. g. 556 clients as member of a servergroup) and especially the here returned response can be properly parsed by the TS3PHPFramework and yes, it works as expected - otherwise the classic "raw" connection would also fail. So we have two checks here. :)
  • It looks like as the SSH TCP connection does not get the full response (line): invalid parameter. ident 'cldbid' does not exist in node '{"_identifier":"+GPk+IrgHZUQ7Vu6aY0mGgtseNw="} (the node looks always a bit different, but always incomplete like this one)
  • If ssh=1, we use the PHP functions ssh2_connect() and ssh2_shell():
    $this->stream = @ssh2_shell($this->session, "raw");
  • Sending data is done using the PHP function fwrite():
    @fwrite($this->stream, $data);
  • Reading data is done using the PHP function fgets():
    $data = @fgets($this->stream, 4096);
  • To be able to build a TeamSpeak bot, you need to set blocking=0:
    if ($this->getTransport()->getConfig("blocking")) {
    throw new AdapterException("only available in non-blocking mode");
    }
  • Based on the PHP documentation of ssh2_shell() a comment mentions, that stream_set_blocking() should be set to true, but we always set it to false:
    @stream_set_blocking($this->stream, $blocking ? 1 : 0);
  • Simply setting @stream_set_blocking($this->stream, true); does unfortunately not solve the issue. Instead it stucks in a very CPU intensive endless loop, where var_dump($data) returns bool(false). So it does somehow not break / exit the while() loop.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Confirmed as a valid bug and requires fix.
Projects
None yet
Development

No branches or pull requests

1 participant