File Coverage

blib/lib/Net/SNTP/Server.pm
Criterion Covered Total %
statement 52 138 37.6
branch 15 42 35.7
condition 4 32 12.5
subroutine 13 17 76.4
pod 1 1 100.0
total 85 230 36.9


line stmt bran cond sub pod time code
1             package Net::SNTP::Server;
2              
3 1 50   1   21945 BEGIN { die 'Perl version 5.6.0 or greater is required' if ($] < 5.006); }
4              
5 1     1   8 use strict;
  1         2  
  1         22  
6 1     1   5 use warnings;
  1         1  
  1         53  
7              
8             =head1 NAME
9              
10             Net::SNTP::Server - Perl Module SNTP Server based on L
11              
12             =head1 VERSION
13              
14             Version 0.04
15              
16             =cut
17              
18             ## Version of the Net::SNTP::Server module
19              
20             our $VERSION = '0.04';
21             $VERSION = eval $VERSION;
22              
23 1     1   832 use POSIX qw();
  1         7645  
  1         29  
24 1     1   1469 use IO::Socket::INET;
  1         26680  
  1         8  
25 1     1   1664 use Time::HiRes qw( gettimeofday );
  1         1642  
  1         5  
26              
27             ## Handle importing/exporting of symbols
28              
29 1     1   165 use base qw( Exporter );
  1         2  
  1         159  
30             our @EXPORT_OK = qw( basicSNTPSetup ); # symbols to export on request
31              
32             =head1 SYNOPSIS
33              
34             The Net::SNTP::Server - Perl module has the ability to reply to NTP or SNTP
35             client queries towards the Server. The moment that the server is correctly
36             initialized, it will wait for client requests on the IP and port defined by
37             the user. The SNTP server uses the local clock to retrieve highest possible
38             accuracy (seconds and nano seconds) in-order to calculate the round-trip delay
39             d and system clock offset t. Accuracy may differ from one OS to the other. The
40             server will encode the message and formated based on L protocol specifications.
41             The server will remain active until the user decides to terminate the connection.
42              
43             use Net::SNTP::Server;
44              
45             my ( $error , $hashRefOutput ) = basicSNTPSetup( %hashInput );
46             ...
47              
48              
49             =head1 ABSTRACT
50              
51             The module retrieves and sends a UDP packet formated according to L to a defined NTP
52             or SNTP server sent by the Client. The received packet, gets decoded end encoded
53             to be retransmitted back to the Client.
54              
55              
56             =head1 DESCRIPTION
57              
58             This module exports a single method (basicSNTPSetup) and returns
59             an associative hash output, based on the user input. In case
60             of an error, the connection will be terminated and an error string
61             will be printed with the possible cause.
62              
63             The response from the SNTP server is been encoded to a human readable
64             format. The obtained information received from the server on the client
65             side can be used into further processing or manipulation according to the
66             user needs. Maximum accuracy down to nano seconds can only be achieved based
67             on different OS.
68              
69             =over 2
70              
71             =item * IP
72              
73             -ip: Is not a mandatory for the method key to operate correctly.
74             By default the module will assign the localhost IP ("127.0.0.1"),
75             but this will restrict the server to localhost communications (only
76             internally it can receive and transmit data).
77              
78              
79             =item * PORT
80              
81             -port: Is a mandatory key, that the user must define. By default the
82             port is not set. The user has to specify the port. We can not use the
83             default 123 NTP due to permission. The user has to choose a port number
84             identical to port that the client will use client to communicate with
85             the server (e.g. -port => 123456).
86              
87             =back
88              
89              
90              
91             =head1 IMPORTANT NOTES
92              
93             Different OS, different precision abilities. In order the user to gain the most
94             out of this module, the script should be executed in Linux-wise OS. Ofcourse
95             the module can operate correctly on all OS but due to OS accuracy limitations
96             and internal OS restrictions we need to have administrator authority to access
97             these data. For more information see L.
98              
99             Given in consideration of the information that we explained why the user should
100             execute the module on Linux, Fedora, Redhat etc. We reccomend to L the L.
101             the L package if you want to use the server as primary
102             time synchronization source and you want the daemon to run in the background
103             continuously to automatically synchronize your internal OS clock. By installing
104             the NTP package, the user can benefit from the L
105             daemon and extract all the useful extra information from the Server. In case
106             the user does not want to install the NTP package the SNTP Server can provide
107             all the information that the Client requires.
108              
109             The module has been tested on LinuxOS and WindowsOS but it should be compatible
110             with MacOS as well, but it has not been tested, not yet at least.
111              
112              
113              
114             =head1 SUBROUTINES/METHODS
115              
116             my ( $error , $hashRefOutput ) = basicSNTPSetup( %hashInput );
117              
118              
119             =cut
120              
121             use constant {
122 1         2113 TRUE => 1,
123             FALSE => 0,
124             MAXBYTES => 512,
125             UNIX_EPOCH => 2208988800,
126             MIN_UDP_PORT => 1,
127             MAX_UDP_PORT => 65535,
128             DEFAULT_LOCAL_HOST_IP => "127.0.0.1",
129 1     1   6 };
  1         2  
