#!/usr/bin/env python3 # # Copyright (C) 2018 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # import sys import os import time import netifaces from vyos.config import Config from vyos.util import ConfigError config_file = r'/etc/powerdns/recursor.conf' # borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX! def get_resolvers(file): resolvers = [] try: with open(file, 'r') as resolvconf: for line in resolvconf.readlines(): line = line.split('#',1)[0]; line = line.rstrip(); if 'nameserver' in line: resolvers.append(line.split()[1]) return resolvers except IOError as error: return error.strerror def get_config(): dns = {} conf = Config() conf.set_level('service dns forwarding') if not conf.exists(''): return dns 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)) dhcp = { "interface": interface, "resolvers": resolvers } dns['dhcp'].append(dhcp) 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 } dns['domain'].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) if conf.exists('name-server'): nameservers = [] nameservers = conf.return_values('name-server') dns.setdefault('name-server', nameservers) if conf.exists('system'): conf.set_level('system') nameservers = [] nameservers = conf.return_values('name-server') if len(nameservers) == 0: print("DNS forwarding warning: No name-servers set under 'system name-server'\n") else: dns.setdefault('system-name-server', nameservers) 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())) 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') if 'listen-on' in dns.keys(): listen4 = [] listen6 = [] for interface in dns['listen-on']: addrs = netifaces.ifaddresses(interface) for ip4 in addrs[netifaces.AF_INET]: listen4.append(ip4['addr']) 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') if 'cache-size' in dns.keys(): f.write("max-cache-entries={0}\n".format(dns['cache-size'])) 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'])) 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') if 'ignore-hosts-file' in dns.keys(): f.write("export-etc-hosts=no\n") 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'])) 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 len(fwds) > 0: f.write('\n') f.write('# ' + '\n# '.join(fwds_src) + '\n') f.write('forward-zones-recurse=.=' + '; '.join(fwds) + '\n') f.close() return None def apply(dns): if len(dns) == 0: cmd = "sudo systemctl stop pdns-recursor" else: cmd = "sudo systemctl restart pdns-recursor" os.system(cmd) return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) sys.exit(1)