BIND DNS: how to override RR generated by $GENERATE directive?

I'm running an authoritative nameserver for a reverse /16 zone, where every IP is mapped to a custom subdomain. This is achieved by a zone file with 256 $GENERATE directives, for example (subnet 11.22.0.0/16):

$GENERATE 0-255 $.1 PTR $.1.22.11.rev.example.com.
$GENERATE 0-255 $.2 PTR $.2.22.11.rev.example.com.
(...)

This works fine, the only issue is that whenever we add a "meaningful" reverse record (4.3.22.11.in-addr.arpa. IN PTR www.example.com.) it will result in a situation where there are 2 PTR records for the same IP address:

4.3.22.11.in-addr.arpa. IN PTR www.example.com.
4.3.22.11.in-addr.arpa. IN PTR 4.3.22.11.rev.example.com.

For the most part this is fine, but in some cases we need to have a single PTR record.

The solution has been to "unroll" the $GENERATE block into individual PTR records and replace the offending one. Is there a way to override a generated record without having to expand the whole range?

This nameserver runs BIND 9.8.2 on RHEL6.


Solution 1:

The $GENERATE Directive only has two forms for range: start-stop or start-stop/step. Because of this you can't exclude one IP from the range, but you have to split the range accordingly, e.g.

$ORIGIN 22.11.in-addr.arpa.
$GENERATE 0-3   $.3  PTR  $.3.22.11.rev.example.com.
                4.3  PTR  www.example.com.
$GENERATE 5-255 $.3  PTR  $.3.22.11.rev.example.com.

Solution 2:

There is no way to do this unfortunately. You're stuck with "unrolling".

In memory, the $GENERATE directive causes individual PTR records to be generated. This can be observed by viewing the zone file received by the secondary servers after zone transfer, which does not contain a $GENERATE directive. There is no syntax that allows you to selectively override the individual PTR records.

An alternative is mentioned in Chapter 8 of DNS for Rocket Scientists, which is to add a step of using named-checkzone to parse out the $GENERATE directive and replace it with individual PTR records:

The $GENERATE statement is executed while loading the zone file and will result in an expanded in-memory version of the zone being used by BIND9 operationally while the zone file itself is unchanged. If you want to see the expansion (you don't trust the outcome or you want to edit it) the bind utility named-checkzone will allow creation of an expanded zone file (including the $GENERATE directive(s)) which may be used as a template (or skeleton) and subsequently edited. As a side effect of using named-checkzone any unqualified labels (names) will also be expanded to full FQDNs and any $TTL directive will be used to populate individual RR TTLs. This may, or may not, be a useful side effect. Assuming the zone file containing the $GENERATE directive is 192.168.199.rev (as per this guide's zone file naming convention) and the zone name is 199.168.192.IN-ADDR.ARPA then the following command will output an expanded zone file to 192.168.199.rev.exp.

named-checkzone -D -o 192.168.199.rev.exp 199.168.192.IN-ADDR.ARPA 192.168.199.rev

The downside is, naturally, the fact that your zone file on the master becomes much larger. At this point you're only using $GENERATE to build the initial reverse zone for you so that the individual PTR records don't have to be typed out by hand, and a shell script could have easily achieved the same end result there.

This probably isn't the solution that you were hoping for, but that's the state of things unfortunately. :(