File Coverage

blib/lib/Net/DNS/Question.pm
Criterion Covered Total %
statement 102 102 100.0
branch 42 42 100.0
condition 15 15 100.0
subroutine 23 23 100.0
pod 14 14 100.0
total 196 196 100.0


line stmt bran cond sub pod time code
1             package Net::DNS::Question;
2              
3 95     95   189311 use strict;
  95         291  
  95         4241  
4 95     95   715 use warnings;
  95         187  
  95         8859  
5              
6             our $VERSION = (qw$Id: Question.pm 2002 2025-01-07 09:57:46Z willem $)[2];
7              
8              
9             =head1 NAME
10              
11             Net::DNS::Question - DNS question record
12              
13             =head1 SYNOPSIS
14              
15             use Net::DNS::Question;
16              
17             $question = Net::DNS::Question->new('example.com', 'AAAA', 'IN');
18              
19             =head1 DESCRIPTION
20              
21             A Net::DNS::Question object represents a record in the question
22             section of a DNS packet.
23              
24             =cut
25              
26              
27 95     95   1281 use integer;
  95         238  
  95         616  
28 95     95   2692 use Carp;
  95         259  
  95         8369  
29              
30 95     95   1370 use Net::DNS::Parameters qw(%classbyname %typebyname :class :type);
  95         190  
  95         16800  
31 95     95   2668 use Net::DNS::Domain;
  95         231  
  95         3284  
32 95     95   2868 use Net::DNS::DomainName;
  95         226  
  95         39360  
33              
34              
35             =head1 METHODS
36              
37             =head2 new
38              
39             $question = Net::DNS::Question->new('example.com', 'AAAA', 'IN');
40             $question = Net::DNS::Question->new('example.com', 'A', 'IN');
41             $question = Net::DNS::Question->new('example.com');
42              
43             $question = Net::DNS::Question->new('2001::DB8::dead:beef', 'PTR', 'IN');
44             $question = Net::DNS::Question->new('2001::DB8::dead:beef');
45              
46             Creates a question object from the domain, type, and class passed as
47             arguments. One or both type and class arguments may be omitted and
48             will assume the default values shown above.
49              
50             RFC4291 and RFC4632 IP address/prefix notation is supported for
51             queries in both in-addr.arpa and ip6.arpa namespaces.
52              
53             =cut
54              
55             sub new {
56 303     303 1 373288 my $self = bless {}, shift;
57 303         678 my $qname = shift;
58 303   100     1242 my $qtype = shift || '';
59 303   100     1225 my $qclass = shift || '';
60              
61             # tolerate (possibly unknown) type and class in zone file order
62 303 100       1363 unless ( exists $classbyname{$qclass} ) {
63 240 100       684 ( $qtype, $qclass ) = ( $qclass, $qtype ) if exists $classbyname{$qtype};
64 240 100       754 ( $qtype, $qclass ) = ( $qclass, $qtype ) if $qtype =~ /CLASS/;
65             }
66 303 100       997 unless ( exists $typebyname{$qtype} ) {
67 142 100       377 ( $qtype, $qclass ) = ( $qclass, $qtype ) if exists $typebyname{$qclass};
68 142 100       333 ( $qtype, $qclass ) = ( $qclass, $qtype ) if $qclass =~ /TYPE/;
69             }
70              
71             # if argument is an IP address, do appropriate reverse lookup
72 303 100 100     6791 if ( defined $qname and $qname =~ m/:|\d$/ ) {
73 84 100       261 if ( my $reverse = _dns_addr($qname) ) {
74 82         157 $qname = $reverse;
75 82   100     338 $qtype ||= 'PTR';
76             }
77             }
78              
79 303         1536 $self->{qname} = Net::DNS::DomainName1035->new($qname);
80 301   100     5720 $self->{qtype} = typebyname( $qtype || 'A' );
81 301   100     1483 $self->{qclass} = classbyname( $qclass || 'IN' );
82              
83 301         1368 return $self;
84             }
85              
86              
87             =head2 decode
88              
89             $question = Net::DNS::Question->decode(\$data, $offset);
90              
91             ($question, $offset) = Net::DNS::Question->decode(\$data, $offset);
92              
93             Decodes the question record at the specified location within a DNS
94             wire-format packet. The first argument is a reference to the buffer
95             containing the packet data. The second argument is the offset of
96             the start of the question record.
97              
98             Returns a Net::DNS::Question object and the offset of the next
99             location in the packet.
100              
101             An exception is raised if the object cannot be created
102             (e.g., corrupt or insufficient data).
103              
104             =cut
105              
106 95     95   853 use constant QFIXEDSZ => length pack 'n2', (0) x 2;
  95         283  
  95         177538  
