"""
Generate zonefile records from hosts
"""
import ipaddr
import logging; log = logging.getLogger('pvl.hosts.zone')
import pvl.dns
class HostZoneError(Exception):
pass
def resolve (origin, domain, name) :
"""
Resolve relative CNAME for label@origin -> alias@domain
"""
if origin:
origin = pvl.dns.fqdn(origin)
if domain:
fqdn = pvl.dns.fqdn(name, domain)
else:
fqdn = pvl.dns.fqdn(name)
if not origin:
return fqdn
elif domain == origin:
return name
elif fqdn.endswith('.' + origin):
return pvl.dns.relative(origin, fqdn)
elif domain:
raise HostZoneError("{name}: domain {domain} out of zone {origin}".format(name=name, domain=domain, origin=origin))
else:
raise HostZoneError("{name}: fqdn {fqdn} out of zone {origin}".format(name=name, fqdn=fqdn, origin=origin))
# TODO: generate location alias CNAMEs even if host itself is outside origin?
def host_forward (host, origin) :
"""
Yield ZoneRecords for hosts within the given zone origin
"""
try:
label = resolve(origin, host.domain, host.name)
except HostZoneError as error:
log.info("%s: skip: %s", host, error)
return
if host.forward:
forward = pvl.dns.fqdn(host.forward)
log.info("%s: forward: %s", host, forward)
yield pvl.dns.ZoneRecord.CNAME(label, forward)
elif host.forward is None:
# forward
if host.ip :
log.info("%s: forward %s[%s]: A %s", host, origin, label, host.ip)
yield pvl.dns.ZoneRecord.A(label, host.ip)
if host.ip6 :
log.info("%s: forward %s[%s]: AAAA %s", host, origin, label, host.ip6)
yield pvl.dns.ZoneRecord.AAAA(label, host.ip6)
else:
log.info("%s: skip forward", host)
return
if host.location:
location_alias, location_domain = host.location
yield pvl.dns.ZoneRecord.CNAME(resolve(origin, location_domain, location_alias), label)
for alias in host.alias:
yield pvl.dns.ZoneRecord.CNAME(resolve(origin, host.domain, alias), label)
for alias in host.alias4:
if not host.ip:
raise HostZoneError(host, "alias4={host.alias4} without ip=".format(host=host))
yield pvl.dns.ZoneRecord.A(resolve(origin, host.domain, alias), host.ip)
for alias in host.alias6:
if not host.ip6:
raise HostZoneError(host, "alias6={host.alias6} without ip6=".format(host=host))
yield pvl.dns.ZoneRecord.AAAA(resolve(origin, host.domain, alias), host.ip6)
def host_reverse (host, prefix) :
"""
Yield (ipaddr.IPAddress, ZoneRecord) tuples for host within given prefix's reverse-dns zone.
"""
if prefix.version == 4 :
ip = host.ip
# reverse= is IPv4-only
reverse = host.reverse
elif prefix.version == 6 :
ip = host.ip6
# if reverse= is set, always omit, for lack of reverse6=
reverse = None if host.reverse is None else False
else :
raise ValueError("%s: unknown ip version: %s" % (prefix, prefix.version))
if not ip :
log.debug("%s: no ip%d", host, prefix.version)
return
if ip not in prefix :
log.debug("%s: %s out of prefix: %s", host, ip, prefix)
return
# relative label
label = pvl.dns.reverse_label(prefix, ip)
if reverse:
alias = pvl.dns.fqdn(reverse)
log.info("%s %s[%s]: CNAME %s", host, prefix, ip, alias)
yield ip, pvl.dns.zone.ZoneRecord.CNAME(label, alias)
elif reverse is None :
fqdn = host.fqdn()
log.info("%s %s[%s]: PTR %s", host, prefix, ip, fqdn)
yield ip, pvl.dns.zone.ZoneRecord.PTR(label, fqdn)
else:
log.info("%s %s[%s]: omit", host, prefix, ip)
def apply_hosts_forward (options, hosts, origin) :
"""
Generate DNS ZoneRecords for for hosts within the given zone origin.
Yields ZoneRecords in Host-order
"""
if options.add_origin :
yield pvl.dns.ZoneDirective.build(None, 'ORIGIN', origin)
by_name = dict()
by_cname = dict()
for host in hosts:
if not host.domain:
log.debug("%s: skip without domain", host)
for rr in host_forward(host, origin) :
if rr.name in by_cname:
raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=by_cname[rr.name].name, rr=rr))
elif rr.type == 'CNAME' and rr.name in by_name:
raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=rr.name, rr=by_name[rr.name]))
by_name[rr.name] = rr
if rr.type == 'CNAME':
by_cname[rr.name] = rr
# preserve ordering
yield rr
def apply_hosts_reverse (options, hosts, prefix) :
"""
Generate DNS ZoneRecords within the given prefix's reverse-dns zone for hosts.
Yields ZoneRecords in IPAddress-order
"""
# collect data for records
by_ip = dict()
for host in hosts:
for ip, rr in host_reverse(host, prefix) :
if ip in by_ip :
raise HostZoneError(host, "{host}: IP {ip} conflict: {other}".format(host=host, ip=ip, other=by_ip[ip]))
# do not retain order
by_ip[ip] = rr
if options.unknown_host :
# enumerate all of them
iter_ips = prefix.iterhosts()
else :
iter_ips = sorted(by_ip)
for ip in iter_ips :
if ip in by_ip :
yield by_ip[ip]
elif options.unknown_host:
# synthesize a record
label = pvl.dns.reverse_label(prefix, ip)
fqdn = pvl.dns.zone.fqdn(options.unknown_host, options.hosts_domain)
log.info("%s %s[%s]: unused PTR %s", options.unknown_host, ip, prefix, fqdn)
yield pvl.dns.zone.ZoneRecord.PTR(label, fqdn)
else :
continue
import pvl.args
import pvl.hosts.config
import optparse
def forward_main () :
"""
Generate bind zonefiles from host definitions.
"""
parser = optparse.OptionParser(forward_main.__doc__)
parser.add_option_group(pvl.args.parser(parser))
parser.add_option_group(pvl.hosts.config.optparser(parser))
parser.add_option('--add-origin', action='store_true',
help="Include $ORIGIN directive in zone")
parser.add_option('--forward-zone', metavar='DOMAIN',
help="Generate forward zone for domain")
# input
options, args = parser.parse_args()
pvl.args.apply(options)
hosts = pvl.hosts.apply(options, args)
# process
for rr in apply_hosts_forward(options, hosts, options.forward_zone):
print unicode(rr)
return 0
def reverse_main () :
"""
Generate bind zonefiles from host definitions.
"""
parser = optparse.OptionParser(reverse_main.__doc__)
parser.add_option_group(pvl.args.parser(parser))
parser.add_option_group(pvl.hosts.config.optparser(parser))
parser.add_option('--reverse-zone', metavar='PREFIX',
help="Generate reverse zone for prefix")
parser.add_option('--unknown-host', metavar='NAME',
help="Generate records for unused IPs")
# input
options, args = parser.parse_args()
pvl.args.apply(options)
hosts = pvl.hosts.apply(options, args)
# process
for rr in apply_hosts_reverse(options, hosts, pvl.dns.parse_prefix(options.reverse_zone)):
print unicode(rr)
return 0