File Coverage

blib/lib/Net/DNSServer/Cache.pm
Criterion Covered Total %
statement 24 129 18.6
branch 0 32 0.0
condition 0 42 0.0
subroutine 8 16 50.0
pod 4 8 50.0
total 36 227 15.8


line stmt bran cond sub pod time code
1             package Net::DNSServer::Cache;
2              
3 1     1   715 use strict;
  1         3  
  1         34  
4 1     1   5 use Exporter;
  1         2  
  1         38  
5 1     1   6 use vars qw(@ISA $expiration_check);
  1         2  
  1         110  
6 1     1   5 use Net::DNSServer::Base;
  1         1  
  1         19  
7 1     1   5 use Net::DNS;
  1         1  
  1         251  
8 1     1   6 use Net::DNS::RR;
  1         2  
  1         22  
9 1     1   5 use Net::DNS::Packet;
  1         2  
  1         26  
10 1     1   5 use Carp qw(croak);
  1         3  
  1         1928  
11              
12             @ISA = qw(Net::DNSServer::Base);
13             $expiration_check = undef;
14              
15             # Created and passed to Net::DNSServer->run()
16             sub new {
17 0   0 0 1   my $class = shift || __PACKAGE__;
18 0   0       my $self = shift || {};
19 0   0       $self -> {dns_cache} ||= {};
20 0           return bless $self, $class;
21             }
22              
23             # Check if the TTL is still good
24             sub validate_ttl {
25 0 0   0 0   my $value = shift or return undef;
26 0 0         return undef unless (ref $value) eq "ARRAY";
27 0           foreach my $entry (@$value) {
28             # If this entry has expired, then throw the whole thing out
29 0 0 0       return undef if (ref $entry) ne "ARRAY" || $entry->[0] < time;
30             }
31             # If nothing has expired, the data is still valid
32 0           return $value;
33             }
34              
35             # Called immediately after incoming request
36             # Takes the Net::DNS::Packet question as an argument
37             sub pre {
38 0     0 1   my $self = shift;
39 0   0       my $net_dns_packet = shift || croak 'Usage> $obj->resolve($Net_DNS_obj)';
40 0           $self -> {question} = $net_dns_packet;
41 0           $self -> {net_server} -> {usecache} = 1;
42 0           return 1;
43             }
44              
45             # Called after all pre methods have finished
46             # Returns a Net::DNS::Packet object as the answer
47             # or undef to pass to the next module to resolve
48             sub resolve {
49 0     0 1   my $self = shift;
50 0           my $dns_packet = $self -> {question};
51 0           my ($question) = $dns_packet -> question();
52 0           my $key = $question->string();
53 0   0       my $cache_structure = $self -> {dns_cache} -> {"$key;structure"} || undef;
54 0 0 0       unless ($cache_structure &&
      0        
55             (ref $cache_structure) eq "ARRAY" &&
56             (scalar @$cache_structure) == 3) {
57 0           print STDERR "DEBUG: Structure Cache miss on [$key]\n";
58 0           return undef;
59             }
60 0           print STDERR "DEBUG: Structure Cache hit on [$key]\n";
61             # Structure key found in cache, so lookup actual values
62              
63             # ANSWER Section
64 0           my $answer_ref = $self->fetch_rrs($cache_structure->[0]);
65              
66             # AUTHORITY Section
67 0           my $authority_ref = $self->fetch_rrs($cache_structure->[1]);
68              
69             # ADDITIONAL Section
70 0           my $additional_ref = $self->fetch_rrs($cache_structure->[2]);
71              
72             # Make sure all sections were loaded successfully from cache.
73 0 0 0       unless ($answer_ref && $authority_ref && $additional_ref) {
      0        
74             # If not, flush structure key to ensure
75             # it will be re-stored in the post() phase.
76 0           delete $self -> {dns_cache} -> {"$key;structure"};
77 0           return undef;
78             }
79              
80             # Initialize the response packet with a copy of the request
81             # packet in order to set the header and question sections
82 0           my $response = bless \%{$dns_packet}, "Net::DNS::Packet"
  0            
83             || die "Could not initialize response packet";
84              
85             # Install the RRs into their corresponding sections
86 0           $response->push("answer", @$answer_ref);
87 0           $response->push("authority", @$authority_ref);
88 0           $response->push("additional", @$additional_ref);
89              
90 0           $self -> {net_server} -> {usecache} = 0;
91 0           return $response;
92             }
93              
94             sub fetch_rrs {
95 0     0 0   my $self = shift;
96 0           my $array_ref = shift;
97 0           my @rrs = ();
98 0 0         if (ref $array_ref ne "ARRAY") {
99 0           return undef;
100             }
101 0           foreach my $rr_string (@$array_ref) {
102 0           my $lookup = validate_ttl($self -> {dns_cache} -> {"$rr_string;lookup"});
103 0 0 0       unless ($lookup && ref $lookup eq "ARRAY") {
104 0           print STDERR "DEBUG: Lookup Cache miss on [$rr_string]\n";
105 0           return undef;
106             }
107 0           print STDERR "DEBUG: Lookup Cache hit on [$rr_string]\n";
108              
109 0           foreach my $entry (@$lookup) {
110 0 0         return undef unless ref $entry eq "ARRAY";
111 0           my ($expire,$rdatastr) = @$entry;
112 0           my $rr = Net::DNS::RR->new ("$rr_string\t$rdatastr");
113 0           $rr->ttl($expire - time);
114 0           push @rrs, $rr;
115             }
116             }
117 0           return \@rrs;
118             }
119              
120             # Called after response is sent to client
121             sub post {
122 0     0 1   my $self = shift;
123 0 0         if ($self -> {net_server} -> {usecache}) {
124             # Grab the answer packet
125 0           my $dns_packet = shift;
126             # Store the answer into the cache
127 0           my ($question) = $dns_packet -> question();
128 0           my $key = $question->string();
129 0           my @s = ();
130 0           push @s, $self->store_rrs($dns_packet->answer);
131 0           push @s, $self->store_rrs($dns_packet->authority);
132 0           push @s, $self->store_rrs($dns_packet->additional);
133 0           print STDERR "DEBUG: Storing cache for [$key;structure]\n";
134 0           $self -> {dns_cache} -> {"$key;structure"} = \@s;
135             }
136 0           $self->flush_expired_ttls;
137 0           return 1;
138             }
139              
140             # Subroutine: store_rrs
141             # PreConds: Takes a list of RR objects
142             # PostConds: Stores rdatastr components into cache
143             # and returns a list of uniques
144             sub store_rrs {
145 0     0 0   my $self = shift;
146 0           my $answer_hash = {};
147 0           foreach my $rr (@_) {
148 0           my $key = join("\t",$rr->name.".",$rr->class,$rr->type);
149 0           my $rdatastr = $rr->rdatastr();
150 0           my $ttl = $rr->ttl();
151 0           my $expiration = $ttl + time;
152 0   0       $answer_hash->{$key} ||= [];
153 0           push @{$answer_hash->{$key}},
  0            
154             [$expiration, $rdatastr];
155 0 0 0       if (!$expiration_check ||
156             $expiration < $expiration_check) {
157             # Keep track of when the earliest entry will expire.
158 0           $expiration_check = $expiration;
159             }
160             }
161 0           foreach my $key (keys %{$answer_hash}) {
  0            
162 0           print STDERR "DEBUG: Storing lookup cache for [$key;lookup] (".(scalar @{$answer_hash->{$key}})." elements)\n";
  0            
163             # Save the rdatastr values into the lookup cache
164 0           $self->{dns_cache}->{"$key;lookup"} = $answer_hash->{$key};
165             }
166 0           return [keys %{$answer_hash}];
  0            
167             }
168              
169             sub flush_expired_ttls {
170 0     0 0   my $self = shift;
171 0           my $now = time;
172 0 0         return unless $now > $expiration_check;
173 0           my ($next_expiration_check, $lookup, $cache);
174 0           $next_expiration_check = undef;
175 0           while (($lookup,$cache) = each %{ $self -> {dns_cache} }) {
  0            
176 0 0         next unless ref $cache eq "ARRAY";
177 0 0         if ($lookup =~ /^(.+)\;lookup$/) {
178 0           my $rr_string = $1;
179 0           foreach my $entry (@$cache) {
180 0 0         if (ref $entry eq "ARRAY") {
181 0           my $expires = $entry->[0];
182 0 0         if ($expires < $now) {
183             # Contains a TTL in the past
184             # so throw the whole thing out
185 0           delete $self -> {dns_cache} -> {"$rr_string;lookup"};
186 0           last;
187             }
188 0 0 0       if ($expires > $expiration_check &&
      0        
189             (!$next_expiration_check ||
190             $expires < $next_expiration_check)) {
191 0           $next_expiration_check = $expires;
192             }
193             }
194             }
195             }
196             }
197 0   0       $expiration_check = $next_expiration_check || undef;
198             }
199              
200             1;
201             __END__