107              
108             sub decode {
109 139     139 1 1147 my ( $class, @argument ) = @_;
110 139         380 my ( $data, $offset ) = @argument;
111 139         428 my $self = bless {}, $class;
112              
113 139         1402 ( $self->{qname}, $offset ) = Net::DNS::DomainName1035->decode(@argument);
114              
115 126         1696 my $next = $offset + QFIXEDSZ;
116 126 100       673 die 'corrupt wire-format data' if length $$data < $next;
117 121         534 @{$self}{qw(qtype qclass)} = unpack "\@$offset n2", $$data;
  121         517  
118              
119 121 100       4672 return wantarray ? ( $self, $next ) : $self;
120             }
121              
122              
123             =head2 encode
124              
125             $data = $question->encode( $offset, $hash );
126              
127             Returns the Net::DNS::Question in binary format suitable for
128             inclusion in a DNS packet buffer.
129              
130             The optional arguments are the offset within the packet data where
131             the Net::DNS::Question is to be stored and a reference to a hash
132             table used to index compressed names within the packet.
133              
134             =cut
135              
136             sub encode {
137 208     208 1 674 my ( $self, @opaque ) = @_;
138 208         1107 return pack 'a* n2', $self->{qname}->encode(@opaque), @{$self}{qw(qtype qclass)};
  208         1561  
139             }
140              
141              
142             =head2 string
143              
144             print "string = ", $question->string, "\n";
145              
146             Returns a string representation of the question record.
147              
148             =cut
149              
150             sub string {
151 85     85 1 1093 my $self = shift;
152 85         334 return join "\t", $self->{qname}->string, $self->qclass, $self->qtype;
153             }
154              
155              
156             =head2 print
157              
158             $object->print;
159              
160             Prints the record to the standard output. Calls the string() method
161             to get the string representation.
162              
163             =cut
164              
165             sub print {
166 1     1 1 377 print &string, "\n";
167 1         6 return;
168             }
169              
170              
171             =head2 name
172              
173             $name = $question->name;
174              
175             Internationalised domain name corresponding to the qname attribute.
176              
177             Decoding non-ASCII domain names is computationally expensive and
178             undesirable for names which are likely to be used to construct
179             further queries.
180              
181             When required to communicate with humans, the 'proper' domain name
182             should be extracted from a query or reply packet.
183              
184             $query = Net::DNS::Packet->new( $example, 'SOA' );
185             $reply = $resolver->send($query) or die;
186             ($question) = $reply->question;
187             $name = $question->name;
188              
189             =cut
190              
191             sub name {
192 2     2 1 8 my ( $self, @argument ) = @_;
193 2         6 for (@argument) { croak 'immutable object: argument invalid' }
  1         136  
194 1         13 return $self->{qname}->xname;
195             }
196              
197              
198             =head2 qname, zname
199              
200             $qname = $question->qname;
201             $zname = $question->zname;
202              
203             Fully qualified domain name in the form required for a query
204             transmitted to a nameserver. In dynamic update packets, this
205             attribute is known as zname() and refers to the zone name.
206              
207             =cut
208              
209             sub qname {
210 129     129 1 1739 my ( $self, @argument ) = @_;
211 129         354 for (@argument) { croak 'immutable object: argument invalid' }
  1         157  
212 128         618 return $self->{qname}->name;
213             }
214              
215 3     3 1 19 sub zname { return &qname; }
216              
217              
218             =head2 qtype, ztype, type
219              
220             $qtype = $question->type;
221             $qtype = $question->qtype;
222             $ztype = $question->ztype;
223              
224             Returns the question type attribute. In dynamic update packets,
225             this attribute is known as ztype() and refers to the zone type.
226              
227             =cut
228              
229             sub type {
230 95     95 1 220 my ( $self, @argument ) = @_;
231 95         181 for (@argument) { croak 'immutable object: argument invalid' }
  1         177  
232 94         246 return typebyval( $self->{qtype} );
233             }
234              
235 92     92 1 191 sub qtype { return &type; }
236 2     2 1 9 sub ztype { return &type; }
237              
238              
239             =head2 qclass, zclass, class
240              
241             $qclass = $question->class;
242             $qclass = $question->qclass;
243             $zclass = $question->zclass;
244              
245             Returns the question class attribute. In dynamic update packets,
246             this attribute is known as zclass() and refers to the zone class.
247              
248             =cut
249              
250             sub class {
251 185     185 1 1280 my ( $self, @argument ) = @_;
252 185         448 for (@argument) { croak 'immutable object: argument invalid' }
  1         142  
253 184         611 return classbyval( $self->{qclass} );
254             }
255              
256 87     87 1 195 sub qclass { return &class; }
257 97     97 1 228 sub zclass { return &class; }
258              
259              
260             ########################################
261              
262             sub _dns_addr { ## Map IP address into reverse lookup namespace
263 85     85   199 local $_ = shift;
264              
265             # IP address must contain address characters only
266 85         442 s/[%].+$//; # discard RFC4007 scopeid
267 85 100       461 return unless m#^[a-fA-F0-9:./]+$#;
268              
269 84         334 my ( $address, $pfxlen ) = split m#/#;
270              
271             # map IPv4 address to in-addr.arpa space
272 84 100       581 if (m#^\d*[.\d]*\d(/\d+)?$#) {
273 22         62 my @parse = split /\./, $address;
274 22 100       60 $pfxlen = scalar(@parse) << 3 unless $pfxlen;
275 22 100       84 my $last = $pfxlen > 24 ? 3 : ( $pfxlen - 1 ) >> 3;
276 22         179 return join '.', reverse( ( @parse, (0) x 3 )[0 .. $last] ), 'in-addr.arpa.';
277             }
278              
279             # map IPv6 address to ip6.arpa space
280 62 100       429 return unless m#^[:\w]+:([.\w]*)(/\d+)?$#;
281 61   100     316 my $rhs = $1 || '0';
282 61 100       217 return _dns_addr($rhs) if m#^[:0]*:0*:[fF]{4}:[^:]+$#; # IPv4
283 60 100       189 $rhs = sprintf '%x%0.2x:%x%0.2x', map { $_ || 0 } split( /\./, $rhs, 4 ) if /\./;
  4 100       22  
284 60         543 $address =~ s/:[^:]*$/:0$rhs/;
285 60         280 my @parse = split /:/, ( reverse "0$address" ), 9;
286 60 100       206 my @xpand = map { /./ ? $_ : ('0') x ( 9 - @parse ) } @parse; # expand ::
  368         1182  
287 60 100       179 $pfxlen = ( scalar(@xpand) << 4 ) unless $pfxlen; # implicit length if unspecified
288 60 100       214 my $len = $pfxlen > 124 ? 32 : ( $pfxlen + 3 ) >> 2;
289 60         167 my $hex = pack 'A4' x 8, map { $_ . '000' } ('0') x ( 8 - @xpand ), @xpand;
  480         1108  
290 60         1107 return join '.', split( //, substr( $hex, -$len ) ), 'ip6.arpa.';
291             }
292              
293              
294             1;
295             __END__