/usr/share/perl5/Net/NTP.pm is in libnet-ntp-perl 1.5-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 | package Net::NTP;
use 5.008;
use strict;
use warnings;
use IO::Socket;
use constant HAVE_SOCKET_INET6 => eval { require IO::Socket::INET6 };
use Time::HiRes qw(time);
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(
get_ntp_response
);
our $VERSION = '1.5';
our $TIMEOUT = 5;
our %MODE = (
'0' => 'reserved',
'1' => 'symmetric active',
'2' => 'symmetric passive',
'3' => 'client',
'4' => 'server',
'5' => 'broadcast',
'6' => 'reserved for NTP control message',
'7' => 'reserved for private use'
);
our %STRATUM = (
'0' => 'unspecified or unavailable',
'1' => 'primary reference (e.g., radio clock)',
);
for (2 .. 15) {
$STRATUM{$_} = 'secondary reference (via NTP or SNTP)';
}
for (16 .. 255) {
$STRATUM{$_} = 'reserved';
}
our %STRATUM_ONE_TEXT = (
'LOCL' =>
'uncalibrated local clock used as a primary reference for a subnet without external means of synchronization',
'PPS' =>
'atomic clock or other pulse-per-second source individually calibrated to national standards',
'ACTS' => 'NIST dialup modem service',
'USNO' => 'USNO modem service',
'PTB' => 'PTB (Germany) modem service',
'TDF' => 'Allouis (France) Radio 164 kHz',
'DCF' => 'Mainflingen (Germany) Radio 77.5 kHz',
'MSF' => 'Rugby (UK) Radio 60 kHz',
'WWV' => 'Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz',
'WWVB' => 'Boulder (US) Radio 60 kHz',
'WWVH' => 'Kaui Hawaii (US) Radio 2.5, 5, 10, 15 MHz',
'CHU' => 'Ottawa (Canada) Radio 3330, 7335, 14670 kHz',
'LORC' => 'LORAN-C radionavigation system',
'OMEG' => 'OMEGA radionavigation system',
'GPS' => 'Global Positioning Service',
'GOES' => 'Geostationary Orbit Environment Satellite',
);
our %LEAP_INDICATOR = (
'0' => 'no warning',
'1' => 'last minute has 61 seconds',
'2' => 'last minute has 59 seconds)',
'3' => 'alarm condition (clock not synchronized)'
);
use constant NTP_ADJ => 2208988800;
my @ntp_packet_fields = (
'Leap Indicator',
'Version Number',
'Mode',
'Stratum',
'Poll Interval',
'Precision',
'Root Delay',
'Root Dispersion',
'Reference Clock Identifier',
'Reference Timestamp', # reftime
'Originate Timestamp', # T1
'Receive Timestamp', # T2
'Transmit Timestamp', # T3
'Destination Timestamp', # T4
'Key Identifier',
'Message Digest',
);
## position matched description above.
my @_ntp_packet_field_names = qw/
leap
version
mode
stratum
poll
precision
rootdelay
rootdisp
refid
reftime
org
rec
xmt
dst
keyid
dgst/;
if (scalar(@_ntp_packet_field_names) != scalar(@ntp_packet_fields)) {
die "Fatal error in packet definition, fields don't match";
}
=head2 offset($packet, $xmttime, $rectime)
Given a NTP Packet (from B), return the offset to local (A) according to its xmttime(T1) and rectime(T4)
theta = T(B) - T(A) = 1/2 * [(T2-T1) + (T3-T4)]
=cut
sub offset {
my $class = shift;
my ($packet, $xmttime, $rectime) = @_;
return ($packet->{rec} - $xmttime + $packet->{xmt} - $rectime) / 2;
}
=head2 delay($packet, $xmttime, $rectime)
Return the delay from the sender (B) of $packet given known local xmttime(T1) and rectime(T4)
delta = T(ABA) = (T4-T1) - (T3-T2).
=cut
sub delay {
my $class = shift;
my ($packet, $xmttime, $rectime) = @_;
return $rectime - $xmttime - ($packet->{xmt} - $packet->{rec});
}
sub get_ntp_response {
my $host = shift || 'localhost';
my $port = shift || 'ntp';
my %args = (
Proto => 'udp',
PeerHost => $host,
PeerPort => $port
);
my $sock;
if (HAVE_SOCKET_INET6) {
$sock = IO::Socket::INET6->new(%args);
}
else {
$sock = IO::Socket::INET->new(%args);
}
die $@ unless $sock;
my $xmttime = time; # T1
my $packet = Net::NTP::Packet->new_client_packet($xmttime);
$sock->send($packet->encode)
or die "send() failed: $!\n";
## receive with deadline
my $data;
eval {
local $SIG{ALRM} = sub { die "Net::NTP timed out getting NTP packet\n"; };
alarm($TIMEOUT);
$sock->recv($data, 960)
or die "recv() failed: $!\n";
alarm(0);
};
alarm 0;
my $rectime = time; # T4
my $pkt = Net::NTP::Packet->decode($data, $xmttime, $rectime);
## Return packet hash as we used to, the pos
my %packet = ();
for (my $i = 0; $i < scalar @ntp_packet_fields; $i++) {
$packet{$ntp_packet_fields[$i]} = $pkt->{$_ntp_packet_field_names[$i]};
}
## some values were using a float formatter.
$packet{"Poll Interval"} = sprintf("%.4f", $pkt->{poll});
$packet{"Root Dispersion"} = sprintf("%.4f", $pkt->{rootdisp});
## some are not in the new object, but can be computed easily.
$packet{Offset} = __PACKAGE__->offset($pkt, $xmttime, $rectime);
$packet{Delay} = sprintf "%0.5f", __PACKAGE__->delay($pkt, $xmttime, $rectime);
return %packet;
}
=encoding utf8
=head1 NAME
Net::NTP - Perl extension for decoding NTP server responses
=head1 SYNOPSIS
use Net::NTP qw(get_ntp_response);
use Time::HiRes qw(time);
my %response = get_ntp_response();
my $xmttime = time();
my $spkt = Net::NTP::Packet->new_client_packet($xmttime);
$socket->send($pkt->encode());
$socket->recv(my $data, 1024);
my $rectime = time();
my $cpkt = Net::NTP::Packet->decode($data, $xmttime, $rectime);
print "Stratum: ", $cpkt->{stratum}, "\n";
print "Offset: ", Net::NTP->offset($pkt, $xmttime, $rectime), "\n"
=head1 ABSTRACT
All this module does is send a packet to an NTP server and then decode
the packet received into it's respective parts - as outlined in
RFC5905 (superseding RFC1305 and RFC2030).
=head1 LIMITATIONS
This only supports Association Mode 3 (Client).
=head1 DESCRIPTION
This module exports a single method (get_ntp_response) and returns an
associative array based upon RFC1305 and RFC2030. The response from
the server is "humanized" to a point that further processing of the
information received from the server can be manipulated. For example:
timestamps are in epoch, so one could use the localtime function to
produce an even more "human" representation of the timestamp.
=head2 EXPORT
get_ntp_response(<server>, <port>);
This module exports a single method - get_ntp_response. It takes the
server as the first argument (localhost is the default) and port to
send/recieve the packets (ntp or 123 by default). It returns an
associative array of the various parts of the packet as outlined in
RFC1305. It "normalizes" or "humanizes" various parts of the packet.
For example: all the timestamps are in epoch, NOT hexidecimal.
Two special fields (C<Delay> and C<Offset>) are calculated and added to
the response.
If there's a timeout or other communications error get_ntp_response
will die (so call get_ntp_response in an eval block).
=head1 SEE ALSO
perl, IO::Socket, RFC5905, RFC1305, RFC2030
=head1 AUTHOR
Now maintained by Ask Bjørn Hansen, E<lt>ask@develooper.com<gt>
Originally by James G. Willmore, E<lt>jwillmore (at) adelphia.net<gt>
or E<lt>owner (at) ljcomputing.net<gt>
Special thanks to Ralf D. Kloth E<lt>ralf (at) qrq.de<gt> for the code
to decode NTP packets.
=head1 COPYRIGHT AND LICENSE
Copyright 2009 by Ask Bjørn Hansen; 2004 by James G. Willmore
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
package Net::NTP::Packet;
=head1 NAME
Net::NTP::Packet
=head1 DESCRIPTION
Representation of a NTP Packet with serialization primitives.
=head2 PROTOCOL - RFC 5905 - Section 7.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN |Mode | Stratum | Poll | Precision |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Delay |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Root Dispersion |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reference ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Reference Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Origin Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Receive Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Transmit Timestamp (64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Extension Field 1 (variable) .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Extension Field 2 (variable) .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Key Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| dgst (128) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
=cut
=head2 new
create a new Net::NTP::Packet instance.
Parameters are the field names, gotten from "7.3. Packet Header Variables"
=cut
sub new {
my $class = shift;
my(%param) = @_;
my %self = ();
foreach my $k (@_ntp_packet_field_names) {
$self{$k} = delete $param{$k};
}
if (keys %param) {
die "unknown fields: ", join(", ", keys %param);
}
return bless \%self, $class;
}
=head2 new_client_packet($xmttime)
Make a packet in association mode 'Client' to be sent to a server.
=cut
sub new_client_packet {
my $class = shift;
my $xmttime = shift || die "a transmit time is required.";
return $class->new(
version => 4,
org => $xmttime,
);
}
=head2 encode()
Encode a packet to its wire format.
NOTE: It only encodes server packets at the moment.
=cut
sub encode {
my $self = shift;
my $t1 = $self->{org};
my $client_adj_localtime = $t1 + Net::NTP::NTP_ADJ;
my $client_frac_localtime = _frac2bin($client_adj_localtime);
## LI=0, VN=3, MODE=3, Stratum,Poll,Precision=0, ...
return pack("B8 C3 N9 N B32", '00011011', (0) x 3, (0) x 9, int($client_adj_localtime), $client_frac_localtime);
}
=head2 $packet = Net::NTP::Packet->decode($data, $xmttime, $rectime)
decode the NTP packet from its wire format.
=cut
my @ntp_fields = qw/byte1 stratum poll precision/;
push @ntp_fields, qw/delay delay_fb disp disp_fb ident/;
push @ntp_fields, qw/ref_time ref_time_fb/;
push @ntp_fields, qw/org_time org_time_fb/;
push @ntp_fields, qw/recv_time recv_time_fb/;
push @ntp_fields, qw/trans_time trans_time_fb/;
sub decode {
my $class = shift;
my $data = shift || die "decode() needs data.";
my $timestamp = shift || die "decode() takes a timestamp.";
my %tmp_pkt;
@tmp_pkt{@ntp_fields} = unpack("a C2 c n B16 n B16 H8 N B32 N B32 N B32 N B32", $data);
return $class->new(
leap => (unpack("C", $tmp_pkt{byte1} & "\xC0") >> 6),
version => (unpack("C", $tmp_pkt{byte1} & "\x38") >> 3),
mode => unpack("C", $tmp_pkt{byte1} & "\x07"),
stratum => $tmp_pkt{stratum},
poll => $tmp_pkt{poll},
precision => $tmp_pkt{precision},
rootdelay => _bin2frac($tmp_pkt{delay_fb}),
rootdisp => $tmp_pkt{disp},
refid => _unpack_refid($tmp_pkt{stratum}, $tmp_pkt{ident}),
reftime => $tmp_pkt{ref_time} + _bin2frac($tmp_pkt{ref_time_fb}) - Net::NTP::NTP_ADJ,
org => $tmp_pkt{org_time} + _bin2frac($tmp_pkt{org_time_fb}) - Net::NTP::NTP_ADJ,
rec => $tmp_pkt{recv_time} + _bin2frac($tmp_pkt{recv_time_fb}) - Net::NTP::NTP_ADJ,
xmt => $tmp_pkt{trans_time} + _bin2frac($tmp_pkt{trans_time_fb}) - Net::NTP::NTP_ADJ,
dst => $timestamp,
keyid => '',
dgst => '',
);
}
sub _unpack_refid {
my $stratum = shift;
my $raw_id = shift;
if ($stratum < 2) {
return unpack("A4", pack("H8", $raw_id));
}
return sprintf("%d.%d.%d.%d", unpack("C4", pack("H8", $raw_id)));
}
sub _frac2bin {
my $bin = '';
my $frac = shift;
while (length($bin) < 32) {
$bin = $bin . int($frac * 2);
$frac = ($frac * 2) - (int($frac * 2));
}
return $bin;
}
sub _bin2frac {
my @bin = split '', shift;
my $frac = 0;
while (@bin) {
$frac = ($frac + pop @bin) / 2;
}
return $frac;
};
1;
|