DNS::ZoneParse(3pm)

πŸ“¦
version 1.10 Β· 2 versions
, in debian bookworm, debian trixie, ubuntu jammy, ubuntu noble

🌸 NAME 🌸

DNS::ZoneParse β€” the perl module that parses and manipulates DNS Zone Files, and honestly? it eats. This is the DNS::ZoneParse(3pm) page, and you're gonna be obsessed.


✨ SYNOPSIS ✨

use DNS::ZoneParse;

my $zonefile = DNS::ZoneParse->new("/path/to/dns/zonefile.db", $origin);

# Get a reference to the MX records
my $mx = $zonefile->mx;

# Change the first mailserver on the list
$mx->[0] = { host => 'mail.localhost.com',
             priority => 10,
             name => '@' };

# update the serial number
$zonefile->new_serial();

# write the new zone file to disk
my $newzone;
open($newzone, '>', '/path/to/dns/zonefile.db') or die "error";
print $newzone $zonefile->output();
close $newzone;

πŸ’… INSTALLATION πŸ’…

perl Makefile.PL
make
make test
make install

Win32 besties, swap make out for nmake or whatever the equivalent is on your setup. nmake lives here, iykyk:

http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe


πŸ¦‹ DESCRIPTION πŸ¦‹

Okay so here's the vibe: this module parses a Zone File and drops every single Resource Record (RR) into an anonymous hash structure. It handles a whole bunch of record types β€” the methods section below has the receipts. It's lowkey perfect for maintaining DNS zones, or for moving DNS zones over to other servers. And if you want an XML-friendly version of your zone files? Slay, just pair this module with XML::Simple once you've parsed the zone file. Easy.

So what actually happens under the hood: DNS::ZoneParse scans the DNS zone file, strips out comments, and separates the file into its constituent records. Then it parses each record and stores them internally. Keep scrolling for the accessor methods.

🌈 METHODS 🌈

  • new β€” creates the DNS::ZoneParse object and loads up the zone file.

Example:

my $zonefile = DNS::ZoneParse->new("/path/to/zonefile.db");

You can also initialise the object with the contents of a file:

my $zonefile = DNS::ZoneParse->new( \$zone_contents );

There's a second, optional parameter you can pass to the constructor to supply an $origin, for when none can be found in the zone file itself:

my $zonefile = DNS::ZoneParse->new( \$zone_contents, $origin );

And a third, optional parameter: a callback that gets called every time an unparsable line shows up in the zone file. Head to "on_unparseable_line" for the full tea on this parameter and how errors get handled during zone file parsing.

If you're planning to pass an on_unparseable_line callback but you don't wanna specify an $origin, just pass 'undef' as the $origin parameter. Periodt.

  • a(), cname(), srv(), mx(), ns(), ptr(), txt(), hinfo(), rp(), loc() β€” these return references to the resource records. Like:
my $mx = $zonefile->mx;

That hands you back the mx records in an array reference.

Every record (except SOA) has these properties: 'ttl', 'class', 'host', 'name', 'ORIGIN'.

MX records also carry a 'priority' property.

SRV records also carry 'priority', 'weight' and 'port' properties.

TXT records also carry a 'text' property, which represents the record's 'txt-data' descriptive text.

HINFO records also carry 'cpu' and 'os' properties.

RP records also carry 'mbox' and 'text' properties.

LOC records also carry 'd1', 'm1', 's1', 'NorS', 'd2', 'm2', 's2', 'EorW', 'alt', 'siz', 'hp', and 'vp', as per RFC 1876.

