File Coverage

blib/lib/Net/SNTP/Server.pm
Criterion Covered Total %
statement 70 156 44.8
branch 15 42 35.7
condition 4 32 12.5
subroutine 19 23 82.6
pod 1 1 100.0
total 109 254 42.9


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