1*2b15cb3dSCy Schubertpackage NTP::Util; 2*2b15cb3dSCy Schubertuse strict; 3*2b15cb3dSCy Schubertuse warnings; 4*2b15cb3dSCy Schubertuse Exporter 'import'; 5*2b15cb3dSCy Schubertuse Carp; 6*2b15cb3dSCy Schubertuse version 0.77; 7*2b15cb3dSCy Schubert 8*2b15cb3dSCy Schubertour @EXPORT_OK = qw(ntp_read_vars do_dns ntp_peers ntp_sntp_line); 9*2b15cb3dSCy Schubert 10*2b15cb3dSCy Schubertmy $ntpq_path = 'ntpq'; 11*2b15cb3dSCy Schubertmy $sntp_path = 'sntp'; 12*2b15cb3dSCy Schubert 13*2b15cb3dSCy Schubertour $IP_AGNOSTIC; 14*2b15cb3dSCy Schubert 15*2b15cb3dSCy SchubertBEGIN { 16*2b15cb3dSCy Schubert require Socket; 17*2b15cb3dSCy Schubert if (version->parse($Socket::VERSION) >= version->parse(1.94)) { 18*2b15cb3dSCy Schubert Socket->import(qw(getaddrinfo getnameinfo SOCK_RAW AF_INET)); 19*2b15cb3dSCy Schubert $IP_AGNOSTIC = 1; 20*2b15cb3dSCy Schubert } 21*2b15cb3dSCy Schubert else { 22*2b15cb3dSCy Schubert Socket->import(qw(inet_aton SOCK_RAW AF_INET)); 23*2b15cb3dSCy Schubert } 24*2b15cb3dSCy Schubert} 25*2b15cb3dSCy Schubert 26*2b15cb3dSCy Schubertmy %obsolete_vars = ( 27*2b15cb3dSCy Schubert phase => 'offset', 28*2b15cb3dSCy Schubert rootdispersion => 'rootdisp', 29*2b15cb3dSCy Schubert); 30*2b15cb3dSCy Schubert 31*2b15cb3dSCy Schubertsub ntp_read_vars { 32*2b15cb3dSCy Schubert my ($peer, $vars, $host) = @_; 33*2b15cb3dSCy Schubert my $do_all = !@$vars; 34*2b15cb3dSCy Schubert my %out_vars = map {; $_ => undef } @$vars; 35*2b15cb3dSCy Schubert 36*2b15cb3dSCy Schubert $out_vars{status_line} = {} if $do_all; 37*2b15cb3dSCy Schubert 38*2b15cb3dSCy Schubert my $cmd = "$ntpq_path -n -c 'rv $peer ".(join ',', @$vars)."'"; 39*2b15cb3dSCy Schubert $cmd .= " $host" if defined $host; 40*2b15cb3dSCy Schubert $cmd .= " |"; 41*2b15cb3dSCy Schubert 42*2b15cb3dSCy Schubert open my $fh, $cmd or croak "Could not start ntpq: $!"; 43*2b15cb3dSCy Schubert 44*2b15cb3dSCy Schubert while (<$fh>) { 45*2b15cb3dSCy Schubert return undef if /Connection refused/; 46*2b15cb3dSCy Schubert 47*2b15cb3dSCy Schubert if (/^asso?c?id=0 status=(\S{4}) (\S+), (\S+),/gi) { 48*2b15cb3dSCy Schubert $out_vars{status_line}{status} = $1; 49*2b15cb3dSCy Schubert $out_vars{status_line}{leap} = $2; 50*2b15cb3dSCy Schubert $out_vars{status_line}{sync} = $3; 51*2b15cb3dSCy Schubert } 52*2b15cb3dSCy Schubert 53*2b15cb3dSCy Schubert while (/(\w+)=([^,]+),?\s/g) { 54*2b15cb3dSCy Schubert my ($var, $val) = ($1, $2); 55*2b15cb3dSCy Schubert $val =~ s/^"([^"]+)"$/$1/; 56*2b15cb3dSCy Schubert $var = $obsolete_vars{$var} if exists $obsolete_vars{$var}; 57*2b15cb3dSCy Schubert if ($do_all) { 58*2b15cb3dSCy Schubert $out_vars{$var} = $val 59*2b15cb3dSCy Schubert } 60*2b15cb3dSCy Schubert else { 61*2b15cb3dSCy Schubert $out_vars{$var} = $val if exists $out_vars{$var}; 62*2b15cb3dSCy Schubert } 63*2b15cb3dSCy Schubert } 64*2b15cb3dSCy Schubert } 65*2b15cb3dSCy Schubert 66*2b15cb3dSCy Schubert close $fh or croak "running ntpq failed: $! (exit status $?)"; 67*2b15cb3dSCy Schubert return \%out_vars; 68*2b15cb3dSCy Schubert} 69*2b15cb3dSCy Schubert 70*2b15cb3dSCy Schubertsub do_dns { 71*2b15cb3dSCy Schubert my ($host) = @_; 72*2b15cb3dSCy Schubert 73*2b15cb3dSCy Schubert if ($IP_AGNOSTIC) { 74*2b15cb3dSCy Schubert my ($err, $res); 75*2b15cb3dSCy Schubert 76*2b15cb3dSCy Schubert ($err, $res) = getaddrinfo($host, '', {socktype => SOCK_RAW}); 77*2b15cb3dSCy Schubert die "getaddrinfo failed: $err\n" if $err; 78*2b15cb3dSCy Schubert 79*2b15cb3dSCy Schubert ($err, $res) = getnameinfo($res->{addr}, 0); 80*2b15cb3dSCy Schubert die "getnameinfo failed: $err\n" if $err; 81*2b15cb3dSCy Schubert 82*2b15cb3dSCy Schubert return $res; 83*2b15cb3dSCy Schubert } 84*2b15cb3dSCy Schubert # Too old perl, do only ipv4 85*2b15cb3dSCy Schubert elsif ($host =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) { 86*2b15cb3dSCy Schubert return gethostbyaddr inet_aton($host), AF_INET; 87*2b15cb3dSCy Schubert } 88*2b15cb3dSCy Schubert else { 89*2b15cb3dSCy Schubert return; 90*2b15cb3dSCy Schubert } 91*2b15cb3dSCy Schubert} 92*2b15cb3dSCy Schubert 93*2b15cb3dSCy Schubertsub ntp_peers { 94*2b15cb3dSCy Schubert my ($host) = @_; 95*2b15cb3dSCy Schubert 96*2b15cb3dSCy Schubert my $cmd = "$ntpq_path -np $host |"; 97*2b15cb3dSCy Schubert 98*2b15cb3dSCy Schubert open my $fh, $cmd or croak "Could not start ntpq: $!"; 99*2b15cb3dSCy Schubert 100*2b15cb3dSCy Schubert <$fh> for 1 .. 2; 101*2b15cb3dSCy Schubert 102*2b15cb3dSCy Schubert my @columns = qw(remote refid st t when poll reach delay offset jitter); 103*2b15cb3dSCy Schubert my @peers; 104*2b15cb3dSCy Schubert while (<$fh>) { 105*2b15cb3dSCy Schubert if (/(?:[\w\.\*-]+\s*){10}/) { 106*2b15cb3dSCy Schubert my $col = 0; 107*2b15cb3dSCy Schubert push @peers, { map {; $columns[ $col++ ] => $_ } split /(?<=.)\s+/ }; 108*2b15cb3dSCy Schubert } 109*2b15cb3dSCy Schubert else { 110*2b15cb3dSCy Schubert #TODO return error (but not needed anywhere now) 111*2b15cb3dSCy Schubert warn "ERROR: $_"; 112*2b15cb3dSCy Schubert } 113*2b15cb3dSCy Schubert } 114*2b15cb3dSCy Schubert 115*2b15cb3dSCy Schubert close $fh or croak "running ntpq failed: $! (exit status $?)"; 116*2b15cb3dSCy Schubert return \@peers; 117*2b15cb3dSCy Schubert} 118*2b15cb3dSCy Schubert 119*2b15cb3dSCy Schubert# TODO: we don't need this but it would be nice to have all the line parsed 120*2b15cb3dSCy Schubertsub ntp_sntp_line { 121*2b15cb3dSCy Schubert my ($host) = @_; 122*2b15cb3dSCy Schubert 123*2b15cb3dSCy Schubert my $cmd = "$sntp_path $host |"; 124*2b15cb3dSCy Schubert open my $fh, $cmd or croak "Could not start sntp: $!"; 125*2b15cb3dSCy Schubert 126*2b15cb3dSCy Schubert my ($offset, $stratum); 127*2b15cb3dSCy Schubert while (<$fh>) { 128*2b15cb3dSCy Schubert next if !/^\d{4}-\d\d-\d\d/; 129*2b15cb3dSCy Schubert chomp; 130*2b15cb3dSCy Schubert my @output = split / /; 131*2b15cb3dSCy Schubert 132*2b15cb3dSCy Schubert $offset = $output[3]; 133*2b15cb3dSCy Schubert ($stratum = pop @output) =~ s/s(\d{1,2})/$1/; 134*2b15cb3dSCy Schubert } 135*2b15cb3dSCy Schubert close $fh or croak "running sntp failed: $! (exit status $?)"; 136*2b15cb3dSCy Schubert return ($offset, $stratum); 137*2b15cb3dSCy Schubert} 138