package NTP::Util; use strict; use warnings; use Exporter 'import'; use Carp; use version 0.77; our @EXPORT_OK = qw(ntp_read_vars do_dns ntp_peers ntp_sntp_line); my $ntpq_path = 'ntpq'; my $sntp_path = 'sntp'; our $IP_AGNOSTIC; BEGIN { require Socket; if (version->parse($Socket::VERSION) >= version->parse(1.94)) { Socket->import(qw(getaddrinfo getnameinfo SOCK_RAW AF_INET)); $IP_AGNOSTIC = 1; } else { Socket->import(qw(inet_aton SOCK_RAW AF_INET)); } } my %obsolete_vars = ( phase => 'offset', rootdispersion => 'rootdisp', ); sub ntp_read_vars { my ($peer, $vars, $host) = @_; my $do_all = !@$vars; my %out_vars = map {; $_ => undef } @$vars; $out_vars{status_line} = {} if $do_all; my $cmd = "$ntpq_path -n -c 'rv $peer ".(join ',', @$vars)."'"; $cmd .= " $host" if defined $host; $cmd .= " |"; open my $fh, $cmd or croak "Could not start ntpq: $!"; while (<$fh>) { return undef if /Connection refused/; if (/^asso?c?id=0 status=(\S{4}) (\S+), (\S+),/gi) { $out_vars{status_line}{status} = $1; $out_vars{status_line}{leap} = $2; $out_vars{status_line}{sync} = $3; } while (/(\w+)=([^,]+),?\s/g) { my ($var, $val) = ($1, $2); $val =~ s/^"([^"]+)"$/$1/; $var = $obsolete_vars{$var} if exists $obsolete_vars{$var}; if ($do_all) { $out_vars{$var} = $val } else { $out_vars{$var} = $val if exists $out_vars{$var}; } } } close $fh or croak "running ntpq failed: $! (exit status $?)"; return \%out_vars; } sub do_dns { my ($host) = @_; if ($IP_AGNOSTIC) { my ($err, $res); ($err, $res) = getaddrinfo($host, '', {socktype => SOCK_RAW}); die "getaddrinfo failed: $err\n" if $err; ($err, $res) = getnameinfo($res->{addr}, 0); die "getnameinfo failed: $err\n" if $err; return $res; } # Too old perl, do only ipv4 elsif ($host =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) { return gethostbyaddr inet_aton($host), AF_INET; } else { return; } } sub ntp_peers { my ($host) = @_; $host ||= ''; my $cmd = "$ntpq_path -npw $host |"; open my $fh, $cmd or croak "Could not start ntpq: $!"; <$fh> for 1 .. 2; my @columns = qw(tally host refid st t when poll reach delay offset jitter); my @peers; while (<$fh>) { if (/^([ x+#*o-])((?:[\w.*:-]+\s+){10}|([\w.*:-]+\s+))$/) { my $col = 0; my @line = ($1, split /\s+/, $2); if( @line == 2 ) { defined ($_ = <$fh>) or last; s/^\s+//; push @line, split /\s+/; } my $r = { map {; $columns[ $col++ ] => $_ } @line }; $r->{remote} = $r->{tally} . $r->{host}; push @peers, $r; } else { #TODO return error (but not needed anywhere now) warn "ERROR: $_"; } } close $fh or croak "running ntpq failed: $! (exit status $?)"; return \@peers; } # TODO: we don't need this but it would be nice to have all the line parsed sub ntp_sntp_line { my ($host) = @_; my $cmd = "$sntp_path $host |"; open my $fh, $cmd or croak "Could not start sntp: $!"; my ($offset, $stratum); while (<$fh>) { next if !/^\d{4}-\d\d-\d\d/; chomp; my @output = split / /; $offset = $output[3]; if (0) { } elsif ($output[7] =~ /s(\d{1,2})/) { $stratum = $1; # warn "Found stratum at #7\n"; } elsif ($output[8] =~ /s(\d{1,2})/) { $stratum = $1; # warn "Found stratum at #8\n"; } } close $fh or croak "running sntp failed: $! (exit status $?)"; return ($offset, $stratum); } 1;