Skip to content

Instantly share code, notes, and snippets.

@ksurent
Last active November 26, 2015 09:21
Show Gist options
  • Save ksurent/b44244892c10de0a8f92 to your computer and use it in GitHub Desktop.
Save ksurent/b44244892c10de0a8f92 to your computer and use it in GitHub Desktop.
Silly ntpd packet decoder for my study group
#!/usr/local/bin/perl
use v5.14;
use warnings;
use IO::Socket 1.18;
use Digest::MD5 qw(md5 md5_hex);
use Time::HiRes qw(time);
# epoch = January 1, 1900
# 64 bit timestamps, 32 bits for seconds since epoch, 32 bits for fractional
# seconds
#
# S = server, C = client
#
# t0 = C sends req
# t1 = S receives req
# t2 = S sends res
# t3 = C receives res
# t3 - t0 = time elapsed between between req and res on C
# t2 - t1 = time spent to compute res on S
# round trip delay = (t3 - t0) - (t2 - t1)
# offset to C's clock = ((t1 - t0) + (t3 - t2)) / 2
use constant {
# seconds between NTP epoch (Jan 1 1900) and UNIX epoch (Jan 1 1970)
NTP_EPOCH_OFFSET => 2_208_988_800,
};
my $pack_format = join(
" ",
qw[
C c c c
n B16
n B16
C C C C
N B32
N B32
N B32
N B32
],
# n n/A
# n n/A
);
my $t0 = time() + NTP_EPOCH_OFFSET;
my $packet;
$packet .= pack(
$pack_format,
pack_leap_version_mode(0, 4, 3), 0, 0, 0, # li/vn/mode, stratum, poll, precision
0, frac2bin(0.0), # root delay
0, frac2bin(0.0), # root dispersion
pack_reference_id("JJY"), # ref_id 10.187.212.22
0, frac2bin(0.0), # t_ref
int($t0), frac2bin($t0), # t0
0, frac2bin(0.0), # t1
0, frac2bin(0.0), # t2
# t3 is computed on the client upon packet arrival
# 1, 123, # extension 1
# 2, 321, # extension 2
);
my $sock = IO::Socket::INET->new(
PeerAddr => "ntp0",
PeerPort => "ntp",
Proto => "udp",
Timeout => 10,
) or die $!;
$sock->send($packet) or die $!;
$sock->recv($packet, 128);
my $t3 = time();
say "Received: " . length($packet);
open my $fh, ">:raw", "/tmp/ntp";
print $fh $packet;
close $fh;
#say md5_hex($packet);
#
#my $md5 = md5($packet);
#$packet .= pack("N", 31337);
#$packet .= $md5;
my @fields = qw(
leap/ver/mode stratum poll precision
root_sec root_frac
disp_sec disp_frac
ref_id_1 ref_id_2 ref_id_3 ref_id_4
ref_sec ref_frac
t0_sec t0_frac
t1_sec t1_frac
t2_sec t2_frac
);
# ext1_type ext1_val
# ext2_type ext2_val
# key
# md5
my $unpack_format = join(
" ",
qw[
C c c c
n B16
n B16
C C C C
N B32
N B32
N B32
N B32
],
# n n/A
# n n/A
# N
# H*
);
my @values = unpack($unpack_format, $packet);
my $stashed_stratum;
my @stashed_ref_id;
my $stashed_epoch;
for my $i (0 .. $#fields) {
my $k = $fields[$i];
my $v = $values[$i];
if($k =~ /_sec\z/) {
$stashed_epoch = $v;
next;
}
elsif($k =~ /^([^_]+)_frac\z/) {
$k = $1;
$stashed_epoch += bin2frac($v);
if($k ne "root" and $k ne "disp") {
$stashed_epoch -= NTP_EPOCH_OFFSET;
}
$v = $stashed_epoch;
}
elsif($k eq "leap/ver/mode") {
$v = unpack_leap_version_mode($v);
}
elsif($k eq "stratum") {
$stashed_stratum = $v;
$v = stratum_with_description($v);
}
elsif($k eq "poll" or $k eq "precision") {
$v = log2d($v);
}
elsif($k =~ /^ref_id_[0-9]$/) {
push @stashed_ref_id, $v;
if(@stashed_ref_id < 4) {
next;
}
$k = "ref_id";
$v = unpack_reference_id(\@stashed_ref_id, $stashed_stratum);
$stashed_stratum = undef;
@stashed_ref_id = ();
}
printf("%-10s = %s\n", $k, $v);
}
printf("%-10s = %s\n", "t3", $t3);
sub log2d {
my($n) = @_;
if($n < 0) {
return 1.0 / (1.0 << -$n);
}
else {
return 1 << $n;
}
}
sub unpack_reference_id {
my($ref_id_bytes, $stratum) = @_;
if($stratum == 0 or $stratum == 1) {
my $string = join("", map(chr, @$ref_id_bytes));
$string =~ s/\s+$//;
if($stratum == 0) {
$string .= " (kiss code)";
}
else {
$string .= " (reference clock id)";
}
return $string;
}
else {
return join(".", @$ref_id_bytes);
}
}
sub pack_reference_id {
my($string) = @_;
while(length($string) < 4) {
$string .= " ";
}
map(ord, split(//, $string));
}
sub pack_leap_version_mode {
my($leap_indicator, $version, $mode) = @_;
((($leap_indicator << 3) | $version) << 3) | $mode;
}
sub unpack_leap_version_mode {
my($num) = @_;
my $leap_indicator = ($num >> 6) & 0b00000011;
my $version = ($num >> 3) & 0b00000111;
my $mode = ($num >> 0) & 0b00000111;
$leap_indicator = [
"no leap second",
"61 seconds",
"59 seconds",
"unknown",
]->[$leap_indicator];
$mode = [
"reserved",
"symmetric active",
"symmetric passive",
"client",
"server",
"broadcast",
"control message",
"reserved",
]->[$mode];
sprintf("%s/%d/%s", $leap_indicator, $version, $mode);
}
sub stratum_with_description {
my($stratum) = @_;
my $desc;
if($stratum == 0) { $desc = "invalid or unspecified"; }
elsif($stratum == 1) { $desc = "primary"; }
elsif($stratum == 16) { $desc = "unsynchronised"; }
elsif($stratum >= 2 and $stratum <= 15) { $desc = "secondary"; }
else { $desc = "reserved"; }
$desc;
}
sub bin2frac {
my($bin) = @_;
my $frac;
for my $digit (reverse(split(//, $bin))) {
$frac += $digit;
$frac /= 2;
}
sprintf("%.5f", $frac);
}
sub frac2bin {
my($frac) = @_;
my $bin = '';
while(length($bin) < 32) {
$bin .= int($frac * 2);
$frac = ($frac * 2) - int($frac * 2);
}
$bin;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment