Jump to content

Telnet response does not output all data (using PHPTelnet Class)


jaybo
 Share

Recommended Posts

I have a telnet request to a chassis that should be returning 500+ responses to the browser (returns 500+ when using the terminal for same request). Currently I am able to output approximately 40 of them. I have used the same code to hit different chassis and cannot output more than 60 results.

Here is my code:

<?php
require_once 'db.php';
require_once "PHPTelnet.php";

$largeParts = array();

$telnet = new PHPTelnet();
$telnet->show_connect_error=1;

$result = $telnet->Connect($server, $user, $pass);

$resultArray1 = [];
$resultArray2 = [];
$resultOutput = [];

########## bridge show all commands ##########
if ($result ==0) {
    $telnet->DoCommand('bridge show', $result);
    $resultArray1 = preg_split('/\n|\r\n?/', $result);
    //second command to include multiple page output from bridge show command
    sleep(1);
    $telnet->DoCommand('a', $result);
    $resultArray2 = preg_split('/\n|\r\n?/', $result);

    //Merge the arrays to combine all the result outputs
    $resultOutput = array_merge($resultArray1, $resultArray2);

    //duplicate cli telnet output to review
    echo '<pre>';
    echo "\n------------------------>>>>>>>\n";
    print_r($resultOutput);
    echo "\n------------------------>>>>>>>\n";
    echo '</pre>';
}

// say Disconnect(0); to break the connection without explicitly logging out
$telnet->Disconnect();


mysqli_close($mysqli);