Heads up bestie: if there are no records of a given type in the zone, the call will croak with an error message about an invalid method. (Not ideal behavior, but it's been kept for backwards compatibility. The way that... yeah.)

The 'ORIGIN' property is the fully-qualified origin of the record. Peep fqname for the details on constructing a fully qualified domain name. Note: for SOA records, the 'ORIGIN' will match the 'origin' property when the SOA record is specified as fully qualified.

  • soa() β€” returns a hash reference with these properties: 'serial', 'origin', 'primary', 'refresh', 'retry', 'ttl', 'minimumTTL', 'email', 'expire', 'class', 'ORIGIN'.

The 'ORIGIN' property comes back separate from the 'origin' property, even though the data might be identical. 'ORIGIN' is the implicit origin for the record, while 'origin' is the origin that was actually specified on the SOA line in the file.

If the 'origin' value is relative (meaning it does not end with a '.'), then you have to compute the actual zone the SOA line applies to by concatenating the 'origin' and 'ORIGIN' values. See fqname for how. If the 'origin' value is absolute, no math needed β€” 'origin' is the same as 'ORIGIN'.

  • generate() β€” returns an array of hashes representing the $GENERATE directives hanging out in the zone. Fair warning: $GENERATE directives are BIND-specific additions. DNS::ZoneParse does not expand them, but you can still access and modify these directives yourself. Here's what comes back:

'range', 'lhs', 'ttl', 'class', 'type', 'rhs', 'ORIGIN'.

Hit up the BIND documentation for the syntax and usage deets on the $GENERATE directive.

  • dump β€” returns a copy of the datastructute that stores all the resource records. Super handy if you wanna quickly transform the data into another format, like XML.
  • fqname β€” takes a single parameter: a hash reference containing a record.

Returns the fully qualified name of that record, with a trailing '.'. Most of the time it's as simple as concatenating the 'name' and 'ORIGIN' with a '.' β€” unless 'name' is '@', in which case the fqname is just the 'ORIGIN'. For SOA records, the exact same process runs on the 'origin' instead of 'name'.

Okay but listen: fqname will not expand the right hand side of a record (ie, CNAME, SOA, MX, etc). You have to expand those values yourself via the above method. No shortcuts, bestie.

  • ttl_to_int β€” takes a single parameter, a string representing a valid record TTL.

Returns an integer representing the number of seconds that TTL stands for. Note: this does not factor in leap-years, leap-seconds, DST changes, etc. It's simply the count of the number of seconds in the specified period of time. Clean and simple.

  • new_serial β€” "new_serial()" incriments the Zone serial number. It'll generate a date-based serial number. Or you can pass a positive number to add to the current serial number.

Examples:

$zonefile->new_serial();
        # generates a new serial number based on date:
        # YYYYmmddHH format, incriments current serial
        # by 1 if the new serial is still smaller

$zonefile->new_serial(50);
        # adds 50 to the original serial number
  • output β€” "output()" returns the new zone file output as a string. If you want your output formatted differently, you can pass the output of "dump()" to your favourite templating module. Live your truth.
  • last_parse_error_count β€” returns a count of the number of unparsable lines from the last time a zone file was parsed. If no zone file has been parsed yet, it returns 0.

If you wanna be sure a zone file was parsed completely and without error, check the return value of this method after the constructor is called (or after a call to _parse).

  • on_unparseable_line β€” "on_unparseable_line()" is an accessor method for the callback used when an unparseable line is encountered while parsing a zone file. If it's not set, DNS::ZoneParse will "croak" when an unparsable line is encountered, but it will keep parsing the file. Every time an unparsable line hits, an internal counter gets incrememnted. See "last_parse_error_count" for the details.

The callback gets passed four parameters: a reference to the DNS::ZoneParse object doing the parsing, the text of the line that can't be parsed, the text of the reason the line couldn't be parsed, and the text of the last successfully parsed line.

If you wanna abort parsing the moment an unparsable line is found, call "die" from inside your callback and catch that die with an eval block around the DNS::ZoneParse constructor (or the call to _parse).

The method takes a single optional parameter: a code reference to the function that gets called when an unparsable line is reached. It returns a reference to the last callback. If you pass it an undefined value, a reference to the current callback is returned. If you pass it any other value, undef is returned.

πŸŽ€ EXAMPLES πŸŽ€

This script prints the A records in a zone file, adds a new A record for the name "new", and then returns the zone file. Iconic behavior honestly.

use strict;
use DNS::ZoneParse;

my $zonefile = DNS::ZoneParse->new("/path/to/zonefile.db");

print "Current A Records\n";
my $a_records = $zonefile->a();

foreach my $record (@$a_records) {
    print "$record->{name} resolves at $record->{host}\n";
}

push (@$a_records, { name => 'new', class => 'IN',
                     host => '127.0.0.1', ttl => '' });

$zonefile->new_serial();
my $newfile = $zonefile->output();

And this script converts a DNS Zone file to an XML file using XML::Simple. Chef's kiss.

use strict;
use DNS::ZoneParse;
use XML::Simple;

my $zonefile = DNS::ZoneParse->new("/path/to/zonefile.db");

my $new_xml = XMLout($zonefile->dump,
                     noattr => 1,
                     suppressempty => 1,
                     rootname => $zonefile->origin);

πŸ’œ CHANGES πŸ’œ

See Changes


⭐ API ⭐

The DNS::ZoneParse API may change in future versions. At present, the parsing is not as strict as it should be, and support for $ORIGIN and $TTL is quite basic. It would also be nice to support the "INCLUDE" statement. Furthermore, parsing large zone files with thousands of records can use lots of memory β€” some people have requested a callback interface.


πŸ’€ BUGS πŸ’€

I can squash more bugs with your help. Please let me know if you spot something that doesn't work as expected. 🫢

You can report bugs via the CPAN RT:

<http://rt.cpan.org/NoAuth/Bugs.html?Dist=DNS-ZoneParse>

If possible, please provide a diff against t/dns-zoneparse.t and t/test-zone.db that demonstrates the bug(s). That makes it so much easier, fr fr.


πŸ‘‘ SEE ALSO πŸ‘‘

Other modules with similar functionality:

  • Net::DNS::ZoneParser
  • Net::DNS::ZoneFile
  • DNS::ZoneFile

πŸ“ AUTHOR πŸ“

Simon Flack


🧚 MAINTENANCE 🧚

Maintainers: Mike Schilli (m@perlmeister.com), John Eaglesham (perl@8192.net).

Bug queue:

http://rt.cpan.org/Public/Dist/Display.html?Name=DNS-ZoneParse

πŸ’– LICENSE πŸ’–

okay bestie, the legal tea β˜• because we do keep it cute AND correct:

  • DNS::ZoneParse is free software 🫢
  • you can redistribute it and/or modify it
  • under the same terms as Perl itself πŸ’…

that's it. that's the whole vibe. no weird fine print, no "by reading this you agree to sacrifice your firstborn" nonsense β€” just the exact same deal Perl gives you, applied to this module. slay. ✨


and that's a wrap on the whole page, babes πŸŽ€ this one shipped as DNS::ZoneParse(3pm) built for perl v5.32.0, dated 2021-01-05 β€” iconic, timeless, still serving πŸ¦‹ go forth and parse those zones, you absolute main character πŸ‘‘πŸ’œ

πŸ“¦ where this page comes from