From 1df3827994bfef6c166bdb0f51cc51c212507361 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 16 Mar 2018 20:09:59 +0100 Subject: Initial CLI interface support for XML DNS forwarder --- src/conf-mode/vyos-config-dns-forwarding.py | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 src/conf-mode/vyos-config-dns-forwarding.py (limited to 'src') diff --git a/src/conf-mode/vyos-config-dns-forwarding.py b/src/conf-mode/vyos-config-dns-forwarding.py new file mode 100755 index 000000000..27e7a5ef5 --- /dev/null +++ b/src/conf-mode/vyos-config-dns-forwarding.py @@ -0,0 +1,45 @@ +#!/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 + +from vyos.config import Config +from vyos.util import ConfigError + +def get_config(): + return None + +def verify(dns): + return None + +def generate(dns): + return None + +def apply(dns): + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) -- cgit v1.2.3 From 28c3a6385143c97c3255f2a476523b103d3b830d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 16 Mar 2018 21:00:43 +0100 Subject: Initial working version for XML interface to configure dnsmasq --- Makefile | 1 + src/conf-mode/vyos-config-dns-forwarding.py | 119 +++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/Makefile b/Makefile index f5800da43..de7c136eb 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ interface_definitions: # XXX: delete top level node.def's that now live in other packages rm -f $(TMPL_DIR)/system/node.def rm -f $(TMPL_DIR)/service/node.def + rm -f $(TMPL_DIR)/service/dns/node.def rm -f $(TMPL_DIR)/protocols/node.def .PHONY: all diff --git a/src/conf-mode/vyos-config-dns-forwarding.py b/src/conf-mode/vyos-config-dns-forwarding.py index 27e7a5ef5..cdbd89eba 100755 --- a/src/conf-mode/vyos-config-dns-forwarding.py +++ b/src/conf-mode/vyos-config-dns-forwarding.py @@ -18,20 +18,137 @@ import sys import os +import time from vyos.config import Config from vyos.util import ConfigError +config_file = r'/etc/dnsmasq.d/vyos.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(): - return None + 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_value("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") + dns.setdefault('system-name-server', True) + + return dns def verify(dns): + if 'listen-on' not in dns.keys(): + raise ConfigError("Error: DNS forwarding requires a configured listen interface!") + 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())) + + # write new configuration file + f = open(config_file, 'w') + f.write(config_header) + f.write("log-facility=/var/log/dnsmasq.log\n") + f.write("no-poll\n") + f.write("edns-packet-max=4096\n") + f.write("bind-interfaces\n") + + if 'listen-on' in dns.keys(): + for interface in dns['listen-on']: + f.write("interface={0}\n".format(interface)) + + if 'dhcp' in dns.keys(): + for dhcp in dns['dhcp']: + for resolver in dhcp['resolvers']: + f.write("server={0}\t# dhcp {1}\n".format(resolver, dhcp['interface'])) + + if 'domain' in dns.keys(): + for domain in dns['domain']: + f.write("server=/{0}/{1}\t# domain-override\n".format(domain['name'], domain['server'])) + + if 'cache-size' in dns.keys(): + f.write("cache-size={0}\n".format(dns['cache-size'])) + + if 'ignore-hosts-file' in dns.keys(): + f.write("no-hosts\n") + + if 'name-server' in dns.keys(): + for nameserver in dns['name-server']: + f.write("server={0}\t# statically configured\n".format(nameserver)) + + if 'system-name-server' in dns.keys(): + # Read the IP addresses of the upstream nameservers from /etc/resolv.conf + f.write("resolv-file=/etc/resolv.conf\n") + + f.close() return None def apply(dns): + if len(dns) == 0: + cmd = "sudo systemctl stop dnsmasq" + else: + cmd = "sudo systemctl restart dnsmasq" + + os.system(cmd) return None if __name__ == '__main__': -- cgit v1.2.3 From d08fbff714dbff0ae7b914a55e58ee141042c4f0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 16 Mar 2018 21:40:41 +0100 Subject: dns-forwarding.py: add missing 'query-all-servers' path --- src/conf-mode/vyos-config-dns-forwarding.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/conf-mode/vyos-config-dns-forwarding.py b/src/conf-mode/vyos-config-dns-forwarding.py index cdbd89eba..742e111cb 100755 --- a/src/conf-mode/vyos-config-dns-forwarding.py +++ b/src/conf-mode/vyos-config-dns-forwarding.py @@ -85,6 +85,9 @@ def get_config(): nameservers = conf.return_values('name-server') dns.setdefault('name-server', nameservers) + if conf.exists('query-all-servers'): + dns.setdefault('query-all-servers', True) + if conf.exists('system'): conf.set_level('system') nameservers = [] @@ -96,8 +99,9 @@ def get_config(): return dns def verify(dns): - if 'listen-on' not in dns.keys(): - raise ConfigError("Error: DNS forwarding requires a configured listen interface!") + if len(dns) > 0: + if 'listen-on' not in dns.keys(): + raise ConfigError("Error: DNS forwarding requires a configured listen interface!") return None @@ -135,6 +139,9 @@ def generate(dns): for nameserver in dns['name-server']: f.write("server={0}\t# statically configured\n".format(nameserver)) + if 'query-all-servers' in dns.keys(): + f.write("all-servers\n") + if 'system-name-server' in dns.keys(): # Read the IP addresses of the upstream nameservers from /etc/resolv.conf f.write("resolv-file=/etc/resolv.conf\n") -- cgit v1.2.3 From 25a57a9f727f908e0027a598d56eaa6375ac5c25 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 13 Apr 2018 20:38:03 +0200 Subject: T560: dns-forwarding: replace dnsmasq with pdns-recursor --- interface-definitions/dns-forwarding.xml | 9 +--- src/conf-mode/vyos-config-dns-forwarding.py | 84 +++++++++++++++++++---------- 2 files changed, 58 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/interface-definitions/dns-forwarding.xml b/interface-definitions/dns-forwarding.xml index f1edec414..d56911c60 100644 --- a/interface-definitions/dns-forwarding.xml +++ b/interface-definitions/dns-forwarding.xml @@ -32,7 +32,6 @@ DNS domain to forward to a local server - txt @@ -77,12 +76,8 @@ ipv6 Domain Name Server (DNS) IPv6 address - - - - - - Query all DNS servers, respond and cache fastest result + + ipv4,ipv6 diff --git a/src/conf-mode/vyos-config-dns-forwarding.py b/src/conf-mode/vyos-config-dns-forwarding.py index 742e111cb..7a8aed75d 100755 --- a/src/conf-mode/vyos-config-dns-forwarding.py +++ b/src/conf-mode/vyos-config-dns-forwarding.py @@ -19,11 +19,12 @@ import sys import os import time +import netifaces from vyos.config import Config from vyos.util import ConfigError -config_file = r'/etc/dnsmasq.d/vyos.conf' +config_file = r'/etc/powerdns/recursor.conf' # borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX! def get_resolvers(file): @@ -65,7 +66,7 @@ def get_config(): if conf.exists('domain'): dns.setdefault('domain', []) for node in conf.list_nodes('domain'): - server = conf.return_value("domain {0} server".format(node)) + server = conf.return_values("domain {0} server".format(node)) domain = { "name": node, "server": server @@ -85,75 +86,102 @@ def get_config(): nameservers = conf.return_values('name-server') dns.setdefault('name-server', nameservers) - if conf.exists('query-all-servers'): - dns.setdefault('query-all-servers', True) - 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") - dns.setdefault('system-name-server', True) + 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!") + 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("log-facility=/var/log/dnsmasq.log\n") - f.write("no-poll\n") - f.write("edns-packet-max=4096\n") - f.write("bind-interfaces\n") + 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']: - f.write("interface={0}\n".format(interface)) + 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']: - for resolver in dhcp['resolvers']: - f.write("server={0}\t# dhcp {1}\n".format(resolver, dhcp['interface'])) + 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']: - f.write("server=/{0}/{1}\t# domain-override\n".format(domain['name'], domain['server'])) - - if 'cache-size' in dns.keys(): - f.write("cache-size={0}\n".format(dns['cache-size'])) + 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("no-hosts\n") + f.write("export-etc-hosts=no\n") if 'name-server' in dns.keys(): - for nameserver in dns['name-server']: - f.write("server={0}\t# statically configured\n".format(nameserver)) - - if 'query-all-servers' in dns.keys(): - f.write("all-servers\n") + 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(): - # Read the IP addresses of the upstream nameservers from /etc/resolv.conf - f.write("resolv-file=/etc/resolv.conf\n") + 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 dnsmasq" + cmd = "sudo systemctl stop pdns-recursor" else: - cmd = "sudo systemctl restart dnsmasq" + cmd = "sudo systemctl restart pdns-recursor" os.system(cmd) return None -- cgit v1.2.3