<?php
##
## this file name is 'class.ping.php'
##
## ping object
##
## [author]
##  - http://linuxchannel.net/
##
## [changes]
##  - 2016.05.23 : add error_print()
##  - 2016.01.06 : some
##  - 2015.12.23 : more
##  - 2008.12.01 : new build
##
## [references]
##  - http://kr.php.net/manual/en/function.socket-create.php
##
## [usage]
##
## [example]
##

@error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);

class 
icmp
{
  public function 
make_socket($timeout = array('sec'=>0,'usec'=>500000))
  {
    if(!
$sock = @socket_create(AF_INETSOCK_RAWgetprotobyname('icmp')))
    {
        return array(-
2socket_last_error());
    }

    
// do not set this options san2@2016.01.06
    //@socket_set_option($sock,SOL_SOCKET,SO_RCVTIMEO,$timeout); // good idea for blocking mode
    //@socket_set_option($sock,SOL_SOCKET,SO_SNDTIMEO,$timeout);

    
return $sock;
  }

  
// timeout in (seconds, microtime) 
  
public function ping($host$sock=NULL$timeout=array('sec'=>1,'usec'=>0))
  {
    static 
$_port 0;
    static 
$_datasize 58// default 58 bytes + 8 = 64 bytes
    
static $_timeout = array('sec'=>1'usec'=>0);
    static 
$_select_timeout 2;

    if(!
$timeout$timeout $_timeout;

    
$ident = array(ord('J'), ord('C'));
    
$seq = array(rand(0255), rand(0255));
    
//$seq = array(55,56);

    ## 8 bytes of ICMP header data
    
$packet '';
    
$packet .= chr(8); // type = 8 : request
    
$packet .= chr(0); // code = 0
    
$packet .= chr(0); // checksum init
    
$packet .= chr(0); // checksum init
    
$packet .= chr($ident[0]); // identifier
    
$packet .= chr($ident[1]); // identifier
    
$packet .= chr($seq[0]); // seq
    
$packet .= chr($seq[1]); // seq

    
for($i=0$i<$_datasize$i++) $packet .= chr(0);

    
$chk icmp::icmpchecksum($packet);

    
$packet[2] = $chk[0]; // checksum init
    
$packet[3] = $chk[1]; // checksum init

    
if(preg_match('/[a-z]/',$host)) $host gethostbyname($host); // to ip

    
if(!$sock)
    {
        
$sock icmp::make_socket($timeout);
        if(
is_array($sock)) return $sock// error
    
}

    
$_time time(); // idle check initial
    
$read   = array($sock);
    
$buf '';

    
$time_start microtime();
    @
socket_sendto($sock$packetstrlen($packet), MSG_DONTWAIT$host$_port);
    
$time_send microtime();

    while(
1)
    {
        if((
time()-$_time) >= $_select_timeout)
        {
            return array(-
1"Timeout Response ${_select_timeout}s,".$isvalid[1]);
            break;
        }
        
$select = @socket_select($read$write=NULL$except=NULL$timeout['sec'], $timeout['usec']);
//$errno = @socket_last_error();
//echo "$i $select $errno\n";
        
if($select === FALSE) continue;
        if(
$select === NULL)
        {
            
//@socket_close($sock);
            
return array(-1'Select Error.'.$isvalid[1]);
        }
        else if(
$select === 0)
        {
            
//@socket_close($sock);
            
return array(-1'Timeout.'.$isvalid[1]);
        }

        
$recv socket_read($sock,1024);
        
$time_stop microtime();
        
$recv unpack('C*',$recv);
        if(
is_array($isvalid=icmp::is_valid_packet($recv,$ident,$seq))) continue;
        break; 
// require
    
}

    if(!
$time_stop$time_stop microtime();
    @
socket_close($sock);

//echo "\n".implode(',',array_slice($recv,0,32))."\n";

    //$ttt = icmp::getms($time_start, $time_send);
    //$gtt = icmp::getms($time_send, $time_stop);
    
$rtt icmp::getms($time_start$time_stop);

    return array(
$rtt);
  }

  private function 
is_valid_packet($recv$ident$seq)
  {
    if(
$recv[4] != 86) return array(-1,"Not Match packet, 86=>$recv[4], 1=>$recv[10], 0=>$recv[21]");
    
// ICMP proto = 1
    
if($recv[10] !== 1) return array(-1,"Not ICMP packet, 1=>$recv[10]");
    
// ICMP response = 0
    
if($recv[21] !== 0) return array(-1,"Not ICMP response, 0=>$recv[21]");
    if(
$ident[0] !== $recv[25] || $ident[1] !== $recv[26]) return array(-1,"Bad identification number, $ident[0]=>$recv[25]$ident[1]=>$recv[26]");
    if(
$seq[0] !== $recv[27] || $seq[1] !== $recv[28]) return array(-1,"Bad sequence number, $seq[0]=>$recv[27]$seq[1]=>$recv[28])");

    return 
TRUE;
  }

  private function 
icmpchecksum($data)
  {
    
$bit unpack('n*'$data);
    
$sum array_sum($bit);

    if(
strlen($data) % 2)
    {
        
$temp unpack('C*'$data[strlen($data) - 1]);
        
$sum += $temp[1];
    }

    
$sum  = ($sum >> 16) + ($sum 0xffff);
    
$sum += ($sum >> 16);

    return 
pack('n*', ~$sum);
  }

  public function 
getms($_start$_end)
  {
    
$end explode(' '$_end);
    
$start explode(' '$_start);

    return 
sprintf('%.3f',(($end[1]+$end[0])-($start[1]+$start[0])) * 1000);
  }

  public function 
error($errstr)
  {
    return 
error_log($errstr,0);
  }

  public function 
error_print($errstr)
  {
    return 
fwrite(STDERR,rtrim($errstr)."\n");
  }

  public function 
socket_info($socket)
  {
    
$opt = array
    (
    
'SO_DEBUG'=>SO_DEBUG,
    
'SO_ACCEPTCONN'=>SO_ACCEPTCONN,
    
'SO_BROADCAST'=>SO_BROADCAST,
    
'SO_REUSEADDR'=>SO_REUSEADDR,
    
'SO_KEEPALIVE'=>SO_KEEPALIVE,
    
'SO_LINGER'=>SO_LINGER,
    
'SO_OOBINLINE'=>SO_OOBINLINE,
    
'SO_SNDBUF'=>SO_SNDBUF,
    
'SO_RCVBUF'=>SO_RCVBUF,
    
'SO_ERROR'=>SO_ERROR,
    
'SO_TYPE'=>SO_TYPE,
    
'SO_DONTROUTE'=>SO_DONTROUTE,
    
'SO_RCVLOWAT'=>SO_RCVLOWAT,
    
'SO_RCVTIMEO'=>SO_RCVTIMEO,
    
'SO_SNDLOWAT'=>SO_SNDLOWAT,
    
'SO_SNDTIMEO'=>SO_SNDTIMEO,
    );

    foreach (
$opt as $k=>$value)
    {
        
$r socket_get_option($socketSOL_SOCKET$value);
        echo 
"$k : \n";
        
print_r($r);
        echo 
"\n";
    }
  }
// end of class
?>