ppping

Anything else such as saidel etc...
Post Reply
dv2
Posts: 3
Joined: Fri Apr 15, 2011 8:41 am

ppping

Post by dv2 » Fri Apr 15, 2011 9:08 am

Hi. thank you for that piece of software. i needed a solution to be able to ping my server's internet connection without opening up the rdp each time. and there is no better way for that other than doing it via web interface. most php script that i found, they would simply execute ping command and show the output. allowing php executing is not a good idea due to security reasons. but your script, generate the ping packet and send it all by itself which was the reason i chose that for my server.
However there are some issues on that script. one of the most important ones is that because of the way your script handles socket, if you run more than 1 session pinging the same ip, there is a great chance that they end up receiving each others packets and therefore giving mismatch identity error.
I changed your script a bit to check for the matching identities and ignore the packet if they weren't matched. i believe i could make that 'while loop' faster if i knew more about php/icmp.
another thing that i added was calculating jitter(http://www.rfc-editor.org/rfc/rfc1889.txt) on every packet. which is pretty important for me.

Here's the ppping.inc:

Code: Select all

<?php
/* -------------------------------------------------------------
This file is part of PurplePixie Ping (PPPing)

PPPing is (C) Copyright 2010 PurplePixie Systems

PPPing is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PPPing is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PPPing.  If not, see www.gnu.org/licenses

For more information see www.purplepixie.org/phpping
-------------------------------------------------------------- */

/**
 * PPPing PHP Ping Utility
 * @package PPPing PHP Ping Utility
 * @author David Cutting
 * @version 0.01
**/

/**
 * Main PPPing Class
 * @package PPPing PHP Ping Utility
**/
class PPPing
{
/**
 * Time-to-Live for IP Packet (DO NOT USE)
 *
 * -1 Uses system default (usually 64). Please note that this is currently
 * not functional.
**/
var $ttl=-1;

/**
 * Hostname to ping (resolvable host or IP address)
**/
var $hostname="";

/**
 * Identifier - will fill with random content (16 bits)
**/
var $identity=0;

/**
 * Sequence number in decimal (16 bits)
**/
var $sequence=0;

/**
 * Timeout in seconds - maximum wait for a response before timeout
**/
var $timeout=10;

/**
 * Timer start seconds
**/
var $timer_start_sec=0;

/**
 * Timer start mseconds
**/
var $timer_start_msec=0;

/**
 * Data package for the ping
**/
var $data_package = "PPPing";

/**
 * Debug - prints output to the screen
**/
var $debug=false;

/**
 * Holds information on last result
**/
var $Last = array();

/**
 * Clears last data
**/
function clearLast()
	{
	$this->last = array(
		"set" => false,
		"result" => $this->last["result"],
		"ttl" => 0,
		"hops" => 0,
		"source" => "",
		"destination" => "",
                "jitter" => $this->last["jitter"] );
	}
/**
 * Get a padded hex identifier
**/
function getIdentity()
	{
	if ( (is_numeric($this->identity)) && ($this->identity>=0) && ($this->identity<65535) )
		$id=$this->identity;
	else $id=0;
	
	$id=dechex($id);
	$id=str_pad($id,4,"0",STR_PAD_LEFT);
	$id=pack("H*",$id);
	
	return $id;
	}

/**
 * Get a padded hex sequence
**/
function getSequence()
	{
	if ( (is_numeric($this->sequence)) && ($this->sequence>=0) && ($this->sequence<65535) )
		$seq=$this->sequence;
	else $seq=0;
	$seq=dechex($seq);
	$seq=str_pad($seq,4,"0",STR_PAD_LEFT);
	$seq=pack("H*",$seq);
	
	return $seq;
	}
	
/**
 * Returns a hex string of the binary data for debug purposes
**/
function getHex($data)
	{
	$parts=unpack("H*",$data);
	return $parts[1];
	}
	
/**
 * Randomise identity and/or sequence within 16 bit parameters
**/
function Randomise($identity=true,$sequence=false)
	{
	mt_srand(microtime()*1000000);
	if ($identity) $this->identity=mt_rand(0,65534);
	if ($sequence) $this->sequence=mt_rand(0,65534);
	}
	
/**
 * Start timer (reset values)
**/
function startTimer()
	{
	$now=microtime();
	$timearray=explode(" ",$now);
	$this->timer_start_sec=$timearray[1];
	$this->timer_start_msec=$timearray[0];
	}
	
/**
 * Stop timer (return result)
**/
function stopTimer()
	{
	$now=microtime();
	$timearray=explode(" ",$now);
	
	$finish_secs=$timearray[1];
	$finish_msecs=$timearray[0];
	
	$elapsed_seconds = $finish_secs - $this->timer_start_sec;
	$elapsed_time = $elapsed_seconds + $finish_msecs - $this->timer_start_msec;
	
	$elapsed_ms = $elapsed_time * 1000;
	
	$elapsed_ms = round($elapsed_ms,3);
	
	return $elapsed_ms;
	}
	
	
/**
 * Constructor - randomises ID
**/
function PPPing()
	{
        $this->last["jitter"] = -1;
	$this->Randomise();
	}
	
/**
 * Returns a dotted quad from hex format IPv4 address
**/
function ipAddress($hexip)
	{
	$quad="";
	for($a=0; $a<=6; $a+=2)
		{
		$portion=substr($hexip,$a,2);
		$decimal=hexdec($portion);
		if ($quad!="") $quad.=".";
		$quad.=$decimal;
		}
	return $quad;
	}
	
/**
 * Generate an ICMP checksum
**/
function Checksum($data)
    {
    if (strlen($data)%2)
		$data .= "\x00";
    
    $bit = unpack('n*', $data);
    $sum = array_sum($bit);
    
    while ($sum >> 16)
    $sum = ($sum >> 16) + ($sum & 0xffff);
    
    return pack('n*', ~$sum);
    }

/**
 * Do a ping of the set hostname
 *
 * @return float
 * Returns a negative number of failure which can be turned into text with
 * the strError method. A positive number is a response in milliseconds (ms)
**/
function Ping()
	{
	$this->clearLast();
    $type = "\x08"; // icmp echo
    $code = "\x00"; 
    $checksum = "\x00\x00"; // initial
    $identifier = $this->getIdentity();
	$dec_identity = $this->identity;
	//$identifier = "\x00\x00";
	//$seqNumber = "\x00\x00";
    $seqNumber = $this->getSequence();
	$dec_sequence = $this->sequence;
    $data = $this->data_package;
    $package = $type.$code.$checksum.$identifier.$seqNumber.$data;
    $checksum = $this->Checksum($package); // proper checksum
    $package = $type.$code.$checksum.$identifier.$seqNumber.$data;
	
	$ip_protocol_code = getprotobyname("ip");
	$ip_ttl_code = 7;
    
	// Lookup hostname
	$ips=str_replace(".","",$this->hostname);
	if (!is_numeric($ips))
		{
		$host=gethostbyname($this->hostname);
		if ($host==$this->hostname) return -5;
		}
	else $host=$this->hostname;
    
    // Create Socket
    $socket = socket_create(AF_INET, SOCK_RAW, 1); // @
    	//or die(socket_strerror(socket_last_error()));
    if (!$socket) return -3;
    
    // Set Non-Blocking
    socket_set_nonblock($socket); // @
	
	$socket_ttl = socket_get_option($socket,$ip_protocol_code,$ip_ttl_code);
	
	//for ($a=0; $a<64; $a++)
	//	echo $a." - ".@socket_get_option($socket,$ip_protocol_code,$a)."\n";
	
	if ($this->ttl>0)
		{
		socket_set_option($socket,$ip_protocol_code,$ip_ttl_code,128);
		$socket_ttl = socket_get_option($socket,$ip_protocol_code,$ip_ttl_code);
		//socket_set_option($socket,Socket::IPPROTO_IP,Socket::IP_TTL,128);
		//$socket_ttl = socket_get_option($socket,Socket::IPPROTO_IP,Socket::IP_TTL);
		
		}
	else $socket_ttl = 64; // standard TTL
		
    	
    // Connect Socket
    $sconn=socket_connect($socket, $host, null); // @
    if (!$sconn) return 0;
    
	// Package Size
	//$package_size = 8+strlen($data);
	$package_size = strlen($package);
	
    // Send Data
    socket_send($socket, $package, $package_size, 0); // @
        
    // Start Timer
    $this->startTimer();
    $startTime = microtime(true); // need this for the looping section
    

    // Read Data
    $keepon=true;
    $check_identity=true;
    $ipheader="";
    $header=false;
    

    while( $check_identity && $keepon ) // @socket_read
    	{ 
         if ($echo_reply=socket_read($socket, 255))
            { 
              $rx_parts = unpack("C*",$echo_reply);
              if ($rx_parts[1] == 0x45) // IP Header Information
			{
                        $header=true;
			$ipheader=substr($echo_reply,0,20);
			$echo_reply=substr($echo_reply,20);
			}
              else $header=false;
              $echo_reply_hex = $this->getHex($echo_reply);
              $reply_identity = hexdec(substr($echo_reply_hex,8,4));
              if ($reply_identity === $dec_identity) 
                   {
                     $check_identity=false;
                     $elapsed=$this->stopTimer();
                   }
            //else echo "check point! <br />";
            }
              
    	
    	 if ( (microtime(true) - $startTime) > $this->timeout )
    		$keepon=false;

         }
    	
		
	if ($check_identity === false) // didn't time out and check_identity = false
    	{
	    
		
		socket_close($socket); // @
		
		if ( $echo_reply === false ) return -4;
		else if (strlen($echo_reply)<2) return -2;
	
		if ($header) $rx_parts = unpack("C*",$echo_reply);
		$tx_parts = unpack("C*",$package);
		$ipheader_hex="";
		$ipheader_hex = $this->getHex($ipheader);
                
			
		if ($this->debug)
			{
			echo "\n";
			echo "    TyCoChksIdenSequData\n";
			echo "TX: ".$this->getHex($package)."\n";
			echo "RX: ".$this->getHex($echo_reply)."\n";
			if ($ipheader!="") echo "HR: ".$ipheader_hex."\n";
			}
			
		
		$reply_type = $rx_parts[1];
		$reply_code = $rx_parts[2];
		$reply_sequence = hexdec(substr($echo_reply_hex,12,4));
			
		$match=true;
		if ($ipheader!="")
			{
			$source=substr($ipheader_hex,24,8);
			$dest=substr($ipheader_hex,32,8);
			$ttl=hexdec(substr($ipheader_hex,16,2));
			if ($this->debug) echo $this->ipAddress($source)." => ".$this->ipAddress($dest)." | ttl: ".$ttl."\n";
			if ($source==$dest) $match=true;
			else $match=false;
			
			$this->last["set"]=true;
			$this->last["source"]=$this->ipAddress($source);
			$this->last["destination"]=$this->ipAddress($dest);
			$this->last["ttl"]=$ttl;
			$this->last["hops"]=$socket_ttl - $ttl;
			
			
			}
	
		if ( (($rx_parts[1]==0) || (($rx_parts[1]==8)&&($match))) && ($rx_parts[2]==0) )
			{ // is echo_reply (0) or is echo_request (8) AND match (from same host)
			  // and has code of 0
			// valid response
			if ($reply_identity != $dec_identity) return -8; // ID mismatch
			else if ($reply_sequence != $dec_sequence) return -7; // sequence mismatch
			else
				{
                                                       if ($this->last["jitter"] == -1) $this->last["jitter"] = 0; // first run
                                                        else $this->last["jitter"] = round($this->last["jitter"] + (abs($elapsed - $this->last["result"]) - $this->last["jitter"]) / 16, 3);
				$this->last["result"]=$elapsed;
				return $elapsed;
				}
			}
		else
			{ // ICMP Error
			return -9;
			}
	
		}
    socket_close($socket); // @
    return -1; // timeout
	}
	
/**
 * Returns textual error for code
**/
function strError($code)
	{
	switch($code)
		{
		case -1: return "Timed Out"; break;
		case -2: return "Reply Too Short"; break;
		case -3: return "Failed to Open Socket"; break;
		case -4: return "Invalid (false) Response"; break;
		case -5: return "Hostname Lookup Failed"; break;
		case -7: return "Sequence Mismatch"; break;
		case -8: return "Identity Mismatch"; break;
		case -9: return "ICMP Generic Error"; break;
		
		default: return "Unknown Error"; break;
		}
	}


}

?>

and my ping.php:

Code: Select all

<html>
<head>
<script type="text/JavaScript">
<!--

function pageScroll() {
    	window.scrollBy(0,300); // horizontal and vertical scroll increments
    	scrolldelay = setTimeout('pageScroll()',100); // scrolls every 100 milliseconds
}

function stopScroll() {
    	clearTimeout(scrolldelay);
}

function timedRefresh(timeoutPeriod) {
	setTimeout("location.reload(true);",timeoutPeriod);
}
//   -->
</script>
</head>
<body onload="JavaScript:timedRefresh(60000);JavaScript:stopScroll();">


      <script type="text/javascript" language="JavaScript">
         pageScroll();
      </script>

</body>
</html>




<?php


include "ppping.inc";


/**
 * Display totals
**/
function DisplayTotals($host,$successes,$failures,$times,$seq_counter)
	{

	if ( ($successes<=0) || ($seq_counter<=0) ) $perc=100;
	else if ($failures<=0) $perc=0;
	else
		{
		$perc = ($failures/$seq_counter)*100;
		$perc = round($perc,2);
		}
        echo "<br/>Ping statistics for ".$host.":<br />";
        echo "Packets: Sent = ".$seq_counter.", Received = ".$successes.", Lost = ".$failures." (".$perc."% loss),<br />";
	$results=count($times);
	$high=0;
	$low=999999999;
	$total=0;
	foreach($times as $time)
		{
		if ($time>$high) $high=$time;
		if ($time<$low) $low=$time;
		$total+=$time;
		}
	if ($total>0) // has something
		{
		$average = round($total/$results,3);
                echo "Approximate round trip times in milli-seconds:<br />";
                echo "Minimum = ".$low."ms, Maximum = ".$high."ms, Average = ".$average."ms";
		}
	}



/** Start the routine **/

 $ping = new PPPing();
 $host = "192.168.103.100";
 $ping->hostname = $host;
 $ping->timeout = 2;
 $successes=0;
 $failures=0;
 $times=array();
 $seq_counter = 1;
// $ping->debug = true;
// $ping->identity = 1;
 ob_implicit_flush();
for($i=0; $i<=99; $i=$i+1)
{
 $ping->sequence = $seq_counter++;
 $result = $ping->Ping();
// echo $ping->identity." ";
// echo $ping->sequence." ";
 if ($result<0) // failed
    {
      $failures++;
      echo "Error: ".$ping->strError($result)."<br />";
    }
 else
    { 
     $successes++;
     $times[]=$result;
     if ($ping->last["set"])
        echo "Reply[".$ping->sequence."] from ".$ping->last["source"].": time=".$result."ms"."  TTL=".$ping->last["ttl"]." jitter=".$ping->last["jitter"]."ms<br />";
     else echo $result."ms"."<br />";
    }
 usleep(1000000);
}
$seq_counter--;
DisplayTotals($host,$successes,$failures,$times,$seq_counter);
usleep(1000000); // for making sure the scrollbar will hit the end
?>
Also on another note, why do you check for existence of packet header in your script? is it even possible to have a packet without a header?

there are a lot of more cool things that could be done. i hope i could find some time for that

so what are your thoughts?

dave
Site Admin
Posts: 260
Joined: Fri May 30, 2008 9:09 pm
Location: UK
Contact:

Re: ppping

Post by dave » Sat Apr 16, 2011 8:06 pm

Hi,

Glad you're finding it useful and thanks for your comments.

When I get a chance I'll have another proper look at the code and your version (if I do incorporate changes for release how would you like to be mentioned in the header as I only see a username on this forum?).

To deal quickly with a few points:
because of the way your script handles socket, if you run more than 1 session pinging the same ip, there is a great chance that they end up receiving each others packets and therefore giving mismatch identity error.
Absolutely an issue. There has always I think been a problem if you are pinging your same interface as the SW will just send and receive the packet interally rather than bouncing back from the kernel. I'll give this some thought but my only concern on first-look with your solution is packets getting totally lost; if the ID is a mis-match it's ignored but then also won't ever go to the thread waiting for it.

I think (though may well be wrong) that if you send two packets to a host and two are returned even in the wrong order you may want the result to be both threads receive a response even if it's the wrong one rather than one thread receiving both (ignoring one) and the other thread receiving nothing?

I'll do some detailed testing on this when I get some time.
why do you check for existence of packet header in your script? is it even possible to have a packet without a header?
At the network level all packets must of course have a header. However at the application level it's rarer for the direct data stream to include a header.

Receiving the header at all caused some problems in the early days when I wasn't sure what was coming back. I think it's probably safe in PHP and with PHP_Sockets that the ICMP header will always be returned but looking at "proper" (C/C++) implementations of ping such as for Windoze they all do check for the header as it looks like it's not always there.

Regards,

Dave.

dv2
Posts: 3
Joined: Fri Apr 15, 2011 8:41 am

Re: ppping

Post by dv2 » Sun Apr 17, 2011 7:31 am

Hi David
Thank you for your response.
if I do incorporate changes for release how would you like to be mentioned in the header as I only see a username on this forum?
Don't worry about it. if you think its useful, use it. if you think its not, b4 judging, just know that I'm a newbie in php :p
my only concern on first-look with your solution is packets getting totally lost; if the ID is a mis-match it's ignored but then also won't ever go to the thread waiting for it.
well, strangely enough its not entirely true. for some reason, that kind of socket reading wont destroy the packet and another thread , could still read a packet thats been read by another one b4. i did some testing. although it needs more to make sure of that, but socket_read was able to read the packet after its been read and ignored by another thread.



also I imagine to make sure that we get the right pong for our ping, we should control the received packet's header/icmp checksum . moreover we should actually check the received data to make sure it matches the one that we've sent. should be quiet easy to implement. if i get a chance to do so, ill share it with you.

Best Regards

dave
Site Admin
Posts: 260
Joined: Fri May 30, 2008 9:09 pm
Location: UK
Contact:

Re: ppping

Post by dave » Sun Apr 17, 2011 12:52 pm

Great - many thanks.

You may also want to see this post about ppping in FreeNATS where its been reported the default packet size can cause problems.

My plan is to incorporate all of this into a release of ppping when I get a chance.

Yes - any other ideas or code much appreciated.

Regards,

Dave.

dv2
Posts: 3
Joined: Fri Apr 15, 2011 8:41 am

Re: ppping

Post by dv2 » Wed Apr 20, 2011 10:27 am

You may also want to see this post about ppping in FreeNATS where its been reported the default packet size can cause problems.
I did not have such a problem at all. as he suggested, he might be using some custom kernel/firewall which uses that packet size for a particular purpose.

Ok. i did some extra work on the script and here's what I've done

the main differences between this script and your original one, are:

1- adding a workaround for Identity Mismatch issue
2- adding jitter calculation
3- randomizing data
4- adding ICMP checksum check
5- adding data check

I also tried to optimize the 'while loop' a bit.

Code: Select all

<?php
/* -------------------------------------------------------------
This file is part of PurplePixie Ping (PPPing)

PPPing is (C) Copyright 2010 PurplePixie Systems

PPPing is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PPPing is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PPPing.  If not, see www.gnu.org/licenses

For more information see www.purplepixie.org/phpping
-------------------------------------------------------------- */

/**
 * PPPing PHP Ping Utility
 * @package PPPing PHP Ping Utility
 * @author David Cutting
 * @version 0.01
**/

/**
 * Main PPPing Class
 * @package PPPing PHP Ping Utility
**/
class PPPing
{
/**
 * Time-to-Live for IP Packet (DO NOT USE)
 *
 * -1 Uses system default (usually 64). Please note that this is currently
 * not functional.
**/
var $ttl=-1;

/**
 * Hostname to ping (resolvable host or IP address)
**/
var $hostname="";

/**
 * Identifier - will fill with random content (16 bits)
**/
var $identity=0;

/**
 * Sequence number in decimal (16 bits)
**/
var $sequence=0;

/**
 * Timeout in seconds - maximum wait for a response before timeout
**/
var $timeout=10;

/**
 * Timer start seconds
**/
var $timer_start_sec=0;

/**
 * Timer start mseconds
**/
var $timer_start_msec=0;

/**
 * Data package size for the ping
**/
var $data_package_size = 32;

/**
 * Debug - prints output to the screen
**/
var $debug=false;

/**
 * Holds information on last result
**/
var $Last = array();

/**
 * Clears last data
**/
function clearLast()
   {
   $this->last = array(
      "set" => false,
      "result" => $this->last["result"],
      "ttl" => 0,
      "hops" => 0,
      "source" => "",
      "destination" => "",
      "jitter" => $this->last["jitter"] );
   }
/**
 * Get a padded hex identifier
**/
function getIdentity()
   {
   if ( (is_numeric($this->identity)) && ($this->identity>=0) && ($this->identity<65535) )
      $id=$this->identity;
   else $id=0;
   
   $id=dechex($id);
   $id=str_pad($id,4,"0",STR_PAD_LEFT);
   $id=pack("H*",$id);
   
   return $id;
   }

/**
 * Get a padded hex sequence
**/
function getSequence()
   {
   if ( (is_numeric($this->sequence)) && ($this->sequence>=0) && ($this->sequence<65535) )
      $seq=$this->sequence;
   else $seq=0;
   $seq=dechex($seq);
   $seq=str_pad($seq,4,"0",STR_PAD_LEFT);
   $seq=pack("H*",$seq);
   
   return $seq;
   }
   
/**
 * Returns a hex string of the binary data for debug purposes
**/
function getHex($data)
   {
   $parts=unpack("H*",$data);
   return $parts[1];
   }
   
/**
 * Randomise identity and/or sequence within 16 bit parameters
**/
function Randomise($identity=true,$sequence=false)
   {
   mt_srand(microtime()*1000000);
   if ($identity) $this->identity=mt_rand(0,65534);
   if ($sequence) $this->sequence=mt_rand(0,65534);
   }
   
/**
 * Start timer (reset values)
**/
function startTimer()
   {
   $now=microtime();
   $timearray=explode(" ",$now);
   $this->timer_start_sec=$timearray[1];
   $this->timer_start_msec=$timearray[0];
   }
   
/**
 * Stop timer (return result)
**/
function stopTimer()
   {
   $now=microtime();
   $timearray=explode(" ",$now);
   
   $finish_secs=$timearray[1];
   $finish_msecs=$timearray[0];
   
   $elapsed_seconds = $finish_secs - $this->timer_start_sec;
   $elapsed_time = $elapsed_seconds + $finish_msecs - $this->timer_start_msec;
   
   $elapsed_ms = $elapsed_time * 1000;
   
   $elapsed_ms = round($elapsed_ms,3);
   
   return $elapsed_ms;
   }
   
   
/**
 * Constructor - randomises ID
**/
function PPPing()
   {
   $this->last["jitter"] = -1;
   $this->Randomise();
   }
   
/**
 * Returns a dotted quad from hex format IPv4 address
**/
function ipAddress($hexip)
   {
   $quad="";
   for($a=0; $a<=6; $a+=2)
      {
      $portion=substr($hexip,$a,2);
      $decimal=hexdec($portion);
      if ($quad!="") $quad.=".";
      $quad.=$decimal;
      }
   return $quad;
   }
   
/**
 * Generate an ICMP checksum
**/
function Checksum($data)
    {
    if (strlen($data)%2)
      $data .= "\x00";
    
    $bit = unpack('n*', $data);
    $sum = array_sum($bit);
    
    while ($sum >> 16)
    $sum = ($sum >> 16) + ($sum & 0xffff);
    
    return pack('n*', ~$sum);
    }


/**
 * Generate a random char string
**/
function rand_str($length , $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890')
{
    $chars_length = (strlen($chars) - 1);
    $string = $chars{rand(0, $chars_length)};

    for ($i = 1; $i < $length; $i = strlen($string))
    {
        $r = $chars{rand(0, $chars_length)};
        $string .=  $r;
    }
    
    return $string;
}


/**
 * Do a ping of the set hostname
 *
 * @return float
 * Returns a negative number of failure which can be turned into text with
 * the strError method. A positive number is a response in milliseconds (ms)
**/
function Ping()
   {
   $this->clearLast();
    $type = "\x08"; // icmp echo
    $code = "\x00"; 
    $checksum = "\x00\x00"; // initial
    $identifier = $this->getIdentity();
   $dec_identity = $this->identity;
   //$identifier = "\x00\x00";
   //$seqNumber = "\x00\x00";
    $seqNumber = $this->getSequence();
   $dec_sequence = $this->sequence;
    $data = $this->rand_str($this->data_package_size);
    $package = $type.$code.$checksum.$identifier.$seqNumber.$data;
    $checksum = $this->Checksum($package); // proper checksum
    $package = $type.$code.$checksum.$identifier.$seqNumber.$data;
   
   $ip_protocol_code = getprotobyname("ip");
   $ip_ttl_code = 7;
    
   // Lookup hostname
   $ips=str_replace(".","",$this->hostname);
   if (!is_numeric($ips))
      {
      $host=gethostbyname($this->hostname);
      if ($host==$this->hostname) return -5;
      }
   else $host=$this->hostname;
    
    // Create Socket
    $socket = socket_create(AF_INET, SOCK_RAW, 1); // @
       //or die(socket_strerror(socket_last_error()));
    if (!$socket) return -3;
    
    // Set Non-Blocking
    socket_set_nonblock($socket); // @
   
   $socket_ttl = socket_get_option($socket,$ip_protocol_code,$ip_ttl_code);
   
   //for ($a=0; $a<64; $a++)
   //   echo $a." - ".@socket_get_option($socket,$ip_protocol_code,$a)."\n";
   
   if ($this->ttl>0)
      {
      socket_set_option($socket,$ip_protocol_code,$ip_ttl_code,128);
      $socket_ttl = socket_get_option($socket,$ip_protocol_code,$ip_ttl_code);
      //socket_set_option($socket,Socket::IPPROTO_IP,Socket::IP_TTL,128);
      //$socket_ttl = socket_get_option($socket,Socket::IPPROTO_IP,Socket::IP_TTL);
      
      }
   else $socket_ttl = 64; // standard TTL
      
       
    // Connect Socket
    $sconn=socket_connect($socket, $host, null); // @
    if (!$sconn) return 0;
    
   // Package Size
   //$package_size = 8+strlen($data);
   $package_size = strlen($package);
   $packet_size = $package_size+20;
   
    // Send Data
    socket_send($socket, $package, $package_size, 0); // @
        
    // Start Timer
    $this->startTimer();
    $startTime = microtime(true); // need this for the looping section
    

    // Read Data
    $keepon=true;
    $check_identity=true;
    
    

    while( $check_identity && $keepon ) // @socket_read
       { 
          if ($echo_reply=socket_read($socket, $packet_size))
            { 
              if ( substr($echo_reply,0,1) == "\x45" ) // has header 
                 {
                   if ( substr($echo_reply,24,2) == $identifier )
                        {
                           $elapsed=$this->stopTimer();
                           $check_identity=false;
                        }
                 }
              else // has no header
                 {  
                   if ( substr($echo_reply,4,2) == $identifier )
                       {
                          $elapsed=$this->stopTimer();
                          $check_identity=false;
                       }
                 }
            }

              
            
         if ( (microtime(true) - $startTime) > $this->timeout )
                  $keepon=false;
                 
        } // end of while
       
      
   if ($check_identity === false) // didn't time out and check_identity = false
       {
       
      
      socket_close($socket); // @
      
      if ( $echo_reply === false ) return -4;
      else if (strlen($echo_reply)<2) return -2;

      $ipheader="";
      $rx_parts = unpack("C*",$echo_reply);
      if ($rx_parts[1] == 0x45) // IP Header Information
         {
         $ipheader=substr($echo_reply,0,20);
         $echo_reply=substr($echo_reply,20);
         $rx_parts = unpack("C*",$echo_reply);
         }
      $echo_reply_hex = $this->getHex($echo_reply);


      if ( $this->Checksum(substr($echo_reply,0,2).substr($echo_reply,4)) != substr($echo_reply,2,2) ) return -8; //checking icmp reply checksum

      $tx_parts = unpack("C*",$package);
      $ipheader_hex="";
      $ipheader_hex = $this->getHex($ipheader);
                
         
      if ($this->debug)
         {
         echo "\n";
         echo "    TyCoChksIdenSequData\n";
         echo "TX: ".$this->getHex($package)."\n";
         echo "RX: ".$this->getHex($echo_reply)."\n";
         if ($ipheader!="") echo "HR: ".$ipheader_hex."\n";
         }
         
      
      $reply_type = $rx_parts[1];
      $reply_code = $rx_parts[2];
      $reply_sequence = hexdec(substr($echo_reply_hex,12,4));
         
      $match=true;
      if ($ipheader!="")
         {
         $source=substr($ipheader_hex,24,8);
         $dest=substr($ipheader_hex,32,8);
         $ttl=hexdec(substr($ipheader_hex,16,2));
         if ($this->debug) echo $this->ipAddress($source)." => ".$this->ipAddress($dest)." | ttl: ".$ttl."\n";
         if ($source==$dest) $match=true;
         else $match=false;
         
         $this->last["set"]=true;
         $this->last["source"]=$this->ipAddress($source);
         $this->last["destination"]=$this->ipAddress($dest);
         $this->last["ttl"]=$ttl;
         $this->last["hops"]=$socket_ttl - $ttl;
         
         
         }
   
      if ( (($rx_parts[1]==0) || (($rx_parts[1]==8)&&($match))) && ($rx_parts[2]==0) )
         { // is echo_reply (0) or is echo_request (8) AND match (from same host)
           // and has code of 0
         // valid response
         if ($reply_sequence != $dec_sequence) return -7; // sequence mismatch
         else
            if ( substr($echo_reply,8) != $data ) return -10;   // data mismatch
            else
             {
             if ($this->last["jitter"] == -1) $this->last["jitter"] = 0; // first run
             else $this->last["jitter"] = round($this->last["jitter"] + (abs($elapsed - $this->last["result"]) - $this->last["jitter"]) / 16, 3);
             $this->last["result"]=$elapsed;
             return $elapsed;
             }
         }
      else
         { // ICMP Error
         return -9;
         }
   
      }
    socket_close($socket); // @
    return -1; // timeout
   }
   
/**
 * Returns textual error for code
**/
function strError($code)
   {
   switch($code)
      {
      case -1: return "Timed Out"; break;
      case -2: return "Reply Too Short"; break;
      case -3: return "Failed to Open Socket"; break;
      case -4: return "Invalid (false) Response"; break;
      case -5: return "Hostname Lookup Failed"; break;
      case -7: return "Sequence Mismatch"; break;
      case -8: return "ICMP Checksum Error"; break;
      case -9: return "ICMP Generic Error"; break;
      case -10: return "Data Mismatch"; break;
      
      default: return "Unknown Error"; break;
      }
   }


}

?>

Edit:
I also made some extra changes to the ping.php to make it more user friendly and easier to read:

Code: Select all

<html>
<head>
<script type="text/JavaScript">
<!--

function pageScroll() {
    	window.scrollBy(0,300); // horizontal and vertical scroll increments
    	scrolldelay = setTimeout('pageScroll()',100); // scrolls every 100 milliseconds
}

function stopScroll() {
    	clearTimeout(scrolldelay);
}

function timedRefresh(timeoutPeriod) {
	setTimeout("location.reload(true);",timeoutPeriod);
}
//   -->
</script>
</head>
<body onload="JavaScript:timedRefresh(60000);JavaScript:stopScroll();">


      <script type="text/javascript" language="JavaScript">
         pageScroll();
         document.onstop = stopScroll;
      </script>




<?php


include "ppping.inc";


/**
 * Display totals
**/
function DisplayTotals($host,$successes,$failures,$times,$seq_counter,$max_jitter)
	{

	if ( ($successes<=0) || ($seq_counter<=0) ) $perc=100;
	else if ($failures<=0) $perc=0;
	else
		{
		$perc = ($failures/$seq_counter)*100;
		$perc = round($perc,2);
		}
        echo "<br/>Ping statistics for ".$host.":<br />";
        echo "Packets: Sent = ".$seq_counter.", Received = ".$successes.", Lost = ".$failures." (".$perc."% loss),<br />";
	$results=count($times);
	$high=0;
	$low=999999999;
	$total=0;
	foreach($times as $time)
		{
		if ($time>$high) $high=$time;
		if ($time<$low) $low=$time;
		$total+=$time;
		}
	if ($total>0) // has something
		{
		$average = round($total/$results,3);
                echo "Approximate round trip times in milli-seconds:<br />";
                echo "Minimum = ".$low."ms, Maximum = ".$high."ms, Average = ".$average."ms<br />";
                echo "Maximum Jitter = ".$max_jitter."ms";
		}
	}



/** Start the routine **/

 $ping = new PPPing();
 $host = "192.168.103.100";
 $ping->hostname = $host;
 $ping->timeout = 2;
 $successes=0;
 $failures=0;
 $max_jitter=0;
 $times=array();
 $seq_counter = 1;
// $ping->debug = true;
// $ping->identity = 1;
 ob_implicit_flush();
 echo "Pinging ".$host." with ".$ping->data_package_size." bytes of data:<br /><br />";

echo '<table border="1">'; // create a table
echo '<tr><th>No.</th><th>Source</th><th>Bytes</th><th>Time (ms)</th><th>TTL</th><th>Jitter (ms)</th></tr>'; // table's header

for($i=0; $i<=9; $i=$i+1)
{
 $ping->sequence = $seq_counter++;
 $result = $ping->Ping();
// echo $ping->identity." ";
// echo $ping->sequence." ";
 if ($result<0) // failed
    {
      $failures++;
      echo '<tr BGCOLOR="#FF0000">'; // start a new row
      echo '<td>'.$ping->sequence.'</td>'.'<td colspan="5" >Error: '.$ping->strError($result).'</td>';
    }
 else
    { 
     $successes++;
     echo '<tr>'; // start a new row
     $times[]=$result;
     if ($max_jitter < $ping->last["jitter"]) $max_jitter = $ping->last["jitter"];
     if ($ping->last["set"])
        echo '<td>'.$ping->sequence.'</td>'.'<td>'.$ping->last["source"].'</td>'.'<td>'.$ping->data_package_size.'</td>'.'<td>'.$result.'</td>'.'<td>'.$ping->last["ttl"].'</td>'.'<td>'.$ping->last["jitter"].'</td>';
     else echo '<td>'.$ping->sequence.'</td>'.'<td colspan="5" >'.$result.'</td>';
    }
 usleep(1000000);
 echo '</tr>'; // end the row
}
echo '</table>'; // end the table
$seq_counter--;
DisplayTotals($host,$successes,$failures,$times,$seq_counter,$max_jitter);
usleep(1000000); // for making sure the scrollbar will hit the end
?>


</body>
</html>
Here's a screen shot of how it looks rt now
Image

Edit 2: socket_read packet size is now dynamic so it could handle packets with more than 227 bytes of data

Post Reply