summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--debian/changelog7
-rw-r--r--interface-definitions/dns-forwarding.xml97
-rwxr-xr-xsrc/conf-mode/vyos-config-dns-forwarding.py197
4 files changed, 306 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index f5800da43..55f5cb876 100644
--- a/Makefile
+++ b/Makefile
@@ -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)