#!/usr/bin/perl -w # Converts en masse an /etc/ethers file to DNS records for IPv6. # Author: James F. Carter , 2008-11-03 # Synopsis: ether2dns {-f|-r} -p 2012:3456:789a:bcde:: [/etc/ethers] > dns.zone # -p IPv6 address prefix (required), no /64, this length is assumed. # -f To produce a forward DNS map # -r To produce a reverse DNS map (exactly one of -f or -r is required) # The "ethers" map is read from the named file, or standard input if no file. # The DNS map comes out on standard output. You could do: # ypcat ethers | ether2dns... # The format of the "ethers" map is # aa:bb:cc:dd:ee:ff fully.qualified.domain.name # Lines starting with # are comments. Comments are preserved but are converted # to DNS style (starting with ;) in the output. If you don't want them, use # grep -v '#' /etc/ethers | ether2dns... # Origin statements are produced -- from the prefix for the reverse map, or # from the first FQDN for the forward map. However, there is no $TTL, no # SOA, nor NS records. Only AAAA and PTR records are produced. # If your /etc/ethers file covers several domains, you will want to use grep # to filter out one domain at a time. use strict; use Getopt::Std; our $opt_f = 0; # For forward map our $opt_r = 0; # For reverse map our $opt_p = ''; # IPv6 prefix our @prefix; our @bad; our $f; OPTS: { # Basic command line arguments getopts("p:fr") or push(@bad, "Bad command line options\n"); push(@bad, "Extra command line arguments, expecting 1 filename\n") unless @ARGV <= 1; push(@bad, "Can't read $ARGV[0]\n") unless -r $ARGV[0]; push(@bad, "One of -f or -r is required\n") unless $opt_f + $opt_r == 1; # Expand the prefix into 8 hex fields and detect screwups. push(@bad, "-p prefix is required\n") and last unless $opt_p ne ''; if ($opt_p =~ s/\/\d*//) { push(@bad, "Do not specify /length after the prefix; /64 is assumed\n"); } push(@bad, "Prefix contains invalid characters (should be hex digits separated by colons)\n") if $opt_p =~ /[^[:xdigit:]:\/]/; # Substitute 0's for :: @prefix = split(/:/, $opt_p, 9); my $nmis = 8 - scalar(@prefix); # Number of 0's to substitute for :: my $k; if ($opt_p eq '::') { @prefix = (0) x 8; } elsif (substr($opt_p,0,2) eq ':;') { splice(@prefix, 0, 2, (0) x ($nmis+2)); } elsif (substr($opt_p,-2) eq '::') { splice(@prefix, -2, 2, (0) x ($nmis+2)); } elsif (($k = index($opt_p, '::')) >= 0) { my @ipfx = split(/:/, substr($opt_p, 0, $k)); my $null = scalar(@ipfx); splice(@prefix, $null, 1, (0) x ($nmis+1)); } push(@bad, "The prefix must be 8 hex numbers separated by colons (:: OK)\n") unless @prefix == 8; # Supply leading 0's in each field. my $over4; foreach $f (@prefix) { my $l = length($f); $over4++ if $l > 4; substr($f, 0, 0) = '0' x (4-$l); } push(@bad, "Each field of the prefix must be 4 hex digits, leading 0 optional\n") if $over4; } if (@bad) { print @bad, "Usage: ether2dns {-f|-r} -p 2012:3456:789a:bcde:: [/etc/ethers] > dns.zone\n"; exit 4; } # Now presumably we have everything useable. Convert to 32 hex # digits for the reverse map. @prefix = split(//, join('', @prefix)); # Already checked the input file; should never die. $f = @ARGV ? $ARGV[0] : '<&STDIN'; open(ETHERS, $f) or die "Can't open $f: $!\n"; our $origin; # Is 1 after the origin is put out our @comments; our %compl2 = qw(0 2 1 3 2 0 3 1 4 6 5 7 6 4 7 5 8 a 9 b a 8 b 9 c e d f e c f d); #Complement bit 2 while () { next if /^\s*$/; # Skip blank lines if (/^\s*\#/) { # A comment s/\#/;/; # In DNS files, comments start with ; push(@comments, $_); # Save comment with its newline next; } chomp; my @line = split(' ', $_); if (@line < 2) { print STDERR "Line with less than 2 fields:\n$_\n"; undef @comments; next; } my @fqdn = split(/\./, $line[1]); # Put out the origin line. if ($origin++) { # Already did the origin line. } elsif ($opt_f) { print '$ORIGIN ', join('.', @fqdn[1..$#fqdn], "\n"); } else { print '$ORIGIN ', join('.', reverse @prefix[0..15]), ".ip6.arpa.\n"; } # Print saved comments. print @comments; undef @comments; # Normalize the MAC address: lower case with leading 0's. $line[0] =~ tr/A-F/a-f/; my @mac = split(/:/, $line[0]); foreach $f (@mac) { substr($f,0,0) = '0' if length($f) < 2; } # Squeeze fffe in the middle. splice(@mac, 3, 0, 'ff', 'fe'); # Complement bit 2 in the first octet. substr($mac[0],1,1) = $compl2{substr($mac[0],1,1)}; # Now the actual record if ($opt_f) { my $k = int((23 - length($fqdn[0]))/8); print $fqdn[0], (($k > 0) ? ("\t" x $k) : ' '), "IN\tAAAA\t"; splice(@mac, 0, 0, @prefix[0..15]); my $addr = join('', @mac); foreach $k (qw(0 4 8 12 16 20 24 28)) { $f = substr($addr, $k, 4); $f =~ s/^0*//; $f = '0' if $f eq ''; substr($f,0,0) = ':' if $k > 0; print $f; } print "\n"; } else { @mac = split(//, join('', @mac)); print join('.', reverse @mac), "\tIN\tPTR\t", $line[1], "\n"; } } close(ETHERS); exit 0;