Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieve Time from NTP server via PHP

Tags:

php

ntp

I have an issue with a Virtual Machine (VMWare/Ubuntu) that is getting out of time sync. We decided that the best thing to do was to get the time directly from an NTP server so I started researching a script to do that but nothing works so far, that's why I decided to come here and see if someone can point me in the right direction.

To clarify, I'm looking for a way to connect to a Time Server via PHP to retrieve the time directly and reuse it on a timestamp that I need to generate not to sync the server.

like image 449
Carlos M. Avatar asked May 16 '13 15:05

Carlos M.


People also ask

Can the system clock get its date and time from a NTP server?

You can easily keep your system's date and time accurate by using network time protocol (NTP). Having an accurate clock on your server ensures that timestamps in emails sent from your machine are correct.

What is ntpdate used for?

ntpdate is a computer program used to quickly synchronize and set computers' date and time by querying a Network Time Protocol (NTP) server. It is available for a wide variety of unix-like operating systems.


2 Answers

I wanted to retrieve time from a ntp server listening on port 123 UDP rfc5905. I found a solution here. Beneath my working page php:

<!doctype html>  

<?php
session_start();
$please_wait = '';
$last = time();
if(isset($_SESSION['last'])) {
$last = $_SESSION['last'];
}
else {
$_SESSION['last'] = $last;
}
//wrap the whole thing in a test for the last hit-time on the page, to avoid abusing NTP servers
if (time() - $last < 15) {
  $wait = time() - $last;
  $please_wait = 'Request limit exceeded, please wait ' . (15 - $wait) . ' s.';
  $server = $vn_response = $mode_response = $stratum_response = $remote_originate = $remote_received
  = $remote_received = $remote_transmitted = $delay_ms = $ntp_time_formatted = $server_time_formatted = $please_wait;
}
else {
  $_SESSION['last'] = time();

  $bit_max = 4294967296;
  $epoch_convert = 2208988800;
  $vn = 3;
  
  $servers = array('0.uk.pool.ntp.org','1.uk.pool.ntp.org','2.uk.pool.ntp.org','3.uk.pool.ntp.org');
  $server_count = count($servers);
  
  //see rfc5905, page 20
  //first byte
  //LI (leap indicator), a 2-bit integer. 00 for 'no warning'
  $header = '00';
  //VN (version number), a 3-bit integer.  011 for version 3
  $header .= sprintf('%03d',decbin($vn));
  //Mode (association mode), a 3-bit integer. 011 for 'client'
  $header .= '011';
  
  //echo bindec($header);    
      
  //construct the packet header, byte 1
  $request_packet = chr(bindec($header));
  
  //we'll use a for loop to try additional servers should one fail to respond
  $i = 0;
  for($i; $i < $server_count; $i++) {
    $socket = @fsockopen('udp://'.$servers[$i], 123, $err_no, $err_str,1);
    if ($socket) {
      
      //add nulls to position 11 (the transmit timestamp, later to be returned as originate)
      //10 lots of 32 bits
      for ($j=1; $j<40; $j++) {
        $request_packet .= chr(0x0);
      }
      
      //the time our packet is sent from our server (returns a string in the form 'msec sec')
      $local_sent_explode = explode(' ',microtime());
      $local_sent = $local_sent_explode[1] + $local_sent_explode[0];
      
      //add 70 years to convert unix to ntp epoch
      $originate_seconds = $local_sent_explode[1] + $epoch_convert;
        
      //convert the float given by microtime to a fraction of 32 bits
      $originate_fractional = round($local_sent_explode[0] * $bit_max);
        
      //pad fractional seconds to 32-bit length
      $originate_fractional = sprintf('%010d',$originate_fractional);
        
      //pack to big endian binary string
      $packed_seconds = pack('N', $originate_seconds);
      $packed_fractional = pack("N", $originate_fractional);
  
      //add the packed transmit timestamp
      $request_packet .= $packed_seconds;
      $request_packet .= $packed_fractional;
      
      if (fwrite($socket, $request_packet)) {
        $data = NULL;
        stream_set_timeout($socket, 1);
        
        $response = fread($socket, 48);
        
        //the time the response was received
        $local_received = microtime(true);
        
        //echo 'response was: '.strlen($response).$response;
      }
      fclose($socket);
      
      if (strlen($response) == 48) {
        //the response was of the right length, assume it's valid and break out of the loop
        break;
      }
      else {
        if ($i == $server_count-1) {
          //this was the last server on the list, so give up
          die('unable to establish a connection');
        }
      }
    }
    else {
      if ($i == $server_count-1) {
        //this was the last server on the list, so give up
        die('unable to establish a connection');
      }
    }
  }
  
  //unpack the response to unsiged lonng for calculations
  $unpack0 = unpack("N12", $response);
  //print_r($unpack0);
  
  //present as a decimal number
  $remote_originate_seconds = sprintf('%u', $unpack0[7])-$epoch_convert;
  $remote_received_seconds = sprintf('%u', $unpack0[9])-$epoch_convert;
  $remote_transmitted_seconds = sprintf('%u', $unpack0[11])-$epoch_convert;
  
  $remote_originate_fraction = sprintf('%u', $unpack0[8]) / $bit_max;
  $remote_received_fraction = sprintf('%u', $unpack0[10]) / $bit_max;
  $remote_transmitted_fraction = sprintf('%u', $unpack0[12]) / $bit_max;
  
  $remote_originate = $remote_originate_seconds + $remote_originate_fraction;
  $remote_received = $remote_received_seconds + $remote_received_fraction;
  $remote_transmitted = $remote_transmitted_seconds + $remote_transmitted_fraction;
  
  //unpack to ascii characters for the header response
  $unpack1 = unpack("C12", $response);
  //print_r($unpack1);
  
  //echo 'byte 1: ' . $unpack1[1] . ' | ';
  
  //the header response in binary (base 2)
  $header_response =  base_convert($unpack1[1], 10, 2);
  
  //pad with zeros to 1 byte (8 bits)
  $header_response = sprintf('%08d',$header_response);
  
  //Mode (the last 3 bits of the first byte), converting to decimal for humans;
  $mode_response = bindec(substr($header_response, -3));
  
  //VN
  $vn_response = bindec(substr($header_response, -6, 3));
  
  //the header stratum response in binary (base 2)
  $stratum_response =  base_convert($unpack1[2], 10, 2);
  $stratum_response = bindec($stratum_response);
  //echo 'stratum: ' . bindec($stratum_response);
  
  //calculations assume a symmetrical delay, fixed point would give more accuracy
  $delay = (($local_received - $local_sent) / 2)  - ($remote_transmitted - $remote_received);
  $delay_ms = round($delay * 1000) . ' ms';
  //echo 'delay: ' . $delay * 1000 . 'ms';
  
  $server = $servers[$i];
  
  $ntp_time =  $remote_transmitted - $delay;
  $ntp_time_explode = explode('.',$ntp_time);
  
  $ntp_time_formatted = date('Y-m-d H:i:s', $ntp_time_explode[0]).'.'.$ntp_time_explode[1];
  
  //compare with the current server time
  $server_time =  microtime();
  $server_time_explode = explode(' ', $server_time);
  $server_time_micro = round($server_time_explode[0],4);
  
  $server_time_formatted = date('Y-m-d H:i:s', time()) .'.'. substr($server_time_micro,2);

}
?>

<html lang="en">  
<head>  
<meta charset="utf-8">
<title></title>  
<meta name="description" content="">  
<meta name="author" content="">
<style type="text/css">
td{
    width: 160px; height: 20px;
    padding: 4px;
    border: 1px solid #000;
    font-size: 12px;
}
.ntp_response{
    width: 240px;
}  
</style>
</head>
<body>
<table border="0">
<tr>
<td>Server:
<td class="ntp_response"><?php echo $server;?></td>
</tr>
<tr>
<td>VN (version number):</td>
<td class="ntp_response"><?php echo $vn_response;?></td>
</tr>
<tr>
<td>Mode:</td>
<td class="ntp_response"><?php echo $mode_response;?></td>
</tr>
<tr>
<td>Stratum:</td>
<td class="ntp_response"><?php echo $stratum_response;?></td>
</tr>
<tr>
<td>Origin time:</td>
<td class="ntp_response"><?php echo $remote_originate;?></td>
</tr>
<td>Received:</td>
<td class="ntp_response"><?php echo $remote_received;?></td>
</tr>
<td>Transmitted:</td>
<td class="ntp_response"><?php echo $remote_transmitted;?></td>
</tr>
<td>Delay:</td>
<td class="ntp_response"><?php echo $delay_ms;?></td>
</tr>
<td>NTP time:</td>
<td class="ntp_response"><?php echo $ntp_time_explode[0];?></td>
</tr>
<td>Server time:</td>
<td class="ntp_response"><?php echo $server_time_formatted;?></td>
</tr>
</table>
</body>
</html>
like image 106
FRa Avatar answered Oct 16 '22 20:10

FRa


Here's a complete working code:

<?php
error_reporting(E_ALL ^ E_NOTICE);
ini_set("display_errors", 1);

date_default_timezone_set("America/Argentina/Buenos_Aires");

/* Query a time server (C) 1999-09-29, Ralf D. Kloth (QRQ.software) <ralf at qrq.de> */
function query_time_server ($timeserver, $socket)
{
    $fp = fsockopen($timeserver,$socket,$err,$errstr,5);
        # parameters: server, socket, error code, error text, timeout
    if($fp)
    {
        fputs($fp, "\n");
        $timevalue = fread($fp, 49);
        fclose($fp); # close the connection
    }
    else
    {
        $timevalue = " ";
    }

    $ret = array();
    $ret[] = $timevalue;
    $ret[] = $err;     # error code
    $ret[] = $errstr;  # error text
    return($ret);
} # function query_time_server


$timeserver = "ntp.pads.ufrj.br";
$timercvd = query_time_server($timeserver, 37);

//if no error from query_time_server
if(!$timercvd[1])
{
    $timevalue = bin2hex($timercvd[0]);
    $timevalue = abs(HexDec('7fffffff') - HexDec($timevalue) - HexDec('7fffffff'));
    $tmestamp = $timevalue - 2208988800; # convert to UNIX epoch time stamp
    $datum = date("Y-m-d (D) H:i:s",$tmestamp - date("Z",$tmestamp)); /* incl time zone offset */
    $doy = (date("z",$tmestamp)+1);

    echo "Time check from time server ",$timeserver," : [<font color=\"red\">",$timevalue,"</font>]";
    echo " (seconds since 1900-01-01 00:00.00).<br>\n";
    echo "The current date and universal time is ",$datum," UTC. ";
    echo "It is day ",$doy," of this year.<br>\n";
    echo "The unix epoch time stamp is $tmestamp.<br>\n";


    echo date("d/m/Y H:i:s", $tmestamp);
}
else
{
    echo "Unfortunately, the time server $timeserver could not be reached at this time. ";
    echo "$timercvd[1] $timercvd[2].<br>\n";
}
?>

More info in this link

like image 20
Matías Cánepa Avatar answered Oct 16 '22 19:10

Matías Cánepa