File Coverage

blib/lib/Net/DNS/Lite.pm
Criterion Covered Total %
statement 252 293 86.0
branch 103 178 57.8
condition 26 67 38.8
subroutine 33 37 89.1
pod 0 13 0.0
total 414 588 70.4


line stmt bran cond sub pod time code
1             package Net::DNS::Lite;
2              
3 6     6   107467 use 5.008_001;
  6         22  
  6         239  
4              
5 6     6   33 use strict;
  6         11  
  6         188  
6 6     6   35 use warnings;
  6         12  
  6         151  
7              
8 6     6   31 use Carp ();
  6         9  
  6         124  
9 6     6   37 use Exporter qw(import);
  6         9  
  6         235  
10 6     6   5964 use List::MoreUtils qw(uniq);
  6         7944  
  6         604  
11 6     6   42 use List::Util qw(min);
  6         11  
  6         561  
12 6     6   5117 use Socket qw(AF_INET SOCK_DGRAM inet_ntoa sockaddr_in unpack_sockaddr_in);
  6         21490  
  6         1503  
13 6     6   11669 use Time::HiRes qw(time);
  6         11577  
  6         31  
14              
15             our $VERSION = '0.12';
16              
17             our @EXPORT = qw();
18             our @EXPORT_OK = qw(inet_aton);
19             our %EXPORT_TAGS = (
20             'all' => [ @EXPORT_OK ],
21             );
22              
23             sub DOMAIN_PORT () { 53 }
24              
25             our %opcode_id = (
26             query => 0,
27             iquery => 1,
28             status => 2,
29             notify => 4,
30             update => 5,
31             map +($_ => $_), 3, 6..15
32             );
33              
34             our %opcode_str = reverse %opcode_id;
35              
36             our %rcode_id = (
37             noerror => 0,
38             formerr => 1,
39             servfail => 2,
40             nxdomain => 3,
41             notimp => 4,
42             refused => 5,
43             yxdomain => 6, # Name Exists when it should not [RFC 2136]
44             yxrrset => 7, # RR Set Exists when it should not [RFC 2136]
45             nxrrset => 8, # RR Set that should exist does not [RFC 2136]
46             notauth => 9, # Server Not Authoritative for zone [RFC 2136]
47             notzone => 10, # Name not contained in zone [RFC 2136]
48             # EDNS0 16 BADVERS Bad OPT Version [RFC 2671]
49             # EDNS0 16 BADSIG TSIG Signature Failure [RFC 2845]
50             # EDNS0 17 BADKEY Key not recognized [RFC 2845]
51             # EDNS0 18 BADTIME Signature out of time window [RFC 2845]
52             # EDNS0 19 BADMODE Bad TKEY Mode [RFC 2930]
53             # EDNS0 20 BADNAME Duplicate key name [RFC 2930]
54             # EDNS0 21 BADALG Algorithm not supported [RFC 2930]
55             map +($_ => $_), 11..15
56             );
57              
58             our %rcode_str = reverse %rcode_id;
59              
60             our %type_id = (
61             a => 1,
62             ns => 2,
63             md => 3,
64             mf => 4,
65             cname => 5,
66             soa => 6,
67             mb => 7,
68             mg => 8,
69             mr => 9,
70             null => 10,
71             wks => 11,
72             ptr => 12,
73             hinfo => 13,
74             minfo => 14,
75             mx => 15,
76             txt => 16,
77             aaaa => 28,
78             srv => 33,
79             naptr => 35, # rfc2915
80             dname => 39, # rfc2672
81             opt => 41,
82             spf => 99,
83             tkey => 249,
84             tsig => 250,
85             ixfr => 251,
86             axfr => 252,
87             mailb => 253,
88             "*" => 255,
89             );
90              
91             our %type_str = reverse %type_id;
92              
93             our %class_id = (
94             in => 1,
95             ch => 3,
96             hs => 4,
97             none => 254,
98             "*" => 255,
99             );
100              
101             our %class_str = reverse %class_id;
102              
103             our $TIMEOUT = 10;
104             our $CACHE;
105             our $CACHE_TTL = 600;
106             our $PID;
107              
108             sub new {
109 6     6 0 67 my ($class, %arg) = @_;
110              
111 6         91 my $self = bless {
112             server => [],
113             timeout => [2, 5, 5],
114             search => [],
115             ndots => 1,
116             reuse => 300,
117             %arg,
118             }, $class;
119              
120 6 100       25 if (@{$self->{server}} == 0) {
  6         92  
121 3 50       203 if (-e '/etc/resolv.conf') {
122 3         17 $self->_parse_resolv_conf_file('/etc/resolv.conf');
123             } else {
124 0         0 Carp::croak "server was not specified and there is no /etc/resolv.conf";
125             }
126             }
127              
128 6         76 $self->_compile;
129              
130 6         40 $self
131             }
132              
133             sub _compile {
134 6     6   14 my $self = shift;
135              
136 6         12 $self->{search} = [ grep { length($_) } uniq @{$self->{search}} ];
  5         25  
  6         163  
137              
138 11 50       1971 $self->{server} = [
139             map {
140 11         33 Socket::inet_aton($_) or Carp::croak "invalid server address: $_"
141             } grep {
142 11         24 ! /:/ # ignore ipv6 address (for now)
143 6         19 } grep { length($_) } uniq @{$self->{server}},
  6         39  
144             ];
145              
146 6         23 my @retry;
147              
148 6         13 for my $timeout (@{$self->{timeout}}) {
  6         23  
149 18         24 for my $server (@{$self->{server}}) {
  18         33  
150 33         117 push @retry, [ $server, $timeout ];
151             }
152             }
153              
154 6         32 $self->{retry} = \@retry;
155             }
156              
157             sub resolve {
158 9     9 0 12016 my ($self, $qname, $qtype, %opt) = @_;
159              
160 0         0 my @search = $qname =~ s/\.$//
161             ? ""
162             : $opt{search}
163 6         33 ? @{ $opt{search} }
164             : ($qname =~ y/.//) >= $self->{ndots}
165 1         6 ? ("", @{ $self->{search} })
166 9 100       85 : (@{ $self->{search} }, "");
    50          
    100          
167              
168 9   50     66 my $class = $opt{class} || "in";
169              
170 0         0 my %atype = $opt{accept}
171 9 50       52 ? map { +($_ => 1) } @{$opt{accept}}
  0         0  
172             : ($qtype => 1);
173              
174             # use some big value as default so that all servers and retries will be
175             # performed before total_timeout
176 9 50       69 my $timeout_at = time + (defined $opt{timeout} ? $opt{timeout} : $TIMEOUT);
177              
178             # advance in searchlist
179 9         27 my ($do_search, $do_req);
180              
181             $do_search = sub {
182             @search
183 12 100   12   259 or (undef $do_search), (undef $do_req), return ();
184              
185 9         74 (my $name = lc "$qname." . shift @search) =~ s/\.$//;
186 9         25 my $depth = 10;
187              
188             # advance in cname-chain
189             $do_req = sub {
190 9 100       48 my $res = $self->request($name, $qtype, $class, $timeout_at)
191             or return $do_search->();
192              
193 8         18 my $cname;
194              
195 8         12 while (1) {
196             # results found?
197 34 50 33     254 my @rr = grep {
198 8         36 $name eq lc $_->[0] && ($atype{"*"} || $atype{$_->[1]})
199 8         87 } @{$res->{an}};
200              
201 8 100       31 if (@rr) {
202 6         179 (undef $do_search), (undef $do_req), return @rr;
203             }
204              
205             # see if there is a cname we can follow
206 0 0       0 @rr = grep {
207 2         7 $name eq lc $_->[0] && $_->[1] eq "cname"
208 2         4 } @{$res->{an}};
209              
210 2 50       14 if (@rr) {
    50          
211 0 0       0 $depth--
212             or return $do_search->(); # cname chain too long
213              
214 0         0 $cname = 1;
215 0         0 $name = lc $rr[0][4];
216              
217             } elsif ($cname) {
218             # follow the cname
219 0         0 return $do_req->();
220              
221             } else {
222             # no, not found anything
223 2         15 return $do_search->();
224             }
225             }
226 9         60 };
227              
228 9         28 $do_req->();
229 9         61 };
230              
231 9         25 $do_search->();
232             }
233              
234             sub request {
235 9     9 0 22 my ($self, $name, $qtype, $class, $total_timeout_at) = @_;
236              
237 9         20 my $cache = $self->{cache};
238 9 50       35 if (! defined $self->{cache}) {
239 9         21 $cache = $CACHE;
240             }
241 9         27 my $cache_key = "$class $qtype $name";
242              
243 9 100       45 if ($cache) {
244 2 100       17 if (my $value = $cache->get($cache_key)) {
245 1         15 my ($res, $expires_at) = @$value;
246 1 50       8 return $res if time < $expires_at;
247 0         0 $cache->remove($cache_key);
248             }
249             }
250              
251             $self->_open_socket()
252 8 100 66     83 if ! $self->{sock_v4} || $self->{pid} != $$;
253              
254 8         42 my $req = {
255             id => $self->_new_id(),
256             rd => 1,
257             qd => [[$name, $qtype, $class]],
258             };
259              
260 8         42 my $req_pkt = dns_pack($req);
261 8         22 my $pkt_sent;
262              
263 8         20 for (my $retry = 0; $retry < @{$self->{retry}}; $retry++) {
  11         55  
264 10         19 my ($server, $server_timeout) = @{$self->{retry}->[$retry]};
  10         37  
265              
266 10         34 my $now = time;
267 10         23 my $server_timeout_at = $now + $server_timeout;
268 10 50       31 $server_timeout_at = $total_timeout_at
269             if $total_timeout_at < $server_timeout_at;
270 10 50       27 if ($server_timeout_at <= $now) {
271 0         0 goto FAIL;
272             }
273              
274             # send request
275 10         15 $pkt_sent = 1;
276             send(
277             $self->{sock_v4}, $req_pkt, 0,
278             scalar sockaddr_in(DOMAIN_PORT, $server),
279 10 50       403 ) or do {
280 0         0 warn "failed to send packet to @{[inet_ntoa($server)]}:$!";
  0         0  
281 0         0 next;
282             };
283              
284             # wait for the response (or the timeout)
285 10         2380 my $res;
286 10         19 for (; ; undef($res), $now = time) {
287 13         38 my $select_timeout = $server_timeout_at - $now;
288 13 100       46 if ($select_timeout <= 0) {
289 3 50       11 goto FAIL if $total_timeout_at <= $now;
290 3         16 last;
291             }
292 10 50       29 last if $select_timeout <= 0;
293 10         17 my $rfd = '';
294 10         144 vec($rfd, fileno($self->{sock_v4}), 1) = 1;
295 10         12265707 my $nfound = select(
296             $rfd, my $wfd = '', my $efd = '', $select_timeout);
297 10 100       153 next unless $nfound > 0;
298 7 50       242 my $from = recv($self->{sock_v4}, my $res_pkt, 1024, 0)
299             or next;
300 7         102 my ($from_port, $from_addr) = unpack_sockaddr_in($from);
301 7 50 33     99 if (! ($from_port == DOMAIN_PORT
  14         280  
302 7         36 && grep { $from_addr eq $_ } @{$self->{server}})) {
303 0         0 next;
304             }
305 7 50       43 $res = dns_unpack($res_pkt)
306             or next;
307 7 50       146 if ($res->{id} == $req->{id}) {
308 7 50       25 $self->_register_unusable_id($req->{id})
309             if $retry != 0;
310 7 100       24 if ($cache) {
311 1         11 my $ttl = min(
312             $self->{cache_ttl} || $CACHE_TTL,
313             map {
314 1         5 $_->[3]
315 1 50 33     5 } (@{$res->{an}} ? @{$res->{an}} : @{$res->{ns}}),
  1         2  
  0         0  
316             );
317 1         12 $cache->set(
318             $cache_key => [ $res, time + $ttl + 0.5 ],
319             );
320             }
321 7         99 return $res;
322             }
323             }
324             }
325              
326 1 50       16 FAIL:
327             $self->_register_unusable_id($req->{id})
328             if $pkt_sent;
329 1         30 return;
330             }
331              
332             sub _open_socket {
333 5     5   12 my $self = shift;
334              
335 5         11 my $got_socket = 0;
336 5 50       257 socket($self->{sock_v4}, AF_INET, SOCK_DGRAM, 0)
337             and $got_socket++;
338             # if (AF_INET6) {
339             # socket($self->{sock_v6}, AF_INET6, SOCK_DGRAM, 0)
340             # and $got_socket++;
341             # }
342              
343 5 50       17 $got_socket
344             or Carp::croak "unable to create either an IPv4 or an IPv6 socket";
345              
346 5         19 $self->{reuse_q} = [];
347 5         16 $self->{reuse_h} = +{};
348 5         24 $self->{pid} = $$;
349             }
350              
351             sub _new_id {
352 8     8   16 my $self = shift;
353 8         21 my $id;
354              
355 8         27 my $now = time;
356              
357 8 50       26 if (@{$self->{reuse_q}} >= 30000) {
  8         45  
358 0         0 $self->_open_socket();
359             } else {
360 8         51 delete $self->{reuse_h}{(shift @{$self->{reuse_q}})->[1]}
  0         0  
361 8   33     13 while @{$self->{reuse_q}} && $self->{reuse_q}[0][0] <= $now;
362             }
363              
364 8         16 while (1) {
365 8         346 $id = int rand(65536);
366 8 50       48 last if not defined $self->{reuse_h}{$id};
367             }
368              
369 8         70 $id;
370             }
371              
372             sub _register_unusable_id {
373 1     1   4 my ($self, $id) = @_;
374              
375 1         3 push @{$self->{reuse_q}}, [ time + $self->{reuse}, $id ];
  1         10  
376 1         5 $self->{reuse_h}{$id} = 1;
377             }
378              
379             sub parse_resolv_conf {
380 7     7 0 2479 my ($self, $resolvconf) = @_;
381              
382 7         17 $self->{server} = [];
383 7         16 $self->{search} = [];
384              
385 7         15 my $attempts;
386             my $timeout;
387              
388 7         37 for (split /\n/, $resolvconf) {
389 26         116 s/\s*[;#].*$//; # not quite legal, but many people insist
390              
391 26 100       187 if (/^\s*nameserver\s+(\S+)\s*$/i) {
    100          
    50          
    50          
    100          
392 11         35 my $ip = $1;
393 11 50       27 if (my $ipn = parse_address($ip)) {
394 11         21 push @{ $self->{server} }, $ip;
  11         49  
395             } else {
396 0         0 warn "nameserver $ip invalid and ignored\n";
397             }
398             } elsif (/^\s*domain\s+(\S*)\s*$/i) {
399 3         21 $self->{search} = [$1];
400             } elsif (/^\s*search\s+(.*?)\s*$/i) {
401 0         0 $self->{search} = [split /\s+/, $1];
402             } elsif (/^\s*sortlist\s+(.*?)\s*$/i) {
403             # ignored, NYI
404             } elsif (/^\s*options\s+(.*?)\s*$/i) {
405 3         12 for (split /\s+/, $1) {
406 7 100       30 if (/^timeout:(\d+)$/) {
    100          
    50          
407 2         8 $timeout = $1;
408             } elsif (/^attempts:(\d+)$/) {
409 2         9 $attempts = $1;
410             } elsif (/^ndots:(\d+)$/) {
411 0         0 $self->{ndots} = $1;
412             } else {
413             # debug, rotate, no-check-names, inet6
414             }
415             }
416             }
417             }
418              
419 7 100 100     161 if ( $timeout || $attempts ) {
420 3   100     9 $timeout ||= 5;
421 3   100     10 $attempts ||= 2;
422 3         9 $self->{timeout} = [ map { $timeout } 1..$attempts ];
  8         23  
423             }
424             }
425              
426             sub _parse_resolv_conf_file {
427 3     3   8 my ($self, $resolv_conf) = @_;
428              
429 3 50       128 open my $fh, '<', $resolv_conf
430             or Carp::croak "could not open file: $resolv_conf: $!";
431              
432 3         7 $self->parse_resolv_conf(do { local $/; join '', <$fh> });
  3         13  
  3         124  
433             }
434              
435             sub _enc_name($) {
436 8     8   340 pack "(C/a*)*", (split /\./, shift), ""
437             }
438              
439             sub _enc_qd() {
440 6     6   21692 no warnings;
  6         15  
  6         1066  
441 8 50 50 8   44 (_enc_name $_->[0]) . pack "nn",
    50          
442             ($_->[1] > 0 ? $_->[1] : $type_id {$_->[1]}),
443             ($_->[2] > 0 ? $_->[2] : $class_id{$_->[2] || "in"})
444             }
445              
446             sub _enc_rr() {
447 0     0   0 die "encoding of resource records is not supported";
448             }
449              
450             sub dns_pack {
451 6     6   34 no warnings;
  6         16  
  6         19097  
452 8     8 0 17 my ($req) = @_;
453              
454 8 50       112 pack "nn nnnn a* a* a* a*",
455             $req->{id},
456              
457             ! !$req->{qr} * 0x8000
458             + $opcode_id{$req->{op}} * 0x0800
459             + ! !$req->{aa} * 0x0400
460             + ! !$req->{tc} * 0x0200
461             + ! !$req->{rd} * 0x0100
462             + ! !$req->{ra} * 0x0080
463             + ! !$req->{ad} * 0x0020
464             + ! !$req->{cd} * 0x0010
465             + $rcode_id{$req->{rc}} * 0x0001,
466              
467 8 50       59 scalar @{ $req->{qd} || [] },
468 8 50       52 scalar @{ $req->{an} || [] },
469 8 50       50 scalar @{ $req->{ns} || [] },
470 8 50       59 scalar @{ $req->{ar} || [] },
471              
472 8 50       59 (join "", map _enc_qd, @{ $req->{qd} || [] }),
473 8 50       74 (join "", map _enc_rr, @{ $req->{an} || [] }),
474 8 50       78 (join "", map _enc_rr, @{ $req->{ns} || [] }),
475 8         449 (join "", map _enc_rr, @{ $req->{ar} || [] })
476             }
477              
478             our $ofs;
479             our $pkt;
480              
481             # bitches
482             sub _dec_name {
483 51     51   60 my @res;
484             my $redir;
485 51         62 my $ptr = $ofs;
486 51         53 my $cnt;
487              
488 51         58 while () {
489 215 50       494 return undef if ++$cnt >= 256; # to avoid DoS attacks
490              
491 215         301 my $len = ord substr $pkt, $ptr++, 1;
492              
493 215 100       756 if ($len >= 0xc0) {
    100          
494 42         46 $ptr++;
495 42 100       109 $ofs = $ptr if $ptr > $ofs;
496 42         104 $ptr = (unpack "n", substr $pkt, $ptr - 2, 2) & 0x3fff;
497             } elsif ($len) {
498 122         305 push @res, substr $pkt, $ptr, $len;
499 122         154 $ptr += $len;
500             } else {
501 51 100       108 $ofs = $ptr if $ptr > $ofs;
502 51         228 return join ".", @res;
503             }
504             }
505             }
506              
507             sub _dec_qd {
508 7     7   33 my $qname = _dec_name;
509 7         27 my ($qt, $qc) = unpack "nn", substr $pkt, $ofs; $ofs += 4;
  7         12  
510 7   33     119 [$qname, $type_str{$qt} || $qt, $class_str{$qc} || $qc]
      33        
511             }
512              
513             our %dec_rr = (
514             1 => sub { join ".", unpack "C4", $_ }, # a 2 => sub { local $ofs = $ofs - length; _dec_name }, # ns
515             5 => sub { local $ofs = $ofs - length; _dec_name }, # cname
516             6 => sub {
517             local $ofs = $ofs - length; my $mname = _dec_name;
518             my $rname = _dec_name;
519             ($mname, $rname, unpack "NNNNN", substr $pkt, $ofs)
520             }, # soa
521             11 => sub { ((join ".", unpack "C4", $_), unpack "C a*", substr $_, 4) }, # wks
522             12 => sub { local $ofs = $ofs - length; _dec_name }, # ptr
523             13 => sub { unpack "C/a* C/a*", $_ }, # hinfo
524             15 => sub { local $ofs = $ofs + 2 - length; ((unpack "n", $_), _dec_name) }, # mx
525             16 => sub { unpack "(C/a*)*", $_ }, # txt
526             28 => sub { format_ipv6 ($_) }, # aaaa
527             33 => sub { local $ofs = $ofs + 6 - length; ((unpack "nnn", $_), _dec_name) }, # srv
528             35 => sub { # naptr
529             # requires perl 5.10, sorry
530             my ($order, $preference, $flags, $service, $regexp, $offset) = unpack "nn C/a* C/a* C/a* .", $_;
531             local $ofs = $ofs + $offset - length;
532             ($order, $preference, $flags, $service, $regexp, _dec_name)
533             },
534             39 => sub { local $ofs = $ofs - length; _dec_name }, # dname
535             99 => sub { unpack "(C/a*)*", $_ }, # spf
536             );
537              
538             sub _dec_rr {
539 35     35   72 my $name = _dec_name;
540              
541 35         245 my ($rt, $rc, $ttl, $rdlen) = unpack "nn N n", substr $pkt, $ofs; $ofs += 10;
  35         256  
542 35         63 local $_ = substr $pkt, $ofs, $rdlen; $ofs += $rdlen;
  35         43  
543              
544             [
545             $name,
546             $type_str{$rt} || $rt,
547             $class_str{$rc} || $rc,
548             $ttl,
549 35   33 0   241 ($dec_rr{$rt} || sub { $_ })->(),
  0   33     0  
      50        
550             ]
551             }
552              
553             sub dns_unpack {
554 7     7 0 36 local $pkt = shift;
555 7         70 my ($id, $flags, $qd, $an, $ns, $ar)
556             = unpack "nn nnnn A*", $pkt;
557              
558 7         82 local $ofs = 6 * 2;
559              
560             {
561 7         85 id => $id,
562             qr => ! ! ($flags & 0x8000),
563             aa => ! ! ($flags & 0x0400),
564             tc => ! ! ($flags & 0x0200),
565             rd => ! ! ($flags & 0x0100),
566             ra => ! ! ($flags & 0x0080),
567             ad => ! ! ($flags & 0x0020),
568             cd => ! ! ($flags & 0x0010),
569             op => $opcode_str{($flags & 0x001e) >> 11},
570             rc => $rcode_str{($flags & 0x000f)},
571              
572             qd => [map _dec_qd, 1 .. $qd],
573             an => [map _dec_rr, 1 .. $an],
574             ns => [map _dec_rr, 1 .. $ns],
575             ar => [map _dec_rr, 1 .. $ar],
576             }
577             }
578              
579             sub parse_address {
580 15     15 0 27 my $text = shift;
581 15 50       37 if (my $addr = parse_ipv6($text)) {
582 0         0 $addr =~ s/^\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff//;
583 0         0 return $addr;
584             } else {
585 15         31 return parse_ipv4($text);
586             }
587             }
588              
589             sub parse_ipv4 {
590 18 100   18 0 137 $_[0] =~ /^ (?: 0x[0-9a-fA-F]+ | 0[0-7]* | [1-9][0-9]* )
591             (?:\. (?: 0x[0-9a-fA-F]+ | 0[0-7]* | [1-9][0-9]* ) ){0,3}$/x
592             or return undef;
593              
594 14 100       169 @_ = map /^0/ ? oct : $_, split /\./, $_[0];
595              
596             # check leading parts against range
597 14 50       102 return undef if grep $_ >= 256, @_[0 .. @_ - 2];
598              
599             # check trailing part against range
600 14 50       57 return undef if $_[-1] >= 2 ** (8 * (4 - $#_));
601              
602 14         101 pack "N", (pop)
603             + ($_[0] << 24)
604             + ($_[1] << 16)
605             + ($_[2] << 8);
606             }
607              
608             sub parse_ipv6 {
609             # quick test to avoid longer processing
610 16     16 0 96 my $n = $_[0] =~ y/://;
611 16 100 66     92 return undef if $n < 2 || $n > 8;
612              
613 1         7 my ($h, $t) = split /::/, $_[0], 2;
614              
615 1 50       4 unless (defined $t) {
616 1         4 ($h, $t) = (undef, $h);
617             }
618              
619 1 50       6 my @h = defined $h ? (split /:/, $h) : ();
620 1         6 my @t = split /:/, $t;
621              
622             # check for ipv4 tail
623 1 50 33     11 if (@t && $t[-1]=~ /\./) {
624 0 0       0 return undef if $n > 6;
625              
626 0 0       0 my $ipn = parse_ipv4(pop @t)
627             or return undef;
628              
629 0         0 push @t, map +(sprintf "%x", $_), unpack "nn", $ipn;
630             }
631              
632             # no :: then we need to have exactly 8 components
633 1 50 33     6 return undef unless @h + @t == 8 || $_[0] =~ /::/;
634              
635             # now check all parts for validity
636 1 50       22 return undef if grep !/^[0-9a-fA-F]{1,4}$/, @h, @t;
637              
638             # now pad...
639 1         6 push @h, 0 while @h + @t < 8;
640              
641             # and done
642 1         19 pack "n*", map hex, @h, @t
643             }
644              
645             our $resolver;
646              
647             sub RESOLVER() {
648 4   66 4 0 40 $resolver ||= Net::DNS::Lite->new;
649             }
650              
651             sub inet_aton {
652 4     4 0 1329 my $name = shift;
653 4 50       18 if (my $address = parse_address($name)) {
654 0         0 return $address;
655             }
656 4 50       14 my @rr = RESOLVER->resolve(
657             $name, 'a',
658             (@_ ? (timeout => $_[0]) : ()),
659             );
660 4         17 while (@rr) {
661 3         11 my $idx = int rand @rr;
662 3         11 my $address = parse_ipv4($rr[$idx][4]);
663 3 50       50 return $address if defined $address;
664 0         0 splice @rr, $idx, 1;
665             }
666 1         7 return undef;
667             }
668              
669             sub format_ipv4($) {
670 0     0 0   join ".", unpack "C4", $_[0]
671             }
672              
673             sub format_ipv6($) {
674 0 0   0 0   if ($_[0] =~ /^\x00\x00\x00\x00\x00\x00\x00\x00/) {
675 0 0         if (v0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 eq $_[0]) {
    0          
    0          
    0          
    0          
676 0           return "::";
677             } elsif (v0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1 eq $_[0]) {
678 0           return "::1";
679             } elsif (v0.0.0.0.0.0.0.0.0.0.0.0 eq substr $_[0], 0, 12) {
680             # v4compatible
681 0           return "::" . format_ipv4 substr $_[0], 12;
682             } elsif (v0.0.0.0.0.0.0.0.0.0.255.255 eq substr $_[0], 0, 12) {
683             # v4mapped
684 0           return "::ffff:" . format_ipv4 substr $_[0], 12;
685             } elsif (v0.0.0.0.0.0.0.0.255.255.0.0 eq substr $_[0], 0, 12) {
686             # v4translated
687 0           return "::ffff:0:" . format_ipv4 substr $_[0], 12;
688             }
689             }
690              
691 0           my $ip = sprintf "%x:%x:%x:%x:%x:%x:%x:%x", unpack "n8", $_[0];
692              
693             # this is admittedly rather sucky
694 0 0 0       $ip =~ s/(?:^|:) 0:0:0:0:0:0:0 (?:$|:)/::/x
      0        
      0        
      0        
      0        
695             or $ip =~ s/(?:^|:) 0:0:0:0:0:0 (?:$|:)/::/x
696             or $ip =~ s/(?:^|:) 0:0:0:0:0 (?:$|:)/::/x
697             or $ip =~ s/(?:^|:) 0:0:0:0 (?:$|:)/::/x
698             or $ip =~ s/(?:^|:) 0:0:0 (?:$|:)/::/x
699             or $ip =~ s/(?:^|:) 0:0 (?:$|:)/::/x
700             or $ip =~ s/(?:^|:) 0 (?:$|:)/::/x;
701              
702 0           $ip
703             }
704              
705             1;
706             __END__