130              
131             =head2 basicSNTPSetup
132              
133             my %hashInput = (
134             -ip => "127.0.0.1", # IP
135             -port => 12345, # Default NTP locked port 123
136             );
137              
138             my ( $error , $hashRefOutput ) = basicSNTPSetup( %hashInput );
139              
140             This module exports a single method (basicSNTPSetup) and returns an associative
141             hash output, based on the user input. In case of an error, the connection will
142             be terminated and an error string will be printed with the possible cause.
143              
144             The response from the SNTP server is been encoded to a human readable format.
145             The obtained information received from the server on the client side can be
146             used into further processing or manipulation according to the user needs.
147             Maximum accuracy down to nano seconds can only be achieved based on different OS.
148              
149              
150             =cut
151              
152             my @SNTP_Transmit = ( { "LI VN Mode" => '00100100' },
153             { "Stratum" => '2' },
154             { "Poll" => '3' },
155             { "Precision" => undef },
156             { "Root Delay" => undef },
157             { "Root Delay Fraction" => undef },
158             { "Root Dispersion" => undef },
159             { "Root Dispersion Fraction" => undef },
160             { "Reference Identifier" => undef },
161             { "Reference Timestamp Sec" => undef },
162             { "Reference Timestamp Micro Sec" => undef },
163             { "Originate Timestamp Sec" => undef },
164             { "Originate Timestamp Micro Sec" => undef },
165             { "Receive Timestamp Sec" => undef },
166             { "Receive Timestamp Micro Sec" => undef },
167             { "Transmit Timestamp Sec" => undef },
168             { "Transmit Timestamp Micro Sec" => undef } );
169              
170             sub basicSNTPSetup {
171 7     7 1 613 my $error = undef;
172 7         26 my %moduleInput = @_;
173              
174 7 100       23 return ($error = "Not defined key(s)", \%moduleInput)
175             if (_checkHashKeys(%moduleInput));
176             return ($error = "Not defined Hostname/IP", \%moduleInput)
177 6 100       25 if (!$moduleInput{-ip});
178             return ($error = "Not defined Port", \%moduleInput)
179 5 100       20 if (!$moduleInput{-port});
180             return ($error = "Not correct port number", \%moduleInput)
181 4 50       11 if (_verifyPort($moduleInput{-port}));
182              
183             my ( @array_IP ) = ( $moduleInput{-ip}
184 4         12 =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/ );
185              
186 4 0 33     39 return ($error = "Not correct input IP syntax", \%moduleInput)
      0        
      0        
187             if ( (!defined $array_IP[0]) ||
188             (!defined $array_IP[1]) ||
189             (!defined $array_IP[2]) ||
190             (!defined $array_IP[3]) );
191              
192 0         0 my $server_socket = undef;
193 0         0 eval {
194             $server_socket = IO::Socket::INET->new(
195             LocalAddr => $moduleInput{-ip} || DEFAULT_LOCAL_HOST_IP,
196             LocalPort => $moduleInput{-port},
197 0 0 0     0 Proto => 'udp',
198             Type => SOCK_DGRAM,
199             Broadcast => 1 ) or die "Error Creating Socket";
200             };
201 0 0 0     0 return ($error = "Problem While Creating Socket '$!'", \%moduleInput)
202             if ( $@ && $@ =~ /Error Creating Socket/ );
203              
204 0         0 print "\n[Server $0 listens at PORT: ".$moduleInput{-port}." and IP: ".$moduleInput{-ip}."]\n\n";
205              
206 0         0 my @SNTP_Receive = ( "LI VN Mode",
207             "Stratum",
208             "Poll",
209             "Precision",
210             "Root Delay",
211             "Root Dispersion",
212             "Reference Identifier",
213             "Reference Timestamp Sec",
214             "Reference Timestamp Micro Sec",
215             "Originate Timestamp Sec",
216             "Originate Timestamp Micro Sec",
217             "Receive Timestamp Sec",
218             "Receive Timestamp Micro Sec",
219             "Transmit Timestamp Sec",
220             "Transmit Timestamp Micro Sec" );
221              
222 0         0 while ( TRUE ) {
223 0         0 my $peer_address = $server_socket->peerhost();
224 0         0 my $peer_port = $server_socket->peerport();
225              
226 0 0       0 if ( $peer_address ) { print "Peer address: ".$peer_address."\n" };
  0         0  
227 0 0       0 if ( $peer_port ) { print "Peer port: ".$peer_port."\n" };
  0         0  
228              
229             ( $SNTP_Transmit[9]{"Reference Timestamp Sec"},
230 0         0 $SNTP_Transmit[10]{"Reference Timestamp Micro Sec"} ) = gettimeofday();
231              
232 0         0 my $rcv_sntp_packet = undef;
233 0         0 eval {
234 0 0       0 $server_socket->recv( $rcv_sntp_packet , MAXBYTES )
235             or die "Error Receiving";
236             };
237 0 0 0     0 return ($error = "Problem While Receiving '$!'", \%moduleInput)
238             if ( $@ && $@ =~ /Error Receiving/ );
239              
240             ( $SNTP_Transmit[13]{"Receive Timestamp Sec"},
241 0         0 $SNTP_Transmit[14]{"Receive Timestamp Micro Sec"} ) = gettimeofday();
242              
243 0         0 my %RcV;
244 0         0 @RcV{@SNTP_Receive} = unpack( "B8 C3 N11" , $rcv_sntp_packet );
245              
246 0         0 my @ntpdate_tmp = `ntpdc -c sysinfo 2>&1`;
247              
248 0 0 0     0 if ( !defined( $ntpdate_tmp[0] ) ||
249             # index is 4 times faster than regex
250             index( $ntpdate_tmp[0] , 'ntpdc' ) != -1 ) {
251 0         0 _setSntpServerValues( @array_IP );
252             }
253             else {
254 0         0 _setNtpServerValues( \@array_IP, \@ntpdate_tmp );
255             }
256              
257 0         0 $SNTP_Transmit[11]{"Originate Timestamp Sec"} = $RcV{"Transmit Timestamp Sec"};
258 0         0 $SNTP_Transmit[12]{"Originate Timestamp Micro Sec"} = $RcV{"Transmit Timestamp Micro Sec"};
259              
260             ( $SNTP_Transmit[15]{"Transmit Timestamp Sec"},
261 0         0 $SNTP_Transmit[16]{"Transmit Timestamp Micro Sec"} ) = gettimeofday();
262              
263 0         0 $SNTP_Transmit[9]{"Reference Timestamp Sec"} += UNIX_EPOCH;
264 0         0 $SNTP_Transmit[13]{"Receive Timestamp Sec"} += UNIX_EPOCH;
265 0         0 $SNTP_Transmit[15]{"Transmit Timestamp Sec"} += UNIX_EPOCH;
266              
267 0         0 my @SNTP;
268 0         0 foreach my $href ( @SNTP_Transmit ) {
269 0         0 foreach my $role ( keys %$href ) {
270 0         0 push @SNTP, $href->{$role};
271             }
272             }
273              
274 0         0 my $send_sntp_packet = pack( "B8 C3 s n3 H8 N8" , @SNTP );
275              
276 0         0 eval {
277 0 0       0 $server_socket->send( $send_sntp_packet )
278             or die "Error Sending";
279             };
280 0 0 0     0 return ($error = "Problem While Sending '$!'", \%moduleInput)
281             if ( $@ && $@ =~ /Error Sending/ );
282              
283             } # End of while(TRUE) loop
284              
285 0         0 $server_socket->close(); # Close socket
286              
287 0         0 my %moduleOutput = ();
288 0         0 return $error, \%moduleOutput;
289             }
290              
291             sub _setSntpServerValues {
292 0     0   0 my ( @serverIP ) = @_;
293 0         0 ( undef , undef , $SNTP_Transmit[3]{"Precision"} , undef , undef )
294             = POSIX::times();
295 0         0 $SNTP_Transmit[4]{"Root Delay"} = 0;
296 0         0 $SNTP_Transmit[5]{"Root Delay Fraction"} = 0;
297 0         0 $SNTP_Transmit[6]{"Root Dispersion"} = 0;
298 0         0 $SNTP_Transmit[7]{"Root Dispersion Fraction"} = 0;
299 0         0 $SNTP_Transmit[8]{"Reference Identifier"} .= _decToHex( @serverIP );
300             }
301              
302             sub _setNtpServerValues {
303 0     0   0 my ( $serverIP , $ntpdate_tmp ) = @_;
304 0         0 my %ntpdc = _setKeyAndValue( @{ $ntpdate_tmp } );
  0         0  
305 0         0 $SNTP_Transmit[1]{"Stratum"} = $ntpdc{"stratum"};
306 0         0 $SNTP_Transmit[3]{"Precision"} = $ntpdc{"precision"};
307 0         0 $SNTP_Transmit[3]{"Precision"} = substr $SNTP_Transmit[3]{"Precision"}, 1;
308 0         0 chop($ntpdc{"root distance"});
309 0         0 chop($ntpdc{"root dispersion"});
310             ( $SNTP_Transmit[4]{"Root Delay"},
311 0         0 $SNTP_Transmit[5]{"Root Delay Fraction"} ) = split(/\./,$ntpdc{"root distance"});
312             ( $SNTP_Transmit[6]{"Root Dispersion"},
313 0         0 $SNTP_Transmit[7]{"Root Dispersion Fraction"} ) = split(/\./,$ntpdc{"root dispersion"});
314 0         0 $ntpdc{"reference ID"} = substr $ntpdc{"reference ID"}, 1, -1;
315 0         0 @{ $serverIP } = split(/\./, $ntpdc{"reference ID"});
  0         0  
316 0         0 $SNTP_Transmit[8]{"Reference Identifier"} .= _decToHex( @{ $serverIP } );
  0         0  
317 0         0 ($ntpdc{"reference time"}, $ntpdc{"tmp"}) = split(/ /, $ntpdc{"reference time"}, 2);
318 0         0 delete $ntpdc{"tmp"};
319             ( $SNTP_Transmit[9]{"Reference Timestamp Sec"},
320 0         0 $SNTP_Transmit[10]{"Reference Timestamp Micro Sec"} ) = split(/\./,$ntpdc{"reference time"});
321             $SNTP_Transmit[9]{"Reference Timestamp Sec"} =
322 0         0 hex($SNTP_Transmit[9]{"Reference Timestamp Sec"}) - UNIX_EPOCH;
323             $SNTP_Transmit[10]{"Reference Timestamp Micro Sec"} =
324 0         0 hex($SNTP_Transmit[10]{"Reference Timestamp Micro Sec"});
325 0         0 %ntpdc = ();
326             }
327              
328             sub _decToHex {
329 0     0   0 my ( @decimal_IP ) = @_;
330 0         0 my $hex = join('', map { sprintf '%02X', $_ } $decimal_IP[0],
  0         0  
331             $decimal_IP[1],
332             $decimal_IP[2],
333             $decimal_IP[3] );
334 0         0 return ( uc( $hex ) );
335             }
336              
337             sub _checkHashKeys {
338 7     7   14 my @keysToCompare = ( "-ip", "-port" );
339 7         16 my %hashInputToCompare = @_;
340 7         16 my @hashInputKeysToCompare = keys %hashInputToCompare;
341 7         19 my @differendKeys = _keyDifference(\@hashInputKeysToCompare, \@keysToCompare);
342 7 100       19 if (@differendKeys) { return TRUE } else { return FALSE };
  1         10  
  6         27  
343             # c - style if condition
344             #return TRUE ? @differendKeys : return FALSE;
345             };
346              
347             sub _keyDifference {
348 7     7   11 my %hashdiff = map{ $_ => 1 } @{$_[1]};
  14         45  
  7         17  
349 7         12 return grep { !defined $hashdiff{$_} } @{$_[0]};
  14         42  
  7         15  
350             }
351              
352             sub _verifyPort {
353 4     4   6 my $port = shift;
354 4 50       10 return FALSE if (!defined $port);
355 4 50       9 if ( !_verifyNumericInput($port) ) {
356 4 50 33     20 if ( $port >= MIN_UDP_PORT && MAX_UDP_PORT >= $port ) {
357 4         13 return FALSE;
358             }
359             }
360 0         0 return TRUE;
361             };
362              
363             sub _verifyNumericInput {
364 4     4   6 my $numericInput = shift;
365 4 50       12 return FALSE if (!defined $numericInput);
366 4 50 33     45 if ( defined $numericInput && $numericInput =~ /^[0-9]+$/ && $numericInput > 0 ) {
      33        
367 4         12 return FALSE;
368             }
369 0           return TRUE;
370             };
371              
372             sub _setKeyAndValue {
373 0     0     my @KeyAndValue = @_;
374 0           @KeyAndValue = map { s/^\s+|\s+$//g; $_; } @KeyAndValue;
  0            
  0            
375 0           my @ntpdc = ();
376 0           foreach my $element (@KeyAndValue) {
377 0           $element =~ s/\s\s+/ /g;
378 0           push @ntpdc, split (/: /, $element);
379             }
380 0           my %ntpdcTmp = @ntpdc;
381 0           return %ntpdcTmp;
382             }
383              
384             =head1 EXAMPLE
385              
386             This example starts a remote SNTP server based on RFC4330 message format. IP and
387             Port need to be provided on the start up based on user preference.
388              
389             We use the L
390             module to print the error output in case of faulty initiliazation. The module
391             does not require to printout the output. It should be used only for initialization
392             purposes to assist the user with debugging in case of an error. The $error string
393             it is also optional that will assist the user to identify the root that can cause
394             a faulty initialization.
395              
396              
397             #!/usr/bin/perl
398             use strict;
399             use warnings;
400             use Data::Dumper;
401              
402             use Net::SNTP::Server qw(basicSNTPSetup);
403              
404             my %hashInput = (
405             -ip => "127.0.0.1",
406             -port => 12345,
407             );
408              
409             my ( $error , $hashRefOutput ) = basicSNTPSetup( %hashInput );
410              
411             print Dumper $hashRefOutput;
412             print "Error: $error\n" if ($error);
413              
414              
415             =head1 AUTHOR
416              
417             Athanasios Garyfalos, C<< >>
418              
419             =head1 BUGS
420              
421             Please report any bugs or feature requests to C, or through
422             the web interface at L. I will be notified, and then you'll
423             automatically be notified of progress on your bug as I make changes.
424              
425              
426             =head1 SUPPORT
427              
428             You can find documentation for this module with the perldoc command.
429              
430             perldoc Net::SNTP::Server
431              
432              
433             You can also look for information at:
434              
435             =over 4
436              
437             =item * RT: CPAN's request tracker (report bugs here)
438              
439             L
440              
441             =item * AnnoCPAN: Annotated CPAN documentation
442              
443             L
444              
445             =item * CPAN Ratings
446              
447             L
448              
449             =item * Search CPAN
450              
451             L
452              
453             =back
454              
455             =head1 SEE ALSO
456              
457             perl, IO::Socket, Net::SNTP::Client, L
458              
459             Net::NTP has a similar focus as this module. In my opinion it
460             is less accurate when it comes to the precission bellow second(s).
461              
462             =head1 REPOSITORY
463              
464             L
465              
466              
467             =head1 DIFFERENCES BETWEEN NTP AND SNTP
468              
469             SNTP (Simple Network Time Protocol) and NTP (Network Time Protocol)
470             are describing exactly the same network package format, the differences
471             can be found in the way how a system deals with the content of these
472             packages in order to synchronize its time.
473              
474              
475             =head1 ACKNOWLEDGEMENTS
476              
477             I want to say thank you to L
478             for their guidance and assistance when ever I had a problem with
479             the implementation process of module.
480              
481             =head1 LICENSE AND COPYRIGHT
482              
483             Copyright 2015 Athanasios Garyfalos.
484              
485             This program is free software; you can redistribute it and/or modify it
486             under the terms of the the Artistic License (2.0). You may obtain a
487             copy of the full license at:
488              
489             L
490              
491             Any use, modification, and distribution of the Standard or Modified
492             Versions is governed by this Artistic License. By using, modifying or
493             distributing the Package, you accept this license. Do not use, modify,
494             or distribute the Package, if you do not accept this license.
495              
496             If your Modified Version has been derived from a Modified Version made
497             by someone other than you, you are nevertheless required to ensure that
498             your Modified Version complies with the requirements of this license.
499              
500             This license does not grant you the right to use any trademark, service
501             mark, tradename, or logo of the Copyright Holder.
502              
503             This license includes the non-exclusive, worldwide, free-of-charge
504             patent license to make, have made, use, offer to sell, sell, import and
505             otherwise transfer the Package with respect to any patent claims
506             licensable by the Copyright Holder that are necessarily infringed by the
507             Package. If you institute patent litigation (including a cross-claim or
508             counterclaim) against any party alleging that the Package constitutes
509             direct or contributory patent infringement, then this Artistic License
510             to you shall terminate on the date that such litigation is filed.
511              
512             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
513             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
514             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
515             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
516             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
517             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
518             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
519             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
520              
521             =head1 CHANGE LOG
522              
523             $Log: Server.pm,v $
524             Revision 4.0 2015/10/09 6:43:31 pm Thanos
525              
526              
527             =cut
528              
529             1; # End of Net::SNTP::Server