The class for the connection is PHPTelnet (https://www.geckotribe.com/php-telnet/)

As you will see in the below code I have tried see if there are any obvious issues by using print_r for the Function GetResponse(). I have also tried to make a change in case of a stream timeout (due to the hundreds of lines that return when making the telnet call through the terminal). Here is my code for that class:

<?php

class PHPTelnet {
	var $show_connect_error=1;

	var $use_usleep=0;	// change to 1 for faster execution
		// don't change to 1 on Windows servers unless you have PHP 5
	var $sleeptime=125000;
	var $loginsleeptime=1000000;

	var $fp=NULL;
	var $loginprompt;

	var $conn1;
	var $conn2;
	
	/*
	0 = success
	1 = couldn't open network connection
	2 = unknown host
	3 = login failed
	4 = PHP version too low
	*/
	function Connect($server,$user,$pass) {
		$rv=0;
		$vers=explode('.',PHP_VERSION);
		$needvers=array(4,3,0);
		$j=count($vers);
		$k=count($needvers);
		if ($k<$j) $j=$k;
		for ($i=0;$i<$j;$i++) {
			if (($vers[$i]+0)>$needvers[$i]) break;
			if (($vers[$i]+0)<$needvers[$i]) {
				$this->ConnectError(4);
				return 4;
			}
		}
		
		$this->Disconnect();
		
		if (strlen($server)) {
			if (preg_match('/[^0-9.]/',$server)) {
				$ip=gethostbyname($server);
				if ($ip==$server) {
					$ip='';
					$rv=2;
				}
			} else $ip=$server;
		} 
		
		if (strlen($ip)) {
			if ($this->fp=fsockopen($ip,23)) {
				fputs($this->fp,$this->conn1);
				$this->Sleep();
				
				fputs($this->fp,$this->conn2);
				$this->Sleep();
				$this->GetResponse($r);
				$r=explode("\n",$r);
				$this->loginprompt=$r[count($r)-1];

				fputs($this->fp,"$user\r");
				$this->Sleep();

				fputs($this->fp,"$pass\r");
				if ($this->use_usleep) usleep($this->loginsleeptime);
				else sleep(1);
				$this->GetResponse($r);
				$r=explode("\n",$r);
				if (($r[count($r)-1]=='')||($this->loginprompt==$r[count($r)-1])) {
					$rv=3;
					$this->Disconnect();
				}
			} else $rv=1;
		}
		
		if ($rv) $this->ConnectError($rv);
		return $rv;
	}
	
	function Disconnect($exit=1) {
		if ($this->fp) {
			if ($exit) $this->DoCommand('exit',$junk);
			fclose($this->fp);
			$this->fp=NULL;
		}
	}

	function DoCommand($c,&$r) {
		if ($this->fp) {
			fputs($this->fp,"$c\r");
			$this->Sleep();
			$this->GetResponse($r);
			$r=preg_replace("/^.*?\n(.*)\n[^\n]*$/","$1",$r);
		}
		return $this->fp?1:0;
	}
	
	function GetResponse(&$r) {
		$r='';
		do { 
			// stream_set_timeout($this->fp, 60000); //This did not change anything to the response
			$r.=fread($this->fp,8000);
			##################### WHERE TO PUT THIS ? ####################################
			// added below line to test value on stream timeout for the 2 servers that have hundreds of results. Did not show any change to results.  
			//stream_set_timeout($this->fp, 60000);
			#############################################################################
			$s=socket_get_status($this->fp);

echo ">>>>>>>>>>>>>> TESTING START >>>>>>>>>>>>>> <br>";
// echo "<pre>";			
// print_r($r);
// echo "</pre>";
// echo "<br><br>";
echo "<pre>";			
print_r($s);
echo "</pre>";
echo "<br> >>>>>>>>>>>>>> TESTING END >>>>>>>>>>>>>> <br>";			

		} while ($s['unread_bytes']);

		##################### TESTING TO GET STREAM META DATA ####################################
		// $stream_meta_data = stream_get_meta_data($this->fp); //Added line
		// 	echo "<br><br><br><br>"; //Added line
		// 	echo "<pre>";
		// 	print_r($stream_meta_data); //Added line
		// 	echo "</pre>";
		#############################################################################
	}
	
	function Sleep() {
		if ($this->use_usleep) usleep($this->sleeptime);
		else sleep(1);
	}
	
	function PHPTelnet() {
		$this->conn1=chr(0xFF).chr(0xFB).chr(0x1F).chr(0xFF).chr(0xFB).
			chr(0x20).chr(0xFF).chr(0xFB).chr(0x18).chr(0xFF).chr(0xFB).
			chr(0x27).chr(0xFF).chr(0xFD).chr(0x01).chr(0xFF).chr(0xFB).
			chr(0x03).chr(0xFF).chr(0xFD).chr(0x03).chr(0xFF).chr(0xFC).
			chr(0x23).chr(0xFF).chr(0xFC).chr(0x24).chr(0xFF).chr(0xFA).
			chr(0x1F).chr(0x00).chr(0x50).chr(0x00).chr(0x18).chr(0xFF).
			chr(0xF0).chr(0xFF).chr(0xFA).chr(0x20).chr(0x00).chr(0x33).
			chr(0x38).chr(0x34).chr(0x30).chr(0x30).chr(0x2C).chr(0x33).
			chr(0x38).chr(0x34).chr(0x30).chr(0x30).chr(0xFF).chr(0xF0).
			chr(0xFF).chr(0xFA).chr(0x27).chr(0x00).chr(0xFF).chr(0xF0).
			chr(0xFF).chr(0xFA).chr(0x18).chr(0x00).chr(0x58).chr(0x54).
			chr(0x45).chr(0x52).chr(0x4D).chr(0xFF).chr(0xF0);
		$this->conn2=chr(0xFF).chr(0xFC).chr(0x01).chr(0xFF).chr(0xFC).
			chr(0x22).chr(0xFF).chr(0xFE).chr(0x05).chr(0xFF).chr(0xFC).chr(0x21);
	}
	
	function ConnectError($num) {
		if ($this->show_connect_error) switch ($num) {
		case 1: echo '<br />[PHP Telnet] <a href="http://www.geckotribe.com/php-telnet/errors/fsockopen.php">Connect failed: Unable to open network connection</a><br />'; break;
		case 2: echo '<br />[PHP Telnet] <a href="http://www.geckotribe.com/php-telnet/errors/unknown-host.php">Connect failed: Unknown host</a><br />'; break;
		case 3: echo '<br />[PHP Telnet] <a href="http://www.geckotribe.com/php-telnet/errors/login.php">Connect failed: Login failed</a><br />'; break;
		case 4: echo '<br />[PHP Telnet] <a href="http://www.geckotribe.com/php-telnet/errors/php-version.php">Connect failed: Your server\'s PHP version is too low for PHP Telnet</a><br />'; break;
		}
	}
}

return;

The print_r of $s from Function getResponse() outputs as follows:

Array
(
    [timed_out] => 
    [blocked] => 1
    [eof] => 
    [stream_type] => tcp_socket/ssl
    [mode] => r+
    [unread_bytes] => 0
    [seekable] => 
)

When looking to watch the telnet process real time, I used the command "-sudo watch ss-t " and it shows that the script is completing as expected and that the socket is not timing out because of a script crash. Our thought was that the socket is timing out prematurely. I tried to increase the stream_set_timeout() but to no avail as you may see from the code. I looked at the documentation https://www.php.net/manual/en/function.stream-set-timeout.php. Not sure if I am just putting that method in the wrong place or if I am missing something else.... such as would the fact that the blocking is True make a difference? 

 

Edited by jaybo
shorten title
Link to comment
Share on other sites

  • jaybo changed the title to Telnet response does not output all data (using PHPTelnet Class)

How fast does the output come? Is it 500 almost instantly or do they come in over the course of a minute? Is the script stopping "normally" because it thinks there's output or because it has some sort of problem (like a timeout)?

Link to comment
Share on other sites

the php documentation for socket_get_status/stream_get_meta_data has a note to not use the unread_bytes value in a script, which is what the posted code's do/while loop is using, probably because it is just the number of bytes currently contained in the PHP's own internal buffer. this does not indicate that you are done reading the stream, just that there's a pause in the data received such that you have emptied php's internal buffer.

in the same documentation, for the eof value -

Quote

To determine if there is more data to be read, use feof() instead of reading this (the eof value) item.

whoever wrote and tested this code probably had a situation such that unread_bytes was always non-zero until the end of the data.

Link to comment
Share on other sites

your print_r() output (you should use var_dump instead) actually confirms this. when you print_r a true value, you get a 1. when you print_r a false value, you get nothing. the print_r output indicates a false eof value, i.e. you are not at the end of the file, with a zero unread_bytes value.

Link to comment
Share on other sites

Thank you mac_gyver for that useful input.

 

I tried to switch out

while ($s['unread_bytes']);

with

while (! feof($s));

but this causes an internal error.

 

Not too sure where to put the feof().

Link to comment
Share on other sites

Unless the telnet server disconnects the socket at the end of your command, feof will not be helpful as the socket does not enter an EOF state until it is disconnected.

What you need to do is watch the incoming data for an input prompt or some kind of identifier that you can use to know when the command has completed.  That's exactly what that newer library linked above does.

My suggestion would be that you abandon the current class you're using and adapt your code to the newer library.  Yes, it sucks having to update a bunch of code but sometimes it just needs to be done.

If you really don't want to switch libraries, you could try modifying your class to read from the socket until a particular marker is seen.

 

Link to comment
Share on other sites

On 4/1/2022 at 10:37 PM, kicken said:

Unless the telnet server disconnects the socket at the end of your command, feof will not be helpful as the socket does not enter an EOF state until it is disconnected.

What you need to do is watch the incoming data for an input prompt or some kind of identifier that you can use to know when the command has completed.  That's exactly what that newer library linked above does.

My suggestion would be that you abandon the current class you're using and adapt your code to the newer library.  Yes, it sucks having to update a bunch of code but sometimes it just needs to be done.

If you really don't want to switch libraries, you could try modifying your class to read from the socket until a particular marker is seen.

 

So in looking at making the current class work through modification:

The last line upon completing the output (identifier) is always
‘[1-4 digits number*] Bridge Interfaces displayed’
for the tasks I am running. So I am looking at where to state this as the EOF prompt in the script? In addition when connecting via telnet from the terminal it takes between 10sec-45sec+ depending on the server chassis I am connecting to. Not sure if this would mean I need to tweak the socket_timeout to allow for this?

Link to comment
Share on other sites

2 hours ago, jaybo said:

So I am looking at where to state this as the EOF prompt in the script?

You'd use the same do/while loop construct to read lines from the socket, but instead of the while condition using the unread_bytes value, you test if the last line that was read is the marker you are interested in.  For example:

function GetResponse(&$r) {
	$r = '';
	do { 
		$line = fgets($this->fp);
		$r .= $line;
	} while (!preg_match('/\d{1,4} Bridge Interfaces displayed/', $line));
}

 

Link to comment
Share on other sites

  • 1 month later...

Update - new class not connecting for me, still looking at that one.

Current class I am also trying to modify still produces a Gateway Timeout with the newly modified code addition;


 

  //edited code to attempt to read the eof to allow all data to pass before close of socket

    function GetResponse(&$r) {

        $r = '';

        do {

             $line = fgets($this->fp);

             $r .= $line;

        } while (!preg_match('/\d{1,4} Bridge Interfaces displayed/', $line));

    }

timeout is occurring at exactly 1min. **From tailing the log file I am also seeing that the Fatal Error is due to the Maximum execution time of 300 seconds exceeded (used php_ini to set this).

Link to comment
Share on other sites

55 minutes ago, jaybo said:

timeout is occurring at exactly 1min.

Make sure that your regex is correctly matching the end of the output.  Sounds like your connection is stalling out because it doesn't detect the end of the output and ends up looping until it hits the script timeout.  In addition to the regex, you could add another test that $line contains some data.  That should break the loop if fgets times out due to no data to read.

do {
    $line = fgets($this->fp);
    $r .= $line;
} while ($line && !preg_match('/\d{1,4} Bridge Interfaces displayed/', $line));

 

Link to comment
Share on other sites

regex is matching and adding the testing of $line still produces the timeout. I am trying to echo out each line as it is read also but not getting any output at all currently.

 

Looking at the http.conf file for apache to see if I can change the timeout for the server and see if this helps.

Link to comment
Share on other sites

  • 2 months later...

UPDATE: Did not find a solution to the exact issue - the newer telnet class was also not connecting for me. I have, however, been able to get a perl connection using Net::Telnet component, then run a short php script to output ALL results. This happened within 3 seconds for 900+ results so all in all a good replacement.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.