diff options
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | debian/changelog | 7 | ||||
-rw-r--r-- | interface-definitions/dns-forwarding.xml | 97 | ||||
-rwxr-xr-x | src/conf-mode/vyos-config-dns-forwarding.py | 197 |
4 files changed, 306 insertions, 0 deletions
@@ -10,8 +10,13 @@ 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 + # Workaround for special nodes that should not have "type: txt" + sed -i '/^type: txt/d' $(TMPL_DIR)/service/dns/forwarding/listen-on/node.def + sed -i '/^type: txt/d' $(TMPL_DIR)/service/dns/forwarding/system/node.def + .PHONY: all all: interface_definitions diff --git a/debian/changelog b/debian/changelog index 3db7c422e..71faeb912 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +vyos-1x (1.0.4) UNRELEASED; urgency=medium + + * T560: dns-forwarding: replace dnsmasq with pdns-recursor + * T588: Rewrite 'service dns forwarding' in new XML style format + + -- Christian Poessinger <christian@poessinger.com> Sun, 15 Apr 2018 16:13:32 +0200 + vyos-1x (1.0.3) unstable; urgency=medium * T379: Add UDP broadcast relay support diff --git a/interface-definitions/dns-forwarding.xml b/interface-definitions/dns-forwarding.xml new file mode 100644 index 000000000..bdb5ddc46 --- /dev/null +++ b/interface-definitions/dns-forwarding.xml @@ -0,0 +1,97 @@ +<?xml version="1.0"?> + +<!-- DNS forwarder configuration --> + +<interfaceDefinition> + <node name="service"> + <children> + <node name="dns"> + <children> + <node name="forwarding" owner="${vyos_sbindir}/vyos-config-dns-forwarding.py"> + <properties> + <help>DNS forwarding</help> + <priority>918</priority> + </properties> + <children> + <leafNode name="cache-size"> + <properties> + <help>DNS forwarding cache size</help> + <valueHelp> + <format>u32:0-10000</format> + <description>DNS forwarding cache size</description> + </valueHelp> + <type>u32</type> + </properties> + </leafNode> + <leafNode name="dhcp"> + <properties> + <help>Use nameservers received from DHCP server for specified interface</help> + <completionHelp> + <script>${vyatta_sbindir}/vyatta-interfaces.pl --show all</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + <tagNode name="domain"> + <properties> + <help>DNS domain to forward to a local server</help> + </properties> + <children> + <leafNode name="server"> + <properties> + <help>Domain Name Server (DNS) to forward queries</help> + <valueHelp> + <format>ipv4</format> + <description>Domain Name Server (DNS) IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Domain Name Server (DNS) IPv6 address</description> + </valueHelp> + <multi/> + <type>ipv4,ipv6</type> + </properties> + </leafNode> + </children> + </tagNode> + <leafNode name="ignore-hosts-file"> + <properties> + <help>Do not use local /etc/hosts file in name resolution</help> + </properties> + </leafNode> + <leafNode name="listen-on"> + <properties> + <help>Interface to listen for DNS queries [REQUIRED]</help> + <completionHelp> + <script>${vyatta_sbindir}/vyatta-interfaces.pl --show all</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="name-server"> + <properties> + <help>Domain Name Server (DNS)</help> + <valueHelp> + <format>ipv4</format> + <description>Domain Name Server (DNS) IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Domain Name Server (DNS) IPv6 address</description> + </valueHelp> + <multi/> + <type>ipv4,ipv6</type> + </properties> + </leafNode> + <leafNode name="system"> + <properties> + <help>DNS forwarding to system nameservers</help> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> 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..7a8aed75d --- /dev/null +++ b/src/conf-mode/vyos-config-dns-forwarding.py @@ -0,0 +1,197 @@ +#!/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 <http://www.gnu.org/licenses/>. +# +# + +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) |