In theory the module should be compatible with all OS (WindowsOS, LinuxOS and MacOS) please verify that with me since I only have LinuxOS.
I am planning to create also another module Net::SNTP::Server which is approximately an SNTP server and when I say approximately is because I can not figure it out how to replicate the server side. But any way first thing first.
Is it possible to take a look and assist me in possible improvements and comments. Since this is my first module I have no experience so maybe the module is not well written.
The execution of the script is very simple, create a script e.g. client.pl and put the code bellow.
The first option is to get an RFC4330 printout way, and the second option is to clear the screen before the printout. I think both options will be useful on the printout of the script.
I have chosen to paste the module in the folder path "/home/username/Desktop/SNTP_Module/Net/SNTP/Client.pl". Remember for testing purposes to change the path on client.pl accordingly on the location that you will place the module.
package Net::SNTP::Client;
=head1 NAME
Net::SNTP::Client - Perl module to retrieve higher accuracy from NTP s
+erver
=head1 SYNOPSIS
use Net::SNTP::Client;
my %hashInput = (
-hostname => "0.pool.ntp.org", # hostnmae or IP
-port => 123, # default NTP port 123
-RFC4330 => 1,
-clearScreen => 1,
);
my ( $error , $hashRefOutput ) = getSNTPTime( %hashInput );
=head1 ABSTRACT
The module sends a UDP packet formated according to L<RFC4330|https://
+tools.ietf.org/html/rfc4330> to a defined NTP server set by the user.
+ The received packet, gets decoded to a human readable form and also
+calculated the roundtrip delay d and system clock offset t, based on
+the decoded data.
=head1 DESCRIPTION
This module exports a single method (getSNTPTime) and returns an assoc
+iative hash of hashes upon RFC4330 and a string in case of an error o
+ccurs. The response from the NTP or SNTP server is beeen decoded to a
+ human readable format. The obtained information recieved from the se
+rver can be can be used into further processing or manipulation accor
+ding to the user needs. Maximum accuracy down to nano seconds can onl
+y be achieved on LinuxOS.
=head2 EXPORT
my %hashInput = (
-hostname => "0.pool.ntp.org", # hostnmae or IP
-port => 123, # default NTP port 123
-RFC4330 => 1,
-clearScreen => 1,
);
my ( $error , $hashRefOutput ) = getSNTPTime( %hashInput );
This module exports a single method - getSNTPTime and an error string
+in case of an error or a faulty operation. It expects a hash as an in
+put. The input can have four different hash keys => values (-hostname
+, port, RFC4330 and -clearScreen).
-hostname: The mandatory key inorder the method to produce an output i
+s only the hostname, the rest of the keys are optional.
-port: By default the the port is set to 123 (NTP default port). The u
+ser has the option to overwite the port based on his preference (e.g.
+ -port => 123456).
-RFC4330: This is an optional way to produce an easy visuable output b
+ased on RFC4330 documentation.
-clearScreen: This is an optional choice based on user preference if h
+e/she desires to clear the "terminal screen" before printing the capt
+ured data.
=cut
use strict;
use warnings;
## Validate the version of Perl
BEGIN
{
die 'Perl version 5.6.0 or greater is required' if ($] < 5.006);
}
## Version of the Net::SNTP::Client module
our $VERSION = '0.01';
$VERSION = eval $VERSION;
## Load our modules
use IO::Socket::INET;
use Time::HiRes qw( gettimeofday );
## Handle importing/exporting of symbols
use base qw( Exporter );
our @ISA = qw ( Exporter );
our @EXPORT = qw ( getSNTPTime );
## Define constands
use constant {
TRUE => 1,
FALSE => 0,
MAXBYTES => 512,
ARGUMENTS => 1,
UNIX_EPOCH => 2208988800,
MIN_UDP_PORT => 1,
MAX_UDP_PORT => 65535,
DEFAULT_NTP_PORT => 123,
};
sub getSNTPTime {
my $unpack_ip = sub {
my $ip;
my $stratum = shift;
my $tmp_ip = shift;
if($stratum < 2){
$ip = unpack("A4",
pack("H8", $tmp_ip)
);
}else{
$ip = sprintf("%d.%d.%d.%d",
unpack("C4",
pack("H8", $tmp_ip)
)
);
}
return $ip;
};
my $bin_2_frac = sub {
my @bin = split '', shift;
my $frac = 0;
while (@bin) {
$frac = ( $frac + pop @bin ) / 2;
}
return $frac;
};
my $bin_2_dec = sub {
my $bits = shift;
my $size = shift;
my $template = shift;
return unpack($template, pack("B$size", substr("0" x $size . $bits
+ , -$size)));
};
my $checkHashKeys = sub {
my @keysToCompare = ( "-hostname", "-port", "-RFC4330", "-clearScr
+een" );
my %hashInputToCompare = @_;
my @hashInputKeysToCompare = keys %hashInputToCompare;
my @differendKeys = keyDifference(\@hashInputKeysToCompare, \@keys
+ToCompare);
if (@differendKeys) { return TRUE } else { return FALSE };
sub keyDifference {
my %hashdiff = map{ $_ => 1 } @{$_[1]};
return grep { !defined $hashdiff{$_} } @{$_[0]};
}
};
my $verify_port = sub {
my $port = shift;
if ( defined $port && $port =~ /^[+-]?(?=\.?\d)\d*\.?\d*(?:e[+-]?\
+d+)?\z/i ) {
if ( $port <= MIN_UDP_PORT || $port >= MAX_UDP_PORT ) {
return TRUE;
}
}
return FALSE;
};
my $dot = ".";
my $error = undef;
my $rcvSntpPacket = undef;
my %moduleInput = @_;
my %moduleOutput = ();
return ($error = "Not defined key(s)", \%moduleInput) if ($checkHa
+shKeys->(%moduleInput));
return ($error = "Not defined Hostname", \%moduleInput) if (!$modu
+leInput{-hostname});
return ($error = "Not correct port number", \%moduleInput) if ($ve
+rify_port->($moduleInput{-port}));
my $client_socket;
eval {
$client_socket = new IO::Socket::INET (
PeerHost => $moduleInput{-hostname},
Type => SOCK_DGRAM,
PeerPort => $moduleInput{-port} || DEFAULT_NTP_PORT, # Default
+ NTP port 123
Proto => 'udp'
) or die "Error Creating Socket";
};
return ($error = "Problem While Creating Socket '$!'", \%moduleInp
+ut) if ( $@ && $@ =~ /Error Creating Socket/ );
my $clientLi = 0;
my $clientVn = 4;
my $clientMode = 3;
# $li = 00 2 bit = value 0 no warning
# $vn = 100 3 bit = Value 4 IPV4
# $mode = 011 3 bit = Value 3 client mode
my $clientLiVnMode = '00100011'; # 8 bit
my $clientStratum = '0'; # 8 bit
my $clientPoll = '0'; # 8 bit
my $clientPrecision = '0'; # 8 bit
my $clientRootDelay = '0'; # 32 bit
my $clientDispersion = '0'; # 32 bit
my $clientReferenceIdentifier = '0'; # 32 bit
my $clientReferenceTimestampSec = '0'; # 32 bit
my $clientReferenceTimestampMicrosec = '0'; # 32 bit
my $clientOriginateTimestampSec = '0'; # 32 bit
my $clientOriginateTimestampMicrosec = '0'; # 32 bit
my $clientReceiveTimestampSec = '0'; # 32 bit
my $clientReceiveTimestampMicrosec = '0'; # 32 bit
my ($clientTransmitSec , $clientTransmitMicrosec) = gettimeofday()
+;
my $clientTransmitTimestamp = $clientTransmitSec . $dot . $clientT
+ransmitMicrosec;
my @arraySendSntpPacket = ( $clientLiVnMode , $clientStratum , $cl
+ientPoll , $clientPrecision , $clientRootDelay , $clientDispersion ,
+$clientReferenceIdentifier , $clientReferenceTimestampSec , $clientRe
+ferenceTimestampMicrosec , $clientOriginateTimestampSec , $clientOrig
+inateTimestampMicrosec , $clientReceiveTimestampSec , $clientReceiveT
+imestampMicrosec , $clientTransmitSec , $clientTransmitMicrosec );
my $sendSntpPacket = pack "B8 C3 N11", @arraySendSntpPacket;
eval {
$client_socket->send( $sendSntpPacket )
or die "Error Sending";
};
return ($error = "Problem While Sending '$!'", \%moduleInput) if (
+ $@ && $@ =~ /Error Sending/ );
eval {
$client_socket->recv( $rcvSntpPacket , MAXBYTES )
or die "Error Receiving";
};
return ($error = "Problem While Receiving '$!'", \%moduleInput) if
+ ( $@ && $@ =~ /Error Receiving/ );
($clientReceiveTimestampSec , $clientReceiveTimestampMicrosec) = g
+ettimeofday();
my $clientReceiveTimestamp = $clientReceiveTimestampSec . $dot . $
+clientReceiveTimestampMicrosec;
eval {
$client_socket->close()
or die "Error Closing Socket";
};
return ($error = "Problem While Clossing Socket '$!'", \%moduleInp
+ut) if ( $@ && $@ =~ /Error Closing Socket/ );
my @arrayRcvSntpPacket = unpack("B8 C3 n B16 n B16 H8 N8" , $rcvSn
+tpPacket);
my ( $serverLiVnMode , $serverStratum , $serverPollInterval , $ser
+verPrecision , $serverRootDelaySec , $serverRootDelayMicrosec , $serv
+erDispersionSec, $serverDispersionMicrosec , $serverReferenceIdentifi
+erBinary , $serverReferenceTimestampSec , $serverReferenceTimestampMi
+crosec , $serverOriginateTimestampSec , $serverOriginateTimestampMicr
+osec , $serverReceiveTimestampSec , $serverReceiveTimestampMicrosec ,
+ $serverTransmitTimestampSec , $serverTransmitTimestampMicrosec ) = @
+arrayRcvSntpPacket;
my $serverLiBinary = substr( $serverLiVnMode , 0 , 2 );
my $serverLi = $bin_2_dec->( $serverLiBinary , 8 , "c" );
my $serverVnBinary = substr( $serverLiVnMode , 2 , 3 );
my $serverVn = $bin_2_dec->( $serverVnBinary , 8 , "c" );
my $serverModeBinary = substr( $serverLiVnMode , 5 , 3 );
my $serverMode = $bin_2_dec->( $serverModeBinary , 8 , "c" );
my $serverPoll = (sprintf("%0.1d", $serverPollInterval));
if ($serverPrecision < 127) {
$serverPrecision = 0;
}
else {
$serverPrecision = $serverPrecision - 255;
}
my $serverRootDelay = ($bin_2_frac -> ($serverRootDelayMicrosec));
my $serverDispersionSeconds = sprintf("%0.10f", $serverDispersionS
+ec);
$serverDispersionSeconds =~ s/\..*$//;
my $serverDispersionFinalSeconds = $bin_2_frac->($serverDispersion
+Seconds);
my $serverDispersionMicroSeconds = sprintf("%0.10f", $serverDisper
+sionMicrosec);
$serverDispersionMicroSeconds =~ s/\..*$//;
my $serverDispersionFinalMicroSeconds = $bin_2_frac->($serverDispe
+rsionMicroSeconds);
my $serverReferenceIdentifier = $unpack_ip->($serverStratum, $serv
+erReferenceIdentifierBinary);
$serverReferenceTimestampSec -= UNIX_EPOCH;
my $serverReferenceTimestamp = $serverReferenceTimestampSec . $dot
+ . $serverReferenceTimestampMicrosec;
my $serverOriginateTimestamp = $serverOriginateTimestampSec . $dot
+ . $serverOriginateTimestampMicrosec;
$serverReceiveTimestampSec -= UNIX_EPOCH;
my $serverReceiveTimestamp = $serverReceiveTimestampSec . $dot . $
+serverReceiveTimestampMicrosec;
$serverTransmitTimestampSec -= UNIX_EPOCH;
my $serverTransmitTimestamp = $serverTransmitTimestampSec . $dot .
+ $serverTransmitTimestampMicrosec;
my $d = ( ( $clientReceiveTimestamp - $clientTransmitTimestamp )
+- ( $serverReceiveTimestamp - $serverTransmitTimestamp ) );
my $t = ( ( ( $serverReceiveTimestamp - $clientTransmitTimestamp
+) + ( $serverTransmitTimestamp - $clientReceiveTimestamp ) ) / 2 );
(system $^O eq 'MSWin32' ? 'cls' : 'clear') if ($moduleInput{-clea
+rScreen});
if ($moduleInput{-RFC4330}) {
$moduleOutput{-RFC4330} = "
\t Timestamp Name \t ID \t When Generated
\t ------------------------------------------------------------
\t Originate Timestamp \t T1 \t time request sent by client
\t Receive Timestamp \t T2 \t time request received by server
\t Transmit Timestamp \t T3 \t time reply sent by server
\t Destination Timestamp \t T4 \t time reply received by client
\t The roundtrip delay d and local clock offset t are defined as
\t d = (T4 - T1) - (T2 - T3) \t t = ((T2 - T1) + (T3 - T4)) / 2 \n
\t Round Trip delay: ".$d."\n
\t Clock offset: ".$t."\n
\t Field Name \t\t\t Unicast/Anycast
\t\t\t\t Request \t\t Reply
\t ------------------------------------------------------------
\t LI \t\t\t ".$clientLi." \t\t\t ".$serverLi."
\t VN \t\t\t ".$clientVn." \t\t\t ".$serverVn."
\t Mode \t\t\t ".$clientMode." \t\t\t ".$serverMode."
\t Stratum \t\t ".$clientStratum." \t\t\t ".$serverStratum."
\t Poll \t\t\t ".$clientPoll." \t\t\t ".$serverPollInterval."
\t Precision \t\t ".$clientPrecision." \t\t\t ".$serverPrecision."
\t Root Delay \t\t ".$clientRootDelay." \t\t\t ".$serverRootDelay."
\t Root Dispersion \t ".$clientDispersion." \t\t\t ".$serverDispersion
+FinalMicroSeconds."
\t Reference Identifier \t ".$clientReferenceIdentifier." \t\t\t ".$se
+rverReferenceIdentifier."
\t Reference Timestamp \t ".$clientReferenceTimestampSec.$clientRefere
+nceTimestampMicrosec." \t\t\t ".$serverReferenceTimestamp."
\t Originate Timestamp \t ".$clientOriginateTimestampSec.$clientOrigin
+ateTimestampMicrosec." \t\t\t ".$serverOriginateTimestamp."
\t Receive Timestamp \t ".$clientReceiveTimestamp." \t ".$serverReceiv
+eTimestamp."
\t Transmit Timestamp \t ".$clientTransmitTimestamp." \t ".$serverTran
+smitTimestamp."\n\n";
}
else {
%moduleOutput = (
$moduleInput{-hostname} => {
"LI" => $serverLi,
"VN" => $serverVn,
"Mode" => $serverMode,
"Stratum" => $serverStratum,
"Poll" => $serverPollInterval,
"Precision" => $serverPrecision,
"Root Delay" => $serverRootDelay,
"Root Dispersion" => $serverDispersionFinalMicroSeconds,
"Reference Identifier" => $serverReferenceIdentifier,
"Reference Timestamp" => $serverReferenceTimestamp,
"Originate Timestamp" => $serverOriginateTimestamp,
"Receive Timestamp" => $serverReceiveTimestamp,
"Transmit Timestamp" => $clientTransmitTimestamp,
},
$0 => {
"LI" => $clientLi,
"VN" => $clientVn,
"Mode" => $clientMode,
"Stratum" => $clientStratum,
"Poll" => $clientPoll,
"Precision" => $clientPrecision,
"Root Delay" => $clientRootDelay,
"Root Dispersion" => $clientDispersion,
"Reference Identifier" => $clientReferenceIdentifier,
"Reference Timestamp" => $clientReferenceTimestampSec.$client
+ReferenceTimestampMicrosec,
"Originate Timestamp" => $clientOriginateTimestampSec.$client
+OriginateTimestampMicrosec,
"Receive Timestamp" => $clientReceiveTimestamp,
"Transmit Timestamp" => $serverTransmitTimestamp,
},
RFC4330 => {
"Round Trip Delay" => $d,
"Clock Offset" => $t
}
);
}
return $error, \%moduleOutput;
}
=head1 BUGS AND SUPPORT
This module should be considered beta quality, everything seems to wor
+k but it may yet contain critical bugs.
If you find any, report it via email (to garyfalos@cpan.org), please.
Feedback and comments are also welcome!
=head1 SEE ALSO
perl, IO::Socket, Net::NTP, RFC4330
Net::NTP has a similar focus as this module. In my opinion it is less
+accurate when it comes to the precission bellow second(s).
=head1 AUTHOR
Athanasios Garyfalos E<lt>garyfalos@cpan.orgE<gt>
=head1 ACKNOWLEDGMENTS
The original concept for this module was based on F<NTP.pm>
written by James G. Willmore E<lt>willmorejg@gmail.comE<gt>.
Copyright 2004 by James G. Willmore
This library is free software; you can redistribute it and/or modify i
+t under
the same terms as Perl itself.
=head1 LICENSE AND COPYRIGHT
Copyright (c) 2015 Athanasios Garyfalos. All rights reserved.
This program is free software; you may redistribute it and/or modify i
+t under
the same terms as the Perl 5 programming language system itself.
=head1 CHANGE LOG
$Log: Client.pm,v $
Revision 1.0 2015/07/01 22:53:31 Thanos
=cut
1;
Thank you for your time and effort reading and replying to my question/review.