-
Notifications
You must be signed in to change notification settings - Fork 36
Add DNAME support (to Zone objects) #1213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,15 +3,15 @@ package Zonemaster::Engine::Zone; | |
| use v5.16.0; | ||
| use warnings; | ||
|
|
||
| use version; our $VERSION = version->declare("v1.1.9"); | ||
| use version; our $VERSION = version->declare("v1.2.0"); | ||
|
|
||
| use Carp qw( confess croak ); | ||
| use List::MoreUtils qw[uniq]; | ||
|
|
||
| use Zonemaster::Engine::DNSName; | ||
| use Zonemaster::Engine::Recursor; | ||
| use Zonemaster::Engine::NSArray; | ||
| use Zonemaster::Engine::Constants qw[:ip]; | ||
| use Zonemaster::Engine::Constants qw[:ip :dname]; | ||
|
|
||
| sub new { | ||
| my ( $class, $attrs ) = @_; | ||
|
|
@@ -46,6 +46,16 @@ sub parent { | |
| return $self->{_parent}; | ||
| } | ||
|
|
||
| sub dname { | ||
| my ( $self ) = @_; | ||
|
|
||
| if ( !exists $self->{_dname} ) { | ||
| $self->{_dname} = $self->_build_dname; | ||
| } | ||
|
|
||
| return $self->{_dname}; | ||
| } | ||
|
|
||
| sub glue_names { | ||
| my ( $self ) = @_; | ||
|
|
||
|
|
@@ -96,6 +106,7 @@ sub glue_addresses { | |
| return $self->{_glue_addresses}; | ||
| } | ||
|
|
||
|
|
||
| ### | ||
| ### Builders | ||
| ### | ||
|
|
@@ -113,25 +124,112 @@ sub _build_parent { | |
| return __PACKAGE__->new( { name => $pname } ); | ||
| } | ||
|
|
||
| sub _build_dname { | ||
| my ( $self ) = @_; | ||
|
|
||
| if ( $self->name eq '.' or not $self->parent ) { | ||
| return undef; | ||
| } | ||
|
|
||
| my $p = $self->parent->query_persistent( $self->name, 'DNAME' ); | ||
|
|
||
| return undef unless $p; | ||
|
|
||
| Zonemaster::Engine->logger->add( DNAME_FOUND => { name => $self->name } ); | ||
|
|
||
| my @dname_rrs = $p->get_records( 'DNAME' ); | ||
|
|
||
| # Remove duplicate DNAME RRs | ||
| my ( %duplicate_dname_rrs, @unique_rrs ); | ||
| for my $rr ( @dname_rrs ) { | ||
| my $rr_hash = $rr->class . '/DNAME/' . lc($rr->owner) . '/' . lc($rr->dname); | ||
|
|
||
| if ( exists $duplicate_dname_rrs{$rr_hash} ) { | ||
| $duplicate_dname_rrs{$rr_hash}++; | ||
| } | ||
| else { | ||
| $duplicate_dname_rrs{$rr_hash} = 0; | ||
| push @unique_rrs, $rr; | ||
| } | ||
| } | ||
|
|
||
| unless ( scalar @unique_rrs == scalar @dname_rrs ) { | ||
| @dname_rrs = @unique_rrs; | ||
| } | ||
|
|
||
| # Break if there are too many records | ||
| if ( scalar @dname_rrs > $DNAME_MAX_RECORDS ) { | ||
| return undef; | ||
| } | ||
|
Comment on lines
+160
to
+163
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could do this check earlier, before we start removing duplicate DNAME resource records maybe.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My original intention was indeed to check for this limit after removing duplicate RRs. I could see both work. @matsduf any thoughts on this? |
||
|
|
||
| my ( %dnames, %seen_targets ); | ||
| for my $rr ( @dname_rrs ) { | ||
| my $rr_owner = Zonemaster::Engine::DNSName->new( lc( $rr->owner ) ); | ||
| my $rr_target = Zonemaster::Engine::DNSName->new( lc( $rr->dname ) ); | ||
|
|
||
| # Multiple DNAME records with same owner name | ||
| if ( exists $dnames{$rr_owner} ) { | ||
| return undef; | ||
| } | ||
|
|
||
| # DNAME owner name is target, or target has already been seen in this response, or owner name cannot be a target | ||
| if ( $rr_owner eq $rr_target or exists $seen_targets{$rr_target} or exists $dnames{$rr_target} ) { | ||
| return undef; | ||
| } | ||
|
|
||
| $seen_targets{$rr_target} = 1; | ||
| $dnames{$rr_owner} = $rr_target; | ||
| } | ||
|
|
||
| # Get final DNAME target | ||
| my $target = $self->name; | ||
| my $dname_counter = 0; | ||
| while ( $dnames{$target} ) { | ||
| return undef if $dname_counter > $DNAME_MAX_RECORDS; # Loop protection (for good measure only - data in %dnames is sanitized already) | ||
| $target = $dnames{$target}; | ||
| $dname_counter++; | ||
| } | ||
|
|
||
| # Make sure that the DNAME chain from the pre-validated DNAME RRset is complete | ||
| if ( $dname_counter != scalar @dname_rrs ) { | ||
| return undef; | ||
| } | ||
|
|
||
| # Make sure that the DNAME target is not a subdomain | ||
| if ( $self->name->is_in_bailiwick( $target ) ) { | ||
| return undef; | ||
| } | ||
|
|
||
| return __PACKAGE__->new( { name => Zonemaster::Engine::DNSName->new( $target ) } ); | ||
| } | ||
|
|
||
| sub _build_glue_names { | ||
| my ( $self ) = @_; | ||
| my $zname = $self->name; | ||
| my $p; | ||
|
|
||
| if ( not $self->parent ) { | ||
| return []; | ||
| } | ||
|
|
||
| my $p = $self->parent->query_persistent( $self->name, 'NS' ); | ||
| if ( $self->dname ) { | ||
| $zname = $self->dname->name; | ||
| $p = $self->dname->parent->query_persistent( $zname, 'NS' ); | ||
| } | ||
| else { | ||
| $p = $self->parent->query_persistent( $zname, 'NS' ); | ||
| } | ||
|
|
||
| return [] if not defined $p; | ||
|
|
||
| return [ uniq sort map { Zonemaster::Engine::DNSName->new( lc( $_->nsdname ) ) } | ||
| $p->get_records_for_name( 'ns', $self->name->string ) ]; | ||
| $p->get_records_for_name( 'ns', $zname->string ) ]; | ||
| } | ||
|
|
||
| sub _build_glue { | ||
| my ( $self ) = @_; | ||
| my @glue_names = @{ $self->glue_names }; | ||
| my $zname = $self->name->string; | ||
| my @glue_names = @{$self->glue_names}; | ||
|
|
||
| if ( Zonemaster::Engine::Recursor->has_fake_addresses( $zname ) ) { | ||
| my @ns_list; | ||
|
|
@@ -153,24 +251,34 @@ sub _build_glue { | |
|
|
||
| sub _build_ns_names { | ||
| my ( $self ) = @_; | ||
| my $zname = $self->name; | ||
| my $servers; | ||
| my $p; | ||
| my $i = 0; | ||
|
|
||
| if ( $self->name eq '.' ) { | ||
| my %u; | ||
| $u{$_} = $_ for map { $_->name } @{ $self->ns }; | ||
| return [ sort values %u ]; | ||
| } | ||
|
|
||
| my $p; | ||
| my $i = 0; | ||
| while ( my $s = $self->glue->[$i] ) { | ||
| $p = $s->query( $self->name, 'NS' ); | ||
| if ( $self->dname ) { | ||
| $zname = $self->dname->name; | ||
| $servers = $self->dname->glue; | ||
| } | ||
| else { | ||
| $servers = $self->glue; | ||
| } | ||
|
|
||
| while ( my $s = $servers->[$i] ) { | ||
| $p = $s->query( $zname, 'NS' ); | ||
| last if ( defined( $p ) and ( $p->type eq 'answer' ) and ( $p->rcode eq 'NOERROR' ) ); | ||
| $i += 1; | ||
| } | ||
| return [] if not defined $p; | ||
|
|
||
| return [ uniq sort map { Zonemaster::Engine::DNSName->new( lc( $_->nsdname ) ) } | ||
| $p->get_records_for_name( 'ns', $self->name->string ) ]; | ||
| $p->get_records_for_name( 'ns', $zname ) ]; | ||
| } ## end sub _build_ns_names | ||
|
|
||
| sub _build_ns { | ||
|
|
@@ -188,12 +296,21 @@ sub _build_ns { | |
|
|
||
| sub _build_glue_addresses { | ||
| my ( $self ) = @_; | ||
| my $zname = $self->name; | ||
| my $p; | ||
|
|
||
| if ( not $self->parent ) { | ||
| return []; | ||
| } | ||
|
|
||
| my $p = $self->parent->query_one( $self->name, 'NS' ); | ||
| if ( $self->dname ) { | ||
| $zname = $self->dname->name; | ||
| $p = $self->dname->parent->query_one( $zname, 'NS' ); | ||
| } | ||
| else { | ||
| $p = $self->parent->query_one( $zname, 'NS' ); | ||
| } | ||
|
|
||
| croak "Failed to get glue addresses" if not defined( $p ); | ||
|
|
||
| return [ $p->get_records( 'a' ), $p->get_records( 'aaaa' ) ]; | ||
|
|
@@ -406,6 +523,10 @@ A L<Zonemaster::Engine::Zone> object for this domain's parent domain. As a | |
| special case, the root zone is considered to be its own parent (so | ||
| look for that if you recurse up the tree). | ||
|
|
||
| =item dname | ||
|
|
||
| A L<Zonemaster::Engine::Zone> object which is this zone's DNAME target, if any. | ||
|
|
||
| =item ns_names | ||
|
|
||
| A reference to an array of L<Zonemaster::Engine::DNSName> objects, holding the | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.