From 62461c73fd6e6616a48d6319d461d89239e4ee2f Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Wed, 10 Jun 2020 12:58:47 +0200 Subject: dns forwarding: T1595: remove references to old listen-on option As part of T1595 listen-on was removed and migrated to listen-address, but some references to it stayed in the variable names and validator error message. --- data/templates/dns-forwarding/recursor.conf.tmpl | 4 ++-- src/conf_mode/dns_forwarding.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index 9d1e019fa..9aea00de5 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -22,8 +22,8 @@ max-negative-ttl={{ negative_ttl }} # ignore-hosts-file export-etc-hosts={{ export_hosts_file }} -# listen-on -local-address={{ listen_on | join(',') }} +# listen-address +local-address={{ listen_address | join(',') }} # dnssec dnssec={{ dnssec }} diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 692ac2456..efe169370 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -40,7 +40,7 @@ default_config_data = { 'allow_from': [], 'cache_size': 10000, 'export_hosts_file': 'yes', - 'listen_on': [], + 'listen_address': [], 'name_servers': [], 'negative_ttl': 3600, 'domains': [], @@ -103,7 +103,7 @@ def get_config(arguments): dns['name_servers'] = bracketize_ipv6_addrs(dns['name_servers']) if conf.exists(['listen-address']): - dns['listen_on'] = conf.return_values(['listen-address']) + dns['listen_address'] = conf.return_values(['listen-address']) if conf.exists(['dnssec']): dns['dnssec'] = conf.return_value(['dnssec']) @@ -134,9 +134,9 @@ def verify(dns): if dns is None: return None - if not dns['listen_on']: + if not dns['listen_address']: raise ConfigError( - "Error: DNS forwarding requires either a listen-address (preferred) or a listen-on option") + "Error: DNS forwarding requires a listen-address") if not dns['allow_from']: raise ConfigError( -- cgit v1.2.3 From 8797a010fcad067fbe604ed3e75c0605e9b5d1f7 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 07:58:49 +0200 Subject: vyos-hostsd: T2583: partial rewrite - update copyright date - validate incoming JSON data against a schema with voluptuous - add usage help describing internal messages syntax at top of vyos-hostsd - move socket and state file to directory /run/vyos-hostsd - replace jinja2 rendering with vyos.template - move all templates out of the executable into dedicated data/templates dirs - move recursor.conf forward-zones-recurse to forward-zones-file - generate lua-config-file for pdns-recursor with addNTA - support adding custom forward zones for pdns-recursor with optional added NTA and/or recursion-desired - move search_domains from set_host_name to separate add/delete/get commands - unify functions to support abstracting them in the future - track number of internal changes in "changes" variable saved in state file (informational in apply function) - do not apply changes immediately, add apply function that applies all changes (to not reload pdns-recursor excessively for a large set of changes, users must call the apply function once at the end) - add pdns_rec_control function that supports sending arbitrary commands to rec_control (fix pdns-recursor process name that caused the old function to think pdns-recursor was never running) - create /run/powerdns if it doesn't exist (on boot vyos-hostsd starts before pdns-recursor but we need to put our generated conf files there) - abstract specific command functions (add_*/del_*) into general functions to manipulate various types of data in the state variable - add command types: - forward_zones (generate custom forward zones for pdns-recursor) - search_domains (move from set_host_name as dhcp client needs to change them too) - name_server_tags_recursor (to set tags whose nameservers are added to pdns-recursor) - name_server_tags_system (to set tags whose nameservers and search domains are added to /etc/resolv.conf) - change hosts data format to make more sense (move tag from within each host dict to the key for a list of host dicts) - do not remove state file when shut down cleanly, to not lose state when restarting vyos-hostsd service that's then impossible to restore without restarting the whole router - a reboot will remove the state file as it lives in a tmpfs (/run) - remove too verbose info log on every received message - set mode of socket to 770 to secure it against processes not in hostsd group --- src/services/vyos-hostsd | 703 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 500 insertions(+), 203 deletions(-) diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index bf5d67cfa..0079f7e5c 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2020 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 @@ -14,7 +14,201 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +######### +# USAGE # +######### +# This daemon listens on its socket for JSON messages. +# The received message format is: # +# { 'type': '', +# 'op': '', +# 'data': +# } +# +# For supported message types, see below. +# 'op' can be 'add', delete', 'get', 'set' or 'apply'. +# Different message types support different sets of operations and different +# data formats. +# +# Changes to configuration made via add or delete don't take effect immediately, +# they are remembered in a state variable and saved to disk to a state file. +# State is remembered across daemon restarts but not across system reboots +# as it's saved in a temporary filesystem (/run). +# +# 'apply' is a special operation that applies the configuration from the cached +# state, rendering all config files and reloading relevant daemons (currently +# just pdns-recursor via rec-control). +# +# note: 'add' operation also acts as 'update' as it uses dict.update, if the +# 'data' dict item value is a dict. If it is a list, it uses list.append. +# +### tags +# Tags can be arbitrary, but they are generally in this format: +# 'static', 'system', 'dhcp(v6)-' or 'dhcp-server-' +# They are used to distinguish entries created by different scripts so they can +# be removed and recreated without having to track what needs to be changed. +# They are also used as a way to control which tags settings (e.g. nameservers) +# get added to various config files via name_server_tags_(recursor|system) +# +### name_server_tags_(recursor|system) +# A list of tags whose nameservers and search domains is used to generate +# /etc/resolv.conf and pdns-recursor config. +# system list is used to generate resolv.conf. +# recursor list is used to generate pdns-rec forward-zones. +# When generating each file, the order of nameservers is as per the order of +# name_server_tags (the order in which tags were added), then the order in +# which the name servers for each tag were added. +# +#### Message types +# +### name_servers +# +# { 'type': 'name_servers', +# 'op': 'add', +# 'data': { +# '': ['', ...], +# ... +# } +# } +# +# { 'type': 'name_servers', +# 'op': 'delete', +# 'data': ['', ...] +# } +# +# { 'type': 'name_servers', +# 'op': 'get', +# 'tag_regex': '' +# } +# response: +# { 'data': { +# '': ['', ...], +# ... +# } +# } +# +### name_server_tags +# +# { 'type': 'name_server_tags', +# 'op': 'add', +# 'data': ['', ...] +# } +# +# { 'type': 'name_server_tags', +# 'op': 'delete', +# 'data': ['', ...] +# } +# +# { 'type': 'name_server_tags', +# 'op': 'get', +# } +# response: +# { 'data': ['', ...] } +# +### forward_zones +## Additional zones added to pdns-recursor forward-zones-file. +## If recursion-desired is true, '+' will be prepended to the zone line. +## If addNTA is true, a NTA will be added via lua-config-file. +# +# { 'type': 'forward_zones', +# 'op': 'add', +# 'data': { +# '': { +# 'nslist': ['', ...], +# 'addNTA': , +# 'recursion-desired': +# } +# ... +# } +# } +# +# { 'type': 'forward_zones', +# 'op': 'delete', +# 'data': ['', ...] +# } +# +# { 'type': 'forward_zones', +# 'op': 'get', +# } +# response: +# { 'data': { +# '': { ... }, +# ... +# } +# } +# +# +### search_domains +# +# { 'type': 'search_domains', +# 'op': 'add', +# 'data': { +# '': ['', ...], +# ... +# } +# } +# +# { 'type': 'search_domains', +# 'op': 'delete', +# 'data': ['', ...] +# } +# +# { 'type': 'search_domains', +# 'op': 'get', +# } +# response: +# { 'data': { +# '': ['', ...], +# ... +# } +# } +# +### hosts +# +# { 'type': 'hosts', +# 'op': 'add', +# 'data': { +# '': { +# '': { +# 'address': '', +# 'aliases': [', ...] +# }, +# ... +# }, +# ... +# } +# } +# +# { 'type': 'hosts', +# 'op': 'delete', +# 'data': ['', ...] +# } +# +# { 'type': 'hosts', +# 'op': 'get' +# 'tag_regex': '' +# } +# response: +# { 'data': { +# '': { +# '': { +# 'address': '', +# 'aliases': [', ...] +# }, +# ... +# }, +# ... +# } +# } +### host_name +# +# { 'type': 'host_name', +# 'op': 'set', +# 'data': { +# 'host_name': '' +# 'domain_name': '' +# } +# } import os import sys @@ -25,10 +219,10 @@ import traceback import re import logging import zmq -import collections - -import jinja2 -from vyos.util import popen, process_named_running +from voluptuous import Schema, MultipleInvalid, Required, Any +from collections import OrderedDict +from vyos.util import popen, chown, chmod_755, makedir, process_named_running +from vyos.template import render debug = True @@ -43,163 +237,271 @@ if debug: else: logger.setLevel(logging.INFO) - -DATA_DIR = "/var/lib/vyos/" -STATE_FILE = os.path.join(DATA_DIR, "hostsd.state") - -SOCKET_PATH = "ipc:///run/vyos-hostsd.sock" +RUN_DIR = "/run/vyos-hostsd" +STATE_FILE = os.path.join(RUN_DIR, "vyos-hostsd.state") +SOCKET_PATH = "ipc://" + os.path.join(RUN_DIR, 'vyos-hostsd.sock') RESOLV_CONF_FILE = '/etc/resolv.conf' HOSTS_FILE = '/etc/hosts' -hosts_tmpl_source = """ -### Autogenerated by VyOS ### -### Do not edit, your changes will get overwritten ### - -# Local host -127.0.0.1 localhost -127.0.1.1 {{ host_name }}{% if domain_name %}.{{ domain_name }} {{ host_name }}{% endif %} - -# The following lines are desirable for IPv6 capable hosts -::1 localhost ip6-localhost ip6-loopback -fe00::0 ip6-localnet -ff00::0 ip6-mcastprefix -ff02::1 ip6-allnodes -ff02::2 ip6-allrouters - -# From DHCP and "system static host-mapping" -{%- if hosts %} -{% for h in hosts -%} -{{hosts[h]['address']}}\t{{h}}\t{% for a in hosts[h]['aliases'] %} {{a}} {% endfor %} -{% endfor %} -{%- endif %} -""" - -hosts_tmpl = jinja2.Template(hosts_tmpl_source) - -resolv_tmpl_source = """ -### Autogenerated by VyOS ### -### Do not edit, your changes will get overwritten ### - -# name server from static configuration -{% for ns in name_servers -%} -{%- if name_servers[ns]['tag'] == "static" %} -nameserver {{ns}} -{%- endif %} -{% endfor -%} - -{% for ns in name_servers -%} -{%- if name_servers[ns]['tag'] != "static" %} -# name server from {{name_servers[ns]['tag']}} -nameserver {{ns}} -{%- endif %} -{% endfor -%} - -{%- if domain_name %} -domain {{ domain_name }} -{%- endif %} - -{%- if search_domains %} -search {{ search_domains | join(" ") }} -{%- endif %} - -""" - -resolv_tmpl = jinja2.Template(resolv_tmpl_source) - -# The state data includes a list of name servers -# and a list of hosts entries. -# -# Name servers have the following structure: -# {"server": {"tag": }} -# -# Hosts entries are similar: -# {"host": {"tag": , "address": , "aliases": }} -# -# The tag is either "static" or "dhcp-" -# It's used to distinguish entries created -# by different scripts so that they can be removed -# and re-created without having to track what needs -# to be changed +PDNS_REC_USER = PDNS_REC_GROUP = 'pdns' +PDNS_REC_RUN_DIR = '/run/powerdns' +PDNS_REC_LUA_CONF_FILE = f'{PDNS_REC_RUN_DIR}/recursor.vyos-hostsd.conf.lua' +PDNS_REC_ZONES_FILE = f'{PDNS_REC_RUN_DIR}/recursor.forward-zones.conf' + STATE = { - "name_servers": collections.OrderedDict({}), + "name_servers": {}, + "name_server_tags_recursor": [], + "name_server_tags_system": [], + "forward_zones": {}, "hosts": {}, "host_name": "vyos", "domain_name": "", - "search_domains": []} + "search_domains": {}, + "changes": 0 + } + +# the base schema that every received message must be in +base_schema = Schema({ + Required('op'): Any('add', 'delete', 'set', 'get', 'apply'), + 'type': Any('name_servers', + 'name_server_tags_recursor', 'name_server_tags_system', + 'forward_zones', 'search_domains', 'hosts', 'host_name'), + 'data': Any(list, dict), + 'tag': str, + 'tag_regex': str + }) + +# more specific schemas +op_schema = Schema({ + 'op': str, + }, required=True) + +op_type_schema = op_schema.extend({ + 'type': str, + }, required=True) + +host_name_add_schema = op_type_schema.extend({ + 'data': { + 'host_name': str, + 'domain_name': Any(str, None) + } + }, required=True) + +data_dict_list_schema = op_type_schema.extend({ + 'data': { + str: [str] + } + }, required=True) + +data_list_schema = op_type_schema.extend({ + 'data': [str] + }, required=True) + +tag_regex_schema = op_type_schema.extend({ + 'tag_regex': str + }, required=True) + +forward_zone_add_schema = op_type_schema.extend({ + 'data': { + str: { + 'nslist': [str], + 'addNTA': bool, + 'recursion-desired': bool + } + } + }, required=True) + +hosts_add_schema = op_type_schema.extend({ + 'data': { + str: { + str: { + 'address': str, + 'aliases': [str] + } + } + } + }, required=True) + + +# op and type to schema mapping +msg_schema_map = { + 'name_servers': { + 'add': data_dict_list_schema, + 'delete': data_list_schema, + 'get': tag_regex_schema + }, + 'name_server_tags_recursor': { + 'add': data_list_schema, + 'delete': data_list_schema, + 'get': op_type_schema + }, + 'name_server_tags_system': { + 'add': data_list_schema, + 'delete': data_list_schema, + 'get': op_type_schema + }, + 'forward_zones': { + 'add': forward_zone_add_schema, + 'delete': data_list_schema, + 'get': op_type_schema + }, + 'search_domains': { + 'add': data_dict_list_schema, + 'delete': data_list_schema, + 'get': tag_regex_schema + }, + 'hosts': { + 'add': hosts_add_schema, + 'delete': data_list_schema, + 'get': tag_regex_schema + }, + 'host_name': { + 'set': host_name_add_schema + }, + None: { + 'apply': op_schema + } + } + +def validate_schema(data): + base_schema(data) + + try: + schema = msg_schema_map[data['type'] if 'type' in data else None][data['op']] + schema(data) + except KeyError: + raise ValueError(( + 'Invalid or unknown combination: ' + f'op: "{data["op"]}", type: "{data["type"]}"')) + + +def pdns_rec_control(command): + # pdns-r process name is NOT equal to the name shown in ps + if not process_named_running('pdns-r/worker'): + logger.info(f'pdns_recursor not running, not sending "{command}"') + return + logger.info(f'Running "rec_control {command}"') + (ret,ret_code) = popen(( + f"rec_control --socket-dir={PDNS_REC_RUN_DIR} {command}")) + if ret_code > 0: + logger.exception(( + f'"rec_control {command}" failed with exit status {ret_code}, ' + f'output: "{ret}"')) -def make_resolv_conf(data): - resolv_conf = resolv_tmpl.render(data) - logger.info("Writing /etc/resolv.conf") - with open(RESOLV_CONF_FILE, 'w') as f: - f.write(resolv_conf) +def make_resolv_conf(state): + logger.info(f"Writing {RESOLV_CONF_FILE}") + render(RESOLV_CONF_FILE, 'vyos-hostsd/resolv.conf.tmpl', state, + user='root', group='root') -def make_hosts_file(state): - logger.info("Writing /etc/hosts") - hosts = hosts_tmpl.render(state) - with open(HOSTS_FILE, 'w') as f: - f.write(hosts) +def make_hosts(state): + logger.info(f"Writing {HOSTS_FILE}") + render(HOSTS_FILE, 'vyos-hostsd/hosts.tmpl', state, + user='root', group='root') -def add_hosts(data, entries, tag): - hosts = data['hosts'] +def make_pdns_rec_conf(state): + logger.info(f"Writing {PDNS_REC_LUA_CONF_FILE}") - if not entries: - return + # on boot, /run/powerdns does not exist, so create it + makedir(PDNS_REC_RUN_DIR, user=PDNS_REC_USER, group=PDNS_REC_GROUP) + chmod_755(PDNS_REC_RUN_DIR) - for e in entries: - host = e['host'] - hosts[host] = {} - hosts[host]['tag'] = tag - hosts[host]['address'] = e['address'] - hosts[host]['aliases'] = e['aliases'] + render(PDNS_REC_LUA_CONF_FILE, + 'dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl', + state, user=PDNS_REC_USER, group=PDNS_REC_GROUP) -def delete_hosts(data, tag): - hosts = data['hosts'] - keys_for_deletion = [] + logger.info(f"Writing {PDNS_REC_ZONES_FILE}") + render(PDNS_REC_ZONES_FILE, + 'dns-forwarding/recursor.forward-zones.conf.tmpl', + state, user=PDNS_REC_USER, group=PDNS_REC_GROUP) - # You can't delete items from a dict while iterating over it, - # so we build a list of doomed items first - for h in hosts: - if hosts[h]['tag'] == tag: - keys_for_deletion.append(h) +def set_host_name(state, data): + if data['host_name']: + state['host_name'] = data['host_name'] + if 'domain_name' in data: + state['domain_name'] = data['domain_name'] + +def add_items_to_dict(_dict, items): + """ + Dedupes and preserves sort order. + """ + assert isinstance(_dict, dict) + assert isinstance(items, dict) + + if not items: + return - for k in keys_for_deletion: - del hosts[k] + _dict.update(items) -def add_name_servers(data, entries, tag): - name_servers = data['name_servers'] +def add_items_to_dict_as_keys(_dict, items): + """ + Added item values are converted to OrderedDict with the value as keys + and null values. This is to emulate a list but with inherent deduplication. + Dedupes and preserves sort order. + """ + assert isinstance(_dict, dict) + assert isinstance(items, dict) - if not entries: + if not items: return - for e in entries: - name_servers[e] = {} - name_servers[e]['tag'] = tag + for item, item_val in items.items(): + if item not in _dict: + _dict[item] = OrderedDict({}) + _dict[item].update(OrderedDict.fromkeys(item_val)) -def delete_name_servers(data, tag): - name_servers = data['name_servers'] - regex_filter = re.compile(tag) - for ns in list(name_servers.keys()): - if regex_filter.match(name_servers[ns]['tag']): - del name_servers[ns] +def add_items_to_list(_list, items): + """ + Dedupes and preserves sort order. + """ + assert isinstance(_list, list) + assert isinstance(items, list) -def set_host_name(state, data): - if data['host_name']: - state['host_name'] = data['host_name'] - if 'domain_name' in data: - state['domain_name'] = data['domain_name'] - if 'search_domains' in data: - state['search_domains'] = data['search_domains'] - -def get_name_servers(state, tag): - ns = [] - data = state['name_servers'] - regex_filter = re.compile(tag) - for n in data: - if regex_filter.match(data[n]['tag']): - ns.append(n) - return ns + if not items: + return + + for item in items: + if item not in _list: + _list.append(item) + +def delete_items_from_dict(_dict, items): + """ + items is a list of keys to delete. + Doesn't error if the key doesn't exist. + """ + assert isinstance(_dict, dict) + assert isinstance(items, list) + + for item in items: + if item in _dict: + del _dict[item] + +def delete_items_from_list(_list, items): + """ + items is a list of items to remove. + Doesn't error if the key doesn't exist. + """ + assert isinstance(_list, list) + assert isinstance(items, list) + + for item in items: + if item in _list: + _list.remove(item) + +def get_items_from_dict_regex(_dict, item_regex_string): + """ + Returns the items whose keys match item_regex_string. + """ + assert isinstance(_dict, dict) + assert isinstance(item_regex_string, str) + + tmp = {} + regex = re.compile(item_regex_string) + for item in _dict: + if regex.match(item): + tmp[item] = _dict[item] + return tmp def get_option(msg, key): if key in msg: @@ -207,85 +509,77 @@ def get_option(msg, key): else: raise ValueError("Missing required option \"{0}\"".format(key)) -def handle_message(msg_json): - msg = json.loads(msg_json) - +def handle_message(msg): + result = None op = get_option(msg, 'op') - _type = get_option(msg, 'type') - changes = 0 + if op in ['add', 'delete', 'set']: + STATE['changes'] += 1 if op == 'delete': - tag = get_option(msg, 'tag') - - if _type == 'name_servers': - delete_name_servers(STATE, tag) - changes += 1 - elif _type == 'hosts': - delete_hosts(STATE, tag) - changes += 1 + _type = get_option(msg, 'type') + data = get_option(msg, 'data') + if _type in ['name_servers', 'forward_zones', 'search_domains', 'hosts']: + delete_items_from_dict(STATE[_type], data) + elif _type in ['name_server_tags_recursor', 'name_server_tags_system']: + delete_items_from_list(STATE[_type], data) else: - raise ValueError("Unknown message type {0}".format(_type)) + raise ValueError(f'Operation "{op}" unknown data type "{_type}"') elif op == 'add': - tag = get_option(msg, 'tag') - entries = get_option(msg, 'data') - if _type == 'name_servers': - add_name_servers(STATE, entries, tag) - changes += 1 - elif _type == 'hosts': - add_hosts(STATE, entries, tag) - changes += 1 + _type = get_option(msg, 'type') + data = get_option(msg, 'data') + if _type in ['name_servers', 'search_domains']: + add_items_to_dict_as_keys(STATE[_type], data) + elif _type in ['forward_zones', 'hosts']: + add_items_to_dict(STATE[_type], data) + # maybe we need to rec_control clear-nta each domain that was removed here? + elif _type in ['name_server_tags_recursor', 'name_server_tags_system']: + add_items_to_list(STATE[_type], data) else: - raise ValueError("Unknown message type {0}".format(_type)) + raise ValueError(f'Operation "{op}" unknown data type "{_type}"') elif op == 'set': - # Host name/domain name/search domain are set without a tag, - # there can be only one anyway + _type = get_option(msg, 'type') data = get_option(msg, 'data') if _type == 'host_name': set_host_name(STATE, data) - changes += 1 else: - raise ValueError("Unknown message type {0}".format(_type)) + raise ValueError(f'Operation "{op}" unknown data type "{_type}"') elif op == 'get': - tag = get_option(msg, 'tag') - if _type == 'name_servers': - result = get_name_servers(STATE, tag) + _type = get_option(msg, 'type') + if _type in ['name_servers', 'search_domains', 'hosts']: + tag_regex = get_option(msg, 'tag_regex') + result = get_items_from_dict_regex(STATE[_type], tag_regex) + elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'forward_zones']: + result = STATE[_type] else: - raise ValueError("Unimplemented") - return result - else: - raise ValueError("Unknown operation {0}".format(op)) + raise ValueError(f'Operation "{op}" unknown data type "{_type}"') + elif op == 'apply': + logger.info(f"Applying {STATE['changes']} changes") + make_resolv_conf(STATE) + make_hosts(STATE) + make_pdns_rec_conf(STATE) + pdns_rec_control('reload-lua-config') + pdns_rec_control('reload-zones') + logger.info("Success") + result = {'message': f'Applied {STATE["changes"]} changes'} + STATE['changes'] = 0 - make_resolv_conf(STATE) - make_hosts_file(STATE) + else: + raise ValueError(f"Unknown operation {op}") - logger.info("Saving state to {0}".format(STATE_FILE)) + logger.debug(f"Saving state to {STATE_FILE}") with open(STATE_FILE, 'w') as f: json.dump(STATE, f) - if changes > 0: - if process_named_running("pdns_recursor"): - (ret,return_code) = popen("sudo rec_control --socket-dir=/run/powerdns reload-zones") - if return_code > 0: - logger.exception("PowerDNS rec_control failed to reload") - -def exit_handler(sig, frame): - """ Clean up the state when shutdown correctly """ - logger.info("Cleaning up state") - os.unlink(STATE_FILE) - sys.exit(0) - + return result if __name__ == '__main__': - signal.signal(signal.SIGTERM, exit_handler) - # Create a directory for state checkpoints - os.makedirs(DATA_DIR, exist_ok=True) + os.makedirs(RUN_DIR, exist_ok=True) if os.path.exists(STATE_FILE): with open(STATE_FILE, 'r') as f: try: - data = json.load(f) - STATE = data + STATE = json.load(f) except: logger.exception(traceback.format_exc()) logger.exception("Failed to load the state file, using default") @@ -294,28 +588,31 @@ if __name__ == '__main__': socket = context.socket(zmq.REP) # Set the right permissions on the socket, then change it back - o_mask = os.umask(0) + o_mask = os.umask(0o007) socket.bind(SOCKET_PATH) os.umask(o_mask) while True: # Wait for next request from client - message = socket.recv().decode() - logger.info("Received a configuration change request") - logger.debug("Request data: {0}".format(message)) - - resp = {} + msg_json = socket.recv().decode() + logger.debug(f"Request data: {msg_json}") try: - result = handle_message(message) - resp['data'] = result + msg = json.loads(msg_json) + validate_schema(msg) + + resp = {} + resp['data'] = handle_message(msg) except ValueError as e: resp['error'] = str(e) + except MultipleInvalid as e: + # raised by schema + resp['error'] = f'Invalid message: {str(e)}' + logger.exception(resp['error']) except: logger.exception(traceback.format_exc()) resp['error'] = "Internal error" - logger.debug("Sent response: {0}".format(resp)) - # Send reply back to client socket.send(json.dumps(resp).encode()) + logger.debug(f"Sent response: {resp}") -- cgit v1.2.3 From dbdd44c8669776efc33131c852a74f457c590aab Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:02:21 +0200 Subject: vyos-hostsd: T2583: add templates Move templates out of the daemon and add new templates. --- .../recursor.forward-zones.conf.tmpl | 28 ++++++++++++++++++++++ .../recursor.vyos-hostsd.conf.lua.tmpl | 24 +++++++++++++++++++ data/templates/vyos-hostsd/hosts.tmpl | 26 ++++++++++++++++++++ data/templates/vyos-hostsd/resolv.conf.tmpl | 26 ++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl create mode 100644 data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl create mode 100644 data/templates/vyos-hostsd/hosts.tmpl create mode 100644 data/templates/vyos-hostsd/resolv.conf.tmpl diff --git a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl new file mode 100644 index 000000000..de5eaee00 --- /dev/null +++ b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl @@ -0,0 +1,28 @@ +# Autogenerated by VyOS (vyos-hostsd) +# Do not edit, your changes will get overwritten + +# dot zone (catch-all): '+' indicates recursion is desired +# (same as forward-zones-recurse) +{#- the code below ensures the order of nameservers is determined first by #} +{#- the order of tags, then by the order of nameservers within that tag #} +{%- set n = namespace(dot_zone_ns='') %} +{%- for tag in name_server_tags_recursor %} +{%- set ns = '' %} +{%- if tag in name_servers %} +{%- set ns = ns + name_servers[tag]|join(', ') %} +{%- set n.dot_zone_ns = (n.dot_zone_ns, ns)|join(', ') if n.dot_zone_ns != '' else ns %} +{%- endif %} +# {{ tag }}: {{ ns }} +{%- endfor %} + +{%- if n.dot_zone_ns %} ++.={{ n.dot_zone_ns }} +{%- endif %} + +{% if forward_zones -%} +# zones added via 'service dns forwarding domain' +{%- for zone, zonedata in forward_zones.items() %} +{% if zonedata['recursion-desired'] %}+{% endif %}{{ zone }}={{ zonedata['nslist']|join(', ') }} +{%- endfor %} +{%- endif %} + diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl new file mode 100644 index 000000000..6d1760199 --- /dev/null +++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl @@ -0,0 +1,24 @@ +-- Autogenerated by VyOS (vyos-hostsd) -- +-- Do not edit, your changes will get overwritten -- + +{% if hosts -%} +-- from 'system static-host-mapping' and DHCP server +{%- for tag, taghosts in hosts.items() %} +{%- for host, hostprops in taghosts.items() %} +addNTA("{{ host }}.", "{{ tag }}") +{%- for a in hostprops['aliases'] %} +addNTA("{{ a }}.", "{{ tag }} alias") +{%- endfor %} +{%- endfor %} +{%- endfor %} +{%- endif %} + +{% if forward_zones -%} +-- from 'service dns forwarding domain' +{%- for zone, zonedata in forward_zones.items() %} +{%- if zonedata['addNTA'] %} +addNTA("{{ zone }}.", "static") +{%- endif %} +{%- endfor %} +{%- endif %} + diff --git a/data/templates/vyos-hostsd/hosts.tmpl b/data/templates/vyos-hostsd/hosts.tmpl new file mode 100644 index 000000000..566f9a5dd --- /dev/null +++ b/data/templates/vyos-hostsd/hosts.tmpl @@ -0,0 +1,26 @@ +### Autogenerated by VyOS ### +### Do not edit, your changes will get overwritten ### + +# Local host +127.0.0.1 localhost +127.0.1.1 {{ host_name }}{% if domain_name %}.{{ domain_name }} {{ host_name }}{% endif %} + +# The following lines are desirable for IPv6 capable hosts +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters + +{% if hosts -%} +# From 'system static-host-mapping' and DHCP server +{%- for tag, taghosts in hosts.items() %} +# {{ tag }} +{%- for host, hostprops in taghosts.items() %} +{%- if hostprops['address'] %} +{{ hostprops['address'] }} {{ host }}{% for a in hostprops['aliases'] %} {{ a }}{% endfor %} +{%- endif %} +{%- endfor %} +{%- endfor %} +{%- endif %} + diff --git a/data/templates/vyos-hostsd/resolv.conf.tmpl b/data/templates/vyos-hostsd/resolv.conf.tmpl new file mode 100644 index 000000000..b920b2e5f --- /dev/null +++ b/data/templates/vyos-hostsd/resolv.conf.tmpl @@ -0,0 +1,26 @@ +### Autogenerated by VyOS ### +### Do not edit, your changes will get overwritten ### + +{#- the code below ensures the order of nameservers is determined first by #} +{# the order of tags, then by the order of nameservers within that tag #} + +{%- for tag in name_server_tags_system %} +{%- if tag in name_servers %} +# {{ tag }} +{%- for ns in name_servers[tag] %} +nameserver {{ ns }} +{%- endfor %} +{%- endif %} +{%- endfor %} + +{%- if domain_name %} +domain {{ domain_name }} +{%- endif %} + +{% for tag in name_server_tags_system %} +{%- if tag in search_domains %} +# {{ tag }} +search {{ search_domains[tag]|join(' ') }} +{%- endif %} +{%- endfor %} + -- cgit v1.2.3 From de8f039cf3a527c87ec595fc94a691b0159cd79a Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:04:29 +0200 Subject: vyos-hostsd: T2583: update systemd service - set RuntimeDirectory to vyos-hostsd - set RuntimeDirectoryPreserve in order to not delete the state file between service restarts (/run will be deleted across reboots as it's on a tmpfs but the state doesn't need to be saved across reboots anyway) - set WorkingDirectory to /run/vyos-hostsd --- src/systemd/vyos-hostsd.service | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index 731e570c9..db8f630d4 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -10,6 +10,9 @@ DefaultDependencies=no After=systemd-remount-fs.service [Service] +WorkingDirectory=/run/vyos-hostsd +RuntimeDirectory=vyos-hostsd +RuntimeDirectoryPreserve=yes ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-hostsd Type=idle KillMode=process -- cgit v1.2.3 From 96ed330e9691f9db79c837505802ae5055f86348 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:06:14 +0200 Subject: vyos-hostsd: T2583: add hostsd group To better control access from other daemons that may not be running as root, create a new group 'hostsd' to which the other daemons running users can be added. Run vyos-hostsd as root:hostsd to create the socket file with correct user and group. --- debian/vyos-1x.postinst | 5 +++++ src/systemd/vyos-hostsd.service | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index a308401ee..672f90dff 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -19,3 +19,8 @@ if ! grep -q '^minion' /etc/passwd; then adduser --quiet minion disk adduser --quiet minion users fi + +# add hostsd group for vyos-hostsd +if ! grep -q '^hostsd' /etc/group; then + addgroup --quiet --system hostsd +fi diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index db8f630d4..b77335778 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -24,7 +24,7 @@ Restart=on-failure # Does't work in Jessie but leave it here User=root -Group=vyattacfg +Group=hostsd [Install] -- cgit v1.2.3 From 3bc07c4fc8bdc865a767aa927bfe9487710ee4d4 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:10:35 +0200 Subject: dhcp(v6)-server: T2583: run as 'dhcpd' user Add a 'dhcpd' system user that is a member of hostsd group and can connect to vyos-hostsd. Run dhcpd as this user. --- debian/vyos-1x.postinst | 6 ++++++ src/systemd/isc-dhcp-server.service | 6 +++--- src/systemd/isc-dhcp-server6.service | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 672f90dff..dc129cb54 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -24,3 +24,9 @@ fi if ! grep -q '^hostsd' /etc/group; then addgroup --quiet --system hostsd fi + +# add dhcpd user for dhcp-server +if ! grep -q '^dhcpd' /etc/passwd; then + adduser --quiet --system --disabled-login --no-create-home --home /run/dhcp-server dhcpd + adduser --quiet dhcpd hostsd +fi diff --git a/src/systemd/isc-dhcp-server.service b/src/systemd/isc-dhcp-server.service index e13c66dc6..9aa70a7cc 100644 --- a/src/systemd/isc-dhcp-server.service +++ b/src/systemd/isc-dhcp-server.service @@ -14,10 +14,10 @@ Environment=PID_FILE=/run/dhcp-server/dhcpd.pid CONFIG_FILE=/run/dhcp-server/dhc PIDFile=/run/dhcp-server/dhcpd.pid ExecStartPre=/bin/sh -ec '\ touch ${LEASE_FILE}; \ -chown nobody:nogroup ${LEASE_FILE}* ; \ +chown dhcpd:nogroup ${LEASE_FILE}* ; \ chmod 664 ${LEASE_FILE}* ; \ -/usr/sbin/dhcpd -4 -t -T -q -user nobody -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} ' -ExecStart=/usr/sbin/dhcpd -4 -q -user nobody -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} +/usr/sbin/dhcpd -4 -t -T -q -user dhcpd -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} ' +ExecStart=/usr/sbin/dhcpd -4 -q -user dhcpd -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} Restart=always [Install] diff --git a/src/systemd/isc-dhcp-server6.service b/src/systemd/isc-dhcp-server6.service index 8ac861d7a..1345c5fc5 100644 --- a/src/systemd/isc-dhcp-server6.service +++ b/src/systemd/isc-dhcp-server6.service @@ -16,8 +16,8 @@ ExecStartPre=/bin/sh -ec '\ touch ${LEASE_FILE}; \ chown nobody:nogroup ${LEASE_FILE}* ; \ chmod 664 ${LEASE_FILE}* ; \ -/usr/sbin/dhcpd -6 -t -T -q -user nobody -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} ' -ExecStart=/usr/sbin/dhcpd -6 -q -user nobody -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} +/usr/sbin/dhcpd -6 -t -T -q -user dhcpd -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} ' +ExecStart=/usr/sbin/dhcpd -6 -q -user dhcpd -group nogroup -pf ${PID_FILE} -cf ${CONFIG_FILE} -lf ${LEASE_FILE} Restart=always [Install] -- cgit v1.2.3 From faa70f9fca2249f061a8890734cc4622f06395a3 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:13:20 +0200 Subject: dns forwarding: T2486: add lua-config-file The file will be generated by vyos-hostsd to add NTAs for zones (domains) from /etc/hosts and forward-zones. --- data/templates/dns-forwarding/recursor.conf.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index 9aea00de5..c3ffea159 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -12,6 +12,7 @@ log-common-errors=yes non-local-bind=yes query-local-address=0.0.0.0 query-local-address6=:: +lua-config-file=recursor.conf.lua # cache-size max-cache-entries={{ cache_size }} -- cgit v1.2.3 From 96595b42259e0e386e15f657857934fa20a55ca6 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:14:53 +0200 Subject: dns forwarding: T2486: add paths to files --- src/conf_mode/dns_forwarding.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index efe169370..26a651c3d 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -34,7 +34,11 @@ parser = argparse.ArgumentParser() parser.add_argument("--dhclient", action="store_true", help="Started from dhclient-script") -config_file = r'/run/powerdns/recursor.conf' +pdns_rec_run_dir = '/run/powerdns' +pdns_rec_lua_conf_file = f'{pdns_rec_run_dir}/recursor.conf.lua' +pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.lua' +pdns_rec_hostsd_zones_file = f'{pdns_rec_run_dir}/recursor.forward-zones.conf' +pdns_rec_config_file = f'{pdns_rec_run_dir}/recursor.conf' default_config_data = { 'allow_from': [], @@ -162,8 +166,8 @@ def apply(dns): if dns is None: # DNS forwarding is removed in the commit call("systemctl stop pdns-recursor.service") - if os.path.isfile(config_file): - os.unlink(config_file) + if os.path.isfile(pdns_rec_config_file): + os.unlink(pdns_rec_config_file) else: call("systemctl restart pdns-recursor.service") -- cgit v1.2.3 From 2b49fbf65aace72a9cd5629db48ae211e18a4e4a Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:21:21 +0200 Subject: dns forwarding: T2486: remove unneeded --dhclient argument The functionality was moved to vyos-hostsd. --- src/conf_mode/dns_forwarding.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 26a651c3d..188a39ef0 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -15,14 +15,12 @@ # along with this program. If not, see . import os -import argparse from sys import exit from copy import deepcopy from vyos.config import Config from vyos.hostsd_client import Client as hostsd_client -from vyos.util import wait_for_commit_lock from vyos import ConfigError from vyos.util import call from vyos.template import render @@ -30,10 +28,6 @@ from vyos.template import render from vyos import airbag airbag.enable() -parser = argparse.ArgumentParser() -parser.add_argument("--dhclient", action="store_true", - help="Started from dhclient-script") - pdns_rec_run_dir = '/run/powerdns' pdns_rec_lua_conf_file = f'{pdns_rec_run_dir}/recursor.conf.lua' pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.lua' @@ -52,16 +46,11 @@ default_config_data = { } -def get_config(arguments): +def get_config(): dns = deepcopy(default_config_data) conf = Config() base = ['service', 'dns', 'forwarding'] - if arguments.dhclient: - conf.exists = conf.exists_effective - conf.return_value = conf.return_effective_value - conf.return_values = conf.return_effective_values - if not conf.exists(base): return None @@ -172,15 +161,10 @@ def apply(dns): call("systemctl restart pdns-recursor.service") if __name__ == '__main__': - args = parser.parse_args() - if args.dhclient: - # There's a big chance it was triggered by a commit still in progress - # so we need to wait until the new values are in the running config - wait_for_commit_lock() try: - c = get_config(args) + c = get_config() verify(c) generate(c) apply(c) -- cgit v1.2.3 From 1eaf0077f2b42405d9bf8699dcf813c1b249d6fc Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:24:06 +0200 Subject: dns forwarding: T2486: move Config() call into main As Config is required in both get_config and verify, init it once and pass it to both functions. --- src/conf_mode/dns_forwarding.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 188a39ef0..aedfb2623 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -46,9 +46,8 @@ default_config_data = { } -def get_config(): +def get_config(conf): dns = deepcopy(default_config_data) - conf = Config() base = ['service', 'dns', 'forwarding'] if not conf.exists(base): @@ -122,7 +121,7 @@ def bracketize_ipv6_addrs(addrs): """Wraps each IPv6 addr in addrs in [], leaving IPv4 addrs untouched.""" return ['[{0}]'.format(a) if a.count(':') > 1 else a for a in addrs] -def verify(dns): +def verify(conf, dns): # bail out early - looks like removal from running config if dns is None: return None @@ -164,8 +163,9 @@ if __name__ == '__main__': try: - c = get_config() - verify(c) + conf = Config() + c = get_config(conf) + verify(conf, c) generate(c) apply(c) except ConfigError as e: -- cgit v1.2.3 From ba08187c069da93c943935ef60a78b506c035af0 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:32:33 +0200 Subject: dns forwarding: T2486: change internal representation of 'domain' config Change internal representation to the new one expected by vyos-hostsd. --- src/conf_mode/dns_forwarding.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index aedfb2623..36e2abb4a 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -41,7 +41,7 @@ default_config_data = { 'listen_address': [], 'name_servers': [], 'negative_ttl': 3600, - 'domains': [], + 'domains': {}, 'dnssec': 'process-no-validate' } @@ -67,13 +67,16 @@ def get_config(conf): dns['negative_ttl'] = negative_ttl if conf.exists(['domain']): - for node in conf.list_nodes(['domain']): - servers = conf.return_values(['domain', node, 'server']) - domain = { - "name": node, - "servers": bracketize_ipv6_addrs(servers) + for domain in conf.list_nodes(['domain']): + conf.set_level(base + ['domain', domain]) + entry = { + 'nslist': bracketize_ipv6_addrs(conf.return_values(['server'])), + 'addNTA': conf.exists(['addnta']), + 'recursion-desired': conf.exists(['recursion-desired']) } - dns['domains'].append(domain) + dns['domains'][domain] = entry + + conf.set_level(base) if conf.exists(['ignore-hosts-file']): dns['export_hosts_file'] = "no" @@ -136,9 +139,9 @@ def verify(conf, dns): 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 not dns['domains'][domain]['nslist']: + raise ConfigError(( + f'Error: No server configured for domain {domain}')) return None -- cgit v1.2.3 From 7f2007964991b95166f4ab1255a57f4652c554ec Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:33:46 +0200 Subject: dns forwarding: T2486: remove unnecessary intermediate name_servers variable --- src/conf_mode/dns_forwarding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 36e2abb4a..959c09bf8 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -82,8 +82,8 @@ def get_config(conf): dns['export_hosts_file'] = "no" if conf.exists(['name-server']): - name_servers = conf.return_values(['name-server']) - dns['name_servers'] = dns['name_servers'] + name_servers + dns['name_servers'] = bracketize_ipv6_addrs( + conf.return_values(['name-server'])) if conf.exists(['system']): conf.set_level(['system']) -- cgit v1.2.3 From e8da1c87f158898cdeb0ca3a16127701c7e8e28b Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:34:39 +0200 Subject: dns forwarding: T2486: change internal handling of 'system' config node Remove manual retrieval of 'system name-server' from config and adding it to the name servers list, as this is now handled by simply adding a 'system' tag in vyos-hostsd. --- src/conf_mode/dns_forwarding.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 959c09bf8..5ee8c53c2 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -41,6 +41,7 @@ default_config_data = { 'listen_address': [], 'name_servers': [], 'negative_ttl': 3600, + 'system': False, 'domains': {}, 'dnssec': 'process-no-validate' } @@ -86,16 +87,7 @@ def get_config(conf): conf.return_values(['name-server'])) if conf.exists(['system']): - conf.set_level(['system']) - 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['name_servers'] = dns['name_servers'] + system_name_servers - conf.set_level(base) - - dns['name_servers'] = bracketize_ipv6_addrs(dns['name_servers']) + dns['system'] = True if conf.exists(['listen-address']): dns['listen_address'] = conf.return_values(['listen-address']) -- cgit v1.2.3 From f02f9622307687282aeca2600953cf6394f09485 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:36:08 +0200 Subject: dns forwarding: T2486: change internal handling of 'dhcp' nameservers Remove the old solution that retrieved dhcp tagged nameservers from hostsd and added it to nameservers, as it didn't work anyway (only once during configuration but it didn't update them later). This is now handled by vyos-hostsd, just retrieve the configured interfaces and send it the list of tags to use. --- src/conf_mode/dns_forwarding.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 5ee8c53c2..556947733 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -43,7 +43,8 @@ default_config_data = { 'negative_ttl': 3600, 'system': False, 'domains': {}, - 'dnssec': 'process-no-validate' + 'dnssec': 'process-no-validate', + 'dhcp_interfaces': [] } @@ -95,20 +96,8 @@ def get_config(conf): if conf.exists(['dnssec']): dns['dnssec'] = conf.return_value(['dnssec']) - # Add name servers received from DHCP if conf.exists(['dhcp']): - interfaces = [] - interfaces = conf.return_values(['dhcp']) - hc = hostsd_client() - - for interface in interfaces: - dhcp_resolvers = hc.get_name_servers(f'dhcp-{interface}') - dhcpv6_resolvers = hc.get_name_servers(f'dhcpv6-{interface}') - - if dhcp_resolvers: - dns['name_servers'] = dns['name_servers'] + dhcp_resolvers - if dhcpv6_resolvers: - dns['name_servers'] = dns['name_servers'] + dhcpv6_resolvers + dns['dhcp_interfaces'] = conf.return_values(['dhcp']) return dns -- cgit v1.2.3 From d6d72d37606849607979d410db75d1803f18f159 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:37:15 +0200 Subject: dns forwarding: T2486: add warning for no dhcp, system or static nameservers Add warning that forwarding will operate as a recursor in case there are no nameservers configured. --- src/conf_mode/dns_forwarding.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 556947733..06bdab12b 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -124,6 +124,19 @@ def verify(conf, dns): raise ConfigError(( f'Error: No server configured for domain {domain}')) + no_system_nameservers = False + if dns['system'] and not ( + conf.exists(['system', 'name-server']) or + conf.exists(['system', 'name-servers-dhcp']) ): + no_system_nameservers = True + print(("DNS forwarding warning: No 'system name-server' or " + "'system name-servers-dhcp' set\n")) + + if (no_system_nameservers or not dns['system']) and not ( + dns['name_servers'] or dns['dhcp_interfaces']): + print(("DNS forwarding warning: No 'dhcp', 'name-server' or 'system' " + "nameservers set. Forwarding will operate as a recursor.\n")) + return None def generate(dns): -- cgit v1.2.3 From 8b3f96e58b865bcaf31d7eca80c7e2edaf9482f2 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:39:15 +0200 Subject: dns forwarding: T2486: generate recursor conf files - generate recursor.conf, recursor.conf.lua - if recursor.vyos-hostsd.conf.lua and recursor.forward-zones.conf don't exist, create empty ones (they are/will be generated by vyos-hostsd) --- src/conf_mode/dns_forwarding.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 06bdab12b..1d5ee84a4 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -22,12 +22,13 @@ from copy import deepcopy from vyos.config import Config from vyos.hostsd_client import Client as hostsd_client from vyos import ConfigError -from vyos.util import call +from vyos.util import call, chown from vyos.template import render from vyos import airbag airbag.enable() +pdns_rec_user = pdns_rec_group = 'pdns' pdns_rec_run_dir = '/run/powerdns' pdns_rec_lua_conf_file = f'{pdns_rec_run_dir}/recursor.conf.lua' pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.lua' @@ -144,7 +145,18 @@ def generate(dns): if dns is None: return None - render(config_file, 'dns-forwarding/recursor.conf.tmpl', dns, trim_blocks=True, user='pdns', group='pdns') + render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl', + dns, user=pdns_rec_user, group=pdns_rec_group) + + render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl', + dns, user=pdns_rec_user, group=pdns_rec_group) + + # if vyos-hostsd didn't create its files yet, create them (empty) + for f in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: + with open(f, 'a'): + pass + chown(f, user=pdns_rec_user, group=pdns_rec_group) + return None def apply(dns): -- cgit v1.2.3 From 66165fa536eb75c046c2dc3f1efcf1f85b2b68ae Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:50:48 +0200 Subject: dns forwarding: T2486: configure vyos-hostsd Removes and adds all required settings. --- src/conf_mode/dns_forwarding.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 1d5ee84a4..51631dc16 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -48,6 +48,7 @@ default_config_data = { 'dhcp_interfaces': [] } +hostsd_tag = 'static' def get_config(conf): dns = deepcopy(default_config_data) @@ -166,11 +167,44 @@ def apply(dns): if os.path.isfile(pdns_rec_config_file): os.unlink(pdns_rec_config_file) else: - call("systemctl restart pdns-recursor.service") + ### first apply vyos-hostsd config + hc = hostsd_client() -if __name__ == '__main__': + # add static nameservers to hostsd so they can be joined with other + # sources + hc.delete_name_servers([hostsd_tag]) + if dns['name_servers']: + hc.add_name_servers({hostsd_tag: dns['name_servers']}) + + # delete all nameserver tags + hc.delete_name_server_tags_recursor(hc.get_name_server_tags_recursor()) + + ## add nameserver tags - the order determines the nameserver order! + # our own tag (static) + hc.add_name_server_tags_recursor([hostsd_tag]) + + if dns['system']: + hc.add_name_server_tags_recursor(['system']) + else: + hc.delete_name_server_tags_recursor(['system']) + # add dhcp nameserver tags for configured interfaces + for intf in dns['dhcp_interfaces']: + hc.add_name_server_tags_recursor(['dhcp-' + intf, 'dhcpv6-' + intf ]) + # hostsd will generate the forward-zones file + # the list and keys() are required as get returns a dict, not list + hc.delete_forward_zones(list(hc.get_forward_zones().keys())) + if dns['domains']: + hc.add_forward_zones(dns['domains']) + + # call hostsd to generate forward-zones and its lua-config-file + hc.apply() + + ### finally (re)start pdns-recursor + call("systemctl restart pdns-recursor.service") + +if __name__ == '__main__': try: conf = Config() c = get_config(conf) -- cgit v1.2.3 From b3af8156e774198b5ca14bb6b21084a95ef1a256 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:53:57 +0200 Subject: host_name: T2486: change internal 'static-host-mapping' representation Change internal representation to the one required by vyos-hostsd. --- src/conf_mode/host_name.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index dbc587d7d..a4794f041 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -42,6 +42,7 @@ default_config_data = { 'domain_search': [], 'nameserver': [], 'no_dhcp_ns': False + 'static_host_mapping': {} } def get_config(): @@ -69,15 +70,10 @@ def get_config(): hosts['no_dhcp_ns'] = True # system static-host-mapping - hosts['static_host_mapping'] = [] - - if conf.exists('system static-host-mapping host-name'): - for hn in conf.list_nodes('system static-host-mapping host-name'): - mapping = {} - mapping['host'] = hn - mapping['address'] = conf.return_value('system static-host-mapping host-name {0} inet'.format(hn)) - mapping['aliases'] = conf.return_values('system static-host-mapping host-name {0} alias'.format(hn)) - hosts['static_host_mapping'].append(mapping) + for hn in conf.list_nodes('system static-host-mapping host-name'): + hosts['static_host_mapping'][hn] = {} + hosts['static_host_mapping'][hn]['address'] = conf.return_value(f'system static-host-mapping host-name {hn} inet') + hosts['static_host_mapping'][hn]['aliases'] = conf.return_values(f'system static-host-mapping host-name {hn} alias') return hosts @@ -108,14 +104,22 @@ def verify(config): raise ConfigError( 'The search list is currently limited to 256 characters') + all_static_host_mapping_addresses = [] # static mappings alias hostname - if config['static_host_mapping']: - for m in config['static_host_mapping']: - if not m['address']: - raise ConfigError('IP address required for ' + m['host']) - for a in m['aliases']: - if not hostname_regex.match(a) and len(a) != 0: - raise ConfigError('Invalid alias \'{0}\' in mapping {1}'.format(a, m['host'])) + for host, hostprops in hosts['static_host_mapping'].items(): + if not hostprops['address']: + raise ConfigError(f'IP address required for static-host-mapping "{host}"') + if hostprops['address'] in all_static_host_mapping_addresses: + raise ConfigError(( + f'static-host-mapping "{host}" address "{hostprops["address"]}"' + f'already used in another static-host-mapping')) + all_static_host_mapping_addresses.append(hostprops['address']) + for a in hostprops['aliases']: + if not hostname_regex.match(a) and len(a) != 0: + raise ConfigError(f'Invalid alias "{a}" in static-host-mapping "{host}"') + + # TODO: add warnings for nameservers_dhcp_interfaces if interface doesn't + # exist or doesn't have address dhcp(v6) return None -- cgit v1.2.3 From 7343e0e184a15eb56dc0ae1ee9bbc8ef60871704 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:54:42 +0200 Subject: host_name: T2486: remove domain-search length limitations Debian Buster doesn't have the length and character limitations of /etc/resolv.conf 'search' any more, it is unlimited. https://sourceware.org/bugzilla/show_bug.cgi?id=19569 (glibc >2.26) --- src/conf_mode/host_name.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index a4794f041..4fe7b4cbd 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -78,32 +78,21 @@ def get_config(): return hosts -def verify(config): - if config is None: +def verify(conf, hosts): + if hosts is None: return None # pattern $VAR(@) "^[[:alnum:]][-.[:alnum:]]*[[:alnum:]]$" ; "invalid host name $VAR(@)" hostname_regex = re.compile("^[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$") - if not hostname_regex.match(config['hostname']): - raise ConfigError('Invalid host name ' + config["hostname"]) + if not hostname_regex.match(hosts['hostname']): + raise ConfigError('Invalid host name ' + hosts["hostname"]) # pattern $VAR(@) "^.{1,63}$" ; "invalid host-name length" - length = len(config['hostname']) + length = len(hosts['hostname']) if length < 1 or length > 63: raise ConfigError( 'Invalid host-name length, must be less than 63 characters') - # The search list is currently limited to six domains with a total of 256 characters. - # https://linux.die.net/man/5/resolv.conf - if len(config['domain_search']) > 6: - raise ConfigError( - 'The search list is currently limited to six domains') - - tmp = ' '.join(config['domain_search']) - if len(tmp) > 256: - raise ConfigError( - 'The search list is currently limited to 256 characters') - all_static_host_mapping_addresses = [] # static mappings alias hostname for host, hostprops in hosts['static_host_mapping'].items(): -- cgit v1.2.3 From 77081a6585fb64c1dba32c97eaaa54cf6a8741d9 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 08:59:04 +0200 Subject: host_name: T2486: replace disable-dhcp-nameservers with name-servers-dhcp The previous implementation only supported disabling DHCP nameservers for all interfaces, and was implemented improperly so it didn't work anyway. It's safe to remove it completely. This adds support for a new config node name-servers-dhcp , which allows us to enable just the interfaces we want to use for system DNS, identical in syntax to 'service dns forwarding dhcp '. The new option works by adding tags to vyos-hostsd that we want to use to add nameservers to resolv.conf, same as adding tags for dns forwarding but for a different destination file. A config migrator will be added in a separate commit. --- src/conf_mode/host_name.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 4fe7b4cbd..a71f9f88f 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -41,7 +41,7 @@ default_config_data = { 'domain_name': '', 'domain_search': [], 'nameserver': [], - 'no_dhcp_ns': False + 'nameservers_dhcp_interfaces': [], 'static_host_mapping': {} } @@ -66,8 +66,7 @@ def get_config(): if conf.exists("system name-server"): hosts['nameserver'] = conf.return_values("system name-server") - if conf.exists("system disable-dhcp-nameservers"): - hosts['no_dhcp_ns'] = True + hosts['nameservers_dhcp_interfaces'] = conf.return_values("system name-servers-dhcp") # system static-host-mapping for hn in conf.list_nodes('system static-host-mapping host-name'): -- cgit v1.2.3 From 71e088fbb42dbbb899dfe73989c306609a9b1135 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:02:35 +0200 Subject: host_name: T2486: move Config() call into main Init Config once in main() and pass it to both get_config() and verify(). --- src/conf_mode/host_name.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index a71f9f88f..4652050ab 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -45,8 +45,7 @@ default_config_data = { 'static_host_mapping': {} } -def get_config(): - conf = Config() +def get_config(conf): hosts = copy.deepcopy(default_config_data) if conf.exists("system host-name"): @@ -176,8 +175,9 @@ def apply(config): if __name__ == '__main__': try: - c = get_config() - verify(c) + conf = Config() + c = get_config(conf) + verify(conf, c) generate(c) apply(c) except ConfigError as e: -- cgit v1.2.3 From 49eab6aaf392d631e041a99239407f953c2d5e64 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:04:24 +0200 Subject: host_name: T2486: remove conf.exists calls The getter methods will return empty values if config nodes don't exist, so there's no point in checking if they exist before. --- src/conf_mode/host_name.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 4652050ab..6cefab2fd 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -48,12 +48,12 @@ default_config_data = { def get_config(conf): hosts = copy.deepcopy(default_config_data) - if conf.exists("system host-name"): - hosts['hostname'] = conf.return_value("system host-name") - # This may happen if the config is not loaded yet, - # e.g. if run by cloud-init - if not hosts['hostname']: - hosts['hostname'] = default_config_data['hostname'] + hosts['hostname'] = conf.return_value("system host-name") + + # This may happen if the config is not loaded yet, + # e.g. if run by cloud-init + if not hosts['hostname']: + hosts['hostname'] = default_config_data['hostname'] if conf.exists("system domain-name"): hosts['domain_name'] = conf.return_value("system domain-name") @@ -62,8 +62,7 @@ def get_config(conf): for search in conf.return_values("system domain-search domain"): hosts['domain_search'].append(search) - if conf.exists("system name-server"): - hosts['nameserver'] = conf.return_values("system name-server") + hosts['nameserver'] = conf.return_values("system name-server") hosts['nameservers_dhcp_interfaces'] = conf.return_values("system name-servers-dhcp") -- cgit v1.2.3 From 469884f4f4f7977085db54d1ce6ad6842252660e Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:06:02 +0200 Subject: host_name: T2486: configure vyos-hostsd Removes and adds all required settings. --- src/conf_mode/host_name.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 6cefab2fd..0ca7011f4 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -45,6 +45,8 @@ default_config_data = { 'static_host_mapping': {} } +hostsd_tag = 'system' + def get_config(conf): hosts = copy.deepcopy(default_config_data) @@ -118,24 +120,32 @@ def apply(config): return None ## Send the updated data to vyos-hostsd + try: + hc = vyos.hostsd_client.Client() - # vyos-hostsd uses "tags" to identify data sources - tag = "static" + hc.set_host_name(config['hostname'], config['domain_name']) - try: - client = vyos.hostsd_client.Client() + hc.delete_search_domains([hostsd_tag]) + if config['domain_search']: + hc.add_search_domains({hostsd_tag: config['domain_search']}) + + hc.delete_name_servers([hostsd_tag]) + if config['nameserver']: + hc.add_name_servers({hostsd_tag: config['nameserver']}) - # Check if disable-dhcp-nameservers is configured, and if yes - delete DNS servers added by DHCP - if config['no_dhcp_ns']: - client.delete_name_servers('dhcp-.+') + # add our own tag's (system) nameservers and search to resolv.conf + hc.delete_name_server_tags_system(hc.get_name_server_tags_system()) + hc.add_name_server_tags_system([hostsd_tag]) - client.set_host_name(config['hostname'], config['domain_name'], config['domain_search']) + # this will add the dhcp client nameservers to resolv.conf + for intf in config['nameservers_dhcp_interfaces']: + hc.add_name_server_tags_system([f'dhcp-{intf}', f'dhcpv6-{intf}']) - client.delete_name_servers(tag) - client.add_name_servers(tag, config['nameserver']) + hc.delete_hosts([hostsd_tag]) + if config['static_host_mapping']: + hc.add_hosts({hostsd_tag: config['static_host_mapping']}) - client.delete_hosts(tag) - client.add_hosts(tag, config['static_host_mapping']) + hc.apply() except vyos.hostsd_client.VyOSHostsdError as e: raise ConfigError(str(e)) -- cgit v1.2.3 From 7000385c68b5816867650781a5d8a87ce90f52aa Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:07:06 +0200 Subject: host_name: T2486: remove pdns-recursor restart It shouldn't be required, if necessary it should be added to vyos-hostsd apply command. --- src/conf_mode/host_name.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 0ca7011f4..3e301477d 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -167,18 +167,6 @@ def apply(config): if process_named_running('snmpd'): call('systemctl restart snmpd.service') - # restart pdns if it is used - we check for the control dir to not raise - # an exception on system startup - # - # File "/usr/lib/python3/dist-packages/vyos/configsession.py", line 128, in __run_command - # raise ConfigSessionError(output) - # vyos.configsession.ConfigSessionError: [ system domain-name vyos.io ] - # Fatal: Unable to generate local temporary file in directory '/run/powerdns': No such file or directory - if os.path.isdir('/run/powerdns'): - ret = run('/usr/bin/rec_control --socket-dir=/run/powerdns ping') - if ret == 0: - call('systemctl restart pdns-recursor.service') - return None -- cgit v1.2.3 From 0f8beae7426dfc1fdb75c993a505672d9dd2ec0f Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:09:42 +0200 Subject: system: T2486: migrate disable-dhcp-nameservers to name-servers-dhcp The previous implementation only supported disabling DHCP nameservers for all interfaces, and was implemented improperly so it didn't work anyway. This migrates it to name-servers-dhcp , which allows us to enable just the interfaces we want to use for system DNS, identical in syntax to 'service dns forwarding dhcp '. The migrator searches through all interfaces that have address 'dhcp(v6)?' and adds them to the name-servers-dhcp list if disable-dhcp-nameservers is not set, else it does nothing. --- interface-definitions/dns-domain-name.xml.in | 20 ++--- src/migration-scripts/system/17-to-18 | 105 +++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 9 deletions(-) create mode 100755 src/migration-scripts/system/17-to-18 diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in index eeaa5b2c1..3b5843b53 100644 --- a/interface-definitions/dns-domain-name.xml.in +++ b/interface-definitions/dns-domain-name.xml.in @@ -5,7 +5,7 @@ - Domain Name Server (DNS) + Domain Name Servers (DNS) used by the system (resolv.conf) 400 ipv4 @@ -22,6 +22,16 @@ + + + Interfaces whose DHCP client nameservers will be used by the system (resolv.conf) + 400 + + + + + + System host name (default: vyos) @@ -56,20 +66,12 @@ - - - Disable DHCP updates of DNS settings - 300 - - - Map host names to addresses 400 - Host name for static address mapping diff --git a/src/migration-scripts/system/17-to-18 b/src/migration-scripts/system/17-to-18 new file mode 100755 index 000000000..dd2abce00 --- /dev/null +++ b/src/migration-scripts/system/17-to-18 @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 . +# + +# migrate disable-dhcp-nameservers (boolean) to name-servers-dhcp +# if disable-dhcp-nameservers is set, just remove it +# else retrieve all interface names that have configured dhcp(v6) address and +# add them to the new name-servers-dhcp node + +from sys import argv, exit +from vyos.ifconfig import Interface +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +base = ['system'] +if not config.exists(base): + # Nothing to do + exit(0) + +if config.exists(base + ['disable-dhcp-nameservers']): + config.delete(base + ['disable-dhcp-nameservers']) +else: + dhcp_interfaces = [] + + # go through all interfaces searching for 'address dhcp(v6)?' + for sect in Interface.sections(): + sect_base = ['interfaces', sect] + + if not config.exists(sect_base): + continue + + for intf in config.list_nodes(sect_base): + intf_base = sect_base + [intf] + + # try without vlans + if config.exists(intf_base + ['address']): + for addr in config.return_values(intf_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(intf) + + # try vif + if config.exists(intf_base + ['vif']): + for vif in config.list_nodes(intf_base + ['vif']): + vif_base = intf_base + ['vif', vif] + if config.exists(vif_base + ['address']): + for addr in config.return_values(vif_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif}') + + # try vif-s + if config.exists(intf_base + ['vif-s']): + for vif_s in config.list_nodes(intf_base + ['vif-s']): + vif_s_base = intf_base + ['vif-s', vif_s] + if config.exists(vif_s_base + ['address']): + for addr in config.return_values(vif_s_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif_s}') + + # try vif-c + if config.exists(intf_base + ['vif-c', vif_c]): + for vif_c in config.list_nodes(vif_s_base + ['vif-c', vif_c]): + vif_c_base = vif_s_base + ['vif-c', vif_c] + if config.exists(vif_c_base + ['address']): + for addr in config.return_values(vif_c_base + ['address']): + if addr in ['dhcp', 'dhcpv6']: + dhcp_interfaces.append(f'{intf}.{vif_s}.{vif_c}') + + # set new config nodes + for intf in dhcp_interfaces: + config.set(base + ['name-servers-dhcp'], value=intf, replace=False) + + # delete old node + config.delete(base + ['disable-dhcp-nameservers']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) + +exit(0) -- cgit v1.2.3 From 64b4f483832b436849ecbbd30c3cbea0bd663648 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:12:00 +0200 Subject: dns forwarding: T2486: add conf nodes 'addnta', 'recursion-desired', migrator Add new nodes for 'service dns forwarding domain': 'addnta': adds addNTA to lua-config-file 'recursion-desired': sets '+' before the zone in forward-zones-file The migrator sets both options for all configured domains. This is usually the desired config. --- interface-definitions/dns-forwarding.xml.in | 14 +++++++- src/migration-scripts/dns-forwarding/2-to-3 | 51 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100755 src/migration-scripts/dns-forwarding/2-to-3 diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index 8b89bf758..993d69fe1 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -28,7 +28,7 @@ - Use DNS servers received from DHCP server for specified interface + Interfaces whose DHCP client nameservers to forward requests to @@ -89,6 +89,18 @@ + + + Add NTA (negative trust anchor) for this domain (must be set if the domain doesn't support DNSSEC) + + + + + + Set the "recursion desired" bit in requests to the upstream nameserver + + + diff --git a/src/migration-scripts/dns-forwarding/2-to-3 b/src/migration-scripts/dns-forwarding/2-to-3 new file mode 100755 index 000000000..01e445b22 --- /dev/null +++ b/src/migration-scripts/dns-forwarding/2-to-3 @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 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 . +# + +# Sets the new options "addnta" and "recursion-desired" for all +# 'dns forwarding domain' as this is usually desired + +import sys +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +base = ['service', 'dns', 'forwarding'] +if not config.exists(base): + # Nothing to do + sys.exit(0) + +if config.exists(base + ['domain']): + for domain in config.list_nodes(base + ['domain']): + domain_base = base + ['domain', domain] + config.set(domain_base + ['addnta']) + config.set(domain_base + ['recursion-desired']) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) -- cgit v1.2.3 From 6f2d87526d9ad4e8e1cb09a607223ee6119f02e7 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:17:51 +0200 Subject: dns forwarding: T2486: add templates --- data/templates/dns-forwarding/recursor.conf.lua.tmpl | 9 +++++++++ data/templates/dns-forwarding/recursor.conf.tmpl | 16 ++-------------- 2 files changed, 11 insertions(+), 14 deletions(-) create mode 100644 data/templates/dns-forwarding/recursor.conf.lua.tmpl diff --git a/data/templates/dns-forwarding/recursor.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.conf.lua.tmpl new file mode 100644 index 000000000..e2506238d --- /dev/null +++ b/data/templates/dns-forwarding/recursor.conf.lua.tmpl @@ -0,0 +1,9 @@ +-- Autogenerated by VyOS (dns_forwarding.py) -- +-- Do not edit, your changes will get overwritten -- + +-- Load DNSSEC root keys from dns-root-data package. +dofile("/usr/share/pdns-recursor/lua-config/rootkeys.lua") + +-- Load lua from vyos-hostsd -- +dofile("recursor.vyos-hostsd.conf.lua") + diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index c3ffea159..d233b8abc 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -29,17 +29,5 @@ local-address={{ listen_address | join(',') }} # dnssec dnssec={{ dnssec }} -# forward-zones / recursion -# -# statement is only inserted if either one forwarding domain or nameserver is configured -# if nothing is given at all, powerdns will act as a real recursor and resolve all requests by its own -# -{% if name_servers or domains %}forward-zones-recurse= -{%- for d in domains %} -{{ d.name }}={{ d.servers | join(";") }} -{{- ", " if not loop.last -}} -{%- endfor -%} -{%- if name_servers -%} -{%- if domains -%}, {% endif -%}.={{ name_servers | join(';') }} -{% endif %} -{% endif %} +forward-zones-file=recursor.forward-zones.conf + -- cgit v1.2.3 From 0eb30e29f9e97f55679970d8dbe65b192b7bf7ca Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:18:42 +0200 Subject: vyos-hostsd: T2583: add dependency on python3-voluptuous --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 104a267ea..24390ae50 100644 --- a/debian/control +++ b/debian/control @@ -35,6 +35,7 @@ Depends: python3, python3-jmespath, python3-xmltodict, python3-pyudev, + python3-voluptuous, bsdmainutils, cron, etherwake, -- cgit v1.2.3 From 1cb120a035ede068ac55de017759dc3333935981 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:20:13 +0200 Subject: hostsd_client.py: T2583: update for hostsd rewrite --- python/vyos/hostsd_client.py | 100 ++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 25 deletions(-) diff --git a/python/vyos/hostsd_client.py b/python/vyos/hostsd_client.py index f009aba98..303b6ea47 100644 --- a/python/vyos/hostsd_client.py +++ b/python/vyos/hostsd_client.py @@ -1,15 +1,11 @@ import json - import zmq - -SOCKET_PATH = "ipc:///run/vyos-hostsd.sock" - +SOCKET_PATH = "ipc:///run/vyos-hostsd/vyos-hostsd.sock" class VyOSHostsdError(Exception): pass - class Client(object): def __init__(self): try: @@ -35,35 +31,89 @@ class Client(object): except zmq.error.Again: raise VyOSHostsdError("Could not connect to vyos-hostsd") - def set_host_name(self, host_name, domain_name, search_domains): - msg = { - 'type': 'host_name', - 'op': 'set', - 'data': { - 'host_name': host_name, - 'domain_name': domain_name, - 'search_domains': search_domains - } - } + def add_name_servers(self, data): + msg = {'type': 'name_servers', 'op': 'add', 'data': data} self._communicate(msg) - def add_hosts(self, tag, hosts): - msg = {'type': 'hosts', 'op': 'add', 'tag': tag, 'data': hosts} + def delete_name_servers(self, data): + msg = {'type': 'name_servers', 'op': 'delete', 'data': data} self._communicate(msg) - def delete_hosts(self, tag): - msg = {'type': 'hosts', 'op': 'delete', 'tag': tag} + def get_name_servers(self, tag_regex): + msg = {'type': 'name_servers', 'op': 'get', 'tag_regex': tag_regex} + return self._communicate(msg) + + def add_name_server_tags_recursor(self, data): + msg = {'type': 'name_server_tags_recursor', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_name_server_tags_recursor(self, data): + msg = {'type': 'name_server_tags_recursor', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_name_server_tags_recursor(self): + msg = {'type': 'name_server_tags_recursor', 'op': 'get'} + return self._communicate(msg) + + def add_name_server_tags_system(self, data): + msg = {'type': 'name_server_tags_system', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_name_server_tags_system(self, data): + msg = {'type': 'name_server_tags_system', 'op': 'delete', 'data': data} self._communicate(msg) - def add_name_servers(self, tag, servers): - msg = {'type': 'name_servers', 'op': 'add', 'tag': tag, 'data': servers} + def get_name_server_tags_system(self): + msg = {'type': 'name_server_tags_system', 'op': 'get'} + return self._communicate(msg) + + def add_forward_zones(self, data): + msg = {'type': 'forward_zones', 'op': 'add', 'data': data} self._communicate(msg) - def delete_name_servers(self, tag): - msg = {'type': 'name_servers', 'op': 'delete', 'tag': tag} + def delete_forward_zones(self, data): + msg = {'type': 'forward_zones', 'op': 'delete', 'data': data} self._communicate(msg) - def get_name_servers(self, tag): - msg = {'type': 'name_servers', 'op': 'get', 'tag': tag} + def get_forward_zones(self): + msg = {'type': 'forward_zones', 'op': 'get'} return self._communicate(msg) + def add_search_domains(self, data): + msg = {'type': 'search_domains', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_search_domains(self, data): + msg = {'type': 'search_domains', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_search_domains(self, tag_regex): + msg = {'type': 'search_domains', 'op': 'get', 'tag_regex': tag_regex} + return self._communicate(msg) + + def add_hosts(self, data): + msg = {'type': 'hosts', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_hosts(self, data): + msg = {'type': 'hosts', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_hosts(self, tag_regex): + msg = {'type': 'hosts', 'op': 'get', 'tag_regex': tag_regex} + return self._communicate(msg) + + def set_host_name(self, host_name, domain_name): + msg = { + 'type': 'host_name', + 'op': 'set', + 'data': { + 'host_name': host_name, + 'domain_name': domain_name, + } + } + self._communicate(msg) + + def apply(self): + msg = {'op': 'apply'} + return self._communicate(msg) -- cgit v1.2.3 From c1afb092815f5ea2bd8bacb8853195fa19103f2f Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:21:51 +0200 Subject: vyos-hostsd-client: T2583: update for vyos-hostsd rewrite - add new commands as arguments - change boolean options with extra required string options to a single string option that supports multiple arguments (makes a list) - track done operations in an extra ops variable (required for apply option) --- src/utils/vyos-hostsd-client | 141 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 112 insertions(+), 29 deletions(-) diff --git a/src/utils/vyos-hostsd-client b/src/utils/vyos-hostsd-client index d3105c9cf..48ebc83f7 100755 --- a/src/utils/vyos-hostsd-client +++ b/src/utils/vyos-hostsd-client @@ -21,56 +21,139 @@ import argparse import vyos.hostsd_client -parser = argparse.ArgumentParser() +parser = argparse.ArgumentParser(allow_abbrev=False) group = parser.add_mutually_exclusive_group() -group.add_argument('--add-hosts', action="store_true") -group.add_argument('--delete-hosts', action="store_true") -group.add_argument('--add-name-servers', action="store_true") -group.add_argument('--delete-name-servers', action="store_true") -group.add_argument('--set-host-name', action="store_true") - -parser.add_argument('--host', type=str, action="append") -parser.add_argument('--name-server', type=str, action="append") -parser.add_argument('--host-name', type=str) + +group.add_argument('--add-name-servers', type=str, nargs='*') +group.add_argument('--delete-name-servers', action='store_true') +group.add_argument('--get-name-servers', type=str, const='.*', nargs='?') + +group.add_argument('--add-name-server-tags-recursor', type=str, nargs='*') +group.add_argument('--delete-name-server-tags-recursor', type=str, nargs='*') +group.add_argument('--get-name-server-tags-recursor', action='store_true') + +group.add_argument('--add-name-server-tags-system', type=str, nargs='*') +group.add_argument('--delete-name-server-tags-system', type=str, nargs='*') +group.add_argument('--get-name-server-tags-system', action='store_true') + +group.add_argument('--add-forward-zone', type=str, nargs='?') +group.add_argument('--delete-forward-zones', type=str, nargs='*') +group.add_argument('--get-forward-zones', action='store_true') + +group.add_argument('--add-search-domains', type=str, nargs='*') +group.add_argument('--delete-search-domains', action='store_true') +group.add_argument('--get-search-domains', type=str, const='.*', nargs='?') + +group.add_argument('--add-hosts', type=str, nargs='*') +group.add_argument('--delete-hosts', action='store_true') +group.add_argument('--get-hosts', type=str, const='.*', nargs='?') + +group.add_argument('--set-host-name', type=str) + +# for --set-host-name parser.add_argument('--domain-name', type=str) -parser.add_argument('--search-domain', type=str, action="append") + +# for forward zones +parser.add_argument('--nameservers', type=str, nargs='*') +parser.add_argument('--addnta', action='store_true') +parser.add_argument('--recursion-desired', action='store_true') parser.add_argument('--tag', type=str) +# users must call --apply either in the same command or after they're done +parser.add_argument('--apply', action="store_true") + args = parser.parse_args() try: client = vyos.hostsd_client.Client() + ops = 1 - if args.add_hosts: + if args.add_name_servers: if not args.tag: - raise ValueError("Tag is required for this operation") - data = [] - for h in args.host: + raise ValueError("--tag is required for this operation") + client.add_name_servers({args.tag: args.add_name_servers}) + elif args.delete_name_servers: + if not args.tag: + raise ValueError("--tag is required for this operation") + client.delete_name_servers([args.tag]) + elif args.get_name_servers: + print(client.get_name_servers(args.get_name_servers)) + + elif args.add_name_server_tags_recursor: + client.add_name_server_tags_recursor(args.add_name_server_tags_recursor) + elif args.delete_name_server_tags_recursor: + client.delete_name_server_tags_recursor(args.delete_name_server_tags_recursor) + elif args.get_name_server_tags_recursor: + print(client.get_name_server_tags_recursor()) + + elif args.add_name_server_tags_system: + client.add_name_server_tags_system(args.add_name_server_tags_system) + elif args.delete_name_server_tags_system: + client.delete_name_server_tags_system(args.delete_name_server_tags_system) + elif args.get_name_server_tags_system: + print(client.get_name_server_tags_system()) + + elif args.add_forward_zone: + if not args.nameservers: + raise ValueError("--nameservers is required for this operation") + client.add_forward_zones( + { args.add_forward_zone: { + 'nslist': args.nameservers, + 'addNTA': args.addnta, + 'recursion-desired': args.recursion_desired + } + }) + elif args.delete_forward_zones: + client.delete_forward_zones(args.delete_forward_zones) + elif args.get_forward_zones: + print(client.get_forward_zones()) + + elif args.add_search_domains: + if not args.tag: + raise ValueError("--tag is required for this operation") + client.add_search_domains({args.tag: args.add_search_domains}) + elif args.delete_search_domains: + if not args.tag: + raise ValueError("--tag is required for this operation") + client.delete_search_domains([args.tag]) + elif args.get_search_domains: + print(client.get_search_domains(args.get_search_domains)) + + elif args.add_hosts: + if not args.tag: + raise ValueError("--tag is required for this operation") + data = {} + for h in args.add_hosts: entry = {} params = h.split(",") if len(params) < 2: raise ValueError("Malformed host entry") - entry['host'] = params[0] entry['address'] = params[1] entry['aliases'] = params[2:] - data.append(entry) - client.add_hosts(args.tag, data) + data[params[0]] = entry + client.add_hosts({args.tag: data}) elif args.delete_hosts: if not args.tag: - raise ValueError("Tag is required for this operation") - client.delete_hosts(args.tag) - elif args.add_name_servers: - if not args.tag: - raise ValueError("Tag is required for this operation") - client.add_name_servers(args.tag, args.name_server) - elif args.delete_name_servers: - if not args.tag: - raise ValueError("Tag is required for this operation") - client.delete_name_servers(args.tag) + raise ValueError("--tag is required for this operation") + client.delete_hosts([args.tag]) + elif args.get_hosts: + print(client.get_hosts(args.get_hosts)) + elif args.set_host_name: - client.set_host_name(args.host_name, args.domain_name, args.search_domain) + if not args.domain_name: + raise ValueError('--domain-name is required for this operation') + client.set_host_name({'host_name': args.set_host_name, 'domain_name': args.domain_name}) + + elif args.apply: + pass else: + ops = 0 + + if args.apply: + client.apply() + + if ops == 0: raise ValueError("Operation required") except ValueError as e: -- cgit v1.2.3 From 6fa9faa76c61fc17b78b600b715df0ed2761dd30 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:28:35 +0200 Subject: on-dhcp-event.sh: T2486: modify for new vyos-hostsd syntax - remove already existing entry check in /etc/hosts as vyos-hostsd will handle it - vyos-hostsd-client syntax changed - change tag "DHCP-$client_ip" to "dhcp-server-$client_ip" to make it more distinct from dhcp client tag "dhcp-$intf" --- src/system/on-dhcp-event.sh | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/system/on-dhcp-event.sh b/src/system/on-dhcp-event.sh index 57f492401..a062dc810 100755 --- a/src/system/on-dhcp-event.sh +++ b/src/system/on-dhcp-event.sh @@ -19,7 +19,7 @@ client_name=$2 client_ip=$3 client_mac=$4 domain=$5 -file=/etc/hosts +hostsd_client="/usr/bin/vyos-hostsd-client" if [ -z "$client_name" ]; then logger -s -t on-dhcp-event "Client name was empty, using MAC \"$client_mac\" instead" @@ -36,26 +36,19 @@ fi case "$action" in commit) # add mapping for new lease - grep -q " $client_search_expr " $file - if [ $? == 0 ]; then - echo host $client_fqdn_name already exists, exiting - exit 1 - fi - # add host - /usr/bin/vyos-hostsd-client --add-hosts --tag "DHCP-$client_ip" --host "$client_fqdn_name,$client_ip" + $hostsd_client --add-hosts "$client_fqdn_name,$client_ip" --tag "dhcp-server-$client_ip" --apply + exit 0 ;; release) # delete mapping for released address - # delete host - /usr/bin/vyos-hostsd-client --delete-hosts --tag "DHCP-$client_ip" + $hostsd_client --delete-hosts --tag "dhcp-server-$client_ip" --apply + exit 0 ;; *) logger -s -t on-dhcp-event "Invalid command \"$1\"" - exit 1; + exit 1 ;; esac exit 0 - - -- cgit v1.2.3 From f9b601ef78f9f3b46ca66ae945a7e9d2dadb521b Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:31:08 +0200 Subject: dns forwarding: T2534: fix incorrect path in recursor override file --- src/etc/systemd/system/pdns-recursor.service.d/override.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/etc/systemd/system/pdns-recursor.service.d/override.conf b/src/etc/systemd/system/pdns-recursor.service.d/override.conf index 750bc9972..158bac02b 100644 --- a/src/etc/systemd/system/pdns-recursor.service.d/override.conf +++ b/src/etc/systemd/system/pdns-recursor.service.d/override.conf @@ -2,6 +2,7 @@ WorkingDirectory= WorkingDirectory=/run/powerdns RuntimeDirectory= -RuntimeDirectory=/run/powerdns +RuntimeDirectory=powerdns +RuntimeDirectoryPreserve=yes ExecStart= ExecStart=/usr/sbin/pdns_recursor --daemon=no --write-pid=no --disable-syslog --log-timestamp=no --config-dir=/run/powerdns --socket-dir=/run/powerdns -- cgit v1.2.3 From 0ccdf15c0acb0be128fa2346b72cfdd8ce16b694 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 11 Jun 2020 09:32:58 +0200 Subject: dhclient hooks: T2486: update for vyos-hostsd rewrite - vyos-hostsd-client syntax changed - track changes in changes variable - call apply only once at the end if any changes were made - remove 'cli-shell-api existsEffective system disable-dhcp-nameservers' condition check as the functionality was moved into vyos-hostsd - remove comparison between old_ and new_ variables as it caused a bug as the nameservers didn't get updated on renew or system restart, the dhclient lease file persists across reboots, so on boot the old variables will contain the values from previous dhclient run so they will usually be equal to the new variables. --- .../dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf | 53 ++++++++++++---------- src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup | 28 ++++++++++-- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf b/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf index ea5562ea8..24090e2a8 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf +++ b/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf @@ -1,39 +1,44 @@ -# modified make_resolv_conf () for Vyatta system below +# modified make_resolv_conf () for VyOS make_resolv_conf() { + hostsd_client="/usr/bin/vyos-hostsd-client" + hostsd_changes= + if [ -n "$new_domain_name" ]; then - logmsg info "Adding search-domain \"$new_domain_name\" via vyos-hostsd-client" - /usr/bin/vyos-hostsd-client --set-host-name --search-domain $new_domain_name + logmsg info "Deleting search domains with tag \"dhcp-$interface\" via vyos-hostsd-client" + $hostsd_client --delete-search-domains --tag "dhcp-$interface" + logmsg info "Adding domain name \"$new_domain_name\" as search domain with tag \"dhcp-$interface\" via vyos-hostsd-client" + $hostsd_client --add-search-domains "$new_domain_name" --tag "dhcp-$interface" + hostsd_changes=y fi if [ -n "$new_dhcp6_domain_search" ]; then - logmsg info "Adding search-domain \"$new_dhcp6_domain_search\" via vyos-hostsd-client" - /usr/bin/vyos-hostsd-client --set-host-name --search-domain $new_dhcp6_domain_search + logmsg info "Deleting search domains with tag \"dhcpv6-$interface\" via vyos-hostsd-client" + $hostsd_client --delete-search-domains --tag "dhcpv6-$interface" + logmsg info "Adding search domain \"$new_dhcp6_domain_search\" with tag \"dhcpv6-$interface\" via vyos-hostsd-client" + $hostsd_client --add-search-domains "$new_dhcp6_domain_search" --tag "dhcpv6-$interface" + hostsd_changes=y fi - if [ -n "$new_domain_name_servers" ] && ! cli-shell-api existsEffective system disable-dhcp-nameservers && [ "$new_domain_name_servers" != "$old_domain_name_servers" ] ; then + if [ -n "$new_domain_name_servers" ]; then logmsg info "Deleting nameservers with tag \"dhcp-$interface\" via vyos-hostsd-client" - vyos-hostsd-client --delete-name-servers --tag dhcp-$interface - NEW_SERVERS="" - for nameserver in $new_domain_name_servers; do - NEW_SERVERS="$NEW_SERVERS --name-server $nameserver" - done - logmsg info "Adding nameservers \"$NEW_SERVERS\" with tag \"dhcp-$interface\" via vyos-hostsd-client" - /usr/bin/vyos-hostsd-client --add-name-servers $NEW_SERVERS --tag dhcp-$interface + $hostsd_client --delete-name-servers --tag "dhcp-$interface" + logmsg info "Adding nameservers \"$new_domain_name_servers\" with tag \"dhcp-$interface\" via vyos-hostsd-client" + $hostsd_client --add-name-servers $new_domain_name_servers --tag "dhcp-$interface" + hostsd_changes=y fi - if [ -n "$new_dhcp6_name_servers" ] && ! cli-shell-api existsEffective system disable-dhcp-nameservers && [ "$new_dhcp6_name_servers" != "$old_dhcp6_name_servers" ] ; then + if [ -n "$new_dhcp6_name_servers" ]; then logmsg info "Deleting nameservers with tag \"dhcpv6-$interface\" via vyos-hostsd-client" - vyos-hostsd-client --delete-name-servers --tag dhcpv6-$interface - NEW_SERVERS="" - for nameserver in $new_dhcp6_name_servers; do - NEW_SERVERS="$NEW_SERVERS --name-server $nameserver" - done - logmsg info "Adding nameservers \"$NEW_SERVERS\" with tag \"dhcpv6-$interface\" via vyos-hostsd-client" - /usr/bin/vyos-hostsd-client --add-name-servers $NEW_SERVERS --tag dhcpv6-$interface + $hostsd_client --delete-name-servers --tag "dhcpv6-$interface" + logmsg info "Adding nameservers \"$new_dhcpv6_name_servers\" with tag \"dhcpv6-$interface\" via vyos-hostsd-client" + $hostsd_client --add-name-servers $new_dhcpv6_name_servers --tag "dhcpv6-$interface" + hostsd_changes=y fi - if cli-shell-api existsEffective service dns forwarding; then - logmsg info "Enabling DNS forwarding" - /usr/libexec/vyos/conf_mode/dns_forwarding.py --dhclient + if [ $hostsd_changes ]; then + logmsg info "Applying changes via vyos-hostsd-client" + $hostsd_client --apply + else + logmsg info "No changes to apply via vyos-hostsd-client" fi } diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup index 88a4d9db9..01981ad04 100644 --- a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup +++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup @@ -1,14 +1,24 @@ +## +## VyOS cleanup +## # NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via /usr/sbin/ip or vtysh, according to the system state +hostsd_client="/usr/bin/vyos-hostsd-client" +hostsd_changes= if [[ $reason =~ (EXPIRE|FAIL|RELEASE|STOP) ]]; then - # delete dynamic nameservers from a configuration if lease was deleted + # delete search domains and nameservers via vyos-hostsd + logmsg info "Deleting search domains with tag \"dhcp-$interface\" via vyos-hostsd-client" + $hostsd_client --delete-search-domains --tag "dhcp-$interface" logmsg info "Deleting nameservers with tag \"dhcp-${interface}\" via vyos-hostsd-client" - vyos-hostsd-client --delete-name-servers --tag dhcp-${interface} + $hostsd_client --delete-name-servers --tag "dhcp-${interface}" + hostsd_changes=y + # try to delete default ip route for router in $old_routers; do logmsg info "Deleting default route: via $router dev ${interface}" ip -4 route del default via $router dev ${interface} done + # delete rfc3442 routes if [ -n "$old_rfc3442_classless_static_routes" ]; then set -- $old_rfc3442_classless_static_routes @@ -72,7 +82,17 @@ if [[ $reason =~ (EXPIRE|FAIL|RELEASE|STOP) ]]; then fi if [[ $reason =~ (EXPIRE6|RELEASE6|STOP6) ]]; then - # delete dynamic nameservers from a configuration if lease was deleted + # delete search domains and nameservers via vyos-hostsd + logmsg info "Deleting search domains with tag \"dhcpv6-$interface\" via vyos-hostsd-client" + $hostsd_client --delete-search-domains --tag "dhcpv6-$interface" logmsg info "Deleting nameservers with tag \"dhcpv6-${interface}\" via vyos-hostsd-client" - vyos-hostsd-client --delete-name-servers --tag dhcpv6-${interface} + $hostsd_client --delete-name-servers --tag "dhcpv6-${interface}" + hostsd_changes=y +fi + +if [ $hostsd_changes ]; then + logmsg info "Applying changes via vyos-hostsd-client" + $hostsd_client --apply +else + logmsg info "No changes to apply via vyos-hostsd-client" fi -- cgit v1.2.3