summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xsrc/conf-mode/vyos-config-dns-forwarding.py210
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__':