File Coverage

blib/lib/GlbDNS/Zone.pm
Criterion Covered Total %
statement 12 98 12.2
branch 0 50 0.0
condition 0 19 0.0
subroutine 4 9 44.4
pod 3 3 100.0
total 19 179 10.6


line stmt bran cond sub pod time code
1             package GlbDNS::Zone;
2              
3              
4 7     7   30061 use strict;
  7         16  
  7         447  
5 7     7   348 use warnings;
  7         15  
  7         199  
6 7     7   40 use Data::Dumper;
  7         14  
  7         395  
7 7     7   13203 use Net::DNS::RR::A;
  7         4616  
  7         11571  
8              
9              
10             =head1 GlbDNS::Zone
11              
12             Parsing zone files with LOC data
13              
14             =head2 load_configs
15              
16             GlbDNS->load_configs($glbdns, $path);
17             GlbDNS->load_configs($glbdns, $file);
18              
19             =cut
20              
21             sub load_configs {
22 0     0 1   my $class = shift;
23 0           my $glbdns = shift;
24 0           my $path = shift;
25 0 0         if (-d $path) {
    0          
26 0 0         opendir(DIR, $path) || die "Cannot open directory '$path': $!\n";
27 0           for my $file (readdir(DIR)) {
28 0 0         next if (-d $file);
29 0 0         next if ($file =~/^(\.|#)/);
30 0 0         next if ($file =~/~$/);
31 0           $class->parse($glbdns, "$path/$file");
32             }
33             } elsif (-f $path) {
34 0           $class->parse($glbdns, $path);
35             } else {
36 0           die "Cannot find zone '$path'\n";
37             }
38 0           $class->geo_fix($glbdns);
39             }
40              
41             =head2 parse
42              
43             GlbDNS->parse($glbdns, $file);
44              
45             =cut
46              
47             sub parse {
48 0     0 1   my $class = shift;
49 0           my $glbdns = shift;
50 0           my $file = shift;
51              
52 0 0         open(my $fh, "<", "$file") || die "Cannot open file '$file': $!\n";
53 0           my $mtime = @{[stat("$file")]}[9];
  0            
54              
55             my $error = sub {
56 0     0     die "$_[0] at $file:$.\n";
57 0           };
58              
59              
60              
61 0           my $base_fqdn;
62             my $base;
63 0   0       my $hosts = $glbdns->{hosts} ||= {};
64 0           while(my $line = <$fh>) {
65 0           chomp($line);
66 0 0         next unless($line);
67 0 0         next if($line =~ /^\s+$/);
68 0 0         next if($line =~ /^;/);
69              
70 0 0         if($line =~/\$ORIGIN\s+([a-zA-Z.\-]+)/) {
    0          
71 0           $base_fqdn = $1;
72 0           ($base) = $base_fqdn =~/(.*)\.$/;
73 0 0         $error->("'$base_fqdn' needs to be terminated with a . to be a FQDN") unless ($base);
74 0           next;
75             } elsif (!$base) {
76 0           $error->("No \$ORIGIN domain has been specified, don't know what domain we are working on");
77             }
78              
79 0           my @record = split /\s+/, $line;
80              
81             # if the first record is a DNS entry
82             # then check if it is a FQDN and complete it
83             # or use the default one
84 0 0         if ($record[0] !~ /^\d+$/) {
85 0 0         $record[0] = "$record[0].$base" if($record[0] !~ /\.$/);
86             } else {
87 0           unshift @record, $base;
88             }
89              
90             # fully qualify CNAMEs
91 0 0 0       if($record[3] eq 'CNAME' && $record[4] !~/\.$/) {
92 0           $record[4] .= ".$base";
93             }
94             # fully qualify MX
95 0 0 0       if($record[3] eq 'MX' && $record[5] !~/\.$/) {
96 0           $record[5] .= ".$base";
97             }
98              
99              
100             my $add_host = sub {
101 0     0     my $record = shift;
102 0   0       my $host = $hosts->{$record->name} ||= {};
103 0   0       my $records = $host->{$record->type} ||= [];
104 0           $host->{__RECORD__} = $record->name;
105 0           $host->{domain} = $host->{__DOMAIN__} = $base;
106 0           push @$records, $record;
107 0           };
108              
109 0           my $rr = Net::DNS::RR->new(join " ", @record);
110              
111             # autocreate PTR records for A records
112             # there can be more than one
113 0 0         if ($rr->type eq 'A') {
114 0           my $address = join(".", reverse( split(/\./, $rr->address)) ) ;
115 0           my $reverse = Net::DNS::RR->new("$address.in-addr.arpa. " . $rr->ttl . " IN PTR " . $rr->name);
116 0           $add_host->($reverse);
117             }
118              
119              
120 0           $add_host->($rr);
121              
122              
123              
124              
125              
126             }
127 0           close($fh);
128             }
129              
130             =head2 geo_fix
131              
132             GlbDNS::Zone->geo_fix($glbdns);
133              
134             =cut
135              
136             sub geo_fix {
137 0     0 1   my $class = shift;
138 0           my $glbdns = shift;
139 0           my $hosts = $glbdns->{hosts};
140             # now go through and fix up the geolocation ones
141 0           foreach my $host (values %{$hosts}) {
  0            
142 0 0 0       if ($host->{CNAME} && @{$host->{CNAME}} > 1) {
  0            
143             # more than one cname is not allowed
144             # so they have to point to geo tagged records
145             # or we abort
146 0           foreach my $cname (@{$host->{CNAME}}) {
  0            
147 0           my $target = $hosts->{$cname->cname};
148 0 0         die "Need record for " . $cname->cname . "\n" unless $target;
149 0 0         die "Record " . $cname->name . " needs LOC data\n" unless $target->{LOC};
150              
151 0           my ($lat, $lon) = $target->{LOC}[0]->latlon;
152 0   0       my $geo = $host->{__GEO__} ||= {};
153              
154 0 0         die "Trying to overwrite geo target $target->{__RECORD__}\n" if($geo->{$target->{__RECORD__}});
155 0           my $geo_entry = $geo->{$target->{__RECORD__}} = {};
156              
157 0           $geo_entry->{lat} = $lat;
158 0           $geo_entry->{lon} = $lon;
159 0   0       $geo_entry->{hosts} = $target->{A} || $target->{CNAME} || die "Need A or CNAME for $target->{__RECORD__}\n";
160 0 0         if ($target->{TXT}) {
161 0           foreach my $txt (@{$target->{TXT}}) {
  0            
162 0           my @txt = $txt->char_str_list;
163 0 0         if($txt[0] eq 'GlbDNS::RADIUS') {
    0          
164 0           $geo_entry->{radius} = $txt[1];
165             } elsif($txt[0] eq 'GlbDNS::CHECK') {
166 0           foreach my $check_host (@{$geo_entry->{hosts}}) {
  0            
167 0           $glbdns->{checks}->{$check_host->address} = {
168             ip => $check_host->address,
169             url => $txt[1],
170             expect => $txt[2],
171             interval => 5,
172             };
173             }
174             }
175             }
176             }
177 0           $geo_entry->{source}->{$host->{__RECORD__}} = $cname;
178             }
179 0           delete($host->{CNAME});
180             }
181             }
182             }
183              
184              
185             1;