ppping
Posted: 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:
and my ping.php:
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?
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
?>
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?