184 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			184 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| package App::Netdisco::Util::DNS;
 | |
| 
 | |
| use strict;
 | |
| use warnings;
 | |
| 
 | |
| use Dancer ':script';
 | |
| use Net::DNS;
 | |
| use AnyEvent::DNS;
 | |
| use NetAddr::IP::Lite ':lower';
 | |
| 
 | |
| use base 'Exporter';
 | |
| our @EXPORT = ();
 | |
| our @EXPORT_OK = qw/
 | |
|   hostname_from_ip hostnames_resolve_async 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
 | |
| 
 | |
| =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 hostname_from_ip( $ip )
 | |
| 
 | |
| Given an IP address (either IPv4 or IPv6), return the canonical hostname.
 | |
| 
 | |
| Returns C<undef> if no PTR record exists for the IP.
 | |
| 
 | |
| =cut
 | |
| 
 | |
| sub hostname_from_ip {
 | |
|   my $ip = shift;
 | |
|   return unless $ip;
 | |
| 
 | |
|   # check /etc/hosts file and short-circuit if found
 | |
|   foreach my $name (reverse sort keys %HOSTS) {
 | |
|       if ($HOSTS{$name}->[0]->[0] eq $ip) {
 | |
|           return $name;
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   my $res   = Net::DNS::Resolver->new;
 | |
|   my $query = $res->search($ip);
 | |
| 
 | |
|   if ($query) {
 | |
|       foreach my $rr ($query->answer) {
 | |
|           next unless $rr->type eq "PTR";
 | |
|           return $rr->ptrdname;
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   return undef;
 | |
| }
 | |
| 
 | |
| =head2 ipv4_from_hostname( $name )
 | |
| 
 | |
| Given a host name will return the first IPv4 address.
 | |
| 
 | |
| Returns C<undef> if no A record exists for the name.
 | |
| 
 | |
| =cut
 | |
| 
 | |
| sub ipv4_from_hostname {
 | |
|   my $name = shift;
 | |
|   return unless $name;
 | |
| 
 | |
|   # 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]);
 | |
|       return $ip->addr if $ip and $ip->bits == 32;
 | |
|   }
 | |
| 
 | |
|   my $res   = Net::DNS::Resolver->new;
 | |
|   my $query = $res->search($name);
 | |
| 
 | |
|   if ($query) {
 | |
|       foreach my $rr ($query->answer) {
 | |
|           next unless $rr->type eq "A";
 | |
|           return $rr->address;
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   return undef;
 | |
| }
 | |
| 
 | |
| =head2 hostnames_resolve_async( $ips )
 | |
| 
 | |
| This method uses a fully asynchronous and high-performance pure-perl stub
 | |
| resolver C<AnyEvent::DNS>.
 | |
| 
 | |
| Given a reference to an array of hashes will resolve the C<IPv4> or C<IPv6>
 | |
| address in the C<ip> or C<alias> key of each hash into its hostname which
 | |
| will be inserted in the C<dns> key of the hash.
 | |
| 
 | |
| Returns the supplied reference to an array of hashes with dns values for
 | |
| addresses which resolved.
 | |
| 
 | |
| =cut
 | |
| 
 | |
| sub hostnames_resolve_async {
 | |
|   my $ips = shift;
 | |
| 
 | |
|   # 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'};
 | |
|     next IP if no_resolve($ip);
 | |
| 
 | |
|     # 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
 | |
|   undef $AnyEvent::DNS::RESOLVER if $AnyEvent::DNS::RESOLVER;
 | |
| 
 | |
|   return $ips;
 | |
| }
 | |
| 
 | |
| =head2 no_resolve( $ip )
 | |
| 
 | |
| Given an IP address, returns true if excluded from DNS resolution by the
 | |
| C<dns_no> configuration directive, otherwise returns false.
 | |
| 
 | |
| =cut
 | |
| 
 | |
| sub no_resolve {
 | |
|     my $ip = shift;
 | |
| 
 | |
|     my $config = setting('dns')->{no} || [];
 | |
|     return 0 if not scalar @$config;
 | |
| 
 | |
|     my $addr = NetAddr::IP::Lite->new($ip)
 | |
|       or return 1;
 | |
| 
 | |
|     foreach my $item (@$config) {
 | |
|         my $c_ip = NetAddr::IP::Lite->new($item)
 | |
|             or next;
 | |
|         next unless $c_ip->bits == $addr->bits;
 | |
| 
 | |
|         return 1 if ($c_ip->contains($addr));
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| 1;
 | |
| 
 |