File Coverage

blib/lib/App/DNS/Zonewalk.pm
Criterion Covered Total %
statement 44 60 73.3
branch 11 34 32.3
condition 2 9 22.2
subroutine 6 6 100.0
pod 1 1 100.0
total 64 110 58.1


line stmt bran cond sub pod time code
1             package App::DNS::Zonewalk;
2              
3 1     1   897 use strict;
  1         2  
  1         36  
4 1     1   4 use warnings;
  1         2  
  1         33  
5 1     1   16 use feature qw(switch);
  1         2  
  1         93  
6              
7 1     1   843 use parent 'Net::DNS::Resolver';
  1         293  
  1         5  
8              
9             our $VERSION = 0.05;
10              
11             =head1 NAME
12              
13             App::DNS::Zonewalk - helper library for recursive DNS zone walks.
14              
15             =head1 ABSTRACT
16              
17             Helper library for B. Adds a B method to Net::DNS::Resolver.
18              
19             =head1 SYNOPSIS
20              
21             use App::DNS::Zonewalk;
22              
23             my $resolver = App::DNS::Zonewalk->new();
24             my @net_dns_rrs = $resolver->raxfr($start_zone)
25             ...
26              
27             =head1 DESCRIPTION
28              
29             See the B documentation for more details, a cli program included in this distribution for recusive DNS zonewalks.
30              
31             =head1 METHODS
32              
33             =head2 raxfr($start_zone)
34              
35             my @resource_records = $resolver->raxfr($start_zone);
36              
37             Walks the $start_zone recursively and returns all DNS resource records. The DNS server from $resolver must be authoritative for the zone und sub-zones and the client must be allowed to fetch the zones via AXFR.
38              
39             =cut
40              
41             sub raxfr {
42 1     1 1 1953 my ( $self, $start_zone ) = @_;
43              
44 1 50       4 unless ($start_zone) {
45 0 0       0 print ";; ERROR: raxfr: no zone specified\n" if $self->{'debug'};
46 0         0 $self->errorstring('no zone');
47 0         0 return;
48             }
49              
50             # housekeeping for recursion
51 1         3 my $dyn_zone_list = {};
52 1         2 my $zones_done = {};
53 1         2 my @zone;
54              
55 1         3 $dyn_zone_list->{$start_zone}++;
56              
57             ZONE:
58 1         8 while ( my ($zone) = sort keys %$dyn_zone_list ) {
59              
60 1 50       12 print ";; processing '$zone' ...\n" if $self->{'debug'};
61              
62 1         3 delete $dyn_zone_list->{$zone};
63 1 50       4 next ZONE if exists $zones_done->{$zone};
64              
65             # mark current zone as done
66 1         2 $zones_done->{$zone}++;
67              
68             # skip zone if resolvers nameserver isn't autoritative
69 1 50       5 next ZONE unless $self->_check_is_auth($zone);
70              
71 0         0 my @zone_records = $self->axfr($zone);
72              
73 0 0       0 unless (@zone_records) {
74 0 0       0 print ";; skipping $zone: ", $self->errorstring, "\n"
75             if $self->{'debug'};
76 0         0 next ZONE;
77             }
78              
79 0         0 foreach my $rr (@zone_records) {
80 0         0 push @zone, $rr;
81              
82 0 0       0 if ( $rr->type eq 'NS' ) {
83              
84 0         0 my $new_zone = lc $rr->name;
85              
86             # push to dyn_zone_list when index('foo.bar.baz', 'bar.baz')
87             # not already handled and not already stored for handling
88 0 0 0     0 if ( index( $new_zone, $zone )
      0        
89             && not exists $zones_done->{$new_zone}
90             && not exists $dyn_zone_list->{$new_zone} )
91             {
92 0         0 $dyn_zone_list->{$new_zone}++;
93             }
94              
95             }
96             }
97              
98             }
99              
100 1 50       160 return wantarray ? @zone : \@zone;
101             }
102              
103             ###############################################
104             # check if nameserver is authoritative for zone
105             ###############################################
106              
107             sub _check_is_auth {
108 1     1   2 my ( $self, $zone ) = @_;
109              
110             # get the nameservers for this zone
111 1         11 my $ans = $self->send( $zone, 'NS' );
112              
113             # uups, something bad happened
114 1 50       10338 unless ( defined $ans ) {
115 0 0       0 print ';; ERROR: ', $self->errorstring, "\n"
116             if $self->{'debug'};
117 0         0 return;
118             }
119              
120             # store the nameserver FQDN names
121 1         3 my @ns_names;
122 1         7 foreach my $rr ( $ans->answer ) {
123 2         76 push @ns_names, $rr->nsdname;
124             }
125              
126             # but we need the addresses for comparison, sigh
127 1         62 my @ns_addresses;
128 1         4 foreach my $ns_name (@ns_names) {
129 2         62 my $any_packet = $self->query( $ns_name, 'ANY' );
130              
131 2 50       9815 next unless defined $any_packet;
132              
133 2         12 foreach my $rr ( $any_packet->answer ) {
134 4 50       47 next unless defined $rr;
135 4 50 66     28 next unless ( $rr->type eq 'A' || $rr->type eq 'AAAA' );
136 4         96 push @ns_addresses, $rr->address;
137             }
138             }
139              
140             # now compare the resolvers nameserver with the authoritattive
141             # nameservers for this zone
142 1         24 my ($resolvers_ns) = $self->nameservers;
143              
144 1 50       37 unless ( $resolvers_ns ~~ @ns_addresses ) {
145 1         8 $self->errorstring("NS $resolvers_ns is nonauth for $zone");
146 1 50       9 print ';; ERROR: ', $self->errorstring, "\n"
147             if $self->{'debug'};
148 1         36 return;
149             }
150              
151             # our resolvers first nameserver is authoritative
152 0           return 1;
153              
154             }
155              
156             =head1 AUTHOR
157              
158             Karl Gaissmaier, C<< >>
159              
160             =head1 BUGS
161              
162             Please report any bugs or feature requests to C, or through
163             the web interface at L. I will be notified, and then you'll
164             automatically be notified of progress on your bug as I make changes.
165              
166             =head1 SUPPORT
167              
168             You can find documentation for this module with the perldoc command.
169              
170             perldoc App::DNS::Zonewalk
171              
172              
173             You can also look for information at:
174              
175             =over 4
176              
177             =item * RT: CPAN's request tracker (report bugs here)
178              
179             L
180              
181             =item * AnnoCPAN: Annotated CPAN documentation
182              
183             L
184              
185             =item * CPAN Ratings
186              
187             L
188              
189             =item * Search CPAN
190              
191             L
192              
193             =back
194              
195             =head1 LICENSE AND COPYRIGHT
196              
197             Copyright 2012 Karl Gaissmaier.
198              
199             This program is free software; you can redistribute it and/or modify it
200             under the terms of either: the GNU General Public License as published
201             by the Free Software Foundation; or the Artistic License.
202              
203             See http://dev.perl.org/licenses/ for more information.
204              
205             =cut
206              
207             1; # End of App::DNS::Zonewalk
208              
209             # vim: sw=4 ft=perl