diff options
-rwxr-xr-x | src/conf-mode/vyos-config-dns-forwarding.py | 210 |
1 files changed, 108 insertions, 102 deletions
diff --git a/src/conf-mode/vyos-config-dns-forwarding.py b/src/conf-mode/vyos-config-dns-forwarding.py index 3b5142ea4..1aa42e1c1 100755 --- a/src/conf-mode/vyos-config-dns-forwarding.py +++ b/src/conf-mode/vyos-config-dns-forwarding.py @@ -19,13 +19,61 @@ import sys import os import time + import netifaces +import jinja2 from vyos.config import Config from vyos.util import ConfigError config_file = r'/etc/powerdns/recursor.conf' +# XXX: pdns recursor doesn't like whitespace near entry separators, +# especially in the semicolon-separated lists of name servers. +# Please be careful if you edit the template. +config_tmpl = """ + +### Autogenerated by vyos-config-dns-forwarding.py ### + +# Non-configurable defaults +daemon=yes +threads=1 +allow-from=0.0.0.0/0 +log-common-errors=yes + +# cache-size +max-cache-entries={{ cache_size }} + +# ignore-hosts-file +export-etc-hosts={{ export_hosts_file }} + +# listen-on +local-address= {{ listen_on | join(',') }} + +# domain ... server ... +{% if domains -%} + +{% for d in domains -%} +forward-zones = {{ d.name }} = {{ d.servers | join(";") }} +{% endfor -%} + +{% endif %} + +# name-server +forward-zones-recurse=.= {{ name_servers | join(';') }} + +""" + +default_config_data = { + 'cache_size' : 10000, + 'export_hosts_file': 'yes', + 'listen_on': [], + 'interfaces': [], + 'name_servers': [], + 'domains': [] +} + + # borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX! def get_resolvers(file): resolvers = [] @@ -41,99 +89,54 @@ def get_resolvers(file): return [] def get_config(): - dns = {} + dns = default_config_data conf = Config() - conf.set_level('service dns forwarding') - if not conf.exists(''): - return dns + if not conf.exists('service dns forwarding'): + return None + else: + conf.set_level('service dns forwarding') if conf.exists('cache-size'): - cache = conf.return_value('cache-size') - dns.setdefault('cache-size', cache) - - if conf.exists('dhcp'): - dns.setdefault('dhcp', []) - interfaces = [] - interfaces = conf.return_values('dhcp') - for interface in interfaces: - resolvers = get_resolvers("/etc/resolv.conf.dhclient-new-{0}".format(interface)) - if len(resolvers) > 0: - dhcp = { - "interface": interface, - "resolvers": resolvers - } - dns['dhcp'].append(dhcp) + cache_size = conf.return_value('cache-size') + dns['cache_size'] = cache_size if conf.exists('domain'): - dns.setdefault('domain', []) for node in conf.list_nodes('domain'): server = conf.return_values("domain {0} server".format(node)) domain = { "name": node, - "server": server + "servers": server } - dns['domain'].append(domain) + dns['domains'].append(domain) if conf.exists('ignore-hosts-file'): - dns.setdefault('ignore-hosts-file', True) - - if conf.exists('listen-on'): - interfaces = [] - interfaces = conf.return_values('listen-on') - dns.setdefault('listen-on', interfaces) + dns.setdefault('export_hosts_file', "no") if conf.exists('name-server'): - nameservers = [] - nameservers = conf.return_values('name-server') - dns.setdefault('name-server', nameservers) + name_servers = conf.return_values('name-server') + dns.setdefault('name_servers', name_servers) if conf.exists('system'): conf.set_level('system') - nameservers = [] - nameservers = conf.return_values('name-server') - if len(nameservers) == 0: + system_name_servers = [] + system_name_servers = conf.return_values('name-server') + if not system_name_servers: print("DNS forwarding warning: No name-servers set under 'system name-server'\n") else: - dns.setdefault('system-name-server', nameservers) + dns['name_servers'] = dns['name_servers'] + system_name_servers + conf.set_level('service dns forwarding') - return dns - -def verify(dns): - if len(dns) > 0: - if 'listen-on' not in dns.keys(): - raise ConfigError('Error: DNS forwarding requires a configured listen interface!') - - for interface in dns['listen-on']: - try: - netifaces.ifaddresses(interface)[netifaces.AF_INET] - except KeyError as e: - raise ConfigError('Error: Interface {0} has no IP address assigned'.format(interface)) - - if 'domain' in dns.keys(): - for domain in dns['domain']: - if len(domain['server']) == 0: - raise ConfigError('Error: No server configured for domain {0}'.format(domain['name'])) - - return None - -def generate(dns): - config_header = '### Autogenerated by vyos-config-dns-forwarding.py on {tm} ###\n'.format(tm=time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())) + ## Hacks and tricks - fwds = [] - fwds_src = [] - - # write new configuration file - f = open(config_file, 'w') - f.write(config_header) - f.write('daemon=yes\n') - f.write('threads=1\n') - f.write('allow-from=0.0.0.0/0\n') - f.write('log-common-errors=yes\n') + # The old VyOS syntax that comes from dnsmasq was "listen-on $interface". + # pdns wants addresses instead, so we emulate it by looking up all addresses + # of a given interface and writing them to the config + if conf.exists('listen-on'): + interfaces = conf.return_values('listen-on') - if 'listen-on' in dns.keys(): listen4 = [] listen6 = [] - for interface in dns['listen-on']: + for interface in interfaces: addrs = netifaces.ifaddresses(interface) for ip4 in addrs[netifaces.AF_INET]: listen4.append(ip4['addr']) @@ -141,52 +144,55 @@ def generate(dns): for ip6 in addrs[netifaces.AF_INET6]: listen6.append(ip6['addr']) - # build local-address string, example: - # local-address=172.16.37.240, 127.0.0.1, 172.16.254.35, 172.16.77.1, 2001:DB8::1:25, ::1 - f.write('local-address=' + ', '.join(listen4) + ', ' + ', '.join(listen6) + '\n') + dns['listen_on'] = listen4 + listen6 - if 'cache-size' in dns.keys(): - f.write("max-cache-entries={0}\n".format(dns['cache-size'])) + # Save interfaces in the dict for the reference + dns['interfaces'] = interfaces - if 'dhcp' in dns.keys(): - for dhcp in dns['dhcp']: - fwds_src.append('DHCP: {0} ({1})'.format(dhcp['interface'], ', '.join(str(ns) for ns in dhcp['resolvers']))) - fwds.append(', '.join(str(ns) for ns in dhcp['resolvers'])) + # Add name servers received from DHCP + if conf.exists('dhcp'): + interfaces = [] + interfaces = conf.return_values('dhcp') + for interface in interfaces: + dhcp_resolvers = get_resolvers("/etc/resolv.conf.dhclient-new-{0}".format(interface)) + if dhcp_resolvers: + dns['name_servers'] = dns['name_servers'] + dhcp_resolvers - if 'domain' in dns.keys(): - zones = [] - for domain in dns['domain']: - zones.append('{0}={1}'.format(domain['name'], ';'.join(str(ns) for ns in domain['server']))) - f.write('forward-zones=' + ', '.join(zones) + '\n') + return dns - if 'ignore-hosts-file' in dns.keys(): - f.write("export-etc-hosts=no\n") - else: - f.write("export-etc-hosts=yes\n") +def verify(dns): + if not dns['interfaces']: + raise ConfigError('Error: DNS forwarding requires a configured listen interface!') - if 'name-server' in dns.keys(): - fwds_src.append('statically configured: {0}'.format(', '.join(str(ns) for ns in dns['name-server']))) - fwds.append(', '.join(str(ns) for ns in dns['name-server'])) + for interface in dns['interfaces']: + try: + netifaces.ifaddresses(interface)[netifaces.AF_INET] + except KeyError as e: + raise ConfigError('Error: Interface {0} has no IP address assigned'.format(interface)) - if 'system-name-server' in dns.keys(): - fwds_src.append('system: {0}'.format(', '.join(str(ns) for ns in dns['system-name-server']))) - fwds.append(', '.join(str(ns) for ns in dns['system-name-server'])) + if dns['domains']: + for domain in dns['domains']: + if not domain['servers']: + raise ConfigError('Error: No server configured for domain {0}'.format(domain['name'])) - if len(fwds) > 0: - f.write('\n') - f.write('# ' + '\n# '.join(fwds_src) + '\n') - f.write('forward-zones-recurse=.=' + '; '.join(fwds) + '\n') + return None + +def generate(dns): + tmpl = jinja2.Template(config_tmpl) - f.close() + config_text = tmpl.render(dns) + with open(config_file, 'w') as f: + f.write(config_text) return None def apply(dns): - if len(dns) == 0: - cmd = "sudo systemctl stop pdns-recursor" + if dns: + os.system("systemctl restart pdns-recursor") else: - cmd = "sudo systemctl restart pdns-recursor" + # DNS forwarding is removed in the commit + os.system("systemctl stop pdns-recursor") + os.unlink(config_file) - os.system(cmd) return None if __name__ == '__main__': |