diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | interface-definitions/dns-domain-name.xml | 68 | ||||
-rw-r--r-- | interface-definitions/host-name.xml | 26 | ||||
-rwxr-xr-x | src/conf_mode/host_name.py | 144 |
4 files changed, 172 insertions, 70 deletions
@@ -42,10 +42,6 @@ clean: rm -rf $(TMPL_DIR)/* rm -rf $(OP_TMPL_DIR)/* -.PHONY: test -test: - PYTHONPATH=python/ python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators,src/tests --verbose - .PHONY: sonar sonar: sonar-scanner -X -Dsonar.login=${SONAR_TOKEN} diff --git a/interface-definitions/dns-domain-name.xml b/interface-definitions/dns-domain-name.xml new file mode 100644 index 000000000..7b8497c09 --- /dev/null +++ b/interface-definitions/dns-domain-name.xml @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<!-- host-name configuration --> +<interfaceDefinition> + <node name="system"> + <children> + <leafNode name="name-server"> + <properties> + <help>Domain Name Server (DNS)</help> + <priority>400</priority> + <valueHelp> + <format>ipv4</format> + <description>Domain Name Server (DNS) address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Domain Name Server (DNS) address</description> + </valueHelp> + <multi/> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="host-name" owner="${vyos_conf_scripts_dir}/host_name.py"> + <properties> + <help>System host name (default: vyos)</help> + <constraint> + <regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="domain-name" owner="${vyos_conf_scripts_dir}/host_name.py"> + <properties> + <help>System domain name</help> + <constraint> + <regex>[A-Za-z0-9][-.A-Za-z0-9]*</regex> + </constraint> + </properties> + </leafNode> + <node name="domain-search" owner="${vyos_conf_scripts_dir}/host_name.py"> + <properties> + <help>Domain Name Server (DNS) domain completion order</help> + <priority>400</priority> + </properties> + <children> + <leafNode name="domain"> + <properties> + <help>DNS domain completion order</help> + <constraint> + <regex>^[-a-zA-Z0-9.]+$</regex> + </constraint> + <constraintErrorMessage>Invalid domain name</constraintErrorMessage> + <multi/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="disable-dhcp-nameservers" owner="${vyos_conf_scripts_dir}/host_name.py"> + <properties> + <help>Disable DHCP updates of DNS settings</help> + <priority>300</priority> + <valueless/> + </properties> + </leafNode> + </children> + </node> +</interfaceDefinition> diff --git a/interface-definitions/host-name.xml b/interface-definitions/host-name.xml deleted file mode 100644 index bbe679607..000000000 --- a/interface-definitions/host-name.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0"?> - -<!-- host-name configuration --> - -<interfaceDefinition> - <node name="system"> - <children> - <leafNode name="host-name" owner="${vyos_conf_scripts_dir}/host_name.py"> - <properties> - <help>System host name (default: vyos)</help> - <constraint> - <regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex> - </constraint> - </properties> - </leafNode> - <leafNode name="domain-name" owner="${vyos_conf_scripts_dir}/host_name.py"> - <properties> - <help>System domain name</help> - <constraint> - <regex>[A-Za-z0-9][-.A-Za-z0-9]*</regex> - </constraint> - </properties> - </leafNode> - </children> - </node> -</interfaceDefinition> diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 030735215..4b6ce76b0 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -24,81 +24,146 @@ import os import re import sys import subprocess +import copy +import jinja2 +import glob from vyos.config import Config from vyos import ConfigError +config_file_hosts = '/etc/hosts' +config_file_resolv = '/etc/resolv.conf' -hosts_file = '/etc/hosts' -hostname_regex = re.compile("^[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$") -local_addr = '127.0.1.1' # NOSONAR +config_tmpl_hosts = """ +### Autogenerated by host_name.py ### +127.0.0.1 localhost {{ hostname }}{% if domain_name %}.{{ domain_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 +""" + +config_tmpl_resolv = """ +### Autogenerated by host_name.py ### +{% for ns in nameserver -%} +nameserver {{ ns }} +{% endfor -%} + +{%- if domain_name %} +domain {{ domain_name }} +{%- endif %} + +{%- if domain_search %} +search {{ domain_search | join(" ") }} +{%- endif %} + +""" + +# borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX! +def get_resolvers(file): + resolvers = [] + try: + with open(file, 'r') as resolvconf: + for line in resolvconf.readlines(): + line = line.split('#',1)[0]; + line = line.rstrip(); + if 'nameserver' in line: + resolvers.append(line.split()[1]) + return resolvers + except IOError: + return [] + +default_config_data = { + 'hostname': 'vyos', + 'domain_name': '', + 'domain_search': [], + 'nameserver': [], + 'no_dhcp_ns': False +} def get_config(): """Get configuration""" conf = Config() + hosts = copy.deepcopy(default_config_data) - hostname = conf.return_value("system host-name") - domain = conf.return_value("system domain-name") + hosts['hostname'] = conf.return_value("system host-name") + hosts['domain_name'] = conf.return_value("system domain-name") - # No one likes fixups, but we really don't want VyOS fail to boot - # if hostname is not in the config - if not hostname: - hostname = "vyos" + if hosts['domain_name']: + hosts['domain_search'].append(hosts['domain_name']) - if domain: - fqdn = "{0}.{1}".format(hostname, domain) - else: - fqdn = hostname - - return {"hostname": hostname, "domain": domain, "fqdn": fqdn} + hosts['domain_search'] = conf.return_values("system domain-search domain") + hosts['nameserver'] = conf.return_values("system name-server") + hosts['no_dhcp_ns'] = conf.exists('system disable-dhcp-nameservers') + return hosts def verify(config): """Verify configuration""" # check for invalid host # pattern $VAR(@) "^[[:alnum:]][-.[:alnum:]]*[[:alnum:]]$" ; "invalid host name $VAR(@)" - if not hostname_regex.match(config["hostname"]): + 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"]) # pattern $VAR(@) "^.{1,63}$" ; "invalid host-name length" - length = len(config["hostname"]) + length = len(config['hostname']) if length < 1 or length > 63: raise ConfigError( 'Invalid host-name length, must be less than 63 characters') - return None + # 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') + + return None def generate(config): """Generate configuration files""" - # read the hosts file - with open(hosts_file, 'r') as f: - hosts = f.read() - - # get the current hostname - old_hostname = subprocess.check_output(['hostname']).decode().strip() - - # replace the local host line - vyos_host_line_re = re.compile(r"({}\s+{}.*)".format(local_addr, old_hostname)) - vyos_host_line = "{}\t{} # VyOS entry\n".format(local_addr, config["fqdn"]) - if re.search(vyos_host_line_re, hosts): - hosts = re.sub(vyos_host_line_re, vyos_host_line, hosts) - else: - # On boot (or after errors), the /etc/hosts file has no line for vyos hostname, - # so we have to add it - hosts = "{0}\n{1}".format(hosts, vyos_host_line) - - with open(hosts_file, 'w') as f: - f.write(hosts) + if config is None: + return None + + # If "system disable-dhcp-nameservers" is __configured__ all DNS resolvers + # received via dhclient should not be added into the final 'resolv.conf'. + # + # We iterate over every resolver file and retrieve the received nameservers + # for later adjustment of the system nameservers + dhcp_ns = [] + for file in glob.glob('/etc/resolv.conf.dhclient-new*'): + for r in get_resolvers(file): + dhcp_ns.append(r) + + if not config['no_dhcp_ns']: + config['nameserver'] += dhcp_ns + + tmpl = jinja2.Template(config_tmpl_hosts) + config_text = tmpl.render(config) + with open(config_file_hosts, 'w') as f: + f.write(config_text) + + tmpl = jinja2.Template(config_tmpl_resolv) + config_text = tmpl.render(config) + with open(config_file_resolv, 'w') as f: + f.write(config_text) return None - def apply(config): """Apply configuration""" - os.system("hostnamectl set-hostname --static {0}".format(config["fqdn"])) + fqdn = config['hostname'] + if config['domain_name']: + fqdn += '.' + config['domain_name'] + + os.system("hostnamectl set-hostname --static {0}".format(fqdn)) # Restart services that use the hostname os.system("systemctl restart rsyslog.service") @@ -109,7 +174,6 @@ def apply(config): return None - if __name__ == '__main__': try: c = get_config() |