diff --git a/Changes b/Changes index 2eac3689..bcc6ca6e 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,9 @@ +2.036001 - TESTING + + [BUG FIXES] + + * #320 DNS subroutines are redefined + 2.036001 - 2017-06-22 [BUG FIXES] diff --git a/MANIFEST b/MANIFEST index a89e77d2..4758a5db 100644 --- a/MANIFEST +++ b/MANIFEST @@ -129,6 +129,7 @@ lib/App/Netdisco/Util/Backend.pm lib/App/Netdisco/Util/Device.pm lib/App/Netdisco/Util/DNS.pm lib/App/Netdisco/Util/ExpandParams.pm +lib/App/Netdisco/Util/FastResolver.pm lib/App/Netdisco/Util/Graph.pm lib/App/Netdisco/Util/Node.pm lib/App/Netdisco/Util/NodeMonitor.pm diff --git a/bin/netdisco-sshcollector b/bin/netdisco-sshcollector index 38f016b1..edb44063 100755 --- a/bin/netdisco-sshcollector +++ b/bin/netdisco-sshcollector @@ -42,7 +42,7 @@ BEGIN { use App::Netdisco; use App::Netdisco::Core::Arpnip 'store_arp'; use App::Netdisco::Util::Node 'check_mac'; -use App::Netdisco::Util::DNS 'hostnames_resolve_async'; +use App::Netdisco::Util::FastResolver 'hostnames_resolve_async'; use Dancer ':script'; use Data::Printer; diff --git a/lib/App/Netdisco/Configuration.pm b/lib/App/Netdisco/Configuration.pm index 441056c2..d32d63d7 100644 --- a/lib/App/Netdisco/Configuration.pm +++ b/lib/App/Netdisco/Configuration.pm @@ -66,6 +66,22 @@ if (exists setting('workers')->{interactives} setting('dns')->{hosts_file} ||= '/etc/hosts'; setting('dns')->{no} ||= ['fe80::/64','169.254.0.0/16']; +# load /etc/hosts +setting('dns')->{'ETCHOSTS'} = {}; +{ + # AE::DNS::EtcHosts only works for A/AAAA/SRV, but we want PTR. + # this loads+parses /etc/hosts file using AE. dirty hack. + use AnyEvent::Socket 'format_address'; + use AnyEvent::DNS::EtcHosts; + AnyEvent::DNS::EtcHosts::_load_hosts_unless(sub{},AE::cv); + no AnyEvent::DNS::EtcHosts; # unimport + + setting('dns')->{'ETCHOSTS'}->{$_} = + [ map { [ $_ ? (format_address $_->[0]) : '' ] } + @{ $AnyEvent::DNS::EtcHosts::HOSTS{ $_ } } ] + for keys %AnyEvent::DNS::EtcHosts::HOSTS; +} + # support unordered dictionary as if it were a single item list if (ref {} eq ref setting('device_identity')) { config->{'device_identity'} = [ setting('device_identity') ]; diff --git a/lib/App/Netdisco/Core/Arpnip.pm b/lib/App/Netdisco/Core/Arpnip.pm index a0ca5249..b3a82307 100644 --- a/lib/App/Netdisco/Core/Arpnip.pm +++ b/lib/App/Netdisco/Core/Arpnip.pm @@ -4,7 +4,7 @@ use Dancer qw/:syntax :script/; use Dancer::Plugin::DBIC 'schema'; use App::Netdisco::Util::Node 'check_mac'; -use App::Netdisco::Util::DNS ':all'; +use App::Netdisco::Util::FastResolver 'hostnames_resolve_async'; use NetAddr::IP::Lite ':lower'; use Time::HiRes 'gettimeofday'; use NetAddr::MAC (); diff --git a/lib/App/Netdisco/Core/Discover.pm b/lib/App/Netdisco/Core/Discover.pm index dca91119..25e5e0f4 100644 --- a/lib/App/Netdisco/Core/Discover.pm +++ b/lib/App/Netdisco/Core/Discover.pm @@ -6,6 +6,7 @@ use Dancer::Plugin::DBIC 'schema'; use App::Netdisco::Util::Device qw/get_device match_devicetype is_discoverable/; use App::Netdisco::Util::Permission 'check_acl_only'; +use App::Netdisco::Util::FastResolver 'hostnames_resolve_async'; use App::Netdisco::Util::DNS ':all'; use App::Netdisco::JobQueue qw/jq_queued jq_insert/; use NetAddr::IP::Lite ':lower'; diff --git a/lib/App/Netdisco/Util/DNS.pm b/lib/App/Netdisco/Util/DNS.pm index 96f498b0..cd8ed25f 100644 --- a/lib/App/Netdisco/Util/DNS.pm +++ b/lib/App/Netdisco/Util/DNS.pm @@ -5,30 +5,13 @@ use warnings; use Dancer ':script'; use Net::DNS; -use AnyEvent::DNS; use NetAddr::IP::Lite ':lower'; -use App::Netdisco::Util::Permission; - use base 'Exporter'; our @EXPORT = (); -our @EXPORT_OK = qw/ - hostname_from_ip hostnames_resolve_async ipv4_from_hostname -/; +our @EXPORT_OK = qw/hostname_from_ip ipv4_from_hostname/; our %EXPORT_TAGS = (all => \@EXPORT_OK); -# AE::DNS::EtcHosts only works for A/AAAA/SRV, but we want PTR. -# this loads+parses /etc/hosts file using AE. dirty hack. -use AnyEvent::Socket 'format_address'; -use AnyEvent::DNS::EtcHosts; -AnyEvent::DNS::EtcHosts::_load_hosts_unless(sub{},AE::cv); -no AnyEvent::DNS::EtcHosts; # unimport - -our %HOSTS = (); -$HOSTS{$_} = [ map { [ $_ ? (format_address $_->[0]) : '' ] } - @{$AnyEvent::DNS::EtcHosts::HOSTS{$_}} ] - for keys %AnyEvent::DNS::EtcHosts::HOSTS; - =head1 NAME App::Netdisco::Util::DNS @@ -53,10 +36,11 @@ Returns C if no PTR record exists for the IP. sub hostname_from_ip { my $ip = shift; return unless $ip; + my $ETCHOSTS = setting('dns')->{'ETCHOSTS'}; # check /etc/hosts file and short-circuit if found - foreach my $name (reverse sort keys %HOSTS) { - if ($HOSTS{$name}->[0]->[0] eq $ip) { + foreach my $name (reverse sort keys %$ETCHOSTS) { + if ($ETCHOSTS->{$name}->[0]->[0] eq $ip) { return $name; } } @@ -85,10 +69,11 @@ Returns C if no A record exists for the name. sub ipv4_from_hostname { my $name = shift; return unless $name; + my $ETCHOSTS = setting('dns')->{'ETCHOSTS'}; # check /etc/hosts file and short-circuit if found - if (exists $HOSTS{$name} and $HOSTS{$name}->[0]->[0]) { - my $ip = NetAddr::IP::Lite->new($HOSTS{$name}->[0]->[0]); + if (exists $ETCHOSTS->{$name} and $ETCHOSTS->{$name}->[0]->[0]) { + my $ip = NetAddr::IP::Lite->new($ETCHOSTS->{$name}->[0]->[0]); return $ip->addr if $ip and $ip->bits == 32; } @@ -105,63 +90,4 @@ sub ipv4_from_hostname { return undef; } -=head2 hostnames_resolve_async( \@ips, \@timeouts? ) - -This method uses a fully asynchronous and high-performance pure-perl stub -resolver C. - -Given a reference to an array of hashes will resolve the C or C -address in the C, C, or C key of each hash into its -hostname which will be inserted in the C key of the hash. - -Optionally provide a set of timeout values in seconds which is also the -number of resolver attempts. The default is C<< [2,5,5] >>. - -Returns the supplied reference to an array of hashes with dns values for -addresses which resolved. - -=cut - -sub hostnames_resolve_async { - my ($ips, $timeouts) = @_; - return [] unless $ips and ref [] eq ref $ips; - $timeouts ||= [2,5,5]; - - my $skip = setting('dns')->{'no'}; - AnyEvent::DNS::resolver->timeout(@$timeouts); - - # Set up the condvar - my $done = AE::cv; - $done->begin( sub { shift->send } ); - - IP: foreach my $hash_ref (@$ips) { - my $ip = $hash_ref->{'ip'} || $hash_ref->{'alias'} || $hash_ref->{'device'}; - next IP if App::Netdisco::Util::Permission::check_acl_no($ip, $skip); - - # check /etc/hosts file and short-circuit if found - foreach my $name (reverse sort keys %HOSTS) { - if ($HOSTS{$name}->[0]->[0] eq $ip) { - $hash_ref->{'dns'} = $name; - next IP; - } - } - - $done->begin; - AnyEvent::DNS::reverse_lookup $ip, - sub { $hash_ref->{'dns'} = shift; $done->end; }; - } - - # Decrement the cv counter to cancel out the send declaration - $done->end; - - # Wait for the resolver to perform all resolutions - $done->recv; - - # Remove reference to resolver so that we close sockets - # and allow return to any instance defaults we have changed - undef $AnyEvent::DNS::RESOLVER if $AnyEvent::DNS::RESOLVER; - - return $ips; -} - 1; diff --git a/lib/App/Netdisco/Util/FastResolver.pm b/lib/App/Netdisco/Util/FastResolver.pm new file mode 100644 index 00000000..569c7af6 --- /dev/null +++ b/lib/App/Netdisco/Util/FastResolver.pm @@ -0,0 +1,88 @@ +package App::Netdisco::Util::FastResolver; + +use strict; +use warnings; +use Dancer ':script'; + +use AnyEvent::DNS; +use App::Netdisco::Util::Permission 'check_acl_no'; + +use base 'Exporter'; +our @EXPORT = (); +our @EXPORT_OK = qw/hostnames_resolve_async/; +our %EXPORT_TAGS = (all => \@EXPORT_OK); + +=head1 NAME + +App::Netdisco::Util::FastResolver + +=head1 DESCRIPTION + +A set of helper subroutines to support parts of the Netdisco application. + +There are no default exports, however the C<:all> tag will export all +subroutines. + +=head1 EXPORT_OK + +=head2 hostnames_resolve_async( \@ips, \@timeouts? ) + +This method uses a fully asynchronous and high-performance pure-perl stub +resolver C. + +Given a reference to an array of hashes will resolve the C or C +address in the C, C, or C key of each hash into its +hostname which will be inserted in the C key of the hash. + +Optionally provide a set of timeout values in seconds which is also the +number of resolver attempts. The default is C<< [2,5,5] >>. + +Returns the supplied reference to an array of hashes with dns values for +addresses which resolved. + +=cut + +sub hostnames_resolve_async { + my ($ips, $timeouts) = @_; + return [] unless $ips and ref [] eq ref $ips; + $timeouts ||= [2,5,5]; + + my $skip = setting('dns')->{'no'}; + my $ETCHOSTS = setting('dns')->{'ETCHOSTS'}; + AnyEvent::DNS::resolver->timeout(@$timeouts); + + # Set up the condvar + my $done = AE::cv; + $done->begin( sub { shift->send } ); + + IP: foreach my $hash_ref (@$ips) { + my $ip = $hash_ref->{'ip'} || $hash_ref->{'alias'} || $hash_ref->{'device'}; + next IP if check_acl_no($ip, $skip); + + # check /etc/hosts file and short-circuit if found + foreach my $name (reverse sort keys %$ETCHOSTS) { + if ($ETCHOSTS->{$name}->[0]->[0] eq $ip) { + $hash_ref->{'dns'} = $name; + next IP; + } + } + + $done->begin; + AnyEvent::DNS::reverse_lookup $ip, + sub { $hash_ref->{'dns'} = shift; $done->end; }; + } + + # Decrement the cv counter to cancel out the send declaration + $done->end; + + # Wait for the resolver to perform all resolutions + $done->recv; + + # Remove reference to resolver so that we close sockets + # and allow return to any instance defaults we have changed + undef $AnyEvent::DNS::RESOLVER if $AnyEvent::DNS::RESOLVER; + + return $ips; +} + +1; diff --git a/lib/App/Netdisco/Web/Plugin/AdminTask/TimedOutDevices.pm b/lib/App/Netdisco/Web/Plugin/AdminTask/TimedOutDevices.pm index f7048c1f..23d2dd8b 100644 --- a/lib/App/Netdisco/Web/Plugin/AdminTask/TimedOutDevices.pm +++ b/lib/App/Netdisco/Web/Plugin/AdminTask/TimedOutDevices.pm @@ -6,7 +6,7 @@ use Dancer::Plugin::DBIC; use Dancer::Plugin::Auth::Extensible; use App::Netdisco::Web::Plugin; -use App::Netdisco::Util::DNS 'hostnames_resolve_async'; +use App::Netdisco::Util::FastResolver 'hostnames_resolve_async'; register_admin_task({ tag => 'timedoutdevices',