File Coverage

lib/App/AllKnowingDNS/Util.pm
Criterion Covered Total %
statement 54 58 93.1
branch 17 20 85.0
condition 4 6 66.6
subroutine 9 9 100.0
pod 2 2 100.0
total 86 95 90.5


line stmt bran cond sub pod time code
1             # vim:ts=4:sw=4:expandtab
2             package App::AllKnowingDNS::Util;
3              
4 5     5   817 use strict;
  5         7  
  5         200  
5 5     5   29 use warnings;
  5         10  
  5         167  
6 5     5   26 use Exporter 'import';
  5         10  
  5         192  
7 5     5   514 use App::AllKnowingDNS::Config;
  5         11  
  5         171  
8 5     5   33 use App::AllKnowingDNS::Zone;
  5         9  
  5         118  
9 5     5   5681 use NetAddr::IP::Util qw(ipv6_aton);
  5         103950  
  5         31  
10 5     5   794 use v5.10;
  5         18  
  5         3728  
11              
12             =head1 NAME
13              
14             App::AllKnowingDNS::Util - utility functions
15              
16             =head1 DESCRIPTION
17              
18             Note: User documentation is in L(1).
19              
20             =head1 FUNCTIONS
21              
22             =cut
23              
24             our @EXPORT = qw(parse_config netmask_to_ptrzone);
25              
26             =head2 parse_config($lines)
27              
28             Parses a block of text as configfile.
29              
30             Returns a corresponding App::AllKnowingDNS::Config object.
31              
32             =cut
33              
34             sub parse_config {
35 5     5 1 6212 my ($input) = @_;
36 5         54 my $config = App::AllKnowingDNS::Config->new;
37              
38 5         24 my @lines = split("\n", $input);
39 5         8 my $current_zone;
40 5         10 for my $line (@lines) {
41             # Strip whitespace.
42 16         45 $line =~ s/^\s+//;
43              
44             # Ignore comments.
45 16 100       43 next if substr($line, 0, 1) eq '#';
46              
47             # Skip empty lines
48 12 100       29 next if length($line) == 0;
49              
50             # If we are not currently parsing a zone, only the 'network' keyword is
51             # appropriate.
52 11 50 66     42 if (!defined($current_zone) &&
      66        
53             !($line =~ /^network/i) && !($line =~ /^listen/i)) {
54 0         0 say STDERR qq|all-knowing-dns: CONFIG: Expected 'network' or 'listen' keyword in line "$line"|;
55 0         0 next;
56             }
57              
58 11 100       38 if (my ($address) = ($line =~ /^listen (.*)/i)) {
59 2         10 $config->add_listen_address(lc $address);
60 2         24 next;
61             }
62              
63 9 100       30 if (my ($network) = ($line =~ /^network (.*)/i)) {
64             # The current zone is done now, if any.
65 3 100       13 $config->add_zone($current_zone) if defined($current_zone);
66 3         44 $current_zone = App::AllKnowingDNS::Zone->new(
67             network => lc $network,
68             );
69 3         100 next;
70             }
71              
72 6 100       94 if (my ($resolves_to) = ($line =~ /^resolves to (.*)/i)) {
73             # We explicitly don’t lowercase the DNS names to which PTR entries
74             # will resolve, since some universities seem to have a fetish for
75             # uppercase DNS names… :)
76 3         14 $current_zone->resolves_to($resolves_to);
77 3         7 next;
78             }
79              
80 3 50       50 if (my ($upstream_dns) = ($line =~ /^with upstream (.*)/i)) {
81 3         15 $current_zone->upstream_dns(lc $upstream_dns);
82 3         7 next;
83             }
84             }
85              
86 5 100       23 $config->add_zone($current_zone) if defined($current_zone);
87 5         18 return $config;
88             }
89              
90             =head2 netmask_to_ptrzone($netmask)
91              
92             Converts the given netmask to a PTR zone.
93              
94             Example:
95              
96             my $ptrzone = netmask_to_ptrzone('2001:4d88:100e:ccc0::/64');
97             say $ptrzone; # 0.c.c.c.e.0.0.1.8.8.d.4.1.0.0.2.ip6.arpa
98              
99             =cut
100              
101             sub netmask_to_ptrzone {
102 12     12 1 2061 my ($netmask) = @_;
103              
104 12         74 my ($address, $mask) = ($netmask =~ m,^([^/]+)/([0-9]+),);
105 12 50       57 if (($mask % 16) != 0) {
106 0         0 say STDERR "all-knowing-dns: ERROR: Only netmasks which " .
107             "are dividable by 16 are supported!";
108 0         0 exit 1;
109             }
110              
111 12         275 my @components = unpack("n8", ipv6_aton($address));
112             # A nibble is a 4-bit aggregation, that is, one "hex digit".
113 12         7298 my @nibbles = map { ((($_ & 0xF000) >> 12),
  96         252  
114             (($_ & 0x0F00) >> 8),
115             (($_ & 0x00F0) >> 4),
116             (($_ & 0x000F) >> 0)) } @components;
117             # Only keep ($mask / 4) digits. E.g., for a /64 network, keep 16 nibbles.
118 12         48 splice(@nibbles, ($mask / 4));
119 12         26 return join('.', map { sprintf('%x', $_) } reverse @nibbles) . '.ip6.arpa';
  204         473  
120             }
121              
122             1