From 842f642c3e64800e786bde4ea4b52bcf26254f9d Mon Sep 17 00:00:00 2001 From: Robert Schindler Date: Tue, 21 Jul 2020 16:27:56 +0200 Subject: vyos.util: T2720: Add file descriptor support to chmod and chown Extended the chmod() and chown() functions in vyos.util to also operate on open file descriptors in addition to paths. This allows code that creates files to quickly change mode and owner even before anything has actually been written to the file. --- python/vyos/util.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'python') diff --git a/python/vyos/util.py b/python/vyos/util.py index 924df6b3a..6c20a865a 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -240,7 +240,8 @@ def chown(path, user, group): if user is None or group is None: return False - if not os.path.exists(path): + # path may also be an open file descriptor + if not isinstance(path, int) and not os.path.exists(path): return False uid = getpwnam(user).pw_uid @@ -250,7 +251,8 @@ def chown(path, user, group): def chmod(path, bitmask): - if not os.path.exists(path): + # path may also be an open file descriptor + if not isinstance(path, int) and not os.path.exists(path): return if bitmask is None: return @@ -261,28 +263,25 @@ def chmod_600(path): """ make file only read/writable by owner """ from stat import S_IRUSR, S_IWUSR - if os.path.exists(path): - bitmask = S_IRUSR | S_IWUSR - os.chmod(path, bitmask) + bitmask = S_IRUSR | S_IWUSR + chmod(path, bitmask) def chmod_750(path): """ make file/directory only executable to user and group """ from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP - if os.path.exists(path): - bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP - os.chmod(path, bitmask) + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP + chmod(path, bitmask) def chmod_755(path): """ make file executable by all """ from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH - if os.path.exists(path): - bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \ - S_IROTH | S_IXOTH - os.chmod(path, bitmask) + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \ + S_IROTH | S_IXOTH + chmod(path, bitmask) def makedir(path, user=None, group=None): -- cgit v1.2.3 From 3ec77c9c878939f8c6e4eca62c4a2cddfef20db1 Mon Sep 17 00:00:00 2001 From: Robert Schindler Date: Tue, 21 Jul 2020 16:29:59 +0200 Subject: vyos.template: T2720: Rework vyos.template Python library Apart from code cleanup, this allows any module that wants to render a template to register custom jinja2 template filters by using the new @register_filter decorator. Examples can be found in vyos.template itself. Using the new render_to_string function, the rendered template can also be returned as a string instead of being written to a file. --- python/vyos/template.py | 163 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 55 deletions(-) (limited to 'python') diff --git a/python/vyos/template.py b/python/vyos/template.py index d9b0c749d..c88ab04a0 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -1,4 +1,4 @@ -# Copyright 2019 VyOS maintainers and contributors +# Copyright 2019-2020 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -13,78 +13,131 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . +import functools import os +from ipaddress import ip_network from jinja2 import Environment from jinja2 import FileSystemLoader from vyos.defaults import directories from vyos.util import chmod, chown, makedir -# reuse the same Environment to improve performance -_templates_env = { - False: Environment(loader=FileSystemLoader(directories['templates'])), - True: Environment(loader=FileSystemLoader(directories['templates']), trim_blocks=True), -} -_templates_mem = { - False: {}, - True: {}, -} -def vyos_address_from_cidr(text): - """ Take an IPv4/IPv6 CIDR prefix and convert the network to an "address". - Example: - 192.0.2.0/24 -> 192.0.2.0, 2001:db8::/48 -> 2001:db8:: - """ - from ipaddress import ip_network - return ip_network(text).network_address +# Holds template filters registered via register_filter() +_FILTERS = {} -def vyos_netmask_from_cidr(text): - """ Take an IPv4/IPv6 CIDR prefix and convert the prefix length to a "subnet mask". - Example: - 192.0.2.0/24 -> 255.255.255.0, 2001:db8::/48 -> ffff:ffff:ffff:: - """ - from ipaddress import ip_network - return ip_network(text).netmask -def render(destination, template, content, trim_blocks=False, formater=None, permission=None, user=None, group=None): +# reuse Environments with identical trim_blocks setting to improve performance +@functools.lru_cache(maxsize=2) +def _get_environment(trim_blocks): + env = Environment( + # Don't check if template files were modified upon re-rendering + auto_reload=False, + # Cache up to this number of templates for quick re-rendering + cache_size=100, + loader=FileSystemLoader(directories["templates"]), + trim_blocks=trim_blocks, + ) + env.filters.update(_FILTERS) + return env + + +def register_filter(name, func=None): + """Register a function to be available as filter in templates under given name. + + It can also be used as a decorator, see below in this module for examples. + + :raise RuntimeError: + when trying to register a filter after a template has been rendered already + :raise ValueError: when trying to register a name which was taken already """ - render a template from the template directory, it will raise on any errors - destination: the file where the rendered template must be saved - template: the path to the template relative to the template folder - content: the dictionary to use to render the template - - This classes cache the renderer, so rendering the same file multiple time - does not cause as too much overhead. If use everywhere, it could be changed - and load the template from python environement variables from an import - python module generated when the debian package is build - (recovering the load time and overhead caused by having the file out of the code) + if func is None: + return functools.partial(register_filter, name) + if _get_environment.cache_info().currsize: + raise RuntimeError( + "Filters can only be registered before rendering the first template" + ) + if name in _FILTERS: + raise ValueError(f"A filter with name {name!r} was registered already") + _FILTERS[name] = func + return func + + +def render_to_string(template, content, trim_blocks=False, formater=None): + """Render a template from the template directory, raise on any errors. + + :param template: the path to the template relative to the template folder + :param content: the dictionary of variables to put into rendering context + :param trim_blocks: controls the trim_blocks jinja2 feature + :param formater: + if given, it has to be a callable the rendered string is passed through + + The parsed template files are cached, so rendering the same file multiple times + does not cause as too much overhead. + If used everywhere, it could be changed to load the template from Python + environment variables from an importable Python module generated when the Debian + package is build (recovering the load time and overhead caused by having the + file out of the code). """ + template = _get_environment(bool(trim_blocks)).get_template(template) + rendered = template.render(content) + if formater is not None: + rendered = formater(rendered) + return rendered + + +def render( + destination, + template, + content, + trim_blocks=False, + formater=None, + permission=None, + user=None, + group=None, +): + """Render a template from the template directory to a file, raise on any errors. - # Create the directory if it does not exists + :param destination: path to the file to save the rendered template in + :param permission: permission bitmask to set for the output file + :param user: user to own the output file + :param group: group to own the output file + + All other parameters are as for :func:`render_to_string`. + """ + # Create the directory if it does not exist folder = os.path.dirname(destination) makedir(folder, user, group) - # Setup a renderer for the given template - # This is cached and re-used for performance - if template not in _templates_mem[trim_blocks]: - _env = _templates_env[trim_blocks] - _env.filters['address_from_cidr'] = vyos_address_from_cidr - _env.filters['netmask_from_cidr'] = vyos_netmask_from_cidr - _templates_mem[trim_blocks][template] = _env.get_template(template) + # As we are opening the file with 'w', we are performing the rendering before + # calling open() to not accidentally erase the file if rendering fails + rendered = render_to_string(template, content, trim_blocks, formater) - template = _templates_mem[trim_blocks][template] + # Write to file + with open(destination, "w") as file: + chmod(file.fileno(), permission) + chown(file.fileno(), user, group) + file.write(rendered) - # As we are opening the file with 'w', we are performing the rendering - # before calling open() to not accidentally erase the file if the - # templating fails - content = template.render(content) - if formater: - content = formater(content) +################################## +# Custom template filters follow # +################################## - # Write client config file - with open(destination, 'w') as f: - f.write(content) - chmod(destination, permission) - chown(destination, user, group) +@register_filter("address_from_cidr") +def vyos_address_from_cidr(text): + """ Take an IPv4/IPv6 CIDR prefix and convert the network to an "address". + Example: + 192.0.2.0/24 -> 192.0.2.0, 2001:db8::/48 -> 2001:db8:: + """ + return ip_network(text).network_address + + +@register_filter("netmask_from_cidr") +def vyos_netmask_from_cidr(text): + """ Take an IPv4/IPv6 CIDR prefix and convert the prefix length to a "subnet mask". + Example: + 192.0.2.0/24 -> 255.255.255.0, 2001:db8::/48 -> ffff:ffff:ffff:: + """ + return ip_network(text).netmask -- cgit v1.2.3 From 142495eae746a689473a29fa4854eb713d42e59e Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 22 Jul 2020 13:14:54 -0500 Subject: config: T2707: add ConfigSource classes for alternative init data --- python/vyos/configsource.py | 318 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 python/vyos/configsource.py (limited to 'python') diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py new file mode 100644 index 000000000..50222e385 --- /dev/null +++ b/python/vyos/configsource.py @@ -0,0 +1,318 @@ + +# Copyright 2020 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +import os +import re +import subprocess + +from vyos.configtree import ConfigTree + +class VyOSError(Exception): + """ + Raised on config access errors. + """ + pass + +class ConfigSourceError(Exception): + ''' + Raised on error in ConfigSource subclass init. + ''' + pass + +class ConfigSource: + def __init__(self): + self._running_config: ConfigTree = None + self._session_config: ConfigTree = None + + def get_configtree_tuple(self): + return self._running_config, self._session_config + + def session_changed(self): + """ + Returns: + True if the config session has uncommited changes, False otherwise. + """ + raise NotImplementedError(f"function not available for {type(self)}") + + def in_session(self): + """ + Returns: + True if called from a configuration session, False otherwise. + """ + raise NotImplementedError(f"function not available for {type(self)}") + + def show_config(self, path=[], default=None, effective=False): + """ + Args: + path (str|list): Configuration tree path, or empty + default (str): Default value to return + + Returns: + str: working configuration + """ + raise NotImplementedError(f"function not available for {type(self)}") + + def is_multi(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node can have multiple values, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + raise NotImplementedError(f"function not available for {type(self)}") + + def is_tag(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a tag node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + raise NotImplementedError(f"function not available for {type(self)}") + + def is_leaf(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a leaf node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + raise NotImplementedError(f"function not available for {type(self)}") + +class ConfigSourceSession(ConfigSource): + def __init__(self, session_env=None): + super().__init__() + self._cli_shell_api = "/bin/cli-shell-api" + self._level = [] + if session_env: + self.__session_env = session_env + else: + self.__session_env = None + + # Running config can be obtained either from op or conf mode, it always succeeds + # once the config system is initialized during boot; + # before initialization, set to empty string + if os.path.isfile('/tmp/vyos-config-status'): + try: + running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', '--show-ignore-edit', 'showConfig']) + except VyOSError: + running_config_text = '' + else: + running_config_text = '' + + # Session config ("active") only exists in conf mode. + # In op mode, we'll just use the same running config for both active and session configs. + if self.in_session(): + try: + session_config_text = self._run([self._cli_shell_api, '--show-working-only', '--show-show-defaults', '--show-ignore-edit', 'showConfig']) + except VyOSError: + session_config_text = '' + else: + session_config_text = running_config_text + + if running_config_text: + self._running_config = ConfigTree(running_config_text) + else: + self._running_config = None + + if session_config_text: + self._session_config = ConfigTree(session_config_text) + else: + self._session_config = None + + def _make_command(self, op, path): + args = path.split() + cmd = [self._cli_shell_api, op] + args + return cmd + + def _run(self, cmd): + if self.__session_env: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=self.__session_env) + else: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + out = p.stdout.read() + p.wait() + p.communicate() + if p.returncode != 0: + raise VyOSError() + else: + return out.decode('ascii') + + def set_level(self, path): + """ + Set the *edit level*, that is, a relative config tree path. + Once set, all operations will be relative to this path, + for example, after ``set_level("system")``, calling + ``exists("name-server")`` is equivalent to calling + ``exists("system name-server"`` without ``set_level``. + + Args: + path (str|list): relative config path + """ + # Make sure there's always a space between default path (level) + # and path supplied as method argument + # XXX: for small strings in-place concatenation is not a problem + if isinstance(path, str): + if path: + self._level = re.split(r'\s+', path) + else: + self._level = [] + elif isinstance(path, list): + self._level = path.copy() + else: + raise TypeError("Level path must be either a whitespace-separated string or a list") + + def session_changed(self): + """ + Returns: + True if the config session has uncommited changes, False otherwise. + """ + try: + self._run(self._make_command('sessionChanged', '')) + return True + except VyOSError: + return False + + def in_session(self): + """ + Returns: + True if called from a configuration session, False otherwise. + """ + try: + self._run(self._make_command('inSession', '')) + return True + except VyOSError: + return False + + def show_config(self, path=[], default=None, effective=False): + """ + Args: + path (str|list): Configuration tree path, or empty + default (str): Default value to return + + Returns: + str: working configuration + """ + + # show_config should be independent of CLI edit level. + # Set the CLI edit environment to the top level, and + # restore original on exit. + save_env = self.__session_env + + env_str = self._run(self._make_command('getEditResetEnv', '')) + env_list = re.findall(r'([A-Z_]+)=\'([^;\s]+)\'', env_str) + root_env = os.environ + for k, v in env_list: + root_env[k] = v + + self.__session_env = root_env + + # FIXUP: by default, showConfig will give you a diff + # if there are uncommitted changes. + # The config parser obviously cannot work with diffs, + # so we need to supress diff production using appropriate + # options for getting either running (active) + # or proposed (working) config. + if effective: + path = ['--show-active-only'] + path + else: + path = ['--show-working-only'] + path + + if isinstance(path, list): + path = " ".join(path) + try: + out = self._run(self._make_command('showConfig', path)) + self.__session_env = save_env + return out + except VyOSError: + self.__session_env = save_env + return(default) + + def is_multi(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node can have multiple values, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + try: + path = " ".join(self._level) + " " + path + self._run(self._make_command('isMulti', path)) + return True + except VyOSError: + return False + + def is_tag(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a tag node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + try: + path = " ".join(self._level) + " " + path + self._run(self._make_command('isTag', path)) + return True + except VyOSError: + return False + + def is_leaf(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a leaf node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ + try: + path = " ".join(self._level) + " " + path + self._run(self._make_command('isLeaf', path)) + return True + except VyOSError: + return False + +class ConfigSourceString(ConfigSource): + def __init__(self, running_config_text=None, session_config_text=None): + super().__init__() + + try: + self._running_config = ConfigTree(running_config_text) if running_config_text else None + self._session_config = ConfigTree(session_config_text) if session_config_text else None + except ValueError: + raise ConfigSourceError(f"Init error in {type(self)}") -- cgit v1.2.3 From 280044183e086c1ef1fbe9e8a7291f738bb7e504 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 22 Jul 2020 13:16:42 -0500 Subject: config: T2707: use ConfigSource and refactor Config methods --- python/vyos/config.py | 152 +++++++------------------------------------------- 1 file changed, 20 insertions(+), 132 deletions(-) (limited to 'python') diff --git a/python/vyos/config.py b/python/vyos/config.py index 5d58316e7..884d6d947 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -63,23 +63,13 @@ In operational mode, all functions return values from the running config. """ -import os import re import json -import subprocess from copy import deepcopy import vyos.util import vyos.configtree - -class VyOSError(Exception): - """ - Raised on config access errors, most commonly if the type of a config tree node - in the system does not match the type of operation. - - """ - pass - +from vyos.configsource import ConfigSource, ConfigSourceSession class Config(object): """ @@ -89,51 +79,18 @@ class Config(object): the only state it keeps is relative *config path* for convenient access to config subtrees. """ - def __init__(self, session_env=None): - self._cli_shell_api = "/bin/cli-shell-api" - self._level = [] - self._dict_cache = {} - - if session_env: - self.__session_env = session_env - else: - self.__session_env = None - - # Running config can be obtained either from op or conf mode, it always succeeds - # once the config system is initialized during boot; - # before initialization, set to empty string - if os.path.isfile('/tmp/vyos-config-status'): - try: - running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', '--show-ignore-edit', 'showConfig']) - except VyOSError: - running_config_text = '' - else: - running_config_text = '' - - # Session config ("active") only exists in conf mode. - # In op mode, we'll just use the same running config for both active and session configs. - if self.in_session(): - try: - session_config_text = self._run([self._cli_shell_api, '--show-working-only', '--show-show-defaults', '--show-ignore-edit', 'showConfig']) - except VyOSError: - session_config_text = '' - else: - session_config_text = running_config_text - - if running_config_text: - self._running_config = vyos.configtree.ConfigTree(running_config_text) - else: - self._running_config = None - - if session_config_text: - self._session_config = vyos.configtree.ConfigTree(session_config_text) + def __init__(self, session_env=None, config_source=None): + if config_source is None: + self._config_source = ConfigSourceSession(session_env) else: - self._session_config = None + if not isinstance(config_source, ConfigSource): + raise TypeError("config_source not of type ConfigSource") + self._config_source = config_source - def _make_command(self, op, path): - args = path.split() - cmd = [self._cli_shell_api, op] + args - return cmd + self._level = [] + self._dict_cache = {} + (self._running_config, + self._session_config) = self._config_source.get_configtree_tuple() def _make_path(self, path): # Backwards-compatibility stuff: original implementation used string paths @@ -149,19 +106,6 @@ class Config(object): raise TypeError("Path must be a whitespace-separated string or a list") return (self._level + path) - def _run(self, cmd): - if self.__session_env: - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=self.__session_env) - else: - p = subprocess.Popen(cmd, stdout=subprocess.PIPE) - out = p.stdout.read() - p.wait() - p.communicate() - if p.returncode != 0: - raise VyOSError() - else: - return out.decode('ascii') - def set_level(self, path): """ Set the *edit level*, that is, a relative config tree path. @@ -229,22 +173,14 @@ class Config(object): Returns: True if the config session has uncommited changes, False otherwise. """ - try: - self._run(self._make_command('sessionChanged', '')) - return True - except VyOSError: - return False + return self._config_source.session_changed() def in_session(self): """ Returns: True if called from a configuration session, False otherwise. """ - try: - self._run(self._make_command('inSession', '')) - return True - except VyOSError: - return False + return self._config_source.in_session() def show_config(self, path=[], default=None, effective=False): """ @@ -255,40 +191,7 @@ class Config(object): Returns: str: working configuration """ - - # show_config should be independent of CLI edit level. - # Set the CLI edit environment to the top level, and - # restore original on exit. - save_env = self.__session_env - - env_str = self._run(self._make_command('getEditResetEnv', '')) - env_list = re.findall(r'([A-Z_]+)=\'([^;\s]+)\'', env_str) - root_env = os.environ - for k, v in env_list: - root_env[k] = v - - self.__session_env = root_env - - # FIXUP: by default, showConfig will give you a diff - # if there are uncommitted changes. - # The config parser obviously cannot work with diffs, - # so we need to supress diff production using appropriate - # options for getting either running (active) - # or proposed (working) config. - if effective: - path = ['--show-active-only'] + path - else: - path = ['--show-working-only'] + path - - if isinstance(path, list): - path = " ".join(path) - try: - out = self._run(self._make_command('showConfig', path)) - self.__session_env = save_env - return out - except VyOSError: - self.__session_env = save_env - return(default) + return self._config_source.show_config(path, default, effective) def get_cached_dict(self, effective=False): cached = self._dict_cache.get(effective, {}) @@ -346,12 +249,8 @@ class Config(object): Note: It also returns False if node doesn't exist. """ - try: - path = " ".join(self._level) + " " + path - self._run(self._make_command('isMulti', path)) - return True - except VyOSError: - return False + self._config_source.set_level(self.get_level) + return self._config_source.is_multi(path) def is_tag(self, path): """ @@ -364,12 +263,8 @@ class Config(object): Note: It also returns False if node doesn't exist. """ - try: - path = " ".join(self._level) + " " + path - self._run(self._make_command('isTag', path)) - return True - except VyOSError: - return False + self._config_source.set_level(self.get_level) + return self._config_source.is_tag(path) def is_leaf(self, path): """ @@ -382,12 +277,8 @@ class Config(object): Note: It also returns False if node doesn't exist. """ - try: - path = " ".join(self._level) + " " + path - self._run(self._make_command('isLeaf', path)) - return True - except VyOSError: - return False + self._config_source.set_level(self.get_level) + return self._config_source.is_leaf(path) def return_value(self, path, default=None): """ @@ -548,9 +439,6 @@ class Config(object): Returns: str list: child node names - - Raises: - VyOSError: if the node is not a tag node """ if self._running_config: try: -- cgit v1.2.3 From faaad2e029fd3520b62983dd78c7692434138360 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Wed, 22 Jul 2020 23:36:44 +0100 Subject: xml: T2582: use xml for is_tag and is_leaf --- python/vyos/validate.py | 7 ++++--- python/vyos/xml/__init__.py | 15 +++++++++++++++ python/vyos/xml/definition.py | 23 +++++++++++++++++++---- python/vyos/xml/test_xml.py | 2 +- src/tests/test_initial_setup.py | 10 ++++++---- 5 files changed, 45 insertions(+), 12 deletions(-) (limited to 'python') diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 9072c5817..a0620e4dd 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -19,6 +19,7 @@ import netifaces import ipaddress from vyos.util import cmd +from vyos import xml # Important note when you are adding new validation functions: # @@ -293,12 +294,12 @@ def is_member(conf, interface, intftype=None): for it in intftype: base = 'interfaces ' + it for intf in conf.list_nodes(base): - memberintf = f'{base} {intf} member interface' - if conf.is_tag(memberintf): + memberintf = [base, intf, 'member', 'interface'] + if xml.is_tag(memberintf): if interface in conf.list_nodes(memberintf): ret_val = intf break - elif conf.is_leaf(memberintf): + elif xml.is_leaf(memberintf): if ( conf.exists(memberintf) and interface in conf.return_values(memberintf) ): ret_val = intf diff --git a/python/vyos/xml/__init__.py b/python/vyos/xml/__init__.py index 6e0e73b1b..0f914fed2 100644 --- a/python/vyos/xml/__init__.py +++ b/python/vyos/xml/__init__.py @@ -35,6 +35,18 @@ def load_configuration(cache=[]): return xml +# def is_multi(lpath): +# return load_configuration().is_multi(lpath) + + +def is_tag(lpath): + return load_configuration().is_tag(lpath) + + +def is_leaf(lpath, flat=True): + return load_configuration().is_leaf(lpath, flat) + + def defaults(lpath, flat=False): return load_configuration().defaults(lpath, flat) @@ -42,3 +54,6 @@ def defaults(lpath, flat=False): if __name__ == '__main__': print(defaults(['service'], flat=True)) print(defaults(['service'], flat=False)) + + print(is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + print(is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index b0339b228..098e64f7e 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -126,10 +126,12 @@ class XML(dict): elif word: if data_node != kw.plainNode or len(passed) == 1: self.options = [_ for _ in self.tree if _.startswith(word)] + self.options.sort() else: self.options = [] else: self.options = named_options + self.options.sort() self.plain = not is_dataNode @@ -143,6 +145,7 @@ class XML(dict): self.word = '' if self.tree.get(kw.node,'') not in (kw.tagNode, kw.leafNode): self.options = [_ for _ in self.tree if not kw.found(_)] + self.options.sort() def checks(self, cmd): # as we move thought the named node twice @@ -300,16 +303,28 @@ class XML(dict): return tree def _get(self, lpath, tag, with_tag=True): - return self._tree(lpath + [tag], with_tag) + tree = self._tree(lpath, with_tag) + if tree is None: + return None + return tree.get(tag, None) def is_multi(self, lpath, with_tag=True): - return self._get(lpath, kw.multi, with_tag) is True + tree = self._get(lpath, kw.multi, with_tag) + if tree is None: + return None + return tree is True def is_tag(self, lpath, with_tag=True): - return self._get(lpath, kw.node, with_tag) == kw.tagNode + tree = self._get(lpath, kw.node, with_tag) + if tree is None: + return None + return tree == kw.tagNode def is_leaf(self, lpath, with_tag=True): - return self._get(lpath, kw.node, with_tag) == kw.leafNode + tree = self._get(lpath, kw.node, with_tag) + if tree is None: + return None + return tree == kw.leafNode def exists(self, lpath, with_tag=True): return self._get(lpath, kw.node, with_tag) is not None diff --git a/python/vyos/xml/test_xml.py b/python/vyos/xml/test_xml.py index ac0620d99..ff55151d2 100644 --- a/python/vyos/xml/test_xml.py +++ b/python/vyos/xml/test_xml.py @@ -33,7 +33,7 @@ class TestSearch(TestCase): last = self.xml.traverse("") self.assertEqual(last, '') self.assertEqual(self.xml.inside, []) - self.assertEqual(self.xml.options, ['protocols', 'service', 'system', 'firewall', 'interfaces', 'vpn', 'nat', 'vrf', 'high-availability']) + self.assertEqual(self.xml.options, ['firewall', 'high-availability', 'interfaces', 'nat', 'protocols', 'service', 'system', 'vpn', 'vrf']) self.assertEqual(self.xml.filling, False) self.assertEqual(self.xml.word, last) self.assertEqual(self.xml.check, False) diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py index c4c59b827..1597025e8 100644 --- a/src/tests/test_initial_setup.py +++ b/src/tests/test_initial_setup.py @@ -21,6 +21,7 @@ import tempfile import unittest from unittest import TestCase, mock +from vyos import xml import vyos.configtree import vyos.initialsetup as vis @@ -30,6 +31,7 @@ class TestInitialSetup(TestCase): with open('tests/data/config.boot.default', 'r') as f: config_string = f.read() self.config = vyos.configtree.ConfigTree(config_string) + self.xml = xml.load_configuration() def test_set_user_password(self): vis.set_user_password(self.config, 'vyos', 'vyosvyos') @@ -56,7 +58,7 @@ class TestInitialSetup(TestCase): self.assertEqual(key_type, 'ssh-rsa') self.assertEqual(key_data, 'fakedata') - self.assertTrue(self.config.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + self.assertTrue(self.xml.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) def test_set_ssh_key_without_name(self): # If key file doesn't include a name, the function will use user name for the key name @@ -69,7 +71,7 @@ class TestInitialSetup(TestCase): self.assertEqual(key_type, 'ssh-rsa') self.assertEqual(key_data, 'fakedata') - self.assertTrue(self.config.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + self.assertTrue(self.xml.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) def test_create_user(self): vis.create_user(self.config, 'jrandomhacker', password='qwerty', key=" ssh-rsa fakedata jrandomhacker@foovax ") @@ -95,8 +97,8 @@ class TestInitialSetup(TestCase): vis.set_default_gateway(self.config, '192.0.2.1') self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1'])) - self.assertTrue(self.config.is_tag(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop'])) - self.assertTrue(self.config.is_tag(['protocols', 'static', 'route'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route'])) if __name__ == "__main__": unittest.main() -- cgit v1.2.3 From 1a85e758b105d493bb9d95916816bd206345bc5d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jul 2020 15:59:06 +0200 Subject: vyos.util: add common helper to load kernel modules l2tpv3, wireguard, wirelessmodem, nat all require additional Kernel modules to be present on the system. Each and every interface implemented their own way of loading a module - by copying code. Use a generic function, vyos.util.check_kmod() to load any arbitrary kernel module passed as string or list. --- python/vyos/util.py | 9 +++++++++ src/conf_mode/interfaces-l2tpv3.py | 12 ++++-------- src/conf_mode/interfaces-wireguard.py | 12 +++--------- src/conf_mode/interfaces-wirelessmodem.py | 9 ++------- src/conf_mode/nat.py | 17 ++++++----------- src/op_mode/wireguard.py | 17 ++++++----------- 6 files changed, 30 insertions(+), 46 deletions(-) (limited to 'python') diff --git a/python/vyos/util.py b/python/vyos/util.py index 924df6b3a..7234be6cb 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -652,3 +652,12 @@ def get_bridge_member_config(conf, br, intf): conf.set_level(old_level) return memberconf + +def check_kmod(k_mod): + """ Common utility function to load required kernel modules on demand """ + if isinstance(k_mod, str): + k_mod = k_mod.split() + for module in k_mod: + if not os.path.exists(f'/sys/module/{module}'): + if call(f'modprobe {module}') != 0: + raise ConfigError(f'Loading Kernel module {module} failed') diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 4ff0bcb57..866419f2c 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -24,11 +24,14 @@ from vyos.config import Config from vyos.ifconfig import L2TPv3If, Interface from vyos import ConfigError from vyos.util import call +from vyos.util import check_kmod from vyos.validate import is_member, is_addr_assigned from vyos import airbag airbag.enable() +k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] + default_config_data = { 'address': [], 'deleted': False, @@ -53,13 +56,6 @@ default_config_data = { 'tunnel_id': '' } -def check_kmod(): - modules = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - def get_config(): l2tpv3 = deepcopy(default_config_data) conf = Config() @@ -283,7 +279,7 @@ def apply(l2tpv3): if __name__ == '__main__': try: - check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index c24c9a7ce..982aefa5f 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -25,6 +25,7 @@ from vyos.config import Config from vyos.configdict import list_diff from vyos.ifconfig import WireGuardIf from vyos.util import chown, chmod_750, call +from vyos.util import check_kmod from vyos.validate import is_member, is_ipv6 from vyos import ConfigError @@ -32,6 +33,7 @@ from vyos import airbag airbag.enable() kdir = r'/config/auth/wireguard' +k_mod = 'wireguard' default_config_data = { 'intfc': '', @@ -50,14 +52,6 @@ default_config_data = { 'vrf': '' } -def _check_kmod(): - modules = ['wireguard'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - - def _migrate_default_keys(): if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'): location = f'{kdir}/default' @@ -315,7 +309,7 @@ def apply(wg): if __name__ == '__main__': try: - _check_kmod() + check_kmod(k_mod) _migrate_default_keys() c = get_config() verify(c) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index ec5a85e54..0964a8f4d 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -29,12 +29,7 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def check_kmod(): - modules = ['option', 'usb_wwan', 'usbserial'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') +k_mod = ['option', 'usb_wwan', 'usbserial'] def find_device_file(device): """ Recurively search /dev for the given device file and return its full path. @@ -153,7 +148,7 @@ def apply(wwan): if __name__ == '__main__': try: - check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 2299717a8..dd34dfd66 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -24,13 +24,17 @@ from netifaces import interfaces from vyos.config import Config from vyos.template import render -from vyos.util import call, cmd +from vyos.util import call +from vyos.util import cmd +from vyos.util import check_kmod from vyos.validate import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() +k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] + default_config_data = { 'deleted': False, 'destination': [], @@ -44,15 +48,6 @@ default_config_data = { iptables_nat_config = '/tmp/vyos-nat-rules.nft' -def _check_kmod(): - """ load required Kernel modules """ - modules = ['nft_nat', 'nft_chain_nat_ipv4'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - - def get_handler(json, chain, target): """ Get nftable rule handler number of given chain/target combination. Handler is required when adding NAT/Conntrack helper targets """ @@ -269,7 +264,7 @@ def apply(nat): if __name__ == '__main__': try: - _check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py index 15bf63e81..e08bc983a 100755 --- a/src/op_mode/wireguard.py +++ b/src/op_mode/wireguard.py @@ -21,22 +21,17 @@ import shutil import syslog as sl import re +from vyos.config import Config from vyos.ifconfig import WireGuardIf - +from vyos.util import cmd +from vyos.util import run +from vyos.util import check_kmod from vyos import ConfigError -from vyos.config import Config -from vyos.util import cmd, run dir = r'/config/auth/wireguard' psk = dir + '/preshared.key' -def check_kmod(): - """ check if kmod is loaded, if not load it """ - if not os.path.exists('/sys/module/wireguard'): - sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") - if run('sudo modprobe wireguard') != 0: - sl.syslog(sl.LOG_ERR, "modprobe wireguard failed") - raise ConfigError("modprobe wireguard failed") +k_mod = 'wireguard' def generate_keypair(pk, pub): """ generates a keypair which is stored in /config/auth/wireguard """ @@ -106,7 +101,7 @@ def del_key_dir(kname): if __name__ == '__main__': - check_kmod() + check_kmod(k_mod) parser = argparse.ArgumentParser(description='wireguard key management') parser.add_argument( '--genkey', action="store_true", help='generate key-pair') -- cgit v1.2.3 From ebefa38b9fa946fde82a4c9b55122c037598143b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 1 Jul 2020 19:06:52 +0200 Subject: ethernet: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. While providing a new update() method in vyos.ifconfig.interfaces() this is extended for ethernet based interfaces which also supports 802.1q, 802.1ad VLANs. This commit migrates the existing codebase for an ethernet based interfaces and implements the missing parts for VLANs. Adding or migrating other interfaces (e.g. bridge or bond) will become much easier as they must reuse the entire functionality - we now walk towards a single codepath. Thanks for all who made this combined effort possible! Signed-off-by: Christian Poessinger --- interface-definitions/interfaces-ethernet.xml.in | 2 + python/vyos/configdict.py | 29 +- python/vyos/configverify.py | 53 +++- python/vyos/ifconfig/ethernet.py | 101 ++++++- python/vyos/ifconfig/interface.py | 106 +++++++- python/vyos/ifconfig_vlan.py | 24 ++ src/conf_mode/interfaces-ethernet.py | 329 +++++------------------ 7 files changed, 371 insertions(+), 273 deletions(-) (limited to 'python') diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index 1e32a15f8..e8f3f09f1 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -56,6 +56,7 @@ duplex must be auto, half or full + auto #include @@ -265,6 +266,7 @@ Speed must be auto, 10, 100, 1000, 2500, 5000, 10000, 25000, 40000, 50000 or 100000 + auto #include #include diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 0dc7578d8..682caed8f 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -102,12 +102,35 @@ def dict_merge(source, destination): return tmp def list_diff(first, second): - """ - Diff two dictionaries and return only unique items - """ + """ Diff two dictionaries and return only unique items """ second = set(second) return [item for item in first if item not in second] +def T2665_default_dict_cleanup(dict): + """ Cleanup default keys for tag nodes https://phabricator.vyos.net/T2665. """ + # Cleanup + for vif in ['vif', 'vif_s']: + if vif in dict.keys(): + for key in ['ip', 'mtu']: + if key in dict[vif].keys(): + del dict[vif][key] + + # cleanup VIF-S defaults + if 'vif_c' in dict[vif].keys(): + for key in ['ip', 'mtu']: + if key in dict[vif]['vif_c'].keys(): + del dict[vif]['vif_c'][key] + # If there is no vif-c defined and we just cleaned the default + # keys - we can clean the entire vif-c dict as it's useless + if not dict[vif]['vif_c']: + del dict[vif]['vif_c'] + + # If there is no real vif/vif-s defined and we just cleaned the default + # keys - we can clean the entire vif dict as it's useless + if not dict[vif]: + del dict[vif] + + return dict def get_ethertype(ethertype_val): if ethertype_val == '0x88A8': diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 32129a048..36b10c956 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -41,14 +41,14 @@ def verify_vrf(config): def verify_address(config): """ - Common helper function used by interface implementations to - perform recurring validation of IP address assignmenr - when interface also is part of a bridge. + Common helper function used by interface implementations to perform + recurring validation of IP address assignment when interface is part + of a bridge or bond. """ if {'is_bridge_member', 'address'} <= set(config): raise ConfigError( - f'Cannot assign address to interface "{ifname}" as it is a ' - f'member of bridge "{is_bridge_member}"!'.format(**config)) + 'Cannot assign address to interface "{ifname}" as it is a ' + 'member of bridge "{is_bridge_member}"!'.format(**config)) def verify_bridge_delete(config): @@ -62,6 +62,15 @@ def verify_bridge_delete(config): 'Interface "{ifname}" cannot be deleted as it is a ' 'member of bridge "{is_bridge_member}"!'.format(**config)) +def verify_interface_exists(config): + """ + Common helper function used by interface implementations to perform + recurring validation if an interface actually exists. + """ + from netifaces import interfaces + if not config['ifname'] in interfaces(): + raise ConfigError(f'Interface "{ifname}" does not exist!' + .format(**config)) def verify_source_interface(config): """ @@ -76,3 +85,37 @@ def verify_source_interface(config): if not config['source_interface'] in interfaces(): raise ConfigError(f'Source interface {source_interface} does not ' f'exist'.format(**config)) + +def verify_dhcpv6(config): + """ + Common helper function used by interface implementations to perform + recurring validation of DHCPv6 options which are mutually exclusive. + """ + if {'parameters_only', 'temporary'} <= set(config.get('dhcpv6_options', {})): + raise ConfigError('DHCPv6 temporary and parameters-only options ' + 'are mutually exclusive!') + +def verify_vlan_config(config): + """ + Common helper function used by interface implementations to perform + recurring validation of interface VLANs + """ + # 802.1q VLANs + for vlan in config.get('vif', {}).keys(): + vlan = config['vif'][vlan] + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) + + # 802.1ad (Q-in-Q) VLANs + for vlan in config.get('vif_s', {}).keys(): + vlan = config['vif_s'][vlan] + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) + + for vlan in config.get('vif_s', {}).get('vif_c', {}).keys(): + vlan = config['vif_c'][vlan] + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 5b18926c9..8a50a8699 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -15,13 +15,14 @@ import os import re +import jmespath +from vyos.configdict import get_ethertype from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.validate import assert_list from vyos.util import run - @Interface.register @VLAN.enable class EthernetIf(Interface): @@ -252,3 +253,101 @@ class EthernetIf(Interface): >>> i.set_udp_offload('on') """ return self.set_interface('ufo', state) + + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # now call the regular function from within our base class + super().update(config) + + # disable ethernet flow control (pause frames) + value = 'off' if 'disable_flow_control' in config.keys() else 'on' + self.set_flow_control(value) + + # GRO (generic receive offload) + tmp = jmespath.search('offload_options.generic_receive', config) + value = tmp if (tmp != None) else 'off' + self.set_gro(value) + + # GSO (generic segmentation offload) + tmp = jmespath.search('offload_options.generic_segmentation', config) + value = tmp if (tmp != None) else 'off' + self.set_gso(value) + + # scatter-gather option + tmp = jmespath.search('offload_options.scatter_gather', config) + value = tmp if (tmp != None) else 'off' + self.set_sg(value) + + # TSO (TCP segmentation offloading) + tmp = jmespath.search('offload_options.udp_fragmentation', config) + value = tmp if (tmp != None) else 'off' + self.set_tso(value) + + # UDP fragmentation offloading + tmp = jmespath.search('offload_options.udp_fragmentation', config) + value = tmp if (tmp != None) else 'off' + self.set_ufo(value) + + # Set physical interface speed and duplex + if {'speed', 'duplex'} <= set(config): + speed = config.get('speed') + duplex = config.get('duplex') + self.set_speed_duplex(speed, duplex) + + # Delete old IPv6 EUI64 addresses before changing MAC + + # Change interface MAC address - re-set to real hardware address (hw-id) + # if custom mac is removed. Skip if bond member. + if 'is_bond_member' not in config: + mac = config.get('hw_id') + if 'mac' in config: + mac = config.get('mac') + if mac: + self.set_mac(mac) + + # Add IPv6 EUI-based addresses + tmp = jmespath.search('ipv6.address.eui64', config) + if tmp: + # XXX: T2636 workaround: convert string to a list with one element + if isinstance(tmp, str): + tmp = [tmp] + for addr in tmp: + self.add_ipv6_eui64_address(addr) + + # re-add ourselves to any bridge we might have fallen out of + if 'is_bridge_member' in config: + bridge = config.get('is_bridge_member') + self.add_to_bridge(bridge) + + # remove no longer required 802.1ad (Q-in-Q VLANs) + for vif_s_id in config.get('vif_s_remove', {}): + self.del_vlan(vif_s_id) + + # create/update 802.1ad (Q-in-Q VLANs) + for vif_s_id, vif_s in config.get('vif_s', {}).items(): + tmp=get_ethertype(vif_s.get('ethertype', '0x88A8')) + s_vlan = self.add_vlan(vif_s_id, ethertype=tmp) + s_vlan.update(vif_s) + + # remove no longer required client VLAN (vif-c) + for vif_c_id in vif_s.get('vif_c_remove', {}): + s_vlan.del_vlan(vif_c_id) + + # create/update client VLAN (vif-c) interface + for vif_c_id, vif_c in vif_s.get('vif_c', {}).items(): + c_vlan = s_vlan.add_vlan(vif_c_id) + c_vlan.update(vif_c) + + # remove no longer required 802.1q VLAN interfaces + for vif_id in config.get('vif_remove', {}): + self.del_vlan(vif_id) + + # create/update 802.1q VLAN interfaces + for vif_id, vif in config.get('vif', {}).items(): + vlan = self.add_vlan(vif_id) + vlan.update(vif) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 8d7b247fc..689faa22b 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -16,6 +16,7 @@ import os import re import json +import jmespath from copy import deepcopy from ipaddress import IPv4Network @@ -322,11 +323,11 @@ class Interface(Control): self.set_admin_state('down') self.set_interface('mac', mac) - + # Turn an interface to the 'up' state if it was changed to 'down' by this fucntion if prev_state == 'up': self.set_admin_state('up') - + def set_vrf(self, vrf=''): """ Add/Remove interface from given VRF instance. @@ -773,14 +774,17 @@ class Interface(Control): on any interface. """ # Update interface description - self.set_alias(config.get('description', None)) + self.set_alias(config.get('description', '')) + + # Ignore link state changes + value = '2' if 'disable_link_detect' in config else '1' + self.set_link_detect(value) # Configure assigned interface IP addresses. No longer # configured addresses will be removed first new_addr = config.get('address', []) - # XXX workaround for T2636, convert IP address string to a list - # with one element + # XXX: T2636 workaround: convert string to a list with one element if isinstance(new_addr, str): new_addr = [new_addr] @@ -800,6 +804,96 @@ class Interface(Control): # Bind interface instance into VRF self.set_vrf(config.get('vrf', '')) + # DHCP options + if 'dhcp_options' in config: + dhcp_options = config.get('dhcp_options') + if 'client_id' in dhcp_options: + self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') + + if 'host_name' in dhcp_options: + self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') + + if 'vendor_class_id' in dhcp_options: + self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') + + # DHCPv6 options + if 'dhcpv6_options' in config: + dhcpv6_options = config.get('dhcpv6_options') + if 'parameters_only' in dhcpv6_options: + self.dhcp.v6.options['dhcpv6_prm_only'] = True + + if 'temporary' in dhcpv6_options: + self.dhcp.v6.options['dhcpv6_temporary'] = True + + if 'prefix_delegation' in dhcpv6_options: + prefix_delegation = dhcpv6_options.get('prefix_delegation') + if 'length' in prefix_delegation: + self.dhcp.v6.options['dhcpv6_pd_length'] = prefix_delegation.get('length') + + if 'interface' in prefix_delegation: + self.dhcp.v6.options['dhcpv6_pd_interfaces'] = prefix_delegation.get('interface') + + # Configure ARP cache timeout in milliseconds - has default value + tmp = jmespath.search('ip.arp_cache_timeout', config) + value = tmp if (tmp != None) else '30' + self.set_arp_cache_tmo(value) + + # Configure ARP filter configuration + tmp = jmespath.search('ip.disable_arp_filter', config) + value = '0' if (tmp != None) else '1' + self.set_arp_filter(value) + + # Configure ARP accept + tmp = jmespath.search('ip.enable_arp_accept', config) + value = '1' if (tmp != None) else '0' + self.set_arp_accept(value) + + # Configure ARP announce + tmp = jmespath.search('ip.enable_arp_announce', config) + value = '1' if (tmp != None) else '0' + self.set_arp_announce(value) + + # Configure ARP ignore + tmp = jmespath.search('ip.enable_arp_ignore', config) + value = '1' if (tmp != None) else '0' + self.set_arp_ignore(value) + + # Enable proxy-arp on this interface + tmp = jmespath.search('ip.enable_proxy_arp', config) + value = '1' if (tmp != None) else '0' + self.set_proxy_arp(value) + + # Enable private VLAN proxy ARP on this interface + tmp = jmespath.search('ip.proxy_arp_pvlan', config) + value = '1' if (tmp != None) else '0' + self.set_proxy_arp_pvlan(value) + + # IPv6 forwarding + tmp = jmespath.search('ipv6.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + self.set_ipv6_forwarding(value) + + # IPv6 router advertisements + tmp = jmespath.search('ipv6.address.autoconf', config) + value = '2' if (tmp != None) else '1' + if 'dhcpv6' in new_addr: + value = '2' + self.set_ipv6_accept_ra(value) + + # IPv6 address autoconfiguration + tmp = jmespath.search('ipv6.address.autoconf', config) + value = '1' if (tmp != None) else '0' + self.set_ipv6_autoconf(value) + + # IPv6 Duplicate Address Detection (DAD) tries + tmp = jmespath.search('ipv6.dup_addr_detect_transmits', config) + value = tmp if (tmp != None) else '1' + self.set_ipv6_dad_messages(value) + + # MTU - Maximum Transfer Unit + if 'mtu' in config: + self.set_mtu(config.get('mtu')) + # Interface administrative state - state = 'down' if 'disable' in config.keys() else 'up' + state = 'down' if 'disable' in config else 'up' self.set_admin_state(state) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index 442cb0db8..ecb6796fa 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -16,6 +16,30 @@ from netifaces import interfaces from vyos import ConfigError +def get_removed_vlans(conf, dict): + """ + Common function to parse a dictionary retrieved via get_config_dict() and + determine any added/removed VLAN interfaces - be it 802.1q or Q-in-Q. + """ + from vyos.configdiff import get_config_diff, Diff + + # Check vif, vif-s/vif-c VLAN interfaces for removal + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(['vif'], expand_nodes=Diff.DELETE)['delete'].keys() + dict['vif_remove'] = [*keys] + + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() + dict['vif_s_remove'] = [*keys] + + for vif in dict.get('vif_s', {}).keys(): + keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() + dict['vif_s'][vif]['vif_c_remove'] = [*keys] + + return dict + def apply_all_vlans(intf, intfconfig): """ Function applies all VLANs to the passed interface. diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 8b895c4d2..60aafae32 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -17,295 +17,108 @@ import os from sys import exit -from copy import deepcopy -from netifaces import interfaces +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import T2665_default_dict_cleanup +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_address +from vyos.configverify import verify_vrf +from vyos.configverify import verify_vlan_config from vyos.ifconfig import EthernetIf -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data +from vyos.ifconfig_vlan import get_removed_vlans from vyos.validate import is_member -from vyos.config import Config +from vyos.xml import defaults from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'deleted': False, - 'duplex': 'auto', - 'flow_control': 'on', - 'hw_id': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'is_bond_member': False, - 'intf': '', - 'offload_gro': 'off', - 'offload_gso': 'off', - 'offload_sg': 'off', - 'offload_tso': 'off', - 'offload_ufo': 'off', - 'speed': 'auto', - 'vif_s': {}, - 'vif_s_remove': [], - 'vif': {}, - 'vif_remove': [], - 'vrf': '' -} - def get_config(): + """ Retrive CLI config as dictionary. Dictionary can never be empty, + as at least the interface name will be added or a deleted flag """ + conf = Config() + # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - ifname = os.environ['VYOS_TAGNODE_VALUE'] - conf = Config() - - # check if ethernet interface has been removed - cfg_base = ['interfaces', 'ethernet', ifname] - if not conf.exists(cfg_base): - eth = deepcopy(default_config_data) - eth['intf'] = ifname - eth['deleted'] = True - # we can not bail out early as ethernet interface can not be removed - # Kernel will complain with: RTNETLINK answers: Operation not supported. - # Thus we need to remove individual settings - return eth - - # set new configuration level - conf.set_level(cfg_base) - - eth, disabled = intf_to_dict(conf, default_config_data) - - # disable ethernet flow control (pause frames) - if conf.exists('disable-flow-control'): - eth['flow_control'] = 'off' - - # retrieve real hardware address - if conf.exists('hw-id'): - eth['hw_id'] = conf.return_value('hw-id') - - # interface duplex - if conf.exists('duplex'): - eth['duplex'] = conf.return_value('duplex') + # retrieve interface default values + base = ['interfaces', 'ethernet'] + default_values = defaults(base) - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - eth['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # Enable private VLAN proxy ARP on this interface - if conf.exists('ip proxy-arp-pvlan'): - eth['ip_proxy_arp_pvlan'] = 1 - - # check if we are a member of any bond - eth['is_bond_member'] = is_member(conf, eth['intf'], 'bonding') - - # GRO (generic receive offload) - if conf.exists('offload-options generic-receive'): - eth['offload_gro'] = conf.return_value('offload-options generic-receive') - - # GSO (generic segmentation offload) - if conf.exists('offload-options generic-segmentation'): - eth['offload_gso'] = conf.return_value('offload-options generic-segmentation') - - # scatter-gather option - if conf.exists('offload-options scatter-gather'): - eth['offload_sg'] = conf.return_value('offload-options scatter-gather') - - # TSO (TCP segmentation offloading) - if conf.exists('offload-options tcp-segmentation'): - eth['offload_tso'] = conf.return_value('offload-options tcp-segmentation') - - # UDP fragmentation offloading - if conf.exists('offload-options udp-fragmentation'): - eth['offload_ufo'] = conf.return_value('offload-options udp-fragmentation') - - # interface speed - if conf.exists('speed'): - eth['speed'] = conf.return_value('speed') - - # remove default IPv6 link-local address if member of a bond - if eth['is_bond_member'] and 'fe80::/64' in eth['ipv6_eui64_prefix']: - eth['ipv6_eui64_prefix'].remove('fe80::/64') - eth['ipv6_eui64_prefix_remove'].append('fe80::/64') - - add_to_dict(conf, disabled, eth, 'vif', 'vif') - add_to_dict(conf, disabled, eth, 'vif-s', 'vif_s') - - return eth - - -def verify(eth): - if eth['deleted']: + ifname = os.environ['VYOS_TAGNODE_VALUE'] + base = base + [ifname] + # setup config level which is extracted in get_removed_vlans() + conf.set_level(base) + ethernet = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + + # Check if interface has been removed + if ethernet == {}: + ethernet.update({'deleted' : ''}) + + # We have gathered the dict representation of the CLI, but there are + # default options which we need to update into the dictionary + # retrived. + ethernet = dict_merge(default_values, ethernet) + + # Add interface instance name into dictionary + ethernet.update({'ifname': ifname}) + + # Check if we are a member of a bridge device + bridge = is_member(conf, ifname, 'bridge') + if bridge: + tmp = {'is_bridge_member' : bridge} + ethernet.update(tmp) + + # Check if we are a member of a bond device + bond = is_member(conf, ifname, 'bonding') + if bond: + tmp = {'is_bond_member' : bond} + ethernet.update(tmp) + + ethernet = T2665_default_dict_cleanup( ethernet ) + # Check vif, vif-s/vif-c VLAN interfaces for removal + ethernet = get_removed_vlans( conf, ethernet ) + return ethernet + +def verify(ethernet): + if 'deleted' in ethernet.keys(): return None - if eth['intf'] not in interfaces(): - raise ConfigError(f"Interface ethernet {eth['intf']} does not exist") + verify_interface_exists(ethernet) - if eth['speed'] == 'auto': - if eth['duplex'] != 'auto': + if ethernet.get('speed', None) == 'auto': + if ethernet.get('duplex', None) != 'auto': raise ConfigError('If speed is hardcoded, duplex must be hardcoded, too') - if eth['duplex'] == 'auto': - if eth['speed'] != 'auto': + if ethernet.get('duplex', None) == 'auto': + if ethernet.get('speed', None) != 'auto': raise ConfigError('If duplex is hardcoded, speed must be hardcoded, too') - if eth['dhcpv6_prm_only'] and eth['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + verify_dhcpv6(ethernet) + verify_address(ethernet) + verify_vrf(ethernet) - memberof = eth['is_bridge_member'] if eth['is_bridge_member'] else eth['is_bond_member'] - - if ( memberof - and ( eth['address'] - or eth['ipv6_eui64_prefix'] - or eth['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{eth["intf"]}" ' - f'as it is a member of "{memberof}"!')) - - if eth['vrf']: - if eth['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{eth["vrf"]}" does not exist') - - if memberof: - raise ConfigError(( - f'Interface "{eth["intf"]}" cannot be member of VRF "{eth["vrf"]}" ' - f'and "{memberof}" at the same time!')) - - if eth['mac'] and eth['is_bond_member']: - print('WARNING: "mac {0}" command will be ignored because {1} is a part of {2}'\ - .format(eth['mac'], eth['intf'], eth['is_bond_member'])) + if {'is_bond_member', 'mac'} <= set(ethernet): + print(f'WARNING: changing mac address "{mac}" will be ignored as "{ifname}" ' + f'is a member of bond "{is_bond_member}"'.format(**ethernet)) # use common function to verify VLAN configuration - verify_vlan_config(eth) + verify_vlan_config(ethernet) return None -def generate(eth): +def generate(ethernet): return None -def apply(eth): - e = EthernetIf(eth['intf']) - if eth['deleted']: - # apply all vlans to interface (they need removing too) - apply_all_vlans(e, eth) - +def apply(ethernet): + e = EthernetIf(ethernet['ifname']) + if 'deleted' in ethernet.keys(): # delete interface e.remove() else: - # update interface description used e.g. within SNMP - e.set_alias(eth['description']) - - if eth['dhcp_client_id']: - e.dhcp.v4.options['client_id'] = eth['dhcp_client_id'] - - if eth['dhcp_hostname']: - e.dhcp.v4.options['hostname'] = eth['dhcp_hostname'] - - if eth['dhcp_vendor_class_id']: - e.dhcp.v4.options['vendor_class_id'] = eth['dhcp_vendor_class_id'] - - if eth['dhcpv6_prm_only']: - e.dhcp.v6.options['dhcpv6_prm_only'] = True - - if eth['dhcpv6_temporary']: - e.dhcp.v6.options['dhcpv6_temporary'] = True - - if eth['dhcpv6_pd_length']: - e.dhcp.v6.options['dhcpv6_pd_length'] = eth['dhcpv6_pd_length'] - - if eth['dhcpv6_pd_interfaces']: - e.dhcp.v6.options['dhcpv6_pd_interfaces'] = eth['dhcpv6_pd_interfaces'] - - # ignore link state changes - e.set_link_detect(eth['disable_link_detect']) - # disable ethernet flow control (pause frames) - e.set_flow_control(eth['flow_control']) - # configure ARP cache timeout in milliseconds - e.set_arp_cache_tmo(eth['ip_arp_cache_tmo']) - # configure ARP filter configuration - e.set_arp_filter(eth['ip_disable_arp_filter']) - # configure ARP accept - e.set_arp_accept(eth['ip_enable_arp_accept']) - # configure ARP announce - e.set_arp_announce(eth['ip_enable_arp_announce']) - # configure ARP ignore - e.set_arp_ignore(eth['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - e.set_proxy_arp(eth['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - e.set_proxy_arp_pvlan(eth['ip_proxy_arp_pvlan']) - # IPv6 accept RA - e.set_ipv6_accept_ra(eth['ipv6_accept_ra']) - # IPv6 address autoconfiguration - e.set_ipv6_autoconf(eth['ipv6_autoconf']) - # IPv6 forwarding - e.set_ipv6_forwarding(eth['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - e.set_ipv6_dad_messages(eth['ipv6_dup_addr_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in eth['ipv6_eui64_prefix_remove']: - e.del_ipv6_eui64_address(addr) - - # Change interface MAC address - re-set to real hardware address (hw-id) - # if custom mac is removed. Skip if bond member. - if not eth['is_bond_member']: - if eth['mac']: - e.set_mac(eth['mac']) - elif eth['hw_id']: - e.set_mac(eth['hw_id']) - - # Add IPv6 EUI-based addresses - for addr in eth['ipv6_eui64_prefix']: - e.add_ipv6_eui64_address(addr) - - # Maximum Transmission Unit (MTU) - e.set_mtu(eth['mtu']) - - # GRO (generic receive offload) - e.set_gro(eth['offload_gro']) - - # GSO (generic segmentation offload) - e.set_gso(eth['offload_gso']) - - # scatter-gather option - e.set_sg(eth['offload_sg']) - - # TSO (TCP segmentation offloading) - e.set_tso(eth['offload_tso']) - - # UDP fragmentation offloading - e.set_ufo(eth['offload_ufo']) - - # Set physical interface speed and duplex - e.set_speed_duplex(eth['speed'], eth['duplex']) - - # Enable/Disable interface - if eth['disable']: - e.set_admin_state('down') - else: - e.set_admin_state('up') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in eth['address_remove']: - e.del_addr(addr) - for addr in eth['address']: - e.add_addr(addr) - - # assign/remove VRF (ONLY when not a member of a bridge or bond, - # otherwise 'nomaster' removes it from it) - if not ( eth['is_bridge_member'] or eth['is_bond_member'] ): - e.set_vrf(eth['vrf']) - - # re-add ourselves to any bridge we might have fallen out of - if eth['is_bridge_member']: - e.add_to_bridge(eth['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(e, eth) + e.update(ethernet) if __name__ == '__main__': -- cgit v1.2.3 From 2b1c3dc86fe4033030855d61bf453aa730b6c230 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Jul 2020 13:55:51 +0200 Subject: vlan: ifconfig: T2653: only enable interface when lower interface is up A VLAN interface can only be placed in admin up state when the lower interface is up, too. If this is not the case the operating system will throw and exception. --- python/vyos/ifconfig/interface.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 689faa22b..be3617f7d 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -17,7 +17,9 @@ import os import re import json import jmespath + from copy import deepcopy +from glob import glob from ipaddress import IPv4Network from ipaddress import IPv6Address @@ -73,8 +75,12 @@ class Interface(Control): _command_get = { 'admin_state': { 'shellcmd': 'ip -json link show dev {ifname}', - 'format': lambda j: 'up' if 'UP' in json.loads(j)[0]['flags'] else 'down', - } + 'format': lambda j: 'up' if 'UP' in jmespath.search('[*].flags | [0]', json.loads(j)) else 'down', + }, + 'vlan_protocol': { + 'shellcmd': 'ip -json -details link show dev {ifname}', + 'format': lambda j: jmespath.search('[*].linkinfo.info_data.protocol | [0]', json.loads(j)), + }, } _command_set = { @@ -544,6 +550,17 @@ class Interface(Control): """ self.set_interface('alias', ifalias) + def get_vlan_protocol(self): + """ + Retrieve VLAN protocol in use, this can be 802.1Q, 802.1ad or None + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0.10').get_vlan_protocol() + '802.1Q' + """ + return self.get_interface('vlan_protocol') + def get_admin_state(self): """ Get interface administrative state. Function will return 'up' or 'down' @@ -565,6 +582,17 @@ class Interface(Control): >>> Interface('eth0').get_admin_state() 'down' """ + # A VLAN interface can only be placed in admin up state when + # the lower interface is up, too + if self.get_vlan_protocol(): + lower_interface = glob(f'/sys/class/net/{self.ifname}/lower*/flags')[0] + with open(lower_interface, 'r') as f: + flags = f.read() + # If parent is not up - bail out as we can not bring up the VLAN. + # Flags are defined in kernel source include/uapi/linux/if.h + if not int(flags, 16) & 1: + return None + return self.set_interface('admin_state', state) def set_proxy_arp(self, enable): -- cgit v1.2.3 From a25d7095e009469d8ef60b63deddd94d30921723 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Jul 2020 20:45:29 +0200 Subject: bridge: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. While providing a new update() method in vyos.ifconfig.interfaces() this is extended for bridge interfaces in the derived bridge class. Signed-off-by: Christian Poessinger --- interface-definitions/interfaces-bridge.xml.in | 7 + python/vyos/configdict.py | 97 ++++++ python/vyos/ifconfig/bridge.py | 68 +++- python/vyos/ifconfig/ethernet.py | 20 -- python/vyos/ifconfig/interface.py | 25 ++ python/vyos/ifconfig_vlan.py | 9 +- python/vyos/util.py | 2 +- src/conf_mode/interfaces-bridge.py | 413 +++++-------------------- src/conf_mode/interfaces-ethernet.py | 54 +--- 9 files changed, 296 insertions(+), 399 deletions(-) (limited to 'python') diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 6b610e623..92356d696 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -32,6 +32,7 @@ + 300 #include #include @@ -51,6 +52,7 @@ Forwarding delay must be between 0 and 200 seconds + 14 @@ -64,6 +66,7 @@ Bridge Hello interval must be between 1 and 10 seconds + 2 @@ -107,6 +110,7 @@ Bridge max aging value must be between 1 and 40 seconds + 20 @@ -133,6 +137,7 @@ Path cost value must be between 1 and 65535 + 100 @@ -146,6 +151,7 @@ Port priority value must be between 0 and 63 + 32 @@ -163,6 +169,7 @@ Bridge priority must be between 0 and 65535 (multiples of 4096) + 32768 diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 682caed8f..4fca426cd 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -17,6 +17,7 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. """ +import jmespath from enum import Enum from copy import deepcopy @@ -132,6 +133,102 @@ def T2665_default_dict_cleanup(dict): return dict +def leaf_node_changed(conf, key): + """ + Check if a leaf node was altered. If it has been altered - values has been + changed, or it was added/removed, we will return the old value. If nothing + has been changed, None is returned + """ + from vyos.configdiff import get_config_diff + + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + (new, old) = D.get_value_diff(key) + if new != old: + if isinstance(old, str): + return old + elif isinstance(old, list): + if isinstance(new, str): + new = [new] + elif isinstance(new, type(None)): + new = [] + return list_diff(old, new) + + return None + +def get_interface_dict(config, base, ifname): + """ + Common utility function to retrieve and mandgle the interfaces available + in CLI configuration. All interfaces have a common base ground where the + value retrival is identical - so it can and should be reused + + Will return a dictionary with the necessary interface configuration + """ + from vyos.xml import defaults + from vyos.ifconfig_vlan import get_removed_vlans + + # retrieve interface default values + default_values = defaults(base) + + # setup config level which is extracted in get_removed_vlans() + config.set_level(base + [ifname]) + dict = config.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + + # Check if interface has been removed + if dict == {}: + dict.update({'deleted' : ''}) + + # Add interface instance name into dictionary + dict.update({'ifname': ifname}) + + # We have gathered the dict representation of the CLI, but there are + # default options which we need to update into the dictionary + # retrived. + dict = dict_merge(default_values, dict) + + # Check if we are a member of a bridge device + bridge = is_member(config, ifname, 'bridge') + if bridge: + dict.update({'is_bridge_member' : bridge}) + + # Check if we are a member of a bond device + bond = is_member(config, ifname, 'bonding') + if bond: + dict.update({'is_bond_member' : bond}) + + mac = leaf_node_changed(config, ['mac']) + if mac: + dict.update({'mac_old' : mac}) + + eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64']) + if eui64: + # XXX: T2636 workaround: convert string to a list with one element + if isinstance(eui64, str): + eui64 = [eui64] + tmp = jmespath.search('ipv6.address', dict) + if not tmp: + dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) + else: + dict['ipv6']['address'].update({'eui64_old': eui64}) + + # remove wrongly inserted values + dict = T2665_default_dict_cleanup(dict) + + # The values are identical for vif, vif-s and vif-c as the all include the same + # XML definitions which hold the defaults + default_vif_values = defaults(base + ['vif']) + for vif, vif_config in dict.get('vif', {}).items(): + vif_config.update(default_vif_values) + for vif_s, vif_s_config in dict.get('vif_s', {}).items(): + vif_s_config.update(default_vif_values) + for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): + vif_c_config.update(default_vif_values) + + # Check vif, vif-s/vif-c VLAN interfaces for removal + dict = get_removed_vlans(config, dict) + + return dict + def get_ethertype(ethertype_val): if ethertype_val == '0x88A8': return '802.1ad' diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 44b92c1db..af950b35d 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -13,12 +13,13 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . +import jmespath from vyos.ifconfig.interface import Interface - +from vyos.ifconfig.stp import STP from vyos.validate import assert_boolean from vyos.validate import assert_positive - +from vyos.util import cmd @Interface.register class BridgeIf(Interface): @@ -187,3 +188,66 @@ class BridgeIf(Interface): >>> BridgeIf('br0').del_port('eth1') """ return self.set_interface('del_port', interface) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # now call the regular function from within our base class + super().update(config) + + # Set ageing time + value = config.get('aging') + self.set_ageing_time(value) + + # set bridge forward delay + value = config.get('forwarding_delay') + self.set_forward_delay(value) + + # set hello time + value = config.get('hello_time') + self.set_hello_time(value) + + # set max message age + value = config.get('max_age') + self.set_max_age(value) + + # set bridge priority + value = config.get('priority') + self.set_priority(value) + + # enable/disable spanning tree + value = '1' if 'stp' in config else '0' + self.set_stp(value) + + # enable or disable IGMP querier + tmp = jmespath.search('igmp.querier', config) + value = '1' if (tmp != None) else '0' + self.set_multicast_querier(value) + + # remove interface from bridge + tmp = jmespath.search('member.interface_remove', config) + if tmp: + for member in tmp: + self.del_port(member) + + STPBridgeIf = STP.enable(BridgeIf) + tmp = jmespath.search('member.interface', config) + if tmp: + for interface, interface_config in tmp.items(): + # if we've come here we already verified the interface doesn't + # have addresses configured so just flush any remaining ones + cmd(f'ip addr flush dev "{interface}"') + # enslave interface port to bridge + self.add_port(interface) + + tmp = STPBridgeIf(interface) + # set bridge port path cost + value = interface_config.get('cost') + tmp.set_path_cost(value) + + # set bridge port path priority + value = interface_config.get('priority') + tmp.set_path_priority(value) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 8a50a8699..1725116e2 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -299,26 +299,6 @@ class EthernetIf(Interface): duplex = config.get('duplex') self.set_speed_duplex(speed, duplex) - # Delete old IPv6 EUI64 addresses before changing MAC - - # Change interface MAC address - re-set to real hardware address (hw-id) - # if custom mac is removed. Skip if bond member. - if 'is_bond_member' not in config: - mac = config.get('hw_id') - if 'mac' in config: - mac = config.get('mac') - if mac: - self.set_mac(mac) - - # Add IPv6 EUI-based addresses - tmp = jmespath.search('ipv6.address.eui64', config) - if tmp: - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(tmp, str): - tmp = [tmp] - for addr in tmp: - self.add_ipv6_eui64_address(addr) - # re-add ourselves to any bridge we might have fallen out of if 'is_bridge_member' in config: bridge = config.get('is_bridge_member') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index be3617f7d..ea770af23 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -922,6 +922,31 @@ class Interface(Control): if 'mtu' in config: self.set_mtu(config.get('mtu')) + # Delete old IPv6 EUI64 addresses before changing MAC + tmp = jmespath.search('ipv6.address.eui64_old', config) + if tmp: + for addr in tmp: + self.del_ipv6_eui64_address(addr) + + # Change interface MAC address - re-set to real hardware address (hw-id) + # if custom mac is removed. Skip if bond member. + if 'is_bond_member' not in config: + mac = config.get('hw_id') + if 'mac' in config: + mac = config.get('mac') + if mac: + self.set_mac(mac) + + # Add IPv6 EUI-based addresses + tmp = jmespath.search('ipv6.address.eui64', config) + if tmp: + # XXX: T2636 workaround: convert string to a list with one element + if isinstance(tmp, str): + tmp = [tmp] + for addr in tmp: + self.add_ipv6_eui64_address(addr) + + # Interface administrative state state = 'down' if 'disable' in config else 'up' self.set_admin_state(state) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index ecb6796fa..0e4ecda53 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -28,15 +28,18 @@ def get_removed_vlans(conf, dict): D.set_level(conf.get_level()) # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 keys = D.get_child_nodes_diff(['vif'], expand_nodes=Diff.DELETE)['delete'].keys() - dict['vif_remove'] = [*keys] + if keys: + dict.update({'vif_remove': [*keys]}) # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() - dict['vif_s_remove'] = [*keys] + if keys: + dict.update({'vif_s_remove': [*keys]}) for vif in dict.get('vif_s', {}).keys(): keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() - dict['vif_s'][vif]['vif_c_remove'] = [*keys] + if keys: + dict.update({'vif_s': { vif : {'vif_c_remove': [*keys]}}}) return dict diff --git a/python/vyos/util.py b/python/vyos/util.py index 7234be6cb..7078762df 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -242,7 +242,7 @@ def chown(path, user, group): if not os.path.exists(path): return False - + uid = getpwnam(user).pw_uid gid = getgrnam(group).gr_gid os.chown(path, uid, gid) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 1e4fa5816..7998a251a 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -16,251 +16,116 @@ import os -from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import BridgeIf, Section -from vyos.ifconfig.stp import STP -from vyos.configdict import list_diff, interface_default_data -from vyos.validate import is_member, has_address_configured from vyos.config import Config -from vyos.util import cmd, get_bridge_member_config +from vyos.configdict import get_interface_dict +from vyos.configdiff import get_config_diff, Diff +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_vrf +from vyos.ifconfig import BridgeIf +from vyos.validate import is_member, has_address_configured +from vyos.xml import defaults + +from vyos.util import cmd from vyos import ConfigError from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'aging': 300, - 'arp_cache_tmo': 30, - 'deleted': False, - 'forwarding_delay': 14, - 'hello_time': 2, - 'igmp_querier': 0, - 'intf': '', - 'max_age': 20, - 'member': [], - 'member_remove': [], - 'priority': 32768, - 'stp': 0 -} +def get_removed_members(conf): + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(['member', 'interface'], expand_nodes=Diff.DELETE)['delete'].keys() + return list(keys) def get_config(): - bridge = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'bridge'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - bridge['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # Check if bridge has been removed - if not conf.exists('interfaces bridge ' + bridge['intf']): - bridge['deleted'] = True - return bridge - - # set new configuration level - conf.set_level('interfaces bridge ' + bridge['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - bridge['address'] = conf.return_values('address') - - # Determine interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed - eff_addr = conf.return_effective_values('address') - bridge['address_remove'] = list_diff(eff_addr, bridge['address']) - - # retrieve aging - how long addresses are retained - if conf.exists('aging'): - bridge['aging'] = int(conf.return_value('aging')) - - # retrieve interface description - if conf.exists('description'): - bridge['description'] = conf.return_value('description') - - # get DHCP client identifier - if conf.exists('dhcp-options client-id'): - bridge['dhcp_client_id'] = conf.return_value('dhcp-options client-id') - - # DHCP client host name (overrides the system host name) - if conf.exists('dhcp-options host-name'): - bridge['dhcp_hostname'] = conf.return_value('dhcp-options host-name') - - # DHCP client vendor identifier - if conf.exists('dhcp-options vendor-class-id'): - bridge['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') - - # DHCPv6 only acquire config parameters, no address - if conf.exists('dhcpv6-options parameters-only'): - bridge['dhcpv6_prm_only'] = True - - # DHCPv6 temporary IPv6 address - if conf.exists('dhcpv6-options temporary'): - bridge['dhcpv6_temporary'] = True - - # Disable this bridge interface - if conf.exists('disable'): - bridge['disable'] = True - - # Ignore link state changes - if conf.exists('disable-link-detect'): - bridge['disable_link_detect'] = 2 - - # Forwarding delay - if conf.exists('forwarding-delay'): - bridge['forwarding_delay'] = int(conf.return_value('forwarding-delay')) - - # Hello packet advertisment interval - if conf.exists('hello-time'): - bridge['hello_time'] = int(conf.return_value('hello-time')) - - # Enable Internet Group Management Protocol (IGMP) querier - if conf.exists('igmp querier'): - bridge['igmp_querier'] = 1 - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - bridge['arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # ARP filter configuration - if conf.exists('ip disable-arp-filter'): - bridge['ip_disable_arp_filter'] = 0 - - # ARP enable accept - if conf.exists('ip enable-arp-accept'): - bridge['ip_enable_arp_accept'] = 1 - - # ARP enable announce - if conf.exists('ip enable-arp-announce'): - bridge['ip_enable_arp_announce'] = 1 - - # ARP enable ignore - if conf.exists('ip enable-arp-ignore'): - bridge['ip_enable_arp_ignore'] = 1 - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists('ipv6 address autoconf'): - bridge['ipv6_autoconf'] = 1 - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - bridge['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - - # Determine currently effective EUI64 addresses - to determine which - # address is no longer valid and needs to be removed - eff_addr = conf.return_effective_values('ipv6 address eui64') - bridge['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, bridge['ipv6_eui64_prefix']) - - # Remove the default link-local address if set. - if conf.exists('ipv6 address no-default-link-local'): - bridge['ipv6_eui64_prefix_remove'].append('fe80::/64') - else: - # add the link-local by default to make IPv6 work - bridge['ipv6_eui64_prefix'].append('fe80::/64') - - # Disable IPv6 forwarding on this interface - if conf.exists('ipv6 disable-forwarding'): - bridge['ipv6_forwarding'] = 0 - - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists('ipv6 dup-addr-detect-transmits'): - bridge['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) - - # Media Access Control (MAC) address - if conf.exists('mac'): - bridge['mac'] = conf.return_value('mac') - - # Find out if MAC has changed - if so, we need to delete all IPv6 EUI64 addresses - # before re-adding them - if ( bridge['mac'] and bridge['intf'] in Section.interfaces(section='bridge') - and bridge['mac'] != BridgeIf(bridge['intf'], create=False).get_mac() ): - bridge['ipv6_eui64_prefix_remove'] += bridge['ipv6_eui64_prefix'] - - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if bridge['ipv6_autoconf'] or 'dhcpv6' in bridge['address']: - bridge['ipv6_accept_ra'] = 2 - - # Interval at which neighbor bridges are removed - if conf.exists('max-age'): - bridge['max_age'] = int(conf.return_value('max-age')) - - # Determine bridge member interface (currently configured) - for intf in conf.list_nodes('member interface'): - # defaults are stored in util.py (they can't be here as all interface - # scripts use the function) - memberconf = get_bridge_member_config(conf, bridge['intf'], intf) - if memberconf: - memberconf['name'] = intf - bridge['member'].append(memberconf) - - # Determine bridge member interface (currently effective) - to determine which - # interfaces is no longer assigend to the bridge and thus can be removed - eff_intf = conf.list_effective_nodes('member interface') - act_intf = conf.list_nodes('member interface') - bridge['member_remove'] = list_diff(eff_intf, act_intf) - - # Priority for this bridge - if conf.exists('priority'): - bridge['priority'] = int(conf.return_value('priority')) - - # Enable spanning tree protocol - if conf.exists('stp'): - bridge['stp'] = 1 - - # retrieve VRF instance - if conf.exists('vrf'): - bridge['vrf'] = conf.return_value('vrf') + ifname = os.environ['VYOS_TAGNODE_VALUE'] + bridge = get_interface_dict(conf, base, ifname) + + # determine which members have been removed + tmp = get_removed_members(conf) + if tmp: + if 'member' in bridge: + bridge['member'].update({'interface_remove': tmp }) + else: + bridge.update({'member': {'interface_remove': tmp }}) + + if 'member' in bridge and 'interface' in bridge['member']: + # XXX TT2665 we need a copy of the dict keys for iteration, else we will get: + # RuntimeError: dictionary changed size during iteration + for interface in list(bridge['member']['interface']): + for key in ['cost', 'priority']: + if interface == key: + del bridge['member']['interface'][key] + continue + + # the default dictionary is not properly paged into the dict (see T2665) + # thus we will ammend it ourself + default_member_values = defaults(base + ['member', 'interface']) + + for interface, interface_config in bridge['member']['interface'].items(): + interface_config.update(default_member_values) + + # Check if we are a member of another bridge device + tmp = is_member(conf, interface, 'bridge') + if tmp and tmp != ifname: + interface_config.update({'is_bridge_member' : tmp}) + + # Check if we are a member of a bond device + tmp = is_member(conf, interface, 'bonding') + if tmp: + interface_config.update({'is_bond_member' : tmp}) + + # Bridge members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: + interface_config.update({'has_address' : ''}) return bridge def verify(bridge): - if bridge['dhcpv6_prm_only'] and bridge['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + if 'deleted' in bridge: + return None - vrf_name = bridge['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + verify_dhcpv6(bridge) + verify_vrf(bridge) - conf = Config() - for intf in bridge['member']: - # the interface must exist prior adding it to a bridge - if intf['name'] not in interfaces(): - raise ConfigError(( - f'Cannot add nonexistent interface "{intf["name"]}" ' - f'to bridge "{bridge["intf"]}"')) + if 'member' in bridge: + member = bridge.get('member') + bridge_name = bridge['ifname'] + for interface, interface_config in member.get('interface', {}).items(): + error_msg = f'Can not add interface "{interface}" to bridge "{bridge_name}", ' - if intf['name'] == 'lo': - raise ConfigError('Loopback interface "lo" can not be added to a bridge') + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bridge') - # bridge members aren't allowed to be members of another bridge - for br in conf.list_nodes('interfaces bridge'): - # it makes no sense to verify ourself in this case - if br == bridge['intf']: - continue + if interface not in interfaces(): + raise ConfigError(error_msg + 'it does not exist!') - tmp = conf.list_nodes(f'interfaces bridge {br} member interface') - if intf['name'] in tmp: - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it is already a member of bridge "{br}"!')) + if 'is_bridge_member' in interface_config: + tmp = interface_config['is_bridge_member'] + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') - # bridge members are not allowed to be bond members - tmp = is_member(conf, intf['name'], 'bonding') - if tmp: - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it is already a member of bond "{tmp}"!')) + if 'is_bond_member' in interface_config: + tmp = interface_config['is_bond_member'] + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') - # bridge members must not have an assigned address - if has_address_configured(conf, intf['name']): - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it has an address assigned!')) + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') return None @@ -268,120 +133,12 @@ def generate(bridge): return None def apply(bridge): - br = BridgeIf(bridge['intf']) - - if bridge['deleted']: + br = BridgeIf(bridge['ifname']) + if 'deleted' in bridge: # delete interface br.remove() else: - # enable interface - br.set_admin_state('up') - # set ageing time - br.set_ageing_time(bridge['aging']) - # set bridge forward delay - br.set_forward_delay(bridge['forwarding_delay']) - # set hello time - br.set_hello_time(bridge['hello_time']) - # configure ARP filter configuration - br.set_arp_filter(bridge['ip_disable_arp_filter']) - # configure ARP accept - br.set_arp_accept(bridge['ip_enable_arp_accept']) - # configure ARP announce - br.set_arp_announce(bridge['ip_enable_arp_announce']) - # configure ARP ignore - br.set_arp_ignore(bridge['ip_enable_arp_ignore']) - # IPv6 accept RA - br.set_ipv6_accept_ra(bridge['ipv6_accept_ra']) - # IPv6 address autoconfiguration - br.set_ipv6_autoconf(bridge['ipv6_autoconf']) - # IPv6 forwarding - br.set_ipv6_forwarding(bridge['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - br.set_ipv6_dad_messages(bridge['ipv6_dup_addr_detect']) - # set max message age - br.set_max_age(bridge['max_age']) - # set bridge priority - br.set_priority(bridge['priority']) - # turn stp on/off - br.set_stp(bridge['stp']) - # enable or disable IGMP querier - br.set_multicast_querier(bridge['igmp_querier']) - # update interface description used e.g. within SNMP - br.set_alias(bridge['description']) - - if bridge['dhcp_client_id']: - br.dhcp.v4.options['client_id'] = bridge['dhcp_client_id'] - - if bridge['dhcp_hostname']: - br.dhcp.v4.options['hostname'] = bridge['dhcp_hostname'] - - if bridge['dhcp_vendor_class_id']: - br.dhcp.v4.options['vendor_class_id'] = bridge['dhcp_vendor_class_id'] - - if bridge['dhcpv6_prm_only']: - br.dhcp.v6.options['dhcpv6_prm_only'] = True - - if bridge['dhcpv6_temporary']: - br.dhcp.v6.options['dhcpv6_temporary'] = True - - if bridge['dhcpv6_pd_length']: - br.dhcp.v6.options['dhcpv6_pd_length'] = br['dhcpv6_pd_length'] - - if bridge['dhcpv6_pd_interfaces']: - br.dhcp.v6.options['dhcpv6_pd_interfaces'] = br['dhcpv6_pd_interfaces'] - - # assign/remove VRF - br.set_vrf(bridge['vrf']) - - # Delete old IPv6 EUI64 addresses before changing MAC - # (adding members to a fresh bridge changes its MAC too) - for addr in bridge['ipv6_eui64_prefix_remove']: - br.del_ipv6_eui64_address(addr) - - # remove interface from bridge - for intf in bridge['member_remove']: - br.del_port(intf) - - # add interfaces to bridge - for member in bridge['member']: - # if we've come here we already verified the interface doesn't - # have addresses configured so just flush any remaining ones - cmd(f'ip addr flush dev "{member["name"]}"') - br.add_port(member['name']) - - # Change interface MAC address - if bridge['mac']: - br.set_mac(bridge['mac']) - - # Add IPv6 EUI-based addresses (must be done after adding the - # 1st bridge member or setting its MAC) - for addr in bridge['ipv6_eui64_prefix']: - br.add_ipv6_eui64_address(addr) - - # up/down interface - if bridge['disable']: - br.set_admin_state('down') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in bridge['address_remove']: - br.del_addr(addr) - for addr in bridge['address']: - br.add_addr(addr) - - STPBridgeIf = STP.enable(BridgeIf) - # configure additional bridge member options - for member in bridge['member']: - i = STPBridgeIf(member['name']) - # configure ARP cache timeout - i.set_arp_cache_tmo(member['arp_cache_tmo']) - # ignore link state changes - i.set_link_detect(member['disable_link_detect']) - # set bridge port path cost - i.set_path_cost(member['cost']) - # set bridge port path priority - i.set_path_priority(member['priority']) + br.update(bridge) return None diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 60aafae32..d43552e50 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -19,72 +19,36 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import T2665_default_dict_cleanup +from vyos.configdict import get_interface_dict from vyos.configverify import verify_interface_exists from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_address from vyos.configverify import verify_vrf from vyos.configverify import verify_vlan_config from vyos.ifconfig import EthernetIf -from vyos.ifconfig_vlan import get_removed_vlans -from vyos.validate import is_member -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'ethernet'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - # retrieve interface default values - base = ['interfaces', 'ethernet'] - default_values = defaults(base) - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] - # setup config level which is extracted in get_removed_vlans() - conf.set_level(base) - ethernet = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) - - # Check if interface has been removed - if ethernet == {}: - ethernet.update({'deleted' : ''}) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - ethernet = dict_merge(default_values, ethernet) - - # Add interface instance name into dictionary - ethernet.update({'ifname': ifname}) - - # Check if we are a member of a bridge device - bridge = is_member(conf, ifname, 'bridge') - if bridge: - tmp = {'is_bridge_member' : bridge} - ethernet.update(tmp) - - # Check if we are a member of a bond device - bond = is_member(conf, ifname, 'bonding') - if bond: - tmp = {'is_bond_member' : bond} - ethernet.update(tmp) - - ethernet = T2665_default_dict_cleanup( ethernet ) - # Check vif, vif-s/vif-c VLAN interfaces for removal - ethernet = get_removed_vlans( conf, ethernet ) + ethernet = get_interface_dict(conf, base, ifname) return ethernet def verify(ethernet): - if 'deleted' in ethernet.keys(): + if 'deleted' in ethernet: return None verify_interface_exists(ethernet) @@ -114,7 +78,7 @@ def generate(ethernet): def apply(ethernet): e = EthernetIf(ethernet['ifname']) - if 'deleted' in ethernet.keys(): + if 'deleted' in ethernet: # delete interface e.remove() else: -- cgit v1.2.3 From 0e2304e0ec903a8183307b51f275097cd87a6995 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jul 2020 21:57:26 +0200 Subject: ifconfig: T2653: move bridge member check to base class This test is reused by a lot of instances and thus must be moved to the base class. --- python/vyos/ifconfig/ethernet.py | 5 ----- python/vyos/ifconfig/interface.py | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 1725116e2..7641e82fd 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -299,11 +299,6 @@ class EthernetIf(Interface): duplex = config.get('duplex') self.set_speed_duplex(speed, duplex) - # re-add ourselves to any bridge we might have fallen out of - if 'is_bridge_member' in config: - bridge = config.get('is_bridge_member') - self.add_to_bridge(bridge) - # remove no longer required 802.1ad (Q-in-Q VLANs) for vif_s_id in config.get('vif_s_remove', {}): self.del_vlan(vif_s_id) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index ea770af23..2d2017b7a 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -946,6 +946,10 @@ class Interface(Control): for addr in tmp: self.add_ipv6_eui64_address(addr) + # re-add ourselves to any bridge we might have fallen out of + if 'is_bridge_member' in config: + bridge = config.get('is_bridge_member') + self.add_to_bridge(bridge) # Interface administrative state state = 'down' if 'disable' in config else 'up' -- cgit v1.2.3 From 3ee6030b98f8afdbc3a606ce458e11323e59b23c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jul 2020 22:08:22 +0200 Subject: vyos.configdict: T2653: add new reusable helper node_changed() This can be used to see if a tagNode has been changed. It will return a list of changed nodes. --- python/vyos/configdict.py | 18 +++++++++++++++--- src/conf_mode/interfaces-bridge.py | 11 ++--------- 2 files changed, 17 insertions(+), 12 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 4fca426cd..7f05a15ed 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -133,17 +133,16 @@ def T2665_default_dict_cleanup(dict): return dict -def leaf_node_changed(conf, key): +def leaf_node_changed(conf, path): """ Check if a leaf node was altered. If it has been altered - values has been changed, or it was added/removed, we will return the old value. If nothing has been changed, None is returned """ from vyos.configdiff import get_config_diff - D = get_config_diff(conf, key_mangling=('-', '_')) D.set_level(conf.get_level()) - (new, old) = D.get_value_diff(key) + (new, old) = D.get_value_diff(path) if new != old: if isinstance(old, str): return old @@ -156,6 +155,19 @@ def leaf_node_changed(conf, key): return None +def node_changed(conf, path): + """ + Check if a leaf node was altered. If it has been altered - values has been + changed, or it was added/removed, we will return the old value. If nothing + has been changed, None is returned + """ + from vyos.configdiff import get_config_diff, Diff + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(path, expand_nodes=Diff.DELETE)['delete'].keys() + return list(keys) + def get_interface_dict(config, base, ifname): """ Common utility function to retrieve and mandgle the interfaces available diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 7998a251a..9c43d1983 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -21,7 +21,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configdiff import get_config_diff, Diff +from vyos.configdict import node_changed from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf @@ -34,13 +34,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_removed_members(conf): - D = get_config_diff(conf, key_mangling=('-', '_')) - D.set_level(conf.get_level()) - # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 - keys = D.get_child_nodes_diff(['member', 'interface'], expand_nodes=Diff.DELETE)['delete'].keys() - return list(keys) - def get_config(): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -57,7 +50,7 @@ def get_config(): bridge = get_interface_dict(conf, base, ifname) # determine which members have been removed - tmp = get_removed_members(conf) + tmp = node_changed(conf, ['member', 'interface']) if tmp: if 'member' in bridge: bridge['member'].update({'interface_remove': tmp }) -- cgit v1.2.3 From 3998e140d13d99fde0c814816f4cf7533a38a61a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:15:55 +0200 Subject: ifconfig: T2653: implement update() in derived classes for admin up/down Every derived class must implement update() to set the interfaces admin up/down state. This is required to prevend extensive link flaps when e.g. reconfiguring bond interfaces. --- python/vyos/ifconfig/bridge.py | 12 +++++++++++- python/vyos/ifconfig/dummy.py | 19 +++++++++++++++++++ python/vyos/ifconfig/ethernet.py | 12 +++++++++++- python/vyos/ifconfig/interface.py | 3 --- python/vyos/ifconfig/loopback.py | 12 +++++++++++- python/vyos/ifconfig/macsec.py | 19 +++++++++++++++++++ python/vyos/ifconfig/macvlan.py | 19 +++++++++++++++++++ 7 files changed, 90 insertions(+), 6 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index af950b35d..da4e1a289 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -195,7 +195,7 @@ class BridgeIf(Interface): interface setup code and provide a single point of entry when workin on any interface. """ - # now call the regular function from within our base class + # call base class first super().update(config) # Set ageing time @@ -251,3 +251,13 @@ class BridgeIf(Interface): # set bridge port path priority value = interface_config.get('priority') tmp.set_path_priority(value) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) diff --git a/python/vyos/ifconfig/dummy.py b/python/vyos/ifconfig/dummy.py index 404c490c7..43614cd1c 100644 --- a/python/vyos/ifconfig/dummy.py +++ b/python/vyos/ifconfig/dummy.py @@ -35,3 +35,22 @@ class DummyIf(Interface): 'prefixes': ['dum', ], }, } + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class first + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 7641e82fd..77128633d 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -261,7 +261,7 @@ class EthernetIf(Interface): interface setup code and provide a single point of entry when workin on any interface. """ - # now call the regular function from within our base class + # call base class first super().update(config) # disable ethernet flow control (pause frames) @@ -326,3 +326,13 @@ class EthernetIf(Interface): for vif_id, vif in config.get('vif', {}).items(): vlan = self.add_vlan(vif_id) vlan.update(vif) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 2d2017b7a..5942904b5 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -951,6 +951,3 @@ class Interface(Control): bridge = config.get('is_bridge_member') self.add_to_bridge(bridge) - # Interface administrative state - state = 'down' if 'disable' in config else 'up' - self.set_admin_state(state) diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py index 7ebd13b54..2b4ebfdcc 100644 --- a/python/vyos/ifconfig/loopback.py +++ b/python/vyos/ifconfig/loopback.py @@ -75,5 +75,15 @@ class LoopbackIf(Interface): # Update IP address entry in our dictionary config.update({'address' : addr}) - # now call the regular function from within our base class + # call base class super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py index ea8c9807e..6f570d162 100644 --- a/python/vyos/ifconfig/macsec.py +++ b/python/vyos/ifconfig/macsec.py @@ -71,3 +71,22 @@ class MACsecIf(Interface): 'source_interface': '', } return config + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class first + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py index b5481f4a7..b068ce873 100644 --- a/python/vyos/ifconfig/macvlan.py +++ b/python/vyos/ifconfig/macvlan.py @@ -68,3 +68,22 @@ class MACVLANIf(Interface): >> dict = MACVLANIf().get_config() """ return deepcopy(cls.default) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class first + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) -- cgit v1.2.3 From 0d21de93ce02fb0ae6e2e3ceb13dfd5b8dbe755f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:17:38 +0200 Subject: vyos.configdict: T2653: use dict_merge() over update() With dict.update() existing keys will get overwritten when blending in interface default values. --- python/vyos/configdict.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 7f05a15ed..f26b47e41 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -230,11 +230,11 @@ def get_interface_dict(config, base, ifname): # XML definitions which hold the defaults default_vif_values = defaults(base + ['vif']) for vif, vif_config in dict.get('vif', {}).items(): - vif_config.update(default_vif_values) + vif_config = dict_merge(default_vif_values, vif_config) for vif_s, vif_s_config in dict.get('vif_s', {}).items(): - vif_s_config.update(default_vif_values) + vif_s_config = dict_merge(default_vif_values, vif_s_config) for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): - vif_c_config.update(default_vif_values) + vif_c_config = dict_merge(default_vif_values, vif_c_config) # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) -- cgit v1.2.3 From add7eaebe7b8ebd4e143eb939d3ba7871ead0502 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:18:45 +0200 Subject: ifconfig: T2653: move vlan configuration code to base class This is required as other interfaces (e.g. pseudo-ethernet or bond) will have VLANs, too. --- python/vyos/ifconfig/ethernet.py | 29 ----------------------------- python/vyos/ifconfig/interface.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 29 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 77128633d..b2f701e00 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -17,7 +17,6 @@ import os import re import jmespath -from vyos.configdict import get_ethertype from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.validate import assert_list @@ -299,34 +298,6 @@ class EthernetIf(Interface): duplex = config.get('duplex') self.set_speed_duplex(speed, duplex) - # remove no longer required 802.1ad (Q-in-Q VLANs) - for vif_s_id in config.get('vif_s_remove', {}): - self.del_vlan(vif_s_id) - - # create/update 802.1ad (Q-in-Q VLANs) - for vif_s_id, vif_s in config.get('vif_s', {}).items(): - tmp=get_ethertype(vif_s.get('ethertype', '0x88A8')) - s_vlan = self.add_vlan(vif_s_id, ethertype=tmp) - s_vlan.update(vif_s) - - # remove no longer required client VLAN (vif-c) - for vif_c_id in vif_s.get('vif_c_remove', {}): - s_vlan.del_vlan(vif_c_id) - - # create/update client VLAN (vif-c) interface - for vif_c_id, vif_c in vif_s.get('vif_c', {}).items(): - c_vlan = s_vlan.add_vlan(vif_c_id) - c_vlan.update(vif_c) - - # remove no longer required 802.1q VLAN interfaces - for vif_id in config.get('vif_remove', {}): - self.del_vlan(vif_id) - - # create/update 802.1q VLAN interfaces - for vif_id, vif in config.get('vif', {}).items(): - vlan = self.add_vlan(vif_id) - vlan.update(vif) - # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() # function. We will only enable the interface if 'up' was called as diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 5942904b5..1fe4f74f2 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -31,6 +31,7 @@ from netifaces import AF_INET6 from vyos import ConfigError from vyos.configdict import list_diff +from vyos.configdict import get_ethertype from vyos.util import mac2eui64 from vyos.validate import is_ipv4 from vyos.validate import is_ipv6 @@ -951,3 +952,30 @@ class Interface(Control): bridge = config.get('is_bridge_member') self.add_to_bridge(bridge) + # remove no longer required 802.1ad (Q-in-Q VLANs) + for vif_s_id in config.get('vif_s_remove', {}): + self.del_vlan(vif_s_id) + + # create/update 802.1ad (Q-in-Q VLANs) + for vif_s_id, vif_s in config.get('vif_s', {}).items(): + tmp=get_ethertype(vif_s.get('ethertype', '0x88A8')) + s_vlan = self.add_vlan(vif_s_id, ethertype=tmp) + s_vlan.update(vif_s) + + # remove no longer required client VLAN (vif-c) + for vif_c_id in vif_s.get('vif_c_remove', {}): + s_vlan.del_vlan(vif_c_id) + + # create/update client VLAN (vif-c) interface + for vif_c_id, vif_c in vif_s.get('vif_c', {}).items(): + c_vlan = s_vlan.add_vlan(vif_c_id) + c_vlan.update(vif_c) + + # remove no longer required 802.1q VLAN interfaces + for vif_id in config.get('vif_remove', {}): + self.del_vlan(vif_id) + + # create/update 802.1q VLAN interfaces + for vif_id, vif in config.get('vif', {}).items(): + vlan = self.add_vlan(vif_id) + vlan.update(vif) -- cgit v1.2.3 From f81b0443cf09c34cb1f2060094e3eb294b8fa192 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:20:50 +0200 Subject: bonding: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-bonding.xml.in | 2 + python/vyos/ifconfig/bond.py | 118 ++++++- python/vyos/ifconfig/interface.py | 16 +- python/vyos/validate.py | 5 +- src/conf_mode/interfaces-bonding.py | 437 ++++++------------------ 5 files changed, 241 insertions(+), 337 deletions(-) (limited to 'python') diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index ddd52979b..7d658f6a0 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -78,6 +78,7 @@ hash-policy must be layer2 layer2+3 or layer3+4 + layer2 @@ -137,6 +138,7 @@ mode must be 802.3ad, active-backup, broadcast, round-robin, transmit-load-balance, adaptive-load-balance, or xor + 802.3ad diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 47dd4ff34..5a48ac632 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -14,14 +14,15 @@ # License along with this library. If not, see . import os +import jmespath from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN +from vyos.util import cmd from vyos.validate import assert_list from vyos.validate import assert_positive - @Interface.register @VLAN.enable class BondIf(Interface): @@ -179,7 +180,13 @@ class BondIf(Interface): >>> BondIf('bond0').get_arp_ip_target() '192.0.2.1' """ - return self.get_interface('bond_arp_ip_target') + # As this function might also be called from update() of a VLAN interface + # we must check if the bond_arp_ip_target retrieval worked or not - as this + # can not be set for a bond vif interface + try: + return self.get_interface('bond_arp_ip_target') + except FileNotFoundError: + return '' def set_arp_ip_target(self, target): """ @@ -209,11 +216,31 @@ class BondIf(Interface): >>> BondIf('bond0').add_port('eth0') >>> BondIf('bond0').add_port('eth1') """ - # An interface can only be added to a bond if it is in 'down' state. If - # interface is in 'up' state, the following Kernel error will be thrown: - # bond0: eth1 is up - this may be due to an out of date ifenslave. - Interface(interface).set_admin_state('down') - return self.set_interface('bond_add_port', f'+{interface}') + + # From drivers/net/bonding/bond_main.c: + # ... + # bond_set_slave_link_state(new_slave, + # BOND_LINK_UP, + # BOND_SLAVE_NOTIFY_NOW); + # ... + # + # The kernel will ALWAYS place new bond members in "up" state regardless + # what the CLI will tell us! + + # Physical interface must be in admin down state before they can be + # enslaved. If this is not the case an error will be shown: + # bond0: eth0 is up - this may be due to an out of date ifenslave + slave = Interface(interface) + slave_state = slave.get_admin_state() + if slave_state == 'up': + slave.set_admin_state('down') + + ret = self.set_interface('bond_add_port', f'+{interface}') + # The kernel will ALWAYS place new bond members in "up" state regardless + # what the LI is configured for - thus we place the interface in its + # desired state + slave.set_admin_state(slave_state) + return ret def del_port(self, interface): """ @@ -277,3 +304,80 @@ class BondIf(Interface): >>> BondIf('bond0').set_mode('802.3ad') """ return self.set_interface('bond_mode', mode) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # use ref-counting function to place an interface into admin down state. + # set_admin_state_up() must be called the same amount of times else the + # interface won't come up. This can/should be used to prevent link flapping + # when changing interface parameters require the interface to be down. + # We will disable it once before reconfiguration and enable it afterwards. + if 'shutdown_required' in config: + self.set_admin_state('down') + + # call base class first + super().update(config) + + # ARP monitor targets need to be synchronized between sysfs and CLI. + # Unfortunately an address can't be send twice to sysfs as this will + # result in the following exception: OSError: [Errno 22] Invalid argument. + # + # We remove ALL addresses prior to adding new ones, this will remove + # addresses manually added by the user too - but as we are limited to 16 adresses + # from the kernel side this looks valid to me. We won't run into an error + # when a user added manual adresses which would result in having more + # then 16 adresses in total. + arp_tgt_addr = list(map(str, self.get_arp_ip_target().split())) + for addr in arp_tgt_addr: + self.set_arp_ip_target('-' + addr) + + # Add configured ARP target addresses + value = jmespath.search('arp_monitor.target', config) + if isinstance(value, str): + value = [value] + if value: + for addr in value: + self.set_arp_ip_target('+' + addr) + + # Bonding transmit hash policy + value = config.get('hash_policy') + if value: self.set_hash_policy(value) + + # Some interface options can only be changed if the interface is + # administratively down + if self.get_admin_state() == 'down': + # Delete bond member port(s) + for interface in self.get_slaves(): + self.del_port(interface) + + # Bonding policy/mode + value = config.get('mode') + if value: self.set_mode(value) + + # Add (enslave) interfaces to bond + value = jmespath.search('member.interface', config) + if value: + for interface in value: + # if we've come here we already verified the interface does + # not have an addresses configured so just flush any + # remaining ones + cmd(f'ip addr flush dev "{interface}"') + self.add_port(interface) + + # Primary device interface - must be set after 'mode' + value = config.get('primary') + if value: self.set_primary(value) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 1fe4f74f2..7e887db1b 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -205,6 +205,7 @@ class Interface(Control): # make sure the ifname is the first argument and not from the dict self.config['ifname'] = ifname + self._admin_state_down_cnt = 0 # we must have updated config before initialising the Interface super().__init__(**kargs) @@ -594,7 +595,13 @@ class Interface(Control): if not int(flags, 16) & 1: return None - return self.set_interface('admin_state', state) + if state == 'up': + self._admin_state_down_cnt -= 1 + if self._admin_state_down_cnt < 1: + return self.set_interface('admin_state', state) + else: + self._admin_state_down_cnt += 1 + return self.set_interface('admin_state', state) def set_proxy_arp(self, enable): """ @@ -829,8 +836,11 @@ class Interface(Control): # There are some items in the configuration which can only be applied # if this instance is not bound to a bridge. This should be checked # by the caller but better save then sorry! - if not config.get('is_bridge_member', False): - # Bind interface instance into VRF + if not any(k in ['is_bond_member', 'is_bridge_member'] for k in config): + # Bind interface to given VRF or unbind it if vrf node is not set. + # unbinding will call 'ip link set dev eth0 nomaster' which will + # also drop the interface out of a bridge or bond - thus this is + # checked before self.set_vrf(config.get('vrf', '')) # DHCP options diff --git a/python/vyos/validate.py b/python/vyos/validate.py index a0620e4dd..ceeb6888a 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -279,7 +279,6 @@ def is_member(conf, interface, intftype=None): False -> interface type cannot have members """ ret_val = None - if intftype not in ['bonding', 'bridge', None]: raise ValueError(( f'unknown interface type "{intftype}" or it cannot ' @@ -292,9 +291,9 @@ def is_member(conf, interface, intftype=None): conf.set_level([]) for it in intftype: - base = 'interfaces ' + it + base = ['interfaces', it] for intf in conf.list_nodes(base): - memberintf = [base, intf, 'member', 'interface'] + memberintf = base + [intf, 'member', 'interface'] if xml.is_tag(memberintf): if interface in conf.list_nodes(memberintf): ret_val = intf diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index a16c4e105..8e87a0059 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -16,41 +16,25 @@ import os -from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import BondIf -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data from vyos.config import Config -from vyos.util import call, cmd -from vyos.validate import is_member, has_address_configured +from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.ifconfig import BondIf +from vyos.validate import is_member +from vyos.validate import has_address_configured from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'arp_mon_intvl': 0, - 'arp_mon_tgt': [], - 'deleted': False, - 'hash_policy': 'layer2', - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'mode': '802.3ad', - 'member': [], - 'shutdown_required': False, - 'primary': '', - 'vif_s': {}, - 'vif_s_remove': [], - 'vif': {}, - 'vif_remove': [], -} - - def get_bond_mode(mode): if mode == 'round-robin': return 'balance-rr' @@ -67,339 +51,144 @@ def get_bond_mode(mode): elif mode == 'adaptive-load-balance': return 'balance-alb' else: - raise ConfigError('invalid bond mode "{}"'.format(mode)) + raise ConfigError(f'invalid bond mode "{mode}"') def get_config(): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + conf = Config() + base = ['interfaces', 'bonding'] + # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') ifname = os.environ['VYOS_TAGNODE_VALUE'] - conf = Config() + bond = get_interface_dict(conf, base, ifname) + + # To make our own life easier transfor the list of member interfaces + # into a dictionary - we will use this to add additional information + # later on for wach member + if 'member' in bond and 'interface' in bond['member']: + # first convert it to a list if only one member is given + if isinstance(bond['member']['interface'], str): + bond['member']['interface'] = [bond['member']['interface']] + + tmp={} + for interface in bond['member']['interface']: + tmp.update({interface: {}}) + + bond['member']['interface'] = tmp + + if 'mode' in bond: + bond['mode'] = get_bond_mode(bond['mode']) + + tmp = leaf_node_changed(conf, ['mode']) + if tmp: + bond.update({'shutdown_required': ''}) + + # determine which members have been removed + tmp = leaf_node_changed(conf, ['member', 'interface']) + if tmp: + bond.update({'shutdown_required': ''}) + if 'member' in bond: + bond['member'].update({'interface_remove': tmp }) + else: + bond.update({'member': {'interface_remove': tmp }}) + + if 'member' in bond and 'interface' in bond['member']: + for interface, interface_config in bond['member']['interface'].items(): + # Check if we are a member of another bond device + tmp = is_member(conf, interface, 'bridge') + if tmp: + interface_config.update({'is_bridge_member' : tmp}) - # initialize kernel module if not loaded - if not os.path.isfile('/sys/class/net/bonding_masters'): - import syslog - syslog.syslog(syslog.LOG_NOTICE, "loading bonding kernel module") - if call('modprobe bonding max_bonds=0 miimon=250') != 0: - syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module") - raise ConfigError("failed loading bonding kernel module") - - # check if bond has been removed - cfg_base = 'interfaces bonding ' + ifname - if not conf.exists(cfg_base): - bond = deepcopy(default_config_data) - bond['intf'] = ifname - bond['deleted'] = True - return bond - - # set new configuration level - conf.set_level(cfg_base) - - bond, disabled = intf_to_dict(conf, default_config_data) - - # ARP link monitoring frequency in milliseconds - if conf.exists('arp-monitor interval'): - bond['arp_mon_intvl'] = int(conf.return_value('arp-monitor interval')) - - # IP address to use for ARP monitoring - if conf.exists('arp-monitor target'): - bond['arp_mon_tgt'] = conf.return_values('arp-monitor target') - - # Bonding transmit hash policy - if conf.exists('hash-policy'): - bond['hash_policy'] = conf.return_value('hash-policy') - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - bond['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # Enable private VLAN proxy ARP on this interface - if conf.exists('ip proxy-arp-pvlan'): - bond['ip_proxy_arp_pvlan'] = 1 - - # Bonding mode - if conf.exists('mode'): - act_mode = conf.return_value('mode') - eff_mode = conf.return_effective_value('mode') - if not (act_mode == eff_mode): - bond['shutdown_required'] = True - - bond['mode'] = get_bond_mode(act_mode) - - # determine bond member interfaces (currently configured) - bond['member'] = conf.return_values('member interface') - - # We can not call conf.return_effective_values() as it would not work - # on reboots. Reboots/First boot will return that running config and - # saved config is the same, thus on a reboot the bond members will - # not be added all (https://phabricator.vyos.net/T2030) - live_members = BondIf(bond['intf']).get_slaves() - if not (bond['member'] == live_members): - bond['shutdown_required'] = True - - # Primary device interface - if conf.exists('primary'): - bond['primary'] = conf.return_value('primary') - - add_to_dict(conf, disabled, bond, 'vif', 'vif') - add_to_dict(conf, disabled, bond, 'vif-s', 'vif_s') + # Check if we are a member of a bond device + tmp = is_member(conf, interface, 'bonding') + if tmp and tmp != ifname: + interface_config.update({'is_bond_member' : tmp}) + + # bond members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: + interface_config.update({'has_address' : ''}) return bond def verify(bond): - if bond['deleted']: - if bond['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{bond["intf"]}" as it is a ' - f'member of bridge "{bond["is_bridge_member"]}"!')) - + if 'deleted' in bond: + verify_bridge_delete(bond) return None - if len(bond['arp_mon_tgt']) > 16: - raise ConfigError('The maximum number of arp-monitor targets is 16') + if 'arp_monitor' in bond: + if 'target' in bond['arp_monitor'] and len(int(bond['arp_monitor']['target'])) > 16: + raise ConfigError('The maximum number of arp-monitor targets is 16') + + if 'interval' in bond['arp_monitor'] and len(int(bond['arp_monitor']['interval'])) > 0: + if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: + raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ + 'transmit-load-balance or adaptive-load-balance') - if bond['primary']: + if 'primary' in bond: if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: - raise ConfigError(( - 'Mode dependency failed, primary not supported in mode ' - f'"{bond["mode"]}"!')) - - if ( bond['is_bridge_member'] - and ( bond['address'] - or bond['ipv6_eui64_prefix'] - or bond['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{bond["intf"]}" ' - f'as it is a member of bridge "{bond["is_bridge_member"]}"!')) - - if bond['vrf']: - if bond['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{bond["vrf"]}" does not exist') - - if bond['is_bridge_member']: - raise ConfigError(( - f'Interface "{bond["intf"]}" cannot be member of VRF ' - f'"{bond["vrf"]}" and bridge {bond["is_bridge_member"]} ' - f'at the same time!')) + raise ConfigError('Option primary - mode dependency failed, not' + 'supported in mode {mode}!'.format(**bond)) + + verify_address(bond) + verify_dhcpv6(bond) + verify_vrf(bond) # use common function to verify VLAN configuration verify_vlan_config(bond) - conf = Config() - for intf in bond['member']: - # check if member interface is "real" - if intf not in interfaces(): - raise ConfigError(f'Interface {intf} does not exist!') - - # a bonding member interface is only allowed to be assigned to one bond! - all_bonds = conf.list_nodes('interfaces bonding') - # We do not need to check our own bond - all_bonds.remove(bond['intf']) - for tmp in all_bonds: - if conf.exists('interfaces bonding {tmp} member interface {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of bond "{tmp}"!')) - - # can not add interfaces with an assigned address to a bond - if has_address_configured(conf, intf): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it has an address assigned!')) - - # bond members are not allowed to be bridge members - tmp = is_member(conf, intf, 'bridge') - if tmp: - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of bridge "{tmp}"!')) - - # bond members are not allowed to be vrrp members - for tmp in conf.list_nodes('high-availability vrrp group'): - if conf.exists('high-availability vrrp group {tmp} interface {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of VRRP group "{tmp}"!')) - - # bond members are not allowed to be underlaying psuedo-ethernet devices - for tmp in conf.list_nodes('interfaces pseudo-ethernet'): - if conf.exists('interfaces pseudo-ethernet {tmp} link {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already the link of pseudo-ethernet "{tmp}"!')) - - # bond members are not allowed to be underlaying vxlan devices - for tmp in conf.list_nodes('interfaces vxlan'): - if conf.exists('interfaces vxlan {tmp} link {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already the link of VXLAN "{tmp}"!')) - - if bond['primary']: - if bond['primary'] not in bond['member']: - raise ConfigError(f'Bond "{bond["intf"]}" primary interface must be a member') + bond_name = bond['ifname'] + if 'member' in bond: + member = bond.get('member') + for interface, interface_config in member.get('interface', {}).items(): + error_msg = f'Can not add interface "{interface}" to bond "{bond_name}", ' + + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bond') + + if interface not in interfaces(): + raise ConfigError(error_msg + 'it does not exist!') + + if 'is_bridge_member' in interface_config: + tmp = interface_config['is_bridge_member'] + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') + + if 'is_bond_member' in interface_config: + tmp = interface_config['is_bond_member'] + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') + + + if 'primary' in bond: + if bond['primary'] not in bond['member']['interface']: + raise ConfigError(f'Primary interface of bond "{bond_name}" must be a member interface') if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: raise ConfigError('primary interface only works for mode active-backup, ' \ 'transmit-load-balance or adaptive-load-balance') - if bond['arp_mon_intvl'] > 0: - if bond['mode'] in ['802.3ad', 'balance-tlb', 'balance-alb']: - raise ConfigError('ARP link monitoring does not work for mode 802.3ad, ' \ - 'transmit-load-balance or adaptive-load-balance') - return None def generate(bond): return None def apply(bond): - b = BondIf(bond['intf']) + b = BondIf(bond['ifname']) - if bond['deleted']: + if 'deleted' in bond: # delete interface b.remove() else: - # ARP link monitoring frequency, reset miimon when arp-montior is inactive - # this is done inside BondIf automatically - b.set_arp_interval(bond['arp_mon_intvl']) - - # ARP monitor targets need to be synchronized between sysfs and CLI. - # Unfortunately an address can't be send twice to sysfs as this will - # result in the following exception: OSError: [Errno 22] Invalid argument. - # - # We remove ALL adresses prior adding new ones, this will remove addresses - # added manually by the user too - but as we are limited to 16 adresses - # from the kernel side this looks valid to me. We won't run into an error - # when a user added manual adresses which would result in having more - # then 16 adresses in total. - arp_tgt_addr = list(map(str, b.get_arp_ip_target().split())) - for addr in arp_tgt_addr: - b.set_arp_ip_target('-' + addr) - - # Add configured ARP target addresses - for addr in bond['arp_mon_tgt']: - b.set_arp_ip_target('+' + addr) - - # update interface description used e.g. within SNMP - b.set_alias(bond['description']) - - if bond['dhcp_client_id']: - b.dhcp.v4.options['client_id'] = bond['dhcp_client_id'] - - if bond['dhcp_hostname']: - b.dhcp.v4.options['hostname'] = bond['dhcp_hostname'] - - if bond['dhcp_vendor_class_id']: - b.dhcp.v4.options['vendor_class_id'] = bond['dhcp_vendor_class_id'] - - if bond['dhcpv6_prm_only']: - b.dhcp.v6.options['dhcpv6_prm_only'] = True - - if bond['dhcpv6_temporary']: - b.dhcp.v6.options['dhcpv6_temporary'] = True - - if bond['dhcpv6_pd_length']: - b.dhcp.v6.options['dhcpv6_pd_length'] = bond['dhcpv6_pd_length'] - - if bond['dhcpv6_pd_interfaces']: - b.dhcp.v6.options['dhcpv6_pd_interfaces'] = bond['dhcpv6_pd_interfaces'] - - # ignore link state changes - b.set_link_detect(bond['disable_link_detect']) - # Bonding transmit hash policy - b.set_hash_policy(bond['hash_policy']) - # configure ARP cache timeout in milliseconds - b.set_arp_cache_tmo(bond['ip_arp_cache_tmo']) - # configure ARP filter configuration - b.set_arp_filter(bond['ip_disable_arp_filter']) - # configure ARP accept - b.set_arp_accept(bond['ip_enable_arp_accept']) - # configure ARP announce - b.set_arp_announce(bond['ip_enable_arp_announce']) - # configure ARP ignore - b.set_arp_ignore(bond['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - b.set_proxy_arp(bond['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - b.set_proxy_arp_pvlan(bond['ip_proxy_arp_pvlan']) - # IPv6 accept RA - b.set_ipv6_accept_ra(bond['ipv6_accept_ra']) - # IPv6 address autoconfiguration - b.set_ipv6_autoconf(bond['ipv6_autoconf']) - # IPv6 forwarding - b.set_ipv6_forwarding(bond['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - b.set_ipv6_dad_messages(bond['ipv6_dup_addr_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in bond['ipv6_eui64_prefix_remove']: - b.del_ipv6_eui64_address(addr) - - # Change interface MAC address - if bond['mac']: - b.set_mac(bond['mac']) - - # Add IPv6 EUI-based addresses - for addr in bond['ipv6_eui64_prefix']: - b.add_ipv6_eui64_address(addr) - - # Maximum Transmission Unit (MTU) - b.set_mtu(bond['mtu']) - - # Primary device interface - if bond['primary']: - b.set_primary(bond['primary']) - - # Some parameters can not be changed when the bond is up. - if bond['shutdown_required']: - # Disable bond prior changing of certain properties - b.set_admin_state('down') - - # The bonding mode can not be changed when there are interfaces enslaved - # to this bond, thus we will free all interfaces from the bond first! - for intf in b.get_slaves(): - b.del_port(intf) - - # Bonding policy/mode - b.set_mode(bond['mode']) - - # Add (enslave) interfaces to bond - for intf in bond['member']: - # if we've come here we already verified the interface doesn't - # have addresses configured so just flush any remaining ones - cmd(f'ip addr flush dev "{intf}"') - b.add_port(intf) - - # As the bond interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not bond['disable']: - b.set_admin_state('up') - else: - b.set_admin_state('down') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in bond['address_remove']: - b.del_addr(addr) - for addr in bond['address']: - b.add_addr(addr) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not bond['is_bridge_member']: - b.set_vrf(bond['vrf']) - - # re-add ourselves to any bridge we might have fallen out of - if bond['is_bridge_member']: - b.add_to_bridge(bond['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(b, bond) + b.update(bond) return None -- cgit v1.2.3 From d81ce482836ab4adf4f71e2b3dc21477db49a9f0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:24:50 +0200 Subject: vlan: ifconfig: T2653: move get_removed_vlans() to vyos.configdiff As we wrap up additional functions from this library it should be part of it. --- python/vyos/configdict.py | 28 +++++++++++++++++++++++++++- python/vyos/ifconfig_vlan.py | 27 --------------------------- 2 files changed, 27 insertions(+), 28 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index f26b47e41..a1553ae61 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -168,6 +168,33 @@ def node_changed(conf, path): keys = D.get_child_nodes_diff(path, expand_nodes=Diff.DELETE)['delete'].keys() return list(keys) +def get_removed_vlans(conf, dict): + """ + Common function to parse a dictionary retrieved via get_config_dict() and + determine any added/removed VLAN interfaces - be it 802.1q or Q-in-Q. + """ + from vyos.configdiff import get_config_diff, Diff + + # Check vif, vif-s/vif-c VLAN interfaces for removal + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(['vif'], expand_nodes=Diff.DELETE)['delete'].keys() + if keys: + dict.update({'vif_remove': [*keys]}) + + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() + if keys: + dict.update({'vif_s_remove': [*keys]}) + + for vif in dict.get('vif_s', {}).keys(): + keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() + if keys: + dict.update({'vif_s': { vif : {'vif_c_remove': [*keys]}}}) + + return dict + def get_interface_dict(config, base, ifname): """ Common utility function to retrieve and mandgle the interfaces available @@ -177,7 +204,6 @@ def get_interface_dict(config, base, ifname): Will return a dictionary with the necessary interface configuration """ from vyos.xml import defaults - from vyos.ifconfig_vlan import get_removed_vlans # retrieve interface default values default_values = defaults(base) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index 0e4ecda53..442cb0db8 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -16,33 +16,6 @@ from netifaces import interfaces from vyos import ConfigError -def get_removed_vlans(conf, dict): - """ - Common function to parse a dictionary retrieved via get_config_dict() and - determine any added/removed VLAN interfaces - be it 802.1q or Q-in-Q. - """ - from vyos.configdiff import get_config_diff, Diff - - # Check vif, vif-s/vif-c VLAN interfaces for removal - D = get_config_diff(conf, key_mangling=('-', '_')) - D.set_level(conf.get_level()) - # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 - keys = D.get_child_nodes_diff(['vif'], expand_nodes=Diff.DELETE)['delete'].keys() - if keys: - dict.update({'vif_remove': [*keys]}) - - # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 - keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() - if keys: - dict.update({'vif_s_remove': [*keys]}) - - for vif in dict.get('vif_s', {}).keys(): - keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() - if keys: - dict.update({'vif_s': { vif : {'vif_c_remove': [*keys]}}}) - - return dict - def apply_all_vlans(intf, intfconfig): """ Function applies all VLANs to the passed interface. -- cgit v1.2.3 From ee65528d720964cf77bc9b28e6f8fb19b9783066 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 21:06:02 +0200 Subject: ifconfig: T2653: move get_ethertype() from configdict to interface ... as it is only used inside the interface class. --- python/vyos/configdict.py | 9 +-------- python/vyos/ifconfig/interface.py | 8 +++++++- 2 files changed, 8 insertions(+), 9 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index a1553ae61..d79365722 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -267,14 +267,6 @@ def get_interface_dict(config, base, ifname): return dict -def get_ethertype(ethertype_val): - if ethertype_val == '0x88A8': - return '802.1ad' - elif ethertype_val == '0x8100': - return '802.1q' - else: - raise ConfigError('invalid ethertype "{}"'.format(ethertype_val)) - dhcpv6_pd_default_data = { 'dhcpv6_prm_only': False, 'dhcpv6_temporary': False, @@ -636,6 +628,7 @@ def add_to_dict(conf, disabled, ifdict, section, key): def vlan_to_dict(conf, default=vlan_default): + from vyos.ifconfig.interface import get_ethertype vlan, disabled = intf_to_dict(conf, default) # if this is a not within vif-s node, we are done diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 7e887db1b..5496499e5 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -31,7 +31,6 @@ from netifaces import AF_INET6 from vyos import ConfigError from vyos.configdict import list_diff -from vyos.configdict import get_ethertype from vyos.util import mac2eui64 from vyos.validate import is_ipv4 from vyos.validate import is_ipv6 @@ -49,6 +48,13 @@ from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.operational import Operational from vyos.ifconfig import Section +def get_ethertype(ethertype_val): + if ethertype_val == '0x88A8': + return '802.1ad' + elif ethertype_val == '0x8100': + return '802.1q' + else: + raise ConfigError('invalid ethertype "{}"'.format(ethertype_val)) class Interface(Control): # This is the class which will be used to create -- cgit v1.2.3 From 72c0ac35b4acf049de29ce1ea67af28659793098 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 22:00:57 +0200 Subject: vyos.configdict: T2653: remove obsolete code from configdict and ifconfig_vlan After all interfaces have been moved to the targetted implementation of T2653 the old implementations of migrating a CLI session to a configuration dict can be dropped. --- python/vyos/configdict.py | 380 ------------------------------------------- python/vyos/ifconfig_vlan.py | 245 ---------------------------- 2 files changed, 625 deletions(-) delete mode 100644 python/vyos/ifconfig_vlan.py (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index d79365722..53b5f9492 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -267,383 +267,3 @@ def get_interface_dict(config, base, ifname): return dict -dhcpv6_pd_default_data = { - 'dhcpv6_prm_only': False, - 'dhcpv6_temporary': False, - 'dhcpv6_pd_length': '', - 'dhcpv6_pd_interfaces': [] -} - -interface_default_data = { - **dhcpv6_pd_default_data, - 'address': [], - 'address_remove': [], - 'description': '', - 'dhcp_client_id': '', - 'dhcp_hostname': '', - 'dhcp_vendor_class_id': '', - 'disable': False, - 'disable_link_detect': 1, - 'ip_disable_arp_filter': 1, - 'ip_enable_arp_accept': 0, - 'ip_enable_arp_announce': 0, - 'ip_enable_arp_ignore': 0, - 'ip_proxy_arp': 0, - 'ipv6_accept_ra': 1, - 'ipv6_autoconf': 0, - 'ipv6_eui64_prefix': [], - 'ipv6_eui64_prefix_remove': [], - 'ipv6_forwarding': 1, - 'ipv6_dup_addr_detect': 1, - 'is_bridge_member': False, - 'mac': '', - 'mtu': 1500, - 'vrf': '' -} - -vlan_default = { - **interface_default_data, - 'egress_qos': '', - 'egress_qos_changed': False, - 'ingress_qos': '', - 'ingress_qos_changed': False, - 'vif_c': {}, - 'vif_c_remove': [] -} - -# see: https://docs.python.org/3/library/enum.html#functional-api -disable = Enum('disable','none was now both') - -def disable_state(conf, check=[3,5,7]): - """ - return if and how a particual section of the configuration is has disable'd - using "disable" including if it was disabled by one of its parent. - - check: a list of the level we should check, here 7,5 and 3 - interfaces ethernet eth1 vif-s 1 vif-c 2 disable - interfaces ethernet eth1 vif 1 disable - interfaces ethernet eth1 disable - - it returns an enum (none, was, now, both) - """ - - # save where we are in the config - current_level = conf.get_level() - - # logic to figure out if the interface (or one of it parent is disabled) - eff_disable = False - act_disable = False - - levels = check[:] - working_level = current_level[:] - - while levels: - position = len(working_level) - if not position: - break - if position not in levels: - working_level = working_level[:-1] - continue - - levels.remove(position) - conf.set_level(working_level) - working_level = working_level[:-1] - - eff_disable = eff_disable or conf.exists_effective('disable') - act_disable = act_disable or conf.exists('disable') - - conf.set_level(current_level) - - # how the disabling changed - if eff_disable and act_disable: - return disable.both - if eff_disable and not eff_disable: - return disable.was - if not eff_disable and act_disable: - return disable.now - return disable.none - - -def intf_to_dict(conf, default): - from vyos.ifconfig import Interface - - """ - Common used function which will extract VLAN related information from config - and represent the result as Python dictionary. - - Function call's itself recursively if a vif-s/vif-c pair is detected. - """ - - intf = deepcopy(default) - intf['intf'] = ifname_from_config(conf) - - current_vif_list = conf.list_nodes(['vif']) - previous_vif_list = conf.list_effective_nodes(['vif']) - - # set the vif to be deleted - for vif in previous_vif_list: - if vif not in current_vif_list: - intf['vif_remove'].append(vif) - - # retrieve interface description - if conf.exists(['description']): - intf['description'] = conf.return_value(['description']) - - # get DHCP client identifier - if conf.exists(['dhcp-options', 'client-id']): - intf['dhcp_client_id'] = conf.return_value(['dhcp-options', 'client-id']) - - # DHCP client host name (overrides the system host name) - if conf.exists(['dhcp-options', 'host-name']): - intf['dhcp_hostname'] = conf.return_value(['dhcp-options', 'host-name']) - - # DHCP client vendor identifier - if conf.exists(['dhcp-options', 'vendor-class-id']): - intf['dhcp_vendor_class_id'] = conf.return_value( - ['dhcp-options', 'vendor-class-id']) - - # DHCPv6 only acquire config parameters, no address - if conf.exists(['dhcpv6-options', 'parameters-only']): - intf['dhcpv6_prm_only'] = True - - # DHCPv6 prefix delegation (RFC3633) - current_level = conf.get_level() - if conf.exists(['dhcpv6-options', 'prefix-delegation']): - dhcpv6_pd_path = current_level + ['dhcpv6-options', 'prefix-delegation'] - conf.set_level(dhcpv6_pd_path) - - # retriebe DHCPv6-PD prefix helper length as some ISPs only hand out a - # /64 by default (https://phabricator.vyos.net/T2506) - if conf.exists(['length']): - intf['dhcpv6_pd_length'] = conf.return_value(['length']) - - for interface in conf.list_nodes(['interface']): - conf.set_level(dhcpv6_pd_path + ['interface', interface]) - pd = { - 'ifname': interface, - 'sla_id': '', - 'sla_len': '', - 'if_id': '' - } - - if conf.exists(['sla-id']): - pd['sla_id'] = conf.return_value(['sla-id']) - - if conf.exists(['sla-len']): - pd['sla_len'] = conf.return_value(['sla-len']) - - if conf.exists(['address']): - pd['if_id'] = conf.return_value(['address']) - - intf['dhcpv6_pd_interfaces'].append(pd) - - # re-set config level - conf.set_level(current_level) - - # DHCPv6 temporary IPv6 address - if conf.exists(['dhcpv6-options', 'temporary']): - intf['dhcpv6_temporary'] = True - - # ignore link state changes - if conf.exists(['disable-link-detect']): - intf['disable_link_detect'] = 2 - - # ARP filter configuration - if conf.exists(['ip', 'disable-arp-filter']): - intf['ip_disable_arp_filter'] = 0 - - # ARP enable accept - if conf.exists(['ip', 'enable-arp-accept']): - intf['ip_enable_arp_accept'] = 1 - - # ARP enable announce - if conf.exists(['ip', 'enable-arp-announce']): - intf['ip_enable_arp_announce'] = 1 - - # ARP enable ignore - if conf.exists(['ip', 'enable-arp-ignore']): - intf['ip_enable_arp_ignore'] = 1 - - # Enable Proxy ARP - if conf.exists(['ip', 'enable-proxy-arp']): - intf['ip_proxy_arp'] = 1 - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists(['ipv6', 'address', 'autoconf']): - intf['ipv6_autoconf'] = 1 - - # Disable IPv6 forwarding on this interface - if conf.exists(['ipv6', 'disable-forwarding']): - intf['ipv6_forwarding'] = 0 - - # check if interface is member of a bridge - intf['is_bridge_member'] = is_member(conf, intf['intf'], 'bridge') - - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists(['ipv6', 'dup-addr-detect-transmits']): - intf['ipv6_dup_addr_detect'] = int( - conf.return_value(['ipv6', 'dup-addr-detect-transmits'])) - - # Media Access Control (MAC) address - if conf.exists(['mac']): - intf['mac'] = conf.return_value(['mac']) - - # Maximum Transmission Unit (MTU) - if conf.exists(['mtu']): - intf['mtu'] = int(conf.return_value(['mtu'])) - - # retrieve VRF instance - if conf.exists(['vrf']): - intf['vrf'] = conf.return_value(['vrf']) - - # egress QoS - if conf.exists(['egress-qos']): - intf['egress_qos'] = conf.return_value(['egress-qos']) - - # egress changes QoS require VLAN interface recreation - if conf.return_effective_value(['egress-qos']): - if intf['egress_qos'] != conf.return_effective_value(['egress-qos']): - intf['egress_qos_changed'] = True - - # ingress QoS - if conf.exists(['ingress-qos']): - intf['ingress_qos'] = conf.return_value(['ingress-qos']) - - # ingress changes QoS require VLAN interface recreation - if conf.return_effective_value(['ingress-qos']): - if intf['ingress_qos'] != conf.return_effective_value(['ingress-qos']): - intf['ingress_qos_changed'] = True - - # Get the interface addresses - intf['address'] = conf.return_values(['address']) - - # addresses to remove - difference between effective and working config - intf['address_remove'] = list_diff( - conf.return_effective_values(['address']), intf['address']) - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - intf['ipv6_eui64_prefix'] = conf.return_values(['ipv6', 'address', 'eui64']) - - # EUI64 to remove - difference between effective and working config - intf['ipv6_eui64_prefix_remove'] = list_diff( - conf.return_effective_values(['ipv6', 'address', 'eui64']), - intf['ipv6_eui64_prefix']) - - # Determine if the interface should be disabled - disabled = disable_state(conf) - if disabled == disable.both: - # was and is still disabled - intf['disable'] = True - elif disabled == disable.now: - # it is now disable but was not before - intf['disable'] = True - elif disabled == disable.was: - # it was disable but not anymore - intf['disable'] = False - else: - # normal change - intf['disable'] = False - - # Remove the default link-local address if no-default-link-local is set, - # if member of a bridge or if disabled (it may not have a MAC if it's down) - if ( conf.exists(['ipv6', 'address', 'no-default-link-local']) - or intf.get('is_bridge_member') or intf['disable'] ): - intf['ipv6_eui64_prefix_remove'].append('fe80::/64') - else: - # add the link-local by default to make IPv6 work - intf['ipv6_eui64_prefix'].append('fe80::/64') - - # If MAC has changed, remove and re-add all IPv6 EUI64 addresses - try: - interface = Interface(intf['intf'], create=False) - if intf['mac'] and intf['mac'] != interface.get_mac(): - intf['ipv6_eui64_prefix_remove'] += intf['ipv6_eui64_prefix'] - except Exception: - # If the interface does not exist, it could not have changed - pass - - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if intf['ipv6_autoconf'] or 'dhcpv6' in intf['address']: - intf['ipv6_accept_ra'] = 2 - - return intf, disable - - - -def add_to_dict(conf, disabled, ifdict, section, key): - """ - parse a section of vif/vif-s/vif-c and add them to the dict - follow the convention to: - * use the "key" for what to add - * use the "key" what what to remove - - conf: is the Config() already at the level we need to parse - disabled: is a disable enum so we know how to handle to data - intf: if the interface dictionary - section: is the section name to parse (vif/vif-s/vif-c) - key: is the dict key to use (vif/vifs/vifc) - """ - - if not conf.exists(section): - return ifdict - - effect = conf.list_effective_nodes(section) - active = conf.list_nodes(section) - - # the section to parse for vlan - sections = [] - - # determine which interfaces to add or remove based on disable state - if disabled == disable.both: - # was and is still disabled - ifdict[f'{key}_remove'] = [] - elif disabled == disable.now: - # it is now disable but was not before - ifdict[f'{key}_remove'] = effect - elif disabled == disable.was: - # it was disable but not anymore - ifdict[f'{key}_remove'] = [] - sections = active - else: - # normal change - # get interfaces (currently effective) - to determine which - # interface is no longer present and needs to be removed - ifdict[f'{key}_remove'] = list_diff(effect, active) - sections = active - - current_level = conf.get_level() - - # add each section, the key must already exists - for s in sections: - # set config level to vif interface - conf.set_level(current_level + [section, s]) - # add the vlan config as a key (vlan id) - value (config) pair - ifdict[key][s] = vlan_to_dict(conf) - - # re-set configuration level to leave things as found - conf.set_level(current_level) - - return ifdict - - -def vlan_to_dict(conf, default=vlan_default): - from vyos.ifconfig.interface import get_ethertype - vlan, disabled = intf_to_dict(conf, default) - - # if this is a not within vif-s node, we are done - if conf.get_level()[-2] != 'vif-s': - return vlan - - # ethertype is mandatory on vif-s nodes and only exists here! - # ethertype uses a default of 0x88A8 - tmp = '0x88A8' - if conf.exists('ethertype'): - tmp = conf.return_value('ethertype') - vlan['ethertype'] = get_ethertype(tmp) - - # check if there is a Q-in-Q vlan customer interface - # and call this function recursively - add_to_dict(conf, disable, vlan, 'vif-c', 'vif_c') - - return vlan diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py deleted file mode 100644 index 442cb0db8..000000000 --- a/python/vyos/ifconfig_vlan.py +++ /dev/null @@ -1,245 +0,0 @@ -# Copyright 2019-2020 VyOS maintainers and contributors -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see . - -from netifaces import interfaces -from vyos import ConfigError - -def apply_all_vlans(intf, intfconfig): - """ - Function applies all VLANs to the passed interface. - - intf: object of Interface class - intfconfig: dict with interface configuration - """ - # remove no longer required service VLAN interfaces (vif-s) - for vif_s in intfconfig['vif_s_remove']: - intf.del_vlan(vif_s) - - # create service VLAN interfaces (vif-s) - for vif_s_id, vif_s in intfconfig['vif_s'].items(): - s_vlan = intf.add_vlan(vif_s_id, ethertype=vif_s['ethertype']) - apply_vlan_config(s_vlan, vif_s) - - # remove no longer required client VLAN interfaces (vif-c) - # on lower service VLAN interface - for vif_c in vif_s['vif_c_remove']: - s_vlan.del_vlan(vif_c) - - # create client VLAN interfaces (vif-c) - # on lower service VLAN interface - for vif_c_id, vif_c in vif_s['vif_c'].items(): - c_vlan = s_vlan.add_vlan(vif_c_id) - apply_vlan_config(c_vlan, vif_c) - - # remove no longer required VLAN interfaces (vif) - for vif in intfconfig['vif_remove']: - intf.del_vlan(vif) - - # create VLAN interfaces (vif) - for vif_id, vif in intfconfig['vif'].items(): - # QoS priority mapping can only be set during interface creation - # so we delete the interface first if required. - if vif['egress_qos_changed'] or vif['ingress_qos_changed']: - try: - # on system bootup the above condition is true but the interface - # does not exists, which throws an exception, but that's legal - intf.del_vlan(vif_id) - except: - pass - - vlan = intf.add_vlan(vif_id, ingress_qos=vif['ingress_qos'], egress_qos=vif['egress_qos']) - apply_vlan_config(vlan, vif) - - -def apply_vlan_config(vlan, config): - """ - Generic function to apply a VLAN configuration from a dictionary - to a VLAN interface - """ - - if not vlan.definition['vlan']: - raise TypeError() - - if config['dhcp_client_id']: - vlan.dhcp.v4.options['client_id'] = config['dhcp_client_id'] - - if config['dhcp_hostname']: - vlan.dhcp.v4.options['hostname'] = config['dhcp_hostname'] - - if config['dhcp_vendor_class_id']: - vlan.dhcp.v4.options['vendor_class_id'] = config['dhcp_vendor_class_id'] - - if config['dhcpv6_prm_only']: - vlan.dhcp.v6.options['dhcpv6_prm_only'] = True - - if config['dhcpv6_temporary']: - vlan.dhcp.v6.options['dhcpv6_temporary'] = True - - if config['dhcpv6_pd_length']: - vlan.dhcp.v6.options['dhcpv6_pd_length'] = config['dhcpv6_pd_length'] - - if config['dhcpv6_pd_interfaces']: - vlan.dhcp.v6.options['dhcpv6_pd_interfaces'] = config['dhcpv6_pd_interfaces'] - - # update interface description used e.g. within SNMP - vlan.set_alias(config['description']) - # ignore link state changes - vlan.set_link_detect(config['disable_link_detect']) - # configure ARP filter configuration - vlan.set_arp_filter(config['ip_disable_arp_filter']) - # configure ARP accept - vlan.set_arp_accept(config['ip_enable_arp_accept']) - # configure ARP announce - vlan.set_arp_announce(config['ip_enable_arp_announce']) - # configure ARP ignore - vlan.set_arp_ignore(config['ip_enable_arp_ignore']) - # configure Proxy ARP - vlan.set_proxy_arp(config['ip_proxy_arp']) - # IPv6 accept RA - vlan.set_ipv6_accept_ra(config['ipv6_accept_ra']) - # IPv6 address autoconfiguration - vlan.set_ipv6_autoconf(config['ipv6_autoconf']) - # IPv6 forwarding - vlan.set_ipv6_forwarding(config['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - vlan.set_ipv6_dad_messages(config['ipv6_dup_addr_detect']) - # Maximum Transmission Unit (MTU) - vlan.set_mtu(config['mtu']) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not config['is_bridge_member']: - vlan.set_vrf(config['vrf']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in config['ipv6_eui64_prefix_remove']: - vlan.del_ipv6_eui64_address(addr) - - # Change VLAN interface MAC address - if config['mac']: - vlan.set_mac(config['mac']) - - # Add IPv6 EUI-based addresses - for addr in config['ipv6_eui64_prefix']: - vlan.add_ipv6_eui64_address(addr) - - # enable/disable VLAN interface - if config['disable']: - vlan.set_admin_state('down') - else: - vlan.set_admin_state('up') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in config['address_remove']: - vlan.del_addr(addr) - for addr in config['address']: - vlan.add_addr(addr) - - # re-add ourselves to any bridge we might have fallen out of - if config['is_bridge_member']: - vlan.add_to_bridge(config['is_bridge_member']) - -def verify_vlan_config(config): - """ - Generic function to verify VLAN config consistency. Instead of re- - implementing this function in multiple places use single source \o/ - """ - - # config['vif'] is a dict with ids as keys and config dicts as values - for vif in config['vif'].values(): - # DHCPv6 parameters-only and temporary address are mutually exclusive - if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - - if ( vif['is_bridge_member'] - and ( vif['address'] - or vif['ipv6_eui64_prefix'] - or vif['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to vif interface {vif["intf"]} ' - f'which is a member of bridge {vif["is_bridge_member"]}')) - - if vif['vrf']: - if vif['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{vif["vrf"]}" does not exist') - - if vif['is_bridge_member']: - raise ConfigError(( - f'vif {vif["intf"]} cannot be member of VRF {vif["vrf"]} ' - f'and bridge {vif["is_bridge_member"]} at the same time!')) - - # e.g. wireless interface has no vif_s support - # thus we bail out eraly. - if 'vif_s' not in config.keys(): - return - - # config['vif_s'] is a dict with ids as keys and config dicts as values - for vif_s_id, vif_s in config['vif_s'].items(): - for vif_id, vif in config['vif'].items(): - if vif_id == vif_s_id: - raise ConfigError(( - f'Cannot use identical ID on vif "{vif["intf"]}" ' - f'and vif-s "{vif_s["intf"]}"')) - - # DHCPv6 parameters-only and temporary address are mutually exclusive - if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: - raise ConfigError(( - 'DHCPv6 temporary and parameters-only options are mutually ' - 'exclusive!')) - - if ( vif_s['is_bridge_member'] - and ( vif_s['address'] - or vif_s['ipv6_eui64_prefix'] - or vif_s['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to vif-s interface {vif_s["intf"]} ' - f'which is a member of bridge {vif_s["is_bridge_member"]}')) - - if vif_s['vrf']: - if vif_s['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{vif_s["vrf"]}" does not exist') - - if vif_s['is_bridge_member']: - raise ConfigError(( - f'vif-s {vif_s["intf"]} cannot be member of VRF {vif_s["vrf"]} ' - f'and bridge {vif_s["is_bridge_member"]} at the same time!')) - - # vif_c is a dict with ids as keys and config dicts as values - for vif_c in vif_s['vif_c'].values(): - # DHCPv6 parameters-only and temporary address are mutually exclusive - if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: - raise ConfigError(( - 'DHCPv6 temporary and parameters-only options are ' - 'mutually exclusive!')) - - if ( vif_c['is_bridge_member'] - and ( vif_c['address'] - or vif_c['ipv6_eui64_prefix'] - or vif_c['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to vif-c interface {vif_c["intf"]} ' - f'which is a member of bridge {vif_c["is_bridge_member"]}')) - - if vif_c['vrf']: - if vif_c['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{vif_c["vrf"]}" does not exist') - - if vif_c['is_bridge_member']: - raise ConfigError(( - f'vif-c {vif_c["intf"]} cannot be member of VRF {vif_c["vrf"]} ' - f'and bridge {vif_c["is_bridge_member"]} at the same time!')) - -- cgit v1.2.3 From e70a304e36fc6456e16fea81ace4a0a5fd8bd1df Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 00:39:14 +0200 Subject: ifconfig: T2653: make ifname an optional argument to get_interface_dict() Further reduce the boiler-plate code to determine interface tag node or not. It can be passed into get_interface_dict() if explicitly required - else it is taken from the environment. --- python/vyos/configdict.py | 11 ++++++++--- src/conf_mode/interfaces-bonding.py | 10 ++-------- src/conf_mode/interfaces-bridge.py | 11 ++--------- src/conf_mode/interfaces-dummy.py | 8 +------- src/conf_mode/interfaces-ethernet.py | 8 +------- src/conf_mode/interfaces-geneve.py | 9 +-------- src/conf_mode/interfaces-loopback.py | 8 +------- src/conf_mode/interfaces-macsec.py | 8 +------- src/conf_mode/interfaces-pppoe.py | 8 +------- src/conf_mode/interfaces-pseudo-ethernet.py | 8 +------- src/conf_mode/interfaces-wireless.py | 8 +------- src/conf_mode/interfaces-wirelessmodem.py | 9 +-------- 12 files changed, 21 insertions(+), 85 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 53b5f9492..126d6195a 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -15,8 +15,8 @@ """ A library for retrieving value dicts from VyOS configs in a declarative fashion. - """ +import os import jmespath from enum import Enum @@ -24,7 +24,6 @@ from copy import deepcopy from vyos import ConfigError from vyos.validate import is_member -from vyos.util import ifname_from_config def retrieve_config(path_hash, base_path, config): """ @@ -195,7 +194,7 @@ def get_removed_vlans(conf, dict): return dict -def get_interface_dict(config, base, ifname): +def get_interface_dict(config, base, ifname=''): """ Common utility function to retrieve and mandgle the interfaces available in CLI configuration. All interfaces have a common base ground where the @@ -205,6 +204,12 @@ def get_interface_dict(config, base, ifname): """ from vyos.xml import defaults + if not ifname: + # determine tagNode instance + if 'VYOS_TAGNODE_VALUE' not in os.environ: + raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') + ifname = os.environ['VYOS_TAGNODE_VALUE'] + # retrieve interface default values default_values = defaults(base) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 8e87a0059..3b238f1ea 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -60,13 +60,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'bonding'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - bond = get_interface_dict(conf, base, ifname) + bond = get_interface_dict(conf, base) # To make our own life easier transfor the list of member interfaces # into a dictionary - we will use this to add additional information @@ -107,7 +101,7 @@ def get_config(): # Check if we are a member of a bond device tmp = is_member(conf, interface, 'bonding') - if tmp and tmp != ifname: + if tmp and tmp != bond['ifname']: interface_config.update({'is_bond_member' : tmp}) # bond members must not have an assigned address diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 9c43d1983..ee8e85e73 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -41,13 +41,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'bridge'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - bridge = get_interface_dict(conf, base, ifname) + bridge = get_interface_dict(conf, base) # determine which members have been removed tmp = node_changed(conf, ['member', 'interface']) @@ -69,13 +63,12 @@ def get_config(): # the default dictionary is not properly paged into the dict (see T2665) # thus we will ammend it ourself default_member_values = defaults(base + ['member', 'interface']) - for interface, interface_config in bridge['member']['interface'].items(): interface_config.update(default_member_values) # Check if we are a member of another bridge device tmp = is_member(conf, interface, 'bridge') - if tmp and tmp != ifname: + if tmp and tmp != bridge['ifname']: interface_config.update({'is_bridge_member' : tmp}) # Check if we are a member of a bond device diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 6d2a78e30..8df86c8ea 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -35,13 +35,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'dummy'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - dummy = get_interface_dict(conf, base, ifname) + dummy = get_interface_dict(conf, base) return dummy def verify(dummy): diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 24ea0af7c..10758e35a 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -37,13 +37,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'ethernet'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - ethernet = get_interface_dict(conf, base, ifname) + ethernet = get_interface_dict(conf, base) return ethernet def verify(ethernet): diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 868ab5ccf..1104bd3c0 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -37,14 +37,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'geneve'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - geneve = get_interface_dict(conf, base, ifname) - + geneve = get_interface_dict(conf, base) return geneve def verify(geneve): diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 68a1392ff..0398cd591 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -32,13 +32,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'loopback'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - loopback = get_interface_dict(conf, base, ifname) + loopback = get_interface_dict(conf, base) return loopback def verify(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 06aee9ea0..ca15212d4 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -42,13 +42,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'macsec'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - macsec = get_interface_dict(conf, base, ifname) + macsec = get_interface_dict(conf, base) # Check if interface has been removed if 'deleted' in macsec: diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 6947cc1e2..b9a88a949 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -38,13 +38,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'pppoe'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - pppoe = get_interface_dict(conf, base, ifname) + pppoe = get_interface_dict(conf, base) # PPPoE is "special" the default MTU is 1492 - update accordingly # as the config_level is already st in get_interface_dict() - we can use [] diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 55f11e65e..4afea2b3a 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -40,13 +40,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'pseudo-ethernet'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - peth = get_interface_dict(conf, base, ifname) + peth = get_interface_dict(conf, base) mode = leaf_node_changed(conf, ['mode']) if mode: diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 42b55ee6a..b6f247952 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -71,13 +71,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'wireless'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - wifi = get_interface_dict(conf, base, ifname) + wifi = get_interface_dict(conf, base) if 'security' in wifi and 'wpa' in wifi['security']: wpa_cipher = wifi['security']['wpa'].get('cipher') diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 9a5dae9e0..4081be3c9 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -48,14 +48,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'wirelessmodem'] - - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - ifname = os.environ['VYOS_TAGNODE_VALUE'] - wwan = get_interface_dict(conf, base, ifname) - + wwan = get_interface_dict(conf, base) return wwan def verify(wwan): -- cgit v1.2.3 From e57d76e86f7e5280eb065e98552c7d6395805c01 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 17:53:32 +0200 Subject: vyos.configverify: T2653: fix some formatting issues --- python/vyos/configverify.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'python') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 36b10c956..8e06d16f2 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -79,12 +79,12 @@ def verify_source_interface(config): required by e.g. peth/MACvlan, MACsec ... """ from netifaces import interfaces - if not 'source_interface' in config.keys(): + if 'source_interface' not in config: raise ConfigError('Physical source-interface required for ' 'interface "{ifname}"'.format(**config)) - if not config['source_interface'] in interfaces(): - raise ConfigError(f'Source interface {source_interface} does not ' - f'exist'.format(**config)) + if config['source_interface'] not in interfaces(): + raise ConfigError('Source interface {source_interface} does not ' + 'exist'.format(**config)) def verify_dhcpv6(config): """ -- cgit v1.2.3 From a156d2f1479affe4e7cfa56785e4d2d61a776cea Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Jul 2020 11:16:13 +0200 Subject: vxlan: ifconfig: T2653: move to get_interface_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-vxlan.xml.in | 1 + python/vyos/ifconfig/vxlan.py | 20 +- src/conf_mode/interfaces-vxlan.py | 253 ++++---------------------- 3 files changed, 46 insertions(+), 228 deletions(-) (limited to 'python') diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index bd3ab4022..8529f6885 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -93,6 +93,7 @@ + 8472 diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 973b4ef05..0dddab7b7 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -47,8 +47,8 @@ class VXLANIf(Interface): 'port': 8472, # The Linux implementation of VXLAN pre-dates # the IANA's selection of a standard destination port 'remote': '', - 'src_address': '', - 'src_interface': '', + 'source_address': '', + 'source_interface': '', 'vni': 0 } definition = { @@ -60,29 +60,29 @@ class VXLANIf(Interface): } } options = Interface.options + \ - ['group', 'remote', 'src_interface', 'port', 'vni', 'src_address'] + ['group', 'remote', 'source_interface', 'port', 'vni', 'source_address'] mapping = { 'ifname': 'add', 'vni': 'id', 'port': 'dstport', - 'src_address': 'local', - 'src_interface': 'dev', + 'source_address': 'local', + 'source_interface': 'dev', } def _create(self): cmdline = ['ifname', 'type', 'vni', 'port'] - if self.config['src_address']: - cmdline.append('src_address') + if self.config['source_address']: + cmdline.append('source_address') if self.config['remote']: cmdline.append('remote') - if self.config['group'] or self.config['src_interface']: - if self.config['group'] and self.config['src_interface']: + if self.config['group'] or self.config['source_interface']: + if self.config['group'] and self.config['source_interface']: cmdline.append('group') - cmdline.append('src_interface') + cmdline.append('source_interface') else: ifname = self.config['ifname'] raise ConfigError( diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 39db814b4..47c0bdcb8 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -21,197 +21,61 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config +from vyos.configdict import get_interface_dict +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete +from vyos.configverify import verify_source_interface from vyos.ifconfig import VXLANIf, Interface -from vyos.validate import is_member from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - 'address': [], - 'deleted': False, - 'description': '', - 'disable': False, - 'group': '', - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_disable_arp_filter': 1, - 'ip_enable_arp_accept': 0, - 'ip_enable_arp_announce': 0, - 'ip_enable_arp_ignore': 0, - 'ip_proxy_arp': 0, - 'ipv6_accept_ra': 1, - 'ipv6_autoconf': 0, - 'ipv6_eui64_prefix': [], - 'ipv6_forwarding': 1, - 'ipv6_dup_addr_detect': 1, - 'is_bridge_member': False, - 'source_address': '', - 'source_interface': '', - 'mtu': 1450, - 'remote': '', - 'remote_port': 8472, # The Linux implementation of VXLAN pre-dates - # the IANA's selection of a standard destination port - 'vni': '' -} - def get_config(): - vxlan = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'vxlan'] + vxlan = get_interface_dict(conf, base) - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - vxlan['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member if a bridge - vxlan['is_bridge_member'] = is_member(conf, vxlan['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists('interfaces vxlan ' + vxlan['intf']): - vxlan['deleted'] = True - return vxlan - - # set new configuration level - conf.set_level('interfaces vxlan ' + vxlan['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - vxlan['address'] = conf.return_values('address') - - # retrieve interface description - if conf.exists('description'): - vxlan['description'] = conf.return_value('description') - - # Disable this interface - if conf.exists('disable'): - vxlan['disable'] = True - - # VXLAN multicast grou - if conf.exists('group'): - vxlan['group'] = conf.return_value('group') - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - vxlan['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # ARP filter configuration - if conf.exists('ip disable-arp-filter'): - vxlan['ip_disable_arp_filter'] = 0 - - # ARP enable accept - if conf.exists('ip enable-arp-accept'): - vxlan['ip_enable_arp_accept'] = 1 - - # ARP enable announce - if conf.exists('ip enable-arp-announce'): - vxlan['ip_enable_arp_announce'] = 1 - - # ARP enable ignore - if conf.exists('ip enable-arp-ignore'): - vxlan['ip_enable_arp_ignore'] = 1 - - # Enable proxy-arp on this interface - if conf.exists('ip enable-proxy-arp'): - vxlan['ip_proxy_arp'] = 1 - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists('ipv6 address autoconf'): - vxlan['ipv6_autoconf'] = 1 - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - vxlan['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - - # Remove the default link-local address if set. - if not ( conf.exists('ipv6 address no-default-link-local') - or vxlan['is_bridge_member'] ): - # add the link-local by default to make IPv6 work - vxlan['ipv6_eui64_prefix'].append('fe80::/64') - - # Disable IPv6 forwarding on this interface - if conf.exists('ipv6 disable-forwarding'): - vxlan['ipv6_forwarding'] = 0 - - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists('ipv6 dup-addr-detect-transmits'): - vxlan['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) - - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if vxlan['ipv6_autoconf'] or 'dhcpv6' in vxlan['address']: - vxlan['ipv6_accept_ra'] = 2 - - # VXLAN source address - if conf.exists('source-address'): - vxlan['source_address'] = conf.return_value('source-address') - - # VXLAN underlay interface - if conf.exists('source-interface'): - vxlan['source_interface'] = conf.return_value('source-interface') - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - vxlan['mtu'] = int(conf.return_value('mtu')) - - # Remote address of VXLAN tunnel - if conf.exists('remote'): - vxlan['remote'] = conf.return_value('remote') - - # Remote port of VXLAN tunnel - if conf.exists('port'): - vxlan['remote_port'] = int(conf.return_value('port')) - - # Virtual Network Identifier - if conf.exists('vni'): - vxlan['vni'] = conf.return_value('vni') + # VXLAN is "special" the default MTU is 1492 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + vxlan['mtu'] = '1450' return vxlan - def verify(vxlan): - if vxlan['deleted']: - if vxlan['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{vxlan["intf"]}" as it is a ' - f'member of bridge "{vxlan["is_bridge_member"]}"!')) - + if 'deleted' in vxlan: + verify_bridge_delete(vxlan) return None - if vxlan['mtu'] < 1500: + if int(vxlan['mtu']) < 1500: print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') - if vxlan['group']: - if not vxlan['source_interface']: + if 'group' in vxlan: + if 'source_interface' not in vxlan: raise ConfigError('Multicast VXLAN requires an underlaying interface ') - if not vxlan['source_interface'] in interfaces(): - raise ConfigError('VXLAN source interface does not exist') + verify_source_interface(vxlan) - if not (vxlan['group'] or vxlan['remote'] or vxlan['source_address']): + if not any(tmp in ['group', 'remote', 'source_address'] for tmp in vxlan): raise ConfigError('Group, remote or source-address must be configured') - if not vxlan['vni']: + if 'vni' not in vxlan: raise ConfigError('Must configure VNI for VXLAN') - if vxlan['source_interface']: + if 'source_interface' in vxlan: # VXLAN adds a 50 byte overhead - we need to check the underlaying MTU # if our configured MTU is at least 50 bytes less underlay_mtu = int(Interface(vxlan['source_interface']).get_mtu()) - if underlay_mtu < (vxlan['mtu'] + 50): + if underlay_mtu < (int(vxlan['mtu']) + 50): raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \ - 'MTU is to small ({})'.format(underlay_mtu)) - - if ( vxlan['is_bridge_member'] - and ( vxlan['address'] - or vxlan['ipv6_eui64_prefix'] - or vxlan['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{vxlan["intf"]}" ' - f'as it is a member of bridge "{vxlan["is_bridge_member"]}"!')) + f'MTU is to small ({underlay_mtu} bytes)') + verify_address(vxlan) return None @@ -221,73 +85,26 @@ def generate(vxlan): def apply(vxlan): # Check if the VXLAN interface already exists - if vxlan['intf'] in interfaces(): - v = VXLANIf(vxlan['intf']) + if vxlan['ifname'] in interfaces(): + v = VXLANIf(vxlan['ifname']) # VXLAN is super picky and the tunnel always needs to be recreated, # thus we can simply always delete it first. v.remove() - if not vxlan['deleted']: + if 'deleted' not in vxlan: # VXLAN interface needs to be created on-block # instead of passing a ton of arguments, I just use a dict # that is managed by vyos.ifconfig conf = deepcopy(VXLANIf.get_config()) # Assign VXLAN instance configuration parameters to config dict - conf['vni'] = vxlan['vni'] - conf['group'] = vxlan['group'] - conf['src_address'] = vxlan['source_address'] - conf['src_interface'] = vxlan['source_interface'] - conf['remote'] = vxlan['remote'] - conf['port'] = vxlan['remote_port'] + for tmp in ['vni', 'group', 'source_address', 'source_interface', 'remote', 'port']: + if tmp in vxlan: + conf[tmp] = vxlan[tmp] # Finally create the new interface - v = VXLANIf(vxlan['intf'], **conf) - # update interface description used e.g. by SNMP - v.set_alias(vxlan['description']) - # Maximum Transfer Unit (MTU) - v.set_mtu(vxlan['mtu']) - - # configure ARP cache timeout in milliseconds - v.set_arp_cache_tmo(vxlan['ip_arp_cache_tmo']) - # configure ARP filter configuration - v.set_arp_filter(vxlan['ip_disable_arp_filter']) - # configure ARP accept - v.set_arp_accept(vxlan['ip_enable_arp_accept']) - # configure ARP announce - v.set_arp_announce(vxlan['ip_enable_arp_announce']) - # configure ARP ignore - v.set_arp_ignore(vxlan['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - v.set_proxy_arp(vxlan['ip_proxy_arp']) - # IPv6 accept RA - v.set_ipv6_accept_ra(vxlan['ipv6_accept_ra']) - # IPv6 address autoconfiguration - v.set_ipv6_autoconf(vxlan['ipv6_autoconf']) - # IPv6 forwarding - v.set_ipv6_forwarding(vxlan['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - v.set_ipv6_dad_messages(vxlan['ipv6_dup_addr_detect']) - - # Configure interface address(es) - no need to implicitly delete the - # old addresses as they have already been removed by deleting the - # interface above - for addr in vxlan['address']: - v.add_addr(addr) - - # IPv6 EUI-based addresses - for addr in vxlan['ipv6_eui64_prefix']: - v.add_ipv6_eui64_address(addr) - - # As the VXLAN interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not vxlan['disable']: - v.set_admin_state('up') - - # re-add ourselves to any bridge we might have fallen out of - if vxlan['is_bridge_member']: - v.add_to_bridge(vxlan['is_bridge_member']) + v = VXLANIf(vxlan['ifname'], **conf) + v.update(vxlan) return None -- cgit v1.2.3 From 789775af9f5e3f9239ef4583eb4ef7538e40b37c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Jul 2020 16:36:48 +0200 Subject: wireguard: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-wireguard.xml.in | 8 +- python/vyos/ifconfig/wireguard.py | 136 +++++----- src/conf_mode/interfaces-wireguard.py | 300 ++++------------------ 3 files changed, 139 insertions(+), 305 deletions(-) (limited to 'python') diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index edf9bf696..981bce826 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -33,6 +33,7 @@ + 0 @@ -41,6 +42,7 @@ + default @@ -103,7 +105,11 @@ #include - how often send keep alives in seconds + Interval to send keepalive messages + + 1-65535 + Interval in seconds + diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index 62ca57ca2..fad4ef282 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -24,7 +24,7 @@ from hurry.filesize import alternative from vyos.config import Config from vyos.ifconfig import Interface from vyos.ifconfig import Operational - +from vyos.validate import is_ipv6 class WireGuardOperational(Operational): def _dump(self): @@ -169,65 +169,79 @@ class WireGuardIf(Interface): ['port', 'private_key', 'pubkey', 'psk', 'allowed_ips', 'fwmark', 'endpoint', 'keepalive'] - """ - Wireguard interface class, contains a comnfig dictionary since - wireguard VPN is being comnfigured via the wg command rather than - writing the config into a file. Otherwise if a pre-shared key is used - (symetric enryption key), it would we exposed within multiple files. - Currently it's only within the config.boot if the config was saved. - - Example: - >>> from vyos.ifconfig import WireGuardIf as wg_if - >>> wg_intfc = wg_if("wg01") - >>> print (wg_intfc.wg_config) - {'private_key': None, 'keepalive': 0, 'endpoint': None, 'port': 0, - 'allowed_ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} - >>> wg_intfc.wg_config['keepalive'] = 100 - >>> print (wg_intfc.wg_config) - {'private_key': None, 'keepalive': 100, 'endpoint': None, 'port': 0, - 'allowed_ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} - """ - - def update(self): - if not self.config['private_key']: - raise ValueError("private key required") - else: - # fmask permission check? - pass - - cmd = 'wg set {ifname}'.format(**self.config) - cmd += ' listen-port {port}'.format(**self.config) - cmd += ' fwmark "{fwmark}" '.format(**self.config) - cmd += ' private-key {private_key}'.format(**self.config) - cmd += ' peer {pubkey}'.format(**self.config) - cmd += ' persistent-keepalive {keepalive}'.format(**self.config) - # allowed-ips must be properly quoted else the interface can't be properly - # created as the wg utility will tread multiple IP addresses as command - # parameters - cmd += ' allowed-ips "{}"'.format(','.join(self.config['allowed-ips'])) - - if self.config['endpoint']: - cmd += ' endpoint "{endpoint}"'.format(**self.config) - - psk_file = '' - if self.config['psk']: - psk_file = '/tmp/{ifname}.psk'.format(**self.config) - with open(psk_file, 'w') as f: - f.write(self.config['psk']) + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # remove no longer associated peers first + if 'peer_remove' in config: + for tmp in config['peer_remove']: + peer = config['peer_remove'][tmp] + peer['ifname'] = config['ifname'] + + cmd = 'wg set {ifname} peer {pubkey} remove' + self._cmd(cmd.format(**peer)) + + # Wireguard base command is identical for every peer + base_cmd = 'wg set {ifname} private-key {private_key}' + if 'port' in config: + base_cmd += ' listen-port {port}' + if 'fwmark' in config: + base_cmd += ' fwmark {fwmark}' + + base_cmd = base_cmd.format(**config) + + for tmp in config['peer']: + peer = config['peer'][tmp] + + # start of with a fresh 'wg' command + cmd = base_cmd + ' peer {pubkey}' + + # If no PSK is given remove it by using /dev/null - passing keys via + # the shell (usually bash) is considered insecure, thus we use a file + no_psk_file = '/dev/null' + psk_file = no_psk_file + if 'preshared_key' in peer: + psk_file = '/tmp/tmp.wireguard.psk' + with open(psk_file, 'w') as f: + f.write(peer['preshared_key']) cmd += f' preshared-key {psk_file}' - self._cmd(cmd) - - # PSK key file is not required to be stored persistently as its backed by CLI - if os.path.exists(psk_file): - os.remove(psk_file) - - def remove_peer(self, peerkey): - """ - Remove a peer of an interface, peers are identified by their public key. - Giving it a readable name is a vyos feature, to remove a peer the pubkey - and the interface is needed, to remove the entry. - """ - cmd = "wg set {0} peer {1} remove".format( - self.config['ifname'], str(peerkey)) - return self._cmd(cmd) + # Persistent keepalive is optional + if 'persistent_keepalive'in peer: + cmd += ' persistent-keepalive {persistent_keepalive}' + + # Multiple allowed-ip ranges can be defined - ensure we are always + # dealing with a list + if isinstance(peer['allowed_ips'], str): + peer['allowed_ips'] = [peer['allowed_ips']] + cmd += ' allowed-ips ' + ','.join(peer['allowed_ips']) + + # Endpoint configuration is optional + if {'address', 'port'} <= set(peer): + if is_ipv6(config['address']): + cmd += ' endpoint [{address}]:{port}' + else: + cmd += ' endpoint {address}:{port}' + + self._cmd(cmd.format(**peer)) + + # PSK key file is not required to be stored persistently as its backed by CLI + if psk_file != no_psk_file and os.path.exists(psk_file): + os.remove(psk_file) + + # call base class + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) + diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 982aefa5f..6325a8b05 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -15,44 +15,29 @@ # along with this program. If not, see . import os -import re from sys import exit from copy import deepcopy -from netifaces import interfaces from vyos.config import Config -from vyos.configdict import list_diff +from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict +from vyos.configdict import node_changed +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete from vyos.ifconfig import WireGuardIf -from vyos.util import chown, chmod_750, call +from vyos.util import chown, chmod_750 from vyos.util import check_kmod -from vyos.validate import is_member, is_ipv6 from vyos import ConfigError - from vyos import airbag airbag.enable() -kdir = r'/config/auth/wireguard' k_mod = 'wireguard' -default_config_data = { - 'intfc': '', - 'address': [], - 'address_remove': [], - 'description': '', - 'listen_port': '', - 'deleted': False, - 'disable': False, - 'fwmark': 0, - 'is_bridge_member': False, - 'mtu': 1420, - 'peer': [], - 'peer_remove': [], # stores public keys of peers to remove - 'pk': f'{kdir}/default/private.key', - 'vrf': '' -} - def _migrate_default_keys(): + kdir = r'/config/auth/wireguard' if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'): location = f'{kdir}/default' if not os.path.exists(location): @@ -65,246 +50,75 @@ def _migrate_default_keys(): def get_config(): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() base = ['interfaces', 'wireguard'] + wireguard = get_interface_dict(conf, base) - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - wg = deepcopy(default_config_data) - wg['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member if a bridge - wg['is_bridge_member'] = is_member(conf, wg['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists(base + [wg['intf']]): - wg['deleted'] = True - return wg - - conf.set_level(base + [wg['intf']]) - - # retrieve configured interface addresses - if conf.exists(['address']): - wg['address'] = conf.return_values(['address']) - - # get interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed - eff_addr = conf.return_effective_values(['address']) - wg['address_remove'] = list_diff(eff_addr, wg['address']) - - # retrieve interface description - if conf.exists(['description']): - wg['description'] = conf.return_value(['description']) - - # disable interface - if conf.exists(['disable']): - wg['disable'] = True - - # local port to listen on - if conf.exists(['port']): - wg['listen_port'] = conf.return_value(['port']) - - # fwmark value - if conf.exists(['fwmark']): - wg['fwmark'] = int(conf.return_value(['fwmark'])) - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - wg['mtu'] = int(conf.return_value(['mtu'])) - - # retrieve VRF instance - if conf.exists('vrf'): - wg['vrf'] = conf.return_value('vrf') - - # private key - if conf.exists(['private-key']): - wg['pk'] = "{0}/{1}/private.key".format( - kdir, conf.return_value(['private-key'])) - - # peer removal, wg identifies peers by its pubkey - peer_eff = conf.list_effective_nodes(['peer']) - peer_rem = list_diff(peer_eff, conf.list_nodes(['peer'])) - for peer in peer_rem: - wg['peer_remove'].append( - conf.return_effective_value(['peer', peer, 'pubkey'])) - - # peer settings - if conf.exists(['peer']): - for p in conf.list_nodes(['peer']): - # set new config level for this peer - conf.set_level(base + [wg['intf'], 'peer', p]) - peer = { - 'allowed-ips': [], - 'address': '', - 'name': p, - 'persistent_keepalive': '', - 'port': '', - 'psk': '', - 'pubkey': '' - } - - # peer allowed-ips - if conf.exists(['allowed-ips']): - peer['allowed-ips'] = conf.return_values(['allowed-ips']) - - # peer address - if conf.exists(['address']): - peer['address'] = conf.return_value(['address']) - - # peer port - if conf.exists(['port']): - peer['port'] = conf.return_value(['port']) + # Wireguard is "special" the default MTU is 1420 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + wireguard['mtu'] = '1420' - # persistent-keepalive - if conf.exists(['persistent-keepalive']): - peer['persistent_keepalive'] = conf.return_value(['persistent-keepalive']) + # Mangle private key - it has a default so its always valid + wireguard['private_key'] = '/config/auth/wireguard/{private_key}/private.key'.format(**wireguard) - # preshared-key - if conf.exists(['preshared-key']): - peer['psk'] = conf.return_value(['preshared-key']) + # Determine which Wireguard peer has been removed. + # Peers can only be removed with their public key! + tmp = node_changed(conf, ['peer']) + if tmp: + dict = {} + for peer in tmp: + peer_config = leaf_node_changed(conf, ['peer', peer, 'pubkey']) + dict = dict_merge({'peer_remove' : {peer : {'pubkey' : peer_config}}}, dict) + wireguard.update(dict) - # peer pubkeys - if conf.exists(['pubkey']): - key_eff = conf.return_effective_value(['pubkey']) - key_cfg = conf.return_value(['pubkey']) - peer['pubkey'] = key_cfg + return wireguard - # on a pubkey change we need to remove the pubkey first - # peers are identified by pubkey, so key update means - # peer removal and re-add - if key_eff != key_cfg and key_eff != None: - wg['peer_remove'].append(key_cfg) - - # if a peer is disabled, we have to exec a remove for it's pubkey - if conf.exists(['disable']): - wg['peer_remove'].append(peer['pubkey']) - else: - wg['peer'].append(peer) - - return wg - - -def verify(wg): - if wg['deleted']: - if wg['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{wg["intf"]}" as it is a member ' - f'of bridge "{wg["is_bridge_member"]}"!')) +def verify(wireguard): + if 'deleted' in wireguard: + verify_bridge_delete(wireguard) return None - if wg['is_bridge_member'] and wg['address']: - raise ConfigError(( - f'Cannot assign address to interface "{wg["intf"]}" ' - f'as it is a member of bridge "{wg["is_bridge_member"]}"!')) - - if wg['vrf']: - if wg['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{wg["vrf"]}" does not exist') + verify_address(wireguard) + verify_vrf(wireguard) - if wg['is_bridge_member']: - raise ConfigError(( - f'Interface "{wg["intf"]}" cannot be member of VRF ' - f'"{wg["vrf"]}" and bridge {wg["is_bridge_member"]} ' - f'at the same time!')) + if not os.path.exists(wireguard['private_key']): + raise ConfigError('Wireguard private-key not found! Execute: ' \ + '"run generate wireguard [default-keypair|named-keypairs]"') - if not os.path.exists(wg['pk']): - raise ConfigError('No keys found, generate them by executing:\n' \ - '"run generate wireguard [keypair|named-keypairs]"') + if 'address' not in wireguard: + raise ConfigError('IP address required!') - if not wg['address']: - raise ConfigError(f'IP address required for interface "{wg["intf"]}"!') - - if not wg['peer']: - raise ConfigError(f'Peer required for interface "{wg["intf"]}"!') + if 'peer' not in wireguard: + raise ConfigError('At least one Wireguard peer is required!') # run checks on individual configured WireGuard peer - for peer in wg['peer']: - if not peer['allowed-ips']: - raise ConfigError(f'Peer allowed-ips required for peer "{peer["name"]}"!') - - if not peer['pubkey']: - raise ConfigError(f'Peer public-key required for peer "{peer["name"]}"!') - - if peer['address'] and not peer['port']: - raise ConfigError(f'Peer "{peer["name"]}" port must be defined if address is defined!') + for tmp in wireguard['peer']: + peer = wireguard['peer'][tmp] - if not peer['address'] and peer['port']: - raise ConfigError(f'Peer "{peer["name"]}" address must be defined if port is defined!') + if 'allowed_ips' not in peer: + raise ConfigError(f'Wireguard allowed-ips required for peer "{tmp}"!') + if 'pubkey' not in peer: + raise ConfigError(f'Wireguard public-key required for peer "{tmp}"!') -def apply(wg): - # init wg class - w = WireGuardIf(wg['intf']) + if ('address' in peer and 'port' not in peer) or ('port' in peer and 'address' not in peer): + raise ConfigError('Both Wireguard port and address must be defined ' + f'for peer "{tmp}" if either one of them is set!') - # single interface removal - if wg['deleted']: - w.remove() +def apply(wireguard): + if 'deleted' in wireguard: + WireGuardIf(wireguard['ifname']).remove() return None - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in wg['address_remove']: - w.del_addr(addr) - for addr in wg['address']: - w.add_addr(addr) - - # Maximum Transmission Unit (MTU) - w.set_mtu(wg['mtu']) - - # update interface description used e.g. within SNMP - w.set_alias(wg['description']) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not wg['is_bridge_member']: - w.set_vrf(wg['vrf']) - - # remove peers - for pub_key in wg['peer_remove']: - w.remove_peer(pub_key) - - # peer pubkey - # setting up the wg interface - w.config['private_key'] = c['pk'] - - for peer in wg['peer']: - # peer pubkey - w.config['pubkey'] = peer['pubkey'] - # peer allowed-ips - w.config['allowed-ips'] = peer['allowed-ips'] - # local listen port - if wg['listen_port']: - w.config['port'] = wg['listen_port'] - # fwmark - if c['fwmark']: - w.config['fwmark'] = wg['fwmark'] - - # endpoint - if peer['address'] and peer['port']: - if is_ipv6(peer['address']): - w.config['endpoint'] = '[{}]:{}'.format(peer['address'], peer['port']) - else: - w.config['endpoint'] = '{}:{}'.format(peer['address'], peer['port']) - - # persistent-keepalive - if peer['persistent_keepalive']: - w.config['keepalive'] = peer['persistent_keepalive'] - - if peer['psk']: - w.config['psk'] = peer['psk'] - - w.update() - - # Enable/Disable interface - if wg['disable']: - w.set_admin_state('down') - else: - w.set_admin_state('up') - + w = WireGuardIf(wireguard['ifname']) + w.update(wireguard) return None if __name__ == '__main__': -- cgit v1.2.3 From 29dd5079ad38e82363032b585304d509db0fea8e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 30 Jul 2020 23:31:55 +0200 Subject: ifconfig: T2653: remove duplicated code for address flush --- python/vyos/ifconfig/bond.py | 8 ++++---- python/vyos/ifconfig/bridge.py | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 5a48ac632..5c9a43c9b 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -362,10 +362,10 @@ class BondIf(Interface): value = jmespath.search('member.interface', config) if value: for interface in value: - # if we've come here we already verified the interface does - # not have an addresses configured so just flush any - # remaining ones - cmd(f'ip addr flush dev "{interface}"') + # if we've come here we already verified the interface + # does not have an addresses configured so just flush + # any remaining ones + self.flush_addrs(interface) self.add_port(interface) # Primary device interface - must be set after 'mode' diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index da4e1a289..e4ae0a80a 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -237,9 +237,10 @@ class BridgeIf(Interface): tmp = jmespath.search('member.interface', config) if tmp: for interface, interface_config in tmp.items(): - # if we've come here we already verified the interface doesn't - # have addresses configured so just flush any remaining ones - cmd(f'ip addr flush dev "{interface}"') + # if we've come here we already verified the interface + # does not have an addresses configured so just flush + # any remaining ones + self.flush_addrs(interface) # enslave interface port to bridge self.add_port(interface) -- cgit v1.2.3 From 55c93007111883308bb49a203d3e8f98f7117092 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 30 Jul 2020 23:32:18 +0200 Subject: ifconfig: T2746: bugfix for non programmed link-local addresses After the fresh rewrite of the interfaces to a unified solution (T2653) IPv6 link-local addresses are no longer added. This will result in e.g. broken RAs. --- python/vyos/ifconfig/interface.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'python') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 5496499e5..d477153e8 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -954,6 +954,15 @@ class Interface(Control): if mac: self.set_mac(mac) + # Manage IPv6 link-local addresses + tmp = jmespath.search('ipv6.address.no_default_link_local', config) + # we must check explicitly for None type as if the key is set we will + # get an empty dict () + if tmp is not None: + self.del_ipv6_eui64_address('fe80::/64') + else: + self.add_ipv6_eui64_address('fe80::/64') + # Add IPv6 EUI-based addresses tmp = jmespath.search('ipv6.address.eui64', config) if tmp: -- cgit v1.2.3 From 7e9266025d97bd51a6a413af8bd7f3f65ea9a65d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 31 Jul 2020 07:05:47 +0200 Subject: ifconfig: T2653: bugfix on wrong flush_addr API Commit 29dd5079 ("ifconfig: T2653: remove duplicated code for address flush") used the class method for address flushing, but it was cvalled in the wrong way. --- python/vyos/ifconfig/bond.py | 2 +- python/vyos/ifconfig/bridge.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 5c9a43c9b..193cea321 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -365,7 +365,7 @@ class BondIf(Interface): # if we've come here we already verified the interface # does not have an addresses configured so just flush # any remaining ones - self.flush_addrs(interface) + Interface(interface).flush_addrs() self.add_port(interface) # Primary device interface - must be set after 'mode' diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index e4ae0a80a..466e6b682 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -240,7 +240,7 @@ class BridgeIf(Interface): # if we've come here we already verified the interface # does not have an addresses configured so just flush # any remaining ones - self.flush_addrs(interface) + Interface(interface).flush_addrs() # enslave interface port to bridge self.add_port(interface) -- cgit v1.2.3 From dfbbdd2950c3db3f56649ff602a86b8bf17de748 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Aug 2020 10:49:59 +0200 Subject: ifconfig: T2752: fix string format in verify_interface_exists() We do not have a formatted string here thus the "f" keyword is wrong and triggered an exception. --- python/vyos/configverify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 8e06d16f2..bb590a514 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -69,7 +69,7 @@ def verify_interface_exists(config): """ from netifaces import interfaces if not config['ifname'] in interfaces(): - raise ConfigError(f'Interface "{ifname}" does not exist!' + raise ConfigError('Interface "{ifname}" does not exist!' .format(**config)) def verify_source_interface(config): -- cgit v1.2.3 From 84331764a81d7e31c5c4dd5466f347054283d377 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 12:56:13 +0200 Subject: ifconfig: T2740: pass config dict to DHCP class for IPv6 This removes additional code paths as we can instatly work with the input dict the same was as it was done for PPPoE. This fixes the entire DHCPv6-PD support on non PPPoE interfaces as this was lost in translation while processing T2653. --- data/templates/dhcp-client/ipv6.tmpl | 53 +++++++++++++++++--------------- data/templates/dhcp-client/ipv6_new.tmpl | 47 ---------------------------- python/vyos/ifconfig/dhcp.py | 36 +++++++++------------- python/vyos/ifconfig/interface.py | 49 ++++++++++++----------------- src/conf_mode/interfaces-pppoe.py | 2 +- 5 files changed, 63 insertions(+), 124 deletions(-) delete mode 100644 data/templates/dhcp-client/ipv6_new.tmpl (limited to 'python') diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index 490f14726..112431c5f 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -4,41 +4,44 @@ interface {{ ifname }} { request domain-name-servers; request domain-name; -{% if dhcpv6_prm_only %} +{% if dhcpv6_options is defined %} +{% if dhcpv6_options.parameters_only is defined %} information-only; -{% endif %} -{% if not dhcpv6_temporary %} +{% endif %} +{% if dhcpv6_options.temporary is not defined %} send ia-na 1; # non-temporary address -{% endif %} -{% if dhcpv6_pd_interfaces %} +{% endif %} +{% if dhcpv6_options.prefix_delegation is defined %} send ia-pd 2; # prefix delegation +{% endif %} {% endif %} }; -{% if not dhcpv6_temporary %} +{% if dhcpv6_options is defined %} +{% if dhcpv6_options.temporary is not defined %} id-assoc na 1 { # Identity association NA }; -{% endif %} +{% endif %} -{% if dhcpv6_pd_interfaces %} +{% if dhcpv6_options.prefix_delegation is defined %} id-assoc pd 2 { -{% if dhcpv6_pd_length %} - prefix ::/{{ dhcpv6_pd_length }} infinity; -{% endif %} -{% for intf in dhcpv6_pd_interfaces %} - prefix-interface {{ intf.ifname }} { -{% if intf.sla_id %} - sla-id {{ intf.sla_id }}; -{% endif %} -{% if intf.sla_len %} - sla-len {{ intf.sla_len }}; -{% endif %} -{% if intf.if_id %} - ifid {{ intf.if_id }}; -{% endif %} +{% if dhcpv6_options.prefix_delegation.length is defined %} + prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; +{% endif %} +{% for interface in dhcpv6_options.prefix_delegation.interface %} + prefix-interface {{ interface }} { +{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} + sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; +{% endif %} +{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} + sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; +{% endif %} +{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} + ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; +{% endif %} }; -{% endfor %} +{% endfor %} }; -{% endif %} - +{% endif %} +{% endif %} diff --git a/data/templates/dhcp-client/ipv6_new.tmpl b/data/templates/dhcp-client/ipv6_new.tmpl deleted file mode 100644 index 112431c5f..000000000 --- a/data/templates/dhcp-client/ipv6_new.tmpl +++ /dev/null @@ -1,47 +0,0 @@ -# generated by dhcp.py -# man https://www.unix.com/man-page/debian/5/dhcp6c.conf/ - -interface {{ ifname }} { - request domain-name-servers; - request domain-name; -{% if dhcpv6_options is defined %} -{% if dhcpv6_options.parameters_only is defined %} - information-only; -{% endif %} -{% if dhcpv6_options.temporary is not defined %} - send ia-na 1; # non-temporary address -{% endif %} -{% if dhcpv6_options.prefix_delegation is defined %} - send ia-pd 2; # prefix delegation -{% endif %} -{% endif %} -}; - -{% if dhcpv6_options is defined %} -{% if dhcpv6_options.temporary is not defined %} -id-assoc na 1 { - # Identity association NA -}; -{% endif %} - -{% if dhcpv6_options.prefix_delegation is defined %} -id-assoc pd 2 { -{% if dhcpv6_options.prefix_delegation.length is defined %} - prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; -{% endif %} -{% for interface in dhcpv6_options.prefix_delegation.interface %} - prefix-interface {{ interface }} { -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} - sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} - sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} - ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; -{% endif %} - }; -{% endfor %} -}; -{% endif %} -{% endif %} diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py index a8b9a2a87..bd37970a2 100644 --- a/python/vyos/ifconfig/dhcp.py +++ b/python/vyos/ifconfig/dhcp.py @@ -15,6 +15,7 @@ import os +from vyos.configverify import verify_dhcpv6 from vyos.dicts import FixedDict from vyos.ifconfig.control import Control from vyos.template import render @@ -82,39 +83,31 @@ class _DHCPv4 (Control): class _DHCPv6 (Control): def __init__(self, ifname): super().__init__() - self.options = FixedDict(**{ - 'ifname': ifname, - 'dhcpv6_prm_only': False, - 'dhcpv6_temporary': False, - 'dhcpv6_pd_interfaces': [], - 'dhcpv6_pd_length': '' - }) - self._conf_file = f'/run/dhcp6c/dhcp6c.{ifname}.conf' + self.options = {'ifname' : ifname} + self._config = f'/run/dhcp6c/dhcp6c.{ifname}.conf' def set(self): """ - Configure interface as DHCPv6 client. The dhclient binary is automatically - started in background! + Configure interface as DHCPv6 client. The client is automatically + started in background when address is configured as DHCP. Example: - >>> from vyos.ifconfig import Interface >>> j = Interface('eth0') >>> j.dhcp.v6.set() """ - # better save then sorry .. should be checked in interface script - # but if you missed it we are safe! - if self.options['dhcpv6_prm_only'] and self.options['dhcpv6_temporary']: - raise Exception( - 'DHCPv6 temporary and parameters-only options are mutually exclusive!') + # better save then sorry .. should be checked in interface script but if you + # missed it we are safe! + verify_dhcpv6(self.options) - render(self._conf_file, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True) - return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format(**self.options)) + render(self._config, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True) + return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format( + **self.options)) def delete(self): """ - De-configure interface as DHCPv6 clinet. All auto generated files like + De-configure interface as DHCPv6 client. All auto generated files like pid, config and lease will be removed. Example: @@ -126,9 +119,8 @@ class _DHCPv6 (Control): self._cmd('systemctl stop dhcp6c@{ifname}.service'.format(**self.options)) # cleanup old config files - if os.path.isfile(self._conf_file): - os.remove(self._conf_file) - + if os.path.isfile(self._config): + os.remove(self._config) class DHCP(object): def __init__(self, ifname): diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index d477153e8..f5e43e172 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -822,6 +822,22 @@ class Interface(Control): value = '2' if 'disable_link_detect' in config else '1' self.set_link_detect(value) + # DHCP options + if 'dhcp_options' in config: + dhcp_options = config.get('dhcp_options') + if 'client_id' in dhcp_options: + self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') + + if 'host_name' in dhcp_options: + self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') + + if 'vendor_class_id' in dhcp_options: + self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') + + # DHCPv6 options + if 'dhcpv6_options' in config: + self.dhcp.v6.options = config + # Configure assigned interface IP addresses. No longer # configured addresses will be removed first new_addr = config.get('address', []) @@ -849,35 +865,6 @@ class Interface(Control): # checked before self.set_vrf(config.get('vrf', '')) - # DHCP options - if 'dhcp_options' in config: - dhcp_options = config.get('dhcp_options') - if 'client_id' in dhcp_options: - self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') - - if 'host_name' in dhcp_options: - self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') - - if 'vendor_class_id' in dhcp_options: - self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') - - # DHCPv6 options - if 'dhcpv6_options' in config: - dhcpv6_options = config.get('dhcpv6_options') - if 'parameters_only' in dhcpv6_options: - self.dhcp.v6.options['dhcpv6_prm_only'] = True - - if 'temporary' in dhcpv6_options: - self.dhcp.v6.options['dhcpv6_temporary'] = True - - if 'prefix_delegation' in dhcpv6_options: - prefix_delegation = dhcpv6_options.get('prefix_delegation') - if 'length' in prefix_delegation: - self.dhcp.v6.options['dhcpv6_pd_length'] = prefix_delegation.get('length') - - if 'interface' in prefix_delegation: - self.dhcp.v6.options['dhcpv6_pd_interfaces'] = prefix_delegation.get('interface') - # Configure ARP cache timeout in milliseconds - has default value tmp = jmespath.search('ip.arp_cache_timeout', config) value = tmp if (tmp != None) else '30' @@ -982,9 +969,11 @@ class Interface(Control): self.del_vlan(vif_s_id) # create/update 802.1ad (Q-in-Q VLANs) + ifname = config['ifname'] for vif_s_id, vif_s in config.get('vif_s', {}).items(): tmp=get_ethertype(vif_s.get('ethertype', '0x88A8')) s_vlan = self.add_vlan(vif_s_id, ethertype=tmp) + vif_s['ifname'] = f'{ifname}.{vif_s_id}' s_vlan.update(vif_s) # remove no longer required client VLAN (vif-c) @@ -994,6 +983,7 @@ class Interface(Control): # create/update client VLAN (vif-c) interface for vif_c_id, vif_c in vif_s.get('vif_c', {}).items(): c_vlan = s_vlan.add_vlan(vif_c_id) + vif_c['ifname'] = f'{ifname}.{vif_s_id}.{vif_c_id}' c_vlan.update(vif_c) # remove no longer required 802.1q VLAN interfaces @@ -1003,4 +993,5 @@ class Interface(Control): # create/update 802.1q VLAN interfaces for vif_id, vif in config.get('vif', {}).items(): vlan = self.add_vlan(vif_id) + vif['ifname'] = f'{ifname}.{vif_id}' vlan.update(vif) diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index b9a88a949..928113b49 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -108,7 +108,7 @@ def generate(pppoe): if tmp and len(tmp) > 0: # ipv6.tmpl relies on ifname - this should be made consitent in the # future better then double key-ing the same value - render(config_wide_dhcp6c, 'dhcp-client/ipv6_new.tmpl', pppoe, trim_blocks=True) + render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe, trim_blocks=True) return None -- cgit v1.2.3 From b6dcb0a887a4fab89ca870d6142d6856cccc181f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 13:51:48 +0200 Subject: ifconfig: T2653: unify DHCPv4 configuration Pass the interface dictionary transparently to the DHCP module and render the DHCP client config template directly from the same source instead of transcoding it once more. --- data/templates/dhcp-client/daemon-options.tmpl | 2 +- data/templates/dhcp-client/ipv4.tmpl | 20 +++++------ python/vyos/ifconfig/dhcp.py | 48 +++++++++++++------------- python/vyos/ifconfig/interface.py | 10 +----- 4 files changed, 36 insertions(+), 44 deletions(-) (limited to 'python') diff --git a/data/templates/dhcp-client/daemon-options.tmpl b/data/templates/dhcp-client/daemon-options.tmpl index 12786b777..a0ba2c9ef 100644 --- a/data/templates/dhcp-client/daemon-options.tmpl +++ b/data/templates/dhcp-client/daemon-options.tmpl @@ -1 +1 @@ -DHCLIENT_OPTS="-nw -cf {{ conf_file }} -pf {{ pid_file }} -lf {{ lease_file }} {{ '-S' if dhcpv6_prm_only }} {{ '-T' if dhcpv6_temporary }} {{ ifname }}" +DHCLIENT_OPTS="-nw -cf /var/lib/dhcp/dhclient_{{ifname}}.conf -pf /var/lib/dhcp/dhclient_{{ifname}}.pid -lf /var/lib/dhcp/dhclient_{{ifname}}.leases {{ifname}}" diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.tmpl index ab772b5f6..fe2a67f08 100644 --- a/data/templates/dhcp-client/ipv4.tmpl +++ b/data/templates/dhcp-client/ipv4.tmpl @@ -4,14 +4,14 @@ timeout 60; retry 300; interface "{{ ifname }}" { - send host-name "{{ hostname }}"; - {% if client_id -%} - send dhcp-client-identifier "{{ client_id }}"; - {% endif -%} - {% if vendor_class_id -%} - send vendor-class-identifier "{{ vendor_class_id }}"; - {% endif -%} - request subnet-mask, broadcast-address, routers, domain-name-servers, - rfc3442-classless-static-routes, domain-name, interface-mtu; - require subnet-mask; + send host-name "{{ dhcp_options.host_name }}"; +{% if dhcp_options.client_id is defined and dhcp_options.client_id is not none %} + send dhcp-client-identifier "{{ dhcp_options.client_id }}"; +{% endif %} +{% if dhcp_options.vendor_class_id is defined and dhcp_options.vendor_class_id is not none %} + send vendor-class-identifier "{{ dhcp_options.vendor_class_id }}"; +{% endif %} + request subnet-mask, broadcast-address, routers, domain-name-servers, + rfc3442-classless-static-routes, domain-name, interface-mtu; + require subnet-mask; } diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py index bd37970a2..5f99a0b7e 100644 --- a/python/vyos/ifconfig/dhcp.py +++ b/python/vyos/ifconfig/dhcp.py @@ -14,26 +14,22 @@ # License along with this library. If not, see . import os +import jmespath +from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 -from vyos.dicts import FixedDict from vyos.ifconfig.control import Control from vyos.template import render class _DHCPv4 (Control): def __init__(self, ifname): super().__init__() - config_base = r'/var/lib/dhcp/dhclient_' - self.options = FixedDict(**{ - 'ifname': ifname, - 'hostname': '', - 'client_id': '', - 'vendor_class_id': '', - 'conf_file': config_base + f'{ifname}.conf', - 'options_file': config_base + f'{ifname}.options', - 'pid_file': config_base + f'{ifname}.pid', - 'lease_file': config_base + f'{ifname}.leases', - }) + config_base = r'/var/lib/dhcp/dhclient' + self._conf_file = f'{config_base}_{ifname}.conf' + self._options_file = f'{config_base}_{ifname}.options' + self._pid_file = f'{config_base}_{ifname}.pid' + self._lease_file = f'{config_base}_{ifname}.leases' + self.options = {'ifname' : ifname} # replace dhcpv4/v6 with systemd.networkd? def set(self): @@ -42,19 +38,24 @@ class _DHCPv4 (Control): started in background! Example: - >>> from vyos.ifconfig import Interface >>> j = Interface('eth0') >>> j.dhcp.v4.set() """ - if not self.options['hostname']: + + if jmespath.search('dhcp_options.host_name', self.options) == None: # read configured system hostname. # maybe change to vyos hostd client ??? + hostname = 'vyos' with open('/etc/hostname', 'r') as f: - self.options['hostname'] = f.read().rstrip('\n') + hostname = f.read().rstrip('\n') + tmp = {'dhcp_options' : { 'host_name' : hostname}} + self.options = dict_merge(tmp, self.options) - render(self.options['options_file'], 'dhcp-client/daemon-options.tmpl', self.options) - render(self.options['conf_file'], 'dhcp-client/ipv4.tmpl', self.options) + render(self._options_file, 'dhcp-client/daemon-options.tmpl', + self.options, trim_blocks=True) + render(self._conf_file, 'dhcp-client/ipv4.tmpl', + self.options, trim_blocks=True) return self._cmd('systemctl restart dhclient@{ifname}.service'.format(**self.options)) @@ -64,21 +65,20 @@ class _DHCPv4 (Control): pid, config and lease will be removed. Example: - >>> from vyos.ifconfig import Interface >>> j = Interface('eth0') >>> j.dhcp.v4.delete() """ - if not os.path.isfile(self.options['pid_file']): + if not os.path.isfile(self._pid_file): self._debug_msg('No DHCP client PID found') return None self._cmd('systemctl stop dhclient@{ifname}.service'.format(**self.options)) # cleanup old config files - for name in ('conf_file', 'options_file', 'pid_file', 'lease_file'): - if os.path.isfile(self.options[name]): - os.remove(self.options[name]) + for file in [self._conf_file, self._options_file, self._pid_file, self._lease_file]: + if os.path.isfile(file): + os.remove(file) class _DHCPv6 (Control): def __init__(self, ifname): @@ -101,7 +101,8 @@ class _DHCPv6 (Control): # missed it we are safe! verify_dhcpv6(self.options) - render(self._config, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True) + render(self._config, 'dhcp-client/ipv6.tmpl', + self.options, trim_blocks=True) return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format( **self.options)) @@ -111,7 +112,6 @@ class _DHCPv6 (Control): pid, config and lease will be removed. Example: - >>> from vyos.ifconfig import Interface >>> j = Interface('eth0') >>> j.dhcp.v6.delete() diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index f5e43e172..214ece8cd 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -824,15 +824,7 @@ class Interface(Control): # DHCP options if 'dhcp_options' in config: - dhcp_options = config.get('dhcp_options') - if 'client_id' in dhcp_options: - self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') - - if 'host_name' in dhcp_options: - self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') - - if 'vendor_class_id' in dhcp_options: - self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') + self.dhcp.v4.options = config # DHCPv6 options if 'dhcpv6_options' in config: -- cgit v1.2.3 From f5ecc5a40fcf07d9ad08864fbac710717330c255 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Aug 2020 08:29:38 +0200 Subject: dhcpv6-pd: T2741: support delegation on non existing interfaces We must ignore any return code when invoking dhcpc6 initially. This is required to enable DHCPv6-PD for interfaces which are yet not up and running and my be started later by VyOS. --- python/vyos/ifconfig/dhcp.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py index 5f99a0b7e..63224fc0f 100644 --- a/python/vyos/ifconfig/dhcp.py +++ b/python/vyos/ifconfig/dhcp.py @@ -103,7 +103,10 @@ class _DHCPv6 (Control): render(self._config, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True) - return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format( + + # We must ignore any return codes. This is required to enable DHCPv6-PD + # for interfaces which are yet not up and running. + return self._popen('systemctl restart dhcp6c@{ifname}.service'.format( **self.options)) def delete(self): -- cgit v1.2.3 From 98e5105de4390780cf12494c59a06aae5433a67b Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sat, 8 Aug 2020 16:45:17 -0500 Subject: http api: T1431: update args of call to install-image commit 3f8884587 added an endpoint for image management; T2753 updated the command line options. Make script call consistent. --- python/vyos/configsession.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index f2524b37e..0994fd974 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -26,7 +26,7 @@ DISCARD = '/opt/vyatta/sbin/my_discard' SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] SAVE_CONFIG = ['/opt/vyatta/sbin/vyatta-save-config.pl'] -INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image'] +INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url'] REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del'] GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show'] -- cgit v1.2.3 From 21bc98f16110d0fa2d7d93409252da73f58878ed Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 12 Aug 2020 23:01:40 +0200 Subject: ifconfig: dhcp: T2767: client must not start when interface is disabled ISC DHCP client will always place an Interface in admin-up state once it is started. We must ensure that if an interface is placed in A/D state that the DHCP client proccess is not launched and terminated if it is running. --- python/vyos/ifconfig/dhcp.py | 131 -------------------------------------- python/vyos/ifconfig/interface.py | 95 ++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 147 deletions(-) delete mode 100644 python/vyos/ifconfig/dhcp.py (limited to 'python') diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py deleted file mode 100644 index 63224fc0f..000000000 --- a/python/vyos/ifconfig/dhcp.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2020 VyOS maintainers and contributors -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see . - -import os -import jmespath - -from vyos.configdict import dict_merge -from vyos.configverify import verify_dhcpv6 -from vyos.ifconfig.control import Control -from vyos.template import render - -class _DHCPv4 (Control): - def __init__(self, ifname): - super().__init__() - config_base = r'/var/lib/dhcp/dhclient' - self._conf_file = f'{config_base}_{ifname}.conf' - self._options_file = f'{config_base}_{ifname}.options' - self._pid_file = f'{config_base}_{ifname}.pid' - self._lease_file = f'{config_base}_{ifname}.leases' - self.options = {'ifname' : ifname} - - # replace dhcpv4/v6 with systemd.networkd? - def set(self): - """ - Configure interface as DHCP client. The dhclient binary is automatically - started in background! - - Example: - >>> from vyos.ifconfig import Interface - >>> j = Interface('eth0') - >>> j.dhcp.v4.set() - """ - - if jmespath.search('dhcp_options.host_name', self.options) == None: - # read configured system hostname. - # maybe change to vyos hostd client ??? - hostname = 'vyos' - with open('/etc/hostname', 'r') as f: - hostname = f.read().rstrip('\n') - tmp = {'dhcp_options' : { 'host_name' : hostname}} - self.options = dict_merge(tmp, self.options) - - render(self._options_file, 'dhcp-client/daemon-options.tmpl', - self.options, trim_blocks=True) - render(self._conf_file, 'dhcp-client/ipv4.tmpl', - self.options, trim_blocks=True) - - return self._cmd('systemctl restart dhclient@{ifname}.service'.format(**self.options)) - - def delete(self): - """ - De-configure interface as DHCP clinet. All auto generated files like - pid, config and lease will be removed. - - Example: - >>> from vyos.ifconfig import Interface - >>> j = Interface('eth0') - >>> j.dhcp.v4.delete() - """ - if not os.path.isfile(self._pid_file): - self._debug_msg('No DHCP client PID found') - return None - - self._cmd('systemctl stop dhclient@{ifname}.service'.format(**self.options)) - - # cleanup old config files - for file in [self._conf_file, self._options_file, self._pid_file, self._lease_file]: - if os.path.isfile(file): - os.remove(file) - -class _DHCPv6 (Control): - def __init__(self, ifname): - super().__init__() - self.options = {'ifname' : ifname} - self._config = f'/run/dhcp6c/dhcp6c.{ifname}.conf' - - def set(self): - """ - Configure interface as DHCPv6 client. The client is automatically - started in background when address is configured as DHCP. - - Example: - >>> from vyos.ifconfig import Interface - >>> j = Interface('eth0') - >>> j.dhcp.v6.set() - """ - - # better save then sorry .. should be checked in interface script but if you - # missed it we are safe! - verify_dhcpv6(self.options) - - render(self._config, 'dhcp-client/ipv6.tmpl', - self.options, trim_blocks=True) - - # We must ignore any return codes. This is required to enable DHCPv6-PD - # for interfaces which are yet not up and running. - return self._popen('systemctl restart dhcp6c@{ifname}.service'.format( - **self.options)) - - def delete(self): - """ - De-configure interface as DHCPv6 client. All auto generated files like - pid, config and lease will be removed. - - Example: - >>> from vyos.ifconfig import Interface - >>> j = Interface('eth0') - >>> j.dhcp.v6.delete() - """ - self._cmd('systemctl stop dhcp6c@{ifname}.service'.format(**self.options)) - - # cleanup old config files - if os.path.isfile(self._config): - os.remove(self._config) - -class DHCP(object): - def __init__(self, ifname): - self.v4 = _DHCPv4(ifname) - self.v6 = _DHCPv6(ifname) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 214ece8cd..36f258301 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -31,6 +31,8 @@ from netifaces import AF_INET6 from vyos import ConfigError from vyos.configdict import list_diff +from vyos.configdict import dict_merge +from vyos.template import render from vyos.util import mac2eui64 from vyos.validate import is_ipv4 from vyos.validate import is_ipv6 @@ -43,7 +45,6 @@ from vyos.validate import assert_positive from vyos.validate import assert_range from vyos.ifconfig.control import Control -from vyos.ifconfig.dhcp import DHCP from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.operational import Operational from vyos.ifconfig import Section @@ -216,7 +217,6 @@ class Interface(Control): # we must have updated config before initialising the Interface super().__init__(**kargs) self.ifname = ifname - self.dhcp = DHCP(ifname) if not self.exists(ifname): # Any instance of Interface, such as Interface('eth0') @@ -722,9 +722,9 @@ class Interface(Control): # add to interface if addr == 'dhcp': - self.dhcp.v4.set() + self.set_dhcp(True) elif addr == 'dhcpv6': - self.dhcp.v6.set() + self.set_dhcpv6(True) elif not is_intf_addr_assigned(self.ifname, addr): self._cmd(f'ip addr add "{addr}" ' f'{"brd + " if addr_is_v4 else ""}dev "{self.ifname}"') @@ -763,9 +763,9 @@ class Interface(Control): # remove from interface if addr == 'dhcp': - self.dhcp.v4.delete() + self.set_dhcp(False) elif addr == 'dhcpv6': - self.dhcp.v6.delete() + self.set_dhcpv6(False) elif is_intf_addr_assigned(self.ifname, addr): self._cmd(f'ip addr del "{addr}" dev "{self.ifname}"') else: @@ -784,8 +784,8 @@ class Interface(Control): Will raise an exception on error. """ # stop DHCP(v6) if running - self.dhcp.v4.delete() - self.dhcp.v6.delete() + self.set_dhcp(False) + self.set_dhcpv6(False) # flush all addresses self._cmd(f'ip addr flush dev "{self.ifname}"') @@ -809,12 +809,83 @@ class Interface(Control): return True + def set_dhcp(self, enable): + """ + Enable/Disable DHCP client on a given interface. + """ + if enable not in [True, False]: + raise ValueError() + + ifname = self.ifname + config_base = r'/var/lib/dhcp/dhclient' + config_file = f'{config_base}_{ifname}.conf' + options_file = f'{config_base}_{ifname}.options' + pid_file = f'{config_base}_{ifname}.pid' + lease_file = f'{config_base}_{ifname}.leases' + + if enable and 'disable' not in self._config: + if jmespath.search('dhcp_options.host_name', self._config) == None: + # read configured system hostname. + # maybe change to vyos hostd client ??? + hostname = 'vyos' + with open('/etc/hostname', 'r') as f: + hostname = f.read().rstrip('\n') + tmp = {'dhcp_options' : { 'host_name' : hostname}} + self._config = dict_merge(tmp, self._config) + + render(options_file, 'dhcp-client/daemon-options.tmpl', + self._config, trim_blocks=True) + render(config_file, 'dhcp-client/ipv4.tmpl', + self._config, trim_blocks=True) + + # 'up' check is mandatory b/c even if the interface is A/D, as soon as + # the DHCP client is started the interface will be placed in u/u state. + # This is not what we intended to do when disabling an interface. + return self._cmd(f'systemctl restart dhclient@{ifname}.service') + else: + self._cmd(f'systemctl stop dhclient@{ifname}.service') + + # cleanup old config files + for file in [config_file, options_file, pid_file, lease_file]: + if os.path.isfile(file): + os.remove(file) + + + def set_dhcpv6(self, enable): + """ + Enable/Disable DHCPv6 client on a given interface. + """ + if enable not in [True, False]: + raise ValueError() + + ifname = self.ifname + config_file = f'/run/dhcp6c/dhcp6c.{ifname}.conf' + + if enable and 'disable' not in self._config: + render(config_file, 'dhcp-client/ipv6.tmpl', + self._config, trim_blocks=True) + + # We must ignore any return codes. This is required to enable DHCPv6-PD + # for interfaces which are yet not up and running. + return self._popen(f'systemctl restart dhcp6c@{ifname}.service') + else: + self._popen(f'systemctl stop dhcp6c@{ifname}.service') + + if os.path.isfile(config_file): + os.remove(config_file) + + def update(self, config): """ General helper function which works on a dictionary retrived by get_config_dict(). It's main intention is to consolidate the scattered interface setup code and provide a single point of entry when workin on any interface. """ + # Cache the configuration - it will be reused inside e.g. DHCP handler + # XXX: maybe pass the option via __init__ in the future and rename this + # method to apply()? + self._config = config + # Update interface description self.set_alias(config.get('description', '')) @@ -822,14 +893,6 @@ class Interface(Control): value = '2' if 'disable_link_detect' in config else '1' self.set_link_detect(value) - # DHCP options - if 'dhcp_options' in config: - self.dhcp.v4.options = config - - # DHCPv6 options - if 'dhcpv6_options' in config: - self.dhcp.v6.options = config - # Configure assigned interface IP addresses. No longer # configured addresses will be removed first new_addr = config.get('address', []) -- cgit v1.2.3 From e7372e132a152477104d7e208a0be956b9879a71 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 13 Aug 2020 07:08:37 +0200 Subject: ifconfig: dhcp: fix ModuleNotFoundError: No module named 'vyos.ifconfig.dhcp' Commit 21bc98f1 ("ifconfig: dhcp: T2767: client must not start when interface is disabled") dropped the vyos.ifconfig.dhcp module but not removed it from the modules import list. --- python/vyos/ifconfig/__init__.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index a7cdeadd1..9cd8d44c1 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -13,12 +13,10 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . - from vyos.ifconfig.section import Section from vyos.ifconfig.control import Control from vyos.ifconfig.interface import Interface from vyos.ifconfig.operational import Operational -from vyos.ifconfig.dhcp import DHCP from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.bond import BondIf -- cgit v1.2.3 From b082a6fb211ef19d75c4c81414be9aa1b9248b45 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 13 Aug 2020 18:31:08 +0200 Subject: lcd: T2564: flatten CLI interface * set system lcd device * set system lcd model Both device and model have completion helpers for supported interfaces and LCD displays. --- data/templates/lcd/LCDd.conf.tmpl | 126 ++ data/templates/lcd/lcdproc.conf.tmpl | 60 + data/templates/system-display/LCDd.conf.tmpl | 1500 -------------------- data/templates/system-display/lcdproc.conf.tmpl | 173 --- interface-definitions/system-display.xml.in | 235 --- interface-definitions/system-lcd.xml.in | 62 + python/vyos/util.py | 12 + src/conf_mode/interfaces-wirelessmodem.py | 12 +- src/conf_mode/system-display.py | 98 -- src/conf_mode/system_lcd.py | 84 ++ .../systemd/system/LCDd.service.d/override.conf | 8 + .../system/hostapd@.service.d/override.conf | 7 +- src/systemd/LCDd@.service | 10 - src/systemd/lcdproc.service | 13 + src/systemd/lcdproc@.service | 10 - 15 files changed, 369 insertions(+), 2041 deletions(-) create mode 100644 data/templates/lcd/LCDd.conf.tmpl create mode 100644 data/templates/lcd/lcdproc.conf.tmpl delete mode 100644 data/templates/system-display/LCDd.conf.tmpl delete mode 100644 data/templates/system-display/lcdproc.conf.tmpl delete mode 100644 interface-definitions/system-display.xml.in create mode 100644 interface-definitions/system-lcd.xml.in delete mode 100755 src/conf_mode/system-display.py create mode 100755 src/conf_mode/system_lcd.py create mode 100644 src/etc/systemd/system/LCDd.service.d/override.conf delete mode 100644 src/systemd/LCDd@.service create mode 100644 src/systemd/lcdproc.service delete mode 100644 src/systemd/lcdproc@.service (limited to 'python') diff --git a/data/templates/lcd/LCDd.conf.tmpl b/data/templates/lcd/LCDd.conf.tmpl new file mode 100644 index 000000000..da749d04a --- /dev/null +++ b/data/templates/lcd/LCDd.conf.tmpl @@ -0,0 +1,126 @@ +### Autogenerted by system-display.py ## + +# LCDd.conf -- configuration file for the LCDproc server daemon LCDd +# +# This file contains the configuration for the LCDd server. +# +# The format is ini-file-like. It is divided into sections that start at +# markers that look like [section]. Comments are all line-based comments, +# and are lines that start with '#' or ';'. +# +# The server has a 'central' section named [server]. For the menu there is +# a section called [menu]. Further each driver has a section which +# defines how the driver acts. +# +# The drivers are activated by specifying them in a driver= line in the +# server section, like: +# +# Driver=curses +# +# This tells LCDd to use the curses driver. +# The first driver that is loaded and is capable of output defines the +# size of the display. The default driver to use is curses. +# If the driver is specified using the -d command line option, +# the Driver= options in the config file are ignored. +# +# The drivers read their own options from the respective sections. + +## Server section with all kinds of settings for the LCDd server ## +[server] + +# Where can we find the driver modules ? +# NOTE: Always place a slash as last character ! +DriverPath=/usr/lib/x86_64-linux-gnu/lcdproc/ + +# Tells the server to load the given drivers. Multiple lines can be given. +# The name of the driver is case sensitive and determines the section +# where to look for further configuration options of the specific driver +# as well as the name of the dynamic driver module to load at runtime. +# The latter one can be changed by giving a File= directive in the +# driver specific section. +# +# The following drivers are supported: +# bayrad, CFontz, CFontzPacket, curses, CwLnx, ea65, EyeboxOne, futaba, +# g15, glcd, glcdlib, glk, hd44780, icp_a106, imon, imonlcd,, IOWarrior, +# irman, joy, lb216, lcdm001, lcterm, linux_input, lirc, lis, MD8800, +# mdm166a, ms6931, mtc_s16209x, MtxOrb, mx5000, NoritakeVFD, +# Olimex_MOD_LCD1x9, picolcd, pyramid, rawserial, sdeclcd, sed1330, +# sed1520, serialPOS, serialVFD, shuttleVFD, sli, stv5730, svga, t6963, +# text, tyan, ula200, vlsys_m428, xosd, yard2LCD + +{% if model is defined and model.startswith('CFA-') %} +Driver=CFontzPacket +{% endif %} + +# Tells the driver to bind to the given interface. [default: 127.0.0.1] +Bind=127.0.0.1 + +# Listen on this specified port. [default: 13666] +Port=13666 + +# Sets the reporting level; defaults to warnings and errors only. +# [default: 2; legal: 0-5] +ReportLevel=3 + +# Should we report to syslog instead of stderr? [default: no; legal: yes, no] +ReportToSyslog=yes + +# User to run as. LCDd will drop its root privileges and run as this user +# instead. [default: nobody] +User=nobody + +# The server will stay in the foreground if set to yes. +# [default: no, legal: yes, no] +Foreground=yes + +# Hello message: each entry represents a display line; default: builtin +Hello="Starting VyOS" +Hello=" ... " + +# GoodBye message: each entry represents a display line; default: builtin +GoodBye=" VyOS shutting" +GoodBye=" down... " + +# Sets the interval in microseconds for updating the display. +# [default: 125000 meaning 8Hz] +FrameInterval=250000 # 4 updates per second + +# Sets the default time in seconds to displays a screen. [default: 4] +WaitTime=1 + +# If set to no, LCDd will start with screen rotation disabled. This has the +# same effect as if the ToggleRotateKey had been pressed. Rotation will start +# if the ToggleRotateKey is pressed. Note that this setting does not turn off +# priority sorting of screens. [default: on; legal: on, off] +AutoRotate=on + +# If yes, the the serverscreen will be rotated as a usual info screen. If no, +# it will be a background screen, only visible when no other screens are +# active. The special value 'blank' is similar to no, but only a blank screen +# is displayed. [default: on; legal: on, off, blank] +ServerScreen=blank + +# Set master backlight setting. If set to 'open' a client may control the +# backlight for its own screens (only). [default: open; legal: off, open, on] +Backlight=on + +# Set master heartbeat setting. If set to 'open' a client may control the +# heartbeat for its own screens (only). [default: open; legal: off, open, on] +Heartbeat=off + +# set title scrolling speed [default: 10; legal: 0-10] +TitleSpeed=10 + +{% if model is defined and model is not none %} +{% if model.startswith('CFA-') %} +## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ## +[CFontzPacket] +Model={{ model.split('-')[1] }} +Device={{ device }} +Contrast=350 +Brightness=500 +OffBrightness=50 +Reboot=yes +USB=yes +{% endif %} +{% endif %} diff --git a/data/templates/lcd/lcdproc.conf.tmpl b/data/templates/lcd/lcdproc.conf.tmpl new file mode 100644 index 000000000..c79f3cd0d --- /dev/null +++ b/data/templates/lcd/lcdproc.conf.tmpl @@ -0,0 +1,60 @@ +### autogenerated by system-lcd.py ### + +# LCDproc client configuration file + +[lcdproc] +Server=127.0.0.1 +Port=13666 + +# set reporting level +ReportLevel=3 + +# report to to syslog ? +ReportToSyslog=true + +Foreground=yes + +[CPU] +Active=true +OnTime=1 +OffTime=2 +ShowInvisible=false + +[SMP-CPU] +Active=false + +[Memory] +Active=false + +[Load] +Active=false + +[Uptime] +Active=true + +[ProcSize] +Active=false + +[Disk] +Active=false + +[About] +Active=false + +[TimeDate] +Active=true +TimeFormat="%H:%M:%S" + +[OldTime] +Active=false + +[BigClock] +Active=false + +[MiniClock] +Active=false + +# Display the title bar in two-line mode. Note that with four lines or more +# the title is always shown. [default: true; legal: true, false] +ShowTitle=false + diff --git a/data/templates/system-display/LCDd.conf.tmpl b/data/templates/system-display/LCDd.conf.tmpl deleted file mode 100644 index 1dd646202..000000000 --- a/data/templates/system-display/LCDd.conf.tmpl +++ /dev/null @@ -1,1500 +0,0 @@ -### Autogenerted by system-display.py ## -# LCDd.conf -- configuration file for the LCDproc server daemon LCDd -# -# This file contains the configuration for the LCDd server. -# -# The format is ini-file-like. It is divided into sections that start at -# markers that look like [section]. Comments are all line-based comments, -# and are lines that start with '#' or ';'. -# -# The server has a 'central' section named [server]. For the menu there is -# a section called [menu]. Further each driver has a section which -# defines how the driver acts. -# -# The drivers are activated by specifying them in a driver= line in the -# server section, like: -# -# Driver=curses -# -# This tells LCDd to use the curses driver. -# The first driver that is loaded and is capable of output defines the -# size of the display. The default driver to use is curses. -# If the driver is specified using the -d command line option, -# the Driver= options in the config file are ignored. -# -# The drivers read their own options from the respective sections. - - - -## Server section with all kinds of settings for the LCDd server ## -[server] - -# Where can we find the driver modules ? -# IMPORTANT: Make sure to change this setting to reflect your -# specific setup! Otherwise LCDd won't be able to find -# the driver modules and will thus not be able to -# function properly. -# NOTE: Always place a slash as last character ! -DriverPath=/usr/lib/x86_64-linux-gnu/lcdproc/ - -# Tells the server to load the given drivers. Multiple lines can be given. -# The name of the driver is case sensitive and determines the section -# where to look for further configuration options of the specific driver -# as well as the name of the dynamic driver module to load at runtime. -# The latter one can be changed by giving a File= directive in the -# driver specific section. -# -# The following drivers are supported: -# bayrad, CFontz, CFontzPacket, curses, CwLnx, ea65, EyeboxOne, futaba, -# g15, glcd, glcdlib, glk, hd44780, icp_a106, imon, imonlcd,, IOWarrior, -# irman, joy, lb216, lcdm001, lcterm, linux_input, lirc, lis, MD8800, -# mdm166a, ms6931, mtc_s16209x, MtxOrb, mx5000, NoritakeVFD, -# Olimex_MOD_LCD1x9, picolcd, pyramid, rawserial, sdeclcd, sed1330, -# sed1520, serialPOS, serialVFD, shuttleVFD, sli, stv5730, svga, t6963, -# text, tyan, ula200, vlsys_m428, xosd, yard2LCD -{%- if model == 'sdec' %} -Driver=sdeclcd -{%- endif %} - -{%- if model == 'ezio' %} -Driver=hd44780 -{%- endif %} - -{%- if model == 'test' %} -Driver=CFontzPacket -{%- endif %} - -# Tells the driver to bind to the given interface. [default: 127.0.0.1] -#Bind=127.0.0.1 - -# Listen on this specified port. [default: 13666] -#Port=13666 - -# Sets the reporting level; defaults to warnings and errors only. -# [default: 2; legal: 0-5] -#ReportLevel=3 - -# Should we report to syslog instead of stderr? [default: no; legal: yes, no] -#ReportToSyslog=yes - -# User to run as. LCDd will drop its root privileges and run as this user -# instead. [default: nobody] -User=nobody - -# The server will stay in the foreground if set to yes. -# [default: no, legal: yes, no] -#Foreground=yes - -# Hello message: each entry represents a display line; default: builtin -Hello="{%- if hello %}{{ hello }}{%- else %}Welcome to VyOS{%- endif %}" - -# GoodBye message: each entry represents a display line; default: builtin -GoodBye="{%- if bye %}{{ bye }}{%- else %}Bye from VyOS{%- endif %}" - -# Sets the interval in microseconds for updating the display. -# [default: 125000 meaning 8Hz] -#FrameInterval=125000 - -# Sets the default time in seconds to displays a screen. [default: 4] -WaitTime={%- if time %}{{ time }}{%- else%}4{%- endif %} - -# If set to no, LCDd will start with screen rotation disabled. This has the -# same effect as if the ToggleRotateKey had been pressed. Rotation will start -# if the ToggleRotateKey is pressed. Note that this setting does not turn off -# priority sorting of screens. [default: on; legal: on, off] -#AutoRotate=off - -# If yes, the the serverscreen will be rotated as a usual info screen. If no, -# it will be a background screen, only visible when no other screens are -# active. The special value 'blank' is similar to no, but only a blank screen -# is displayed. [default: on; legal: on, off, blank] -ServerScreen=no - -# Set master backlight setting. If set to 'open' a client may control the -# backlight for its own screens (only). [default: open; legal: off, open, on] -#Backlight=open - -# Set master heartbeat setting. If set to 'open' a client may control the -# heartbeat for its own screens (only). [default: open; legal: off, open, on] -#Heartbeat=open - -# set title scrolling speed [default: 10; legal: 0-10] -#TitleSpeed=10 - -# The "...Key=" lines define what the server does with keypresses that -# don't go to any client. The ToggleRotateKey stops rotation of screens, while -# the PrevScreenKey and NextScreenKey go back / forward one screen (even if -# rotation is disabled. -# Assign the key string returned by the driver to the ...Key setting. These -# are the defaults: -ToggleRotateKey=Enter -PrevScreenKey=Left -NextScreenKey=Right -#ScrollUpKey=Up -#ScrollDownKey=Down - -## The menu section. The menu is an internal LCDproc client. ## -[menu] -# If true the server allows transitions between different client's menus -# [default: false; legal: true, false] -#PermissiveGoto=false - -# You can configure what keys the menu should use. Note that the MenuKey -# will be reserved exclusively, the others work in shared mode. - -# Up to six keys are supported. The MenuKey (to enter and exit the menu), the -# EnterKey (to select values) and at least one movement keys are required. -# These are the default key assignments: -MenuKey=Escape -EnterKey=Enter -UpKey=Up -DownKey=Down -#LeftKey=Left -#RightKey=Right - - -### Driver sections are below this line, in alphabetical order ### - - -## EMAC BayRAD driver ## -[bayrad] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the communication speed [default: 9600; legal: 1200, 2400, 9600, 19200] -Speed=9600 - - - -## CrystalFontz driver (for CF632 & CF634) ## -[CFontz] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/ttyS0 -# Select the LCD size [default: 20x4] -Size=20x4 -# Set the initial contrast [default: 560; legal: 0 - 1000] -Contrast=350 -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=0 -# Set the communication speed [default: 9600; legal: 1200, 2400, 9600, 19200, -# 115200] -Speed=9600 -# Set the firmware version (New means >= 2.0) [default: no; legal: yes, no] -NewFirmware=no -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -# normally you shouldn't need this -Reboot=no - - - -## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ## -[CFontzPacket] -{%- if model == 'test' %} -Model=533 -Device=/dev/serial/by-bus/usb0b1.1p1.0 -Contrast=350 -Brightness=1000 -OffBrightness=50 -Reboot=yes -USB=yes -{%- endif %} - - -## Curses driver ## -[curses] - -# color settings -# foreground color [default: blue] -Foreground=blue -# background color when "backlight" is off [default: cyan] -Background=cyan -# background color when "backlight" is on [default: red] -Backlight=red - -# display size [default: 20x4] -Size=20x2 - -# What position (X,Y) to start the left top corner at... -# Default: (7,7) -TopLeftX=7 -TopLeftY=7 - -# use ASC symbols for icons & bars [default: no; legal: yes, no] -UseACS=no - -# draw Border [default: yes; legal: yes, no] -DrawBorder=yes - - - -## Cwlinux driver ## -[CwLnx] - -# Select the LCD model [default: 12232; legal: 12232, 12832, 1602] -Model=12232 - -# Select the output device to use [default: /dev/lcd] -Device=/dev/ttyUSB0 - -# Select the LCD size. Default depends on model: -# 12232: 20x4 -# 12832: 21x4 -# 1602: 16x2 -Size=20x4 - -# Set the communication speed [default: 19200; legal: 9600, 19200] -Speed=19200 - -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -# normally you shouldn't need this -Reboot=no - -# If you have a keypad connected. Keypad layout is currently not -# configureable from the config file. -Keypad=yes - -# If you have a non-standard keypad you can associate any keystrings to keys. -# There are 6 input keys in the CwLnx hardware that generate characters -# from 'A' to 'F'. -# -# The following is the built-in default mapping hardcoded in the driver. -# You can leave those unchanged if you have a standard keypad. -# You can change it if you want to report other keystrings or have a non -# standard keypad. -# KeyMap_A=Up -# KeyMap_B=Down -# KeyMap_C=Left -# KeyMap_D=Right -# KeyMap_E=Enter -# KeyMap_F=Escape - -# keypad_test_mode permits one to test keypad assignment -# Default value is no -#keypad_test_mode=yes - - - -## ea65 driver for the display in AOpen XC Cube AV EA65 media barebones ## -[ea65] - -# Device is fixed /dev/ttyS1 -# Width and Height are fixed 9x1 - -# As the VFD is self luminescent we don't have a backlight -# But we can use the backlight functions to control the front LEDs -# Brightness 0 to 299 -> LEDs off -# Brightness 300 to 699 -> LEDs half bright -# Brightness 700 to 1000 -> LEDs full bright -Brightness=500 -# OffBrightness is the the value used for the 'backlight off' state -OffBrightness=0 - - - -## EyeboxOne driver ## -[EyeboxOne] - -# Select the output device to use [default: /dev/ttyS1] -# Device=/dev/cua01 -Device=/dev/ttyS1 - -# Set the display size [default: 20x4] -Size=20x4 - -# Switch on the backlight? [default: yes] -Backlight=yes - -# Switch on the cursor? [default: no] -Cursor=no - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] -Speed=19200 - -# Enter Key is a \r character, so it's hardcoded in the driver -LeftKey=D -RightKey=C -UpKey=A -DownKey=B -EscapeKey=P - -# You can find out which key of your display sends which -# character by setting keypad_test_mode to yes and running -# LCDd. LCDd will output all characters it receives. -# Afterwards you can modify the settings above and set -# keypad_set_mode to no again. -keypad_test_mode=no - -## Futaba TOSD-5711BB VFD Driver ## -[futaba] - -## g15 driver for Logitech G15 Keyboard LCDs ## -[g15] - -# Display size (currently unused) -size=20x5 - - - -## glcd generic graphical display driver -[glcd] -# Select what type of connection. See documentation for types. -ConnectionType=t6963 - -# Width and height of the display in pixel. The supported sizes may depend on -# the ConnectionType. [default: 128x64; legal: 1x1 - 640x480] -#Size=128x64 - -# Width and height of a character cell in pixels. This value is only used if -# the driver has been compiled with FreeType and it is enabled. Otherwise the -# default 6x8 cell is used. -#CellSize=12x16 - -# If LCDproc has been compiled with FreeType 2 support this option can be used -# to turn if off intentionally. [default: yes; legal: yes, no] -#useFT2=no - -# Path to font file to use for FreeType rendering. This font must be monospace -# and should contain some special Unicode characters like arrows (Andale Mono -# is recommended and can be fetched at http://corefonts.sf.net). -#normal_font=/usr/local/lib/X11/fonts/TTF/andalemo.ttf - -# Some fonts miss the Unicode characters used to represent icons. In this case -# the built-in 5x8 font can used if this option is turned off. [default: yes; -# legal: yes, no] -#fontHasIcons=no - -# Set the initial contrast if supported by connection type. -# [default: 600; legal: 0 - 1000] -#Contrast=600 - -# Set brightness of the backlight if the backlight is switched 'on'. -# [default: 800; legal: 0 - 1000] -#Brightness=1000 - -# Set brightness of the backlight if the backlight is switched 'off'. Set this -# to zero to completely turn off the backlight. [default: 100; legal: 0 - 1000] -#OffBrightness=0 - -# Time (ms) from first key report to first repeat. Set to 0 to disable repeated -# key reports. [default: 500; legal: 0 - 3000] -#KeyRepeatDelay=500 - -# Time (ms) between repeated key reports. Ignored if KeyRepeatDelay is disabled -# (set to zero). [default: 300; legal: 0 - 3000] -#KeyRepeatInterval=300 - -# Assign key strings to keys. There may be up to 16 keys numbered 'A' to 'Z'. -# By default keys 'A' to 'F' are assigned Up, Down, Left, Right, Enter, Escape. -KeyMap_A=Up -KeyMap_B=Down -KeyMap_C=Enter -KeyMap_D=Escape - -# --- t6963 options --- - -# Parallel port to use [default: 0x378; legal: 0x200 - 0x400] -#Port=0x378 - -# Use LPT port in bi-directional mode. This should work on most LPT port -# and is required for proper timing! [default: yes; legal: yes, no] -#bidirectional=yes - -# Insert additional delays into reads / writes. [default: no; legal: yes, no] -#delayBus=no - -# --- serdisplib options --- - -# Name of the underlying serdisplib driver, e.g. ctinclud. See -# serdisplib documentation for details. -serdisp_name=t6963 - -# The display device to use, e.g. serraw:/dev/ttyS0, -# parport:/dev/parport0 or USB:07c0/1501. -serdisp_device=/dev/ppi0 - -# Options string to pass to serdisplib during initialization. Use -# this to set any display related options (e.g. wiring). The display size is -# always set based on the Size configured above! By default, no options are -# set. -# Important: The value must be quoted as it contains equal signs! -#serdisp_options="INVERT=1" - -# --- x11 options --- - -# PixelSize is size of each dot in pixels + a pixel gap. [default: 3+1] -#x11_PixelSize=3+1 - -# Colors are in RRGGBB format prefixed with "0x". -# PixelColor: The color of each dot at full contrast. [default: 0x000000] -#x11_PixelColor=0x000000 - -# BacklightColor: The color of the backlight as full brightness. -# [default: 0x80FF80] -#x11_BacklightColor=0x80FF80 - -# Border: Adds a border (empty space) around the LCD portion of X11 window. -# [default: 20] -#x11_Border=20 - -# Inverted: inverts the pixels [default: no; legal: yes, no] -#x11_Inverted=no - -# --- picolcdgfx options --- - -# Time in ms for usb_read to wait on a key press. [default: 125; legal: >0] -#picolcdgfx_KeyTimeout=125 - -# Inverted: Inverts the pixels. [default: no; legal: yes or no] -#picolcdgfx_Inverted=no - - - -## glcdlib meta driver for graphical LCDs ## -[glcdlib] - -## mandatory: - -# which graphical display supported by graphlcd-base to use [default: image] -# (see /etc/graphlcd.conf for possible drivers) -Driver=noritake800 - -# no=use graphlcd bitmap fonts (they have only one size / font file) -# yes=use fonts supported by FreeType2 (needs Freetype2 support in -# libglcdprocdriver and its dependants) -UseFT2=yes - -# text resolution in fixed width characters [default: 16x4] -# (if it won't fit according to available physical pixel resolution -# and the minimum available font face size in pixels, then -# 'DebugBorder' will automatically be turned on) -TextResolution=20x4 - -# path to font file to use -FontFile=/usr/share/fonts/corefonts/courbd.ttf - -## these only apply if UseFT2=yes: - -# character encoding to use -CharEncoding=iso8859-2 - -# minimum size in pixels in which fonts should be rendered -MinFontFaceSize=7x12 - -## optional: -Brightness=50 # Brightness (in %) if applicable -Contrast=50 # Contrast (in %) if applicable -Backlight=no # Backlight if applicable -UpsideDown=no # flip image upside down -Invert=no # invert light/dark pixels -ShowDebugFrame=no # turns on/off 1 pixel thick debugging - # border within the usable text area, - # for setting up TextResolution and - # MinFontFaceSize (if using FT2); -ShowBigBorder=no # border around the unused area -ShowThinBorder=yes # border around the unused area -PixelShiftX=0 -PixelShiftY=2 - - - -## Matrix Orbital GLK driver ## -[glk] - -# select the serial device to use [default: /dev/lcd] -Device=/dev/lcd - -# set the initial contrast value [default: 500; legal: 0 - 1000] -Contrast=500 - -# set the serial port speed [default: 19200; legal: 9600, 19200, 38400, 57600, 115200] -Speed=19200 - - - -## Hitachi HD44780 driver ## -[hd44780] -{%- if model == 'ezio' %} -ConnectionType=ezio -Device=/dev/ttyUSB0 -Keypad=yes -Size=16x2 -KeyMatrix_4_1=Enter -KeyMatrix_4_2=Up -KeyMatrix_4_3=Down -KeyMatrix_4_4=Escape -{%- endif %} - -# Select what type of connection. See documentation for available types. -#ConnectionType=4bit - -# Select model if have non-standard one which require extra initialization or handling or -# just want extra features it offers. -# Available: standard (default), extended, winstar_oled, pt6314_vfd -# - standard is default, use for LCDs not mentioned below. -# - extended, hd66712, ks0073: allows use 4-line "extended" mode, -# same as deprecated now option ExtendedMode=yes -# - winstar_oled, weh00xxyya: changes initialization for WINSTAR's WEH00xxyyA displays -# and allows handling brightness -# - pt6314_vfd: allows handling brightness on PTC's PT6314 VFDs -# -# This option should be independent of connection type. -#Model = standard - -# I/O address of the LPT port. Usual values are: 0x278, 0x378 and 0x3BC. -# For I2C connections this sets the slave address (usually 0x20). -#Port=0x378 - -# Device of the serial, I2C, or SPI interface [default: /dev/lcd] -#Device=/dev/ttyS0 - -# Bitrate of the serial port (0 for interface default) -#Speed=0 - -# If you have a keypad connected. -# You may also need to configure the keypad layout further on in this file. -#Keypad=no - -# Set the initial contrast (bwctusb, lcd2usb, and usb4all) -# [default: 800; legal: 0 - 1000] -#Contrast=0 - -# Set brightness of the backlight (lcd2usb and usb4all): -# Brightness is the brightness while the backlight is set to 'on'. -# [default: 800; legal: 0 - 1000] -#Brightness=1000 - -# OffBrightness is the brightness while the backlight is set to 'off'. -# [default: 300; legal: 0 - 1000] -#OffBrightness=0 - -# Specify if you have a switchable backlight and if yes, can select method for turning it on/off: -# -# - none - no switchable backlight is available. For compability also boolean -# 0, n, no, off and false are aliases. -# - external - use external pin or any other method defined with ConnectionType backlight -# handling. For backward compability also this value is chosen for boolean -# TRUE values: 1, y, yes, on and true. -# - internal - means that backlight is handled using internal commands according -# to selected display model (with Model option). Depending on model, -# Brightness and OffBrightness options can be taken into account. -# - internalCmds - means that commands for turning on and off backlight are given -# with extra options BacklightOnCmd and BacklightOffCmd, which would be treated -# as catch up (last resort) for other types of displays which have similar features. -# -# You can provide multiple occurences of this option to use more than one method. -# Default is model specific: Winstar OLED and PT6314 VFD enables internal backlight mode, -# for others it is set to none. -#Backlight = none - -# Commands for enabling internal backlight for use with Backlight=internalCmds. -# Up to 4 bytes can be encoded, as integer number in big-endian order. -# -# NOTE: this is advanced option, if command contains bits other than only brighness handling, -# they must be set accordingly to not disrupt display state. If for example 'FUNCTION SET' command -# is used for this purpose, bits of interface length (4-bit / 8-bit) must be set according to -# selected ConnectionType. -#BacklightCmdOn=0x1223 - -# Commands for disabling internal backlight for use with Backlight=internalCmds. -# Up to 4 bytes can be encoded, as integer number in big-endian order. -#BacklightCmdOff=0x1234 - - -# If you have the additional output port ("bargraph") and you want to -# be able to control it with the lcdproc OUTPUT command -#OutputPort=no - -# Specifies if the last line is pixel addressable (yes) or it controls an -# underline effect (no). [default: yes; legal: yes, no] -#Lastline=yes - -# Specifies the size of the LCD. -# In case of multiple combined displays, this should be the total size. -#Size=20x4 - -# For multiple combined displays: how many lines does each display have. -# Vspan=2,2 means both displays have 2 lines. -#vspan=2,2 - -# If you have an HD66712, a KS0073 or another controller with 'extended mode', -# set this flag to get into 4-line mode. On displays with just two lines, do -# not set this flag. -# As an additional restriction, controllers with and without extended mode -# AND 4 lines cannot be mixed for those connection types that support more -# than one display! -# NOTE: This option is deprecated in favour of choosing Model=extended option. -#ExtendedMode=yes - -# In extended mode, on some controllers like the ST7036 (in 3 line mode) -# the next line in DDRAM won't start 0x20 higher. [default: 0x20] -#LineAddress=0x10 - -# Character map to to map ISO-8859-1 to the LCD's character set -# [default: hd44780_default; legal: hd44780_default, hd44780_euro, ea_ks0073, -# sed1278f_0b, hd44780_koi8_r, hd44780_cp1251, hd44780_8859_5, upd16314, -# weh001602a_1] -# (hd44780_koi8_r, hd44780_cp1251, hd44780_8859_5, upd16314 and weh001602a_1 -# are possible if compiled with additional charmaps) -CharMap=hd44780_default - -# Font bank to be used for some displays such as the WINSTAR WEH001602A -# 0: English/Japanese (default) -# 1: Western Europe I -# 2: English/Rusian -# 3: Western Europe II -#FontBank=0 - -# If your display is slow and cannot keep up with the flow of data from -# LCDd, garbage can appear on the LCDd. Set this delay factor to 2 or 4 -# to increase the delays. Default: 1. -#DelayMult=2 - -# Some displays (e.g. vdr-wakeup) need a message from the driver to that it -# is still alive. When set to a value bigger then null the character in the -# upper left corner is updated every seconds. Default: 0. -#KeepAliveDisplay=0 - -# If you experience occasional garbage on your display you can use this -# option as workaround. If set to a value bigger than null it forces a -# full screen refresh seconds. Default: 0. -#RefreshDisplay=5 - -# You can reduce the inserted delays by setting this to false. -# On fast PCs it is possible your LCD does not respond correctly. -# Default: true. -#DelayBus=true - -# If you have a keypad you can assign keystrings to the keys. -# See documentation for used terms and how to wire it. -# For example to give directly connected key 4 the string "Enter", use: -# KeyDirect_4=Enter -# For matrix keys use the X and Y coordinates of the key: -# KeyMatrix_1_3=Enter -#KeyMatrix_4_1=Enter -#KeyMatrix_4_2=Up -#KeyMatrix_4_3=Down -#KeyMatrix_4_4=Escape - -## ICP Peripheral Comminication Protocol driver ## -# Supports A125 and A106 -# -# Short Press Select: Down -# Long Press Select: Up -# Short Press Enter: Enter -# Long Press Enter: Escape -# -[icp_a106] -Device=/dev/ttyS1 - -# Display dimensions -Size=20x2 - - -## Code Mercenaries IO-Warrior driver ## -[IOWarrior] - -# display dimensions -Size=20x4 - -# serial number. Must be exactly as listed by usbview -# (if not given, the 1st IOWarrior found gets used) -#SerialNumber=00000674 - -# If you have an HD66712, a KS0073 or another 'almost HD44780-compatible', -# set this flag to get into extended mode (4-line linear). -#ExtendedMode=yes - -# Specifies if the last line is pixel addressable (yes) or it controls an -# underline effect (no). [default: yes; legal: yes, no] -#Lastline=yes - - - -## Soundgraph/Ahanix/Silverstone/Uneed/Accent iMON driver ## -[imon] - -# select the device to use -Device=/dev/lcd0 - -# display dimensions -Size=16x2 - -# Character map to to map ISO-8859-1 to the displays character set. -# [default: none; legal: none, hd44780_euro, upd16314, hd44780_koi8_r, -# hd44780_cp1251, hd44780_8859_5 ] (upd16314, hd44780_koi8_r, -# hd44780_cp1251, hd44780_8859_5 are possible if compiled with additional -# charmaps) -CharMap=hd44780_euro - -## Soundgraph iMON LCD ## -[imonlcd] -# Specify which iMon protocol should be used -# [legal: 0, 1; default: 0] -# Choose 0 for 15c2:ffdc device, -# Choose 1 for 15c2:0038 device -Protocol=0 - -# Set the exit behavior [legal: 0-2; default: 1] -# 0 means leave shutdown message, -# 1 means show the big clock, -# 2 means blank device -#OnExit=2 - -# Select the output device to use [default: /dev/lcd0] -Device=/dev/lcd0 - -# Select the displays contrast [default: 200; legal: 0-1000] -Contrast=200 - -# Specify the size of the display in pixels [default: 96x16] -#Size=96x16 - -# Set the backlight state [default: on; legal: on, off] -#Backlight=on - -# Set the disc mode [legal: 0,1; default: 0] -# 0 => spin the "slim" disc - two disc segments, -# 1 => their complement spinning; -#DiscMode=0 - - - -## IrMan driver ## -[IrMan] -# in case of trouble with IrMan, try the Lirc emulator for IrMan - -# Select the input device to use -#Device=/dev/irman - -# Select the configuration file to use -#Config=/etc/irman.cfg - - - -## IRtrans driver ## -[irtrans] - -# Does the device have a backlight? [default: no; legal: yes, no] -#Backlight=no - -# IRTrans device to connect to [default: localhost] -#Hostname=localhost - -# display dimensions -Size=16x2 - - - -## Joystick driver ## -[joy] - -# Select the input device to use [default: /dev/js0] -Device=/dev/js0 - -# set the axis map -Map_Axis1neg=Left -Map_Axis1pos=Right -Map_Axis2neg=Up -Map_Axis2pos=Down - -# set the button map -Map_Button1=Enter -Map_Button2=Escape - - -## JW-002 driver ## -[jw002] - -# Select the output device to use [default: /dev/lcd] -#Device=/dev/ttyS0 - -# Set the display size [default: 24x8] -Size=24x8 - -# Optional X and Y offsets (in characters) to center a smaller display -# size on the full 24x8 panel -X_offset=0 -Y_offset=0 - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] -Speed=19200 - -# Pick which font page to use [default: 0] -# Note that different fonts probably have their bargraph chars in different -# spots. For ROM-based fonts 0-3, those characters are already known. -Font=0 - -# The following table translates from jw002 key letters to logical key names. -# By default no keys are mapped, meaning the keypad is not used at all. -#KeyMap_I=Left -#KeyMap_J=Right -#KeyMap_H=Up -#KeyMap_K=Down -#KeyMap_L=Enter -#KeyMap_A=Escape -# See the [menu] section for an explanation of the key mappings - -# You can find out which key of your display sends which -# character by setting keypad_test_mode to yes and running -# LCDd. LCDd will output all characters it receives. -# Afterwards you can modify the settings above and set -# keypad_set_mode to no again. -keypad_test_mode=no - - -## LB216 driver ## -[lb216] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the initial brightness [default: 255; legal: 0 - 255] -Brightness=255 - -# Set the communication speed [default: 9600; legal: 2400, 9600] -Speed=9600 - -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -Reboot=no - - - -## LCDM001 driver ## -[lcdm001] - -Device=/dev/ttyS1 - -# keypad settings -# Keyname Function -# Normal context Menu context -# ------- -------------- ------------ -# PauseKey Pause/Continue Enter/select -# BackKey Back(Go to previous screen) Up/Left -# ForwardKey Forward(Go to next screen) Down/Right -# MainMenuKey Open main menu Exit/Cancel -PauseKey=LeftKey -BackKey=UpKey -ForwardKey=DownKey -MainMenuKey=RightKey - -# You can rearrange the settings here. -# If your device is broken, have a look at server/drivers/lcdm001.h - - - -## HNE LCTerm driver ## -[lcterm] -Device=/dev/ttyS1 -Size=16x2 - - -## Linux event device input driver ## -[linux_input] - -# Select the input device to use [default: /dev/input/event0]. This may be -# either an absolute path to the input node, starting with '/', or -# an input device name, e.g. "Logitech Gaming Keyboard Gaming Keys". -# Device=/dev/input/event0 - -# specify a non-default key map -#key=1,Escape -#key=28,Enter -#key=96,Enter -#key=105,Left -#key=106,Right -#key=103,Up -#key=108,Down - - -## LIRC input driver ## -[lirc] - -# Specify an alternative location of the lircrc file [default: ~/.lircrc] -#lircrc=/etc/lircrc.lcdproc - -# Must be the same as in your lircrc -#prog=lcdd - - - -## LIS MCE 2005 driver ## -[lis] - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -# 0-250 = 25%, 251-500 = 50%, 501-750 = 75%, 751-1000 = 100% -#Brightness=1000 - -# Columns by lines [default: 20x2] -#Size=20x2 - -# USB Vendor ID [default: 0x0403] -# Change only if testing a compatible device. -#VendorID=0x0403 - -# USB Product ID [default: 0x6001] -# Change only if testing a compatible device. -#ProductID=0x6001 - -# Specifies if the last line is pixel addressable (yes) or it only controls an -# underline effect (no). [default: yes; legal: yes, no] -#Lastline=yes - - - -##The driver for the VFD of the Medion MD8800 PC ## -[MD8800] -# device to use [default: /dev/ttyS1] -#Device=/dev/ttyS1 - -# display size [default: 16x2] -#Size=16x2 - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=50 - - - -## Futuba MDM166A Display -[mdm166a] -# Show self-running clock after LCDd shutdown -# Possible values: [default: no; legal: no, small, big] -Clock=big -# Dim display, no dimming gives full brightness [default: no, legal: yes, no] -Dimming=no -# Dim display in case LCDd is inactive [default: no, legal: yes, no] -OffDimming=yes - - - -## MSI MS-6931 driver for displays in 1HU servers ## -[ms6931] - -# device to use [default: /dev/ttyS1] -Device=/dev/ttyS1 - -# display size [default: 16x2] -#Size=16x2 - - - -## MTC-S16209x driver ## -[mtc_s16209x] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the initial brightness [default: 255; legal: 0 - 255] -Brightness=255 - -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -Reboot=no - - - -## Matrix Orbital driver ## -[MtxOrb] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/ttyS0 - -# Set the display size [default: 20x4] -Size=20x4 - -# Set the display type [default: lcd; legal: lcd, lkd, vfd, vkd] -Type=lkd - -# Set the initial contrast [default: 480] -# NOTE: The driver will ignore this if the display -# is a vfd or vkd as they don't have this feature -Contrast=480 - -# Some old displays do not have an adjustable backlight but only can -# switch the backlight on/off. If you experience randomly appearing block -# characters, try setting this to false. [default: yes; legal: yes, no] -hasAdjustableBacklight=no - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=0 - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] -Speed=19200 - -# The following table translates from MtxOrb key letters to logical key names. -# By default no keys are mapped, meaning the keypad is not used at all. -#KeyMap_A=Left -#KeyMap_B=Right -#KeyMap_C=Up -#KeyMap_D=Down -#KeyMap_E=Enter -#KeyMap_F=Escape -# See the [menu] section for an explanation of the key mappings - -# You can find out which key of your display sends which -# character by setting keypad_test_mode to yes and running -# LCDd. LCDd will output all characters it receives. -# Afterwards you can modify the settings above and set -# keypad_set_mode to no again. -keypad_test_mode=no - - - -## mx5000 driver for LCD display on the Logitech MX5000 keyboard ## -[mx5000] - -# Select the output device to use [default: /dev/hiddev0] -Device = /dev/hiddev0 -# Time to wait in ms after the refresh screen has been sent [default: 1000] -WaitAfterRefresh = 1000 - - - -## Noritake VFD driver ## -[NoritakeVFD] -# device where the VFD is. Usual values are /dev/ttyS0 and /dev/ttyS1 -# [default: /dev/lcd] -Device=/dev/ttyS0 -# Specifies the size of the LCD. -Size=20x4 -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=50 -# set the serial port speed [default: 9600, legal: 1200, 2400, 9600, 19200, 115200] -Speed=9600 -# Set serial data parity [default: 0; legal: 0-2 ] -# Meaning: 0(=none), 1(=odd), 2(=even) -Parity=0 -# re-initialize the VFD [default: no; legal: yes, no] -Reboot=no - - - -## Olimex MOD-LCD1x9 driver ## -[Olimex_MOD_LCD1x9] - -# device file of the i2c controler -Device=/dev/i2c-0 - - -## Mini-box.com picoLCD (usblcd) driver ## -[picolcd] - -# KeyTimeout is only used if the picoLCD driver is built with libusb-0.1. When -# built with libusb-1.0 key and IR data is input asynchronously so there is no -# need to wait for the USB data. -# KeyTimeout is the time in ms that LCDd spends waiting for a key press before -# cycling through other duties. Higher values make LCDd use less CPU time and -# make key presses more detectable. Lower values make LCDd more responsive -# but a little prone to missing key presses. 500 (.5 second) is the default -# and a balanced value. [default: 500; legal: 0 - 1000] -KeyTimeout=500 - -# Key auto repeat is only available if the picoLCD driver is built with -# libusb-1.0. Use KeyRepeatDelay and KeyRepeatInterval to configure key auto -# repeat. -# -# Key auto repeat delay (time in ms from first key report to first repeat). Use -# zero to disable auto repeat. [default: 300; legal: 0 - 3000] -KeyRepeatDelay=300 - -# Key auto repeat interval (time in ms between repeat reports). Only used if -# KeyRepeatDelay is not zero. [default: 200; legal: 0 - 3000] -KeyRepeatInterval=200 - -# Sets the initial state of the backlight upon start-up. -# [default: on; legal: on, off] -#Backlight=on - -# Set the initial brightness [default: 1000; legal: 0 - 1000]. Works only -# with the 20x4 device -Brightness=1000 - -# Set the brightness while the backlight is 'off' [default: 0; legal: 0 - 1000]. -# Works only with the 20x4 device. -#OffBrightness=0 - -# Set the initial contrast [default: 1000; legal: 0 - 1000] -Contrast=1000 - -# Link the key lights to the backlight? [default: on; legal: on, off] -#LinkLights=off - -# Light the keys? [default: on; legal: on, off] -Keylights=on - -# If Keylights is on, the you can unlight specific keys below: -# Key0 is the directional pad. Key1 - Key5 correspond to the F1 - F5 keys. -# There is no LED for the +/- keys. This is a handy way to indicate to users -# which keys are disabled. [default: on; legal: on, off] -Key0Light=on -Key1Light=on -Key2Light=on -Key3Light=on -Key4Light=on -Key5Light=on - -# Host name or IP address of the LIRC instance that is to receive IR codes -# If not set, or set to an empty value, IR support is disabled. -#LircHost=127.0.0.1 - -# UDP port on which LIRC is listening [default: 8765; legal: 1 - 65535] -LircPort=8765 - -# UDP data time unit for LIRC [default: off; legal: on, off] -# On: times sent in microseconds (requires LIRC UDP driver that accepts this). -# Off: times sent in 'jiffies' (1/16384s) (supported by standard LIRC UDP driver). -LircTime_us=on - -# Threshold in microseconds of the gap that triggers flushing the IR data -# to lirc [default: 8000; legal: 1000 - ] -# If LircTime_us is on values greater than 32.767ms will disable the flush -# If LircTime_us is off values greater than 1.999938s will disable the flush -LircFlushThreshold=10000 - - - -## Pyramid LCD driver ## -[pyramid] - -# device to connect to [default: /dev/lcd] -Device=/dev/ttyUSB0 - - - -## rawserial driver ## -[rawserial] - -# Select the output device to use [default: /dev/cuaU0] -Device=/dev/ttyS0 - -# Serial port baudrate [default: 9600] -Speed=9600 - -# Specifies the size of the LCD. If this driver is loaded as a secondary driver -# it always adopts to the size of the primary driver. If loaded as the only -# (or primary) driver, the size can be set. [default: 40x4] -#Size=16x2 - -# How often to dump the LCD contents out the port, in Hertz (times per second) -# 1 = once per second, 4 is 4 times per second, 0.1 is once every 10 seconds. -# [default: 1; legal: 0.0005 - 10] -UpdateRate=1 - - - -## SDEC driver for Watchguard Firebox ## -[sdeclcd] -# No options - - -## Seiko Epson 1330 driver ## -[sed1330] - -# Port where the LPT is. Common values are 0x278, 0x378 and 0x3BC -Port=0x378 - -# Type of LCD module (legal: G321D, G121C, G242C, G191D, G2446, SP14Q002) -# Note: Currently only tested with G321D & SP14Q002. -Type=G321D - -# Width x Height of a character cell in pixels [legal: 6x7 - 8x16; default: 6x10] -CellSize=6x10 - -# Select what type of connection [legal: classic, bitshaker; default: classic] -ConnectionType=classic - - - -## Seiko Epson 1520 driver ## -[sed1520] - -# Port where the LPT is. Usual values are 0x278, 0x378 and 0x3BC -Port=0x378 - -# Select the interface type (wiring) for the display. Supported values are -# 68 for 68-style connection (RESET level high) and 80 for 80-style connection -# (RESET level low). [legal: 68, 80; default: 80] -InterfaceType=80 - -# On fast machines it may be necessary to slow down transfer to the display. -# If this value is set to zero, delay is disabled. Any value greater than -# zero slows down each write by one microsecond. [legal: 0-1000; default: 1] -DelayMult=0 - -# The original wiring used an inverter to drive the control lines. If you do -# not use an inverter set haveInverter to no. [default: yes; legal: yes, no] -HaveInverter=no - -# On some displays column data in memory is mapped to segment lines from right -# to left. This is called inverted mapping (not to be confused with -# 'haveInverter' from above). [default: no; legal: yes, no] -#InvertedMapping=yes - -# At least one display is reported (Everbouquet MG1203D) that requires sending -# three times 0xFF before a reset during initialization. -# [default: no; legal: yes, no] -#UseHardReset=yes - - -## serial POS display driver ## -[serialPOS] - -# Device to use in serial mode [default: /dev/ttyS0] -Device=/dev/ttyS0 - -# Specifies the size of the display in characters. [default: 16x2] -Size=16x2 - -# Specifies the cell size of each character cell on the display in characters. -# [default: 5x8] -Cellsize=5x8 - -# Specifies the number of custom characters supported by the display. -# [default: 0] -Custom_chars=0 - -# Set the communication protocol to use with the POS display. -# [default: AEDEX; legal: AEDEX, CD5220, Epson, Emax, LogicControls, Ultimate] -Type=AEDEX - -# communication baud rate with the display [default: 9600; legal: 1200, 2400, -# 4800, 9600, 19200, 115200] -Speed=9600 - - - -## Serial VFD driver ## -## Drives various (see below) serial 5x7dot VFD's. ## -[serialVFD] - -# Specifies the displaytype.[default: 0] -# 0 NEC (FIPC8367 based) VFDs. -# 1 KD Rev 2.1. -# 2 Noritake VFDs (*). -# 3 Futaba VFDs -# 4 IEE S03601-95B -# 5 IEE S03601-96-080 (*) -# 6 Futaba NA202SD08FA (allmost IEE compatible) -# 7 Samsung 20S207DA4 and 20S207DA6 -# 8 Nixdorf BA6x / VT100 -# (* most should work, not tested yet.) -Type=0 - -# "no" if display connected serial, "yes" if connected parallel. [default: no] -# I.e. serial by default -use_parallel=no - -# Number of Custom-Characters. default is display type dependent -#Custom-Characters=0 - -# Portaddress where the LPT is. Used in parallel mode only. Usual values are -# 0x278, 0x378 and 0x3BC. -Port=0x378 - -# Set parallel port timing delay (us). Used in parallel mode only. -# [default: 2; legal: 0 - 255] -#PortWait=2 - -# Device to use in serial mode. Usual values are /dev/ttyS0 and /dev/ttyS1 -Device=/dev/ttyS1 - -# Specifies the size of the VFD. -Size=20x2 - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -# (4 steps 0-250, 251-500, 501-750, 751-1000) -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -# (4 steps 0-250, 251-500, 501-750, 751-1000) -OffBrightness=0 - -# set the serial port speed [default: 9600; legal: 1200, 2400, 9600, 19200, 115200] -Speed=9600 - -# enable ISO 8859 1 compatibility [default: yes; legal: yes, no] -#ISO_8859_1=yes - - - -## shuttleVFD driver ## -[shuttleVFD] -# No options - - - -## stv5730 driver ## -[stv5730] - -# Port the device is connected to [default: 0x378] -Port=0x378 - - -[SureElec] - -# Port the device is connected to (by default first USB serial port) -Device=/dev/ttyUSB0 - -# Edition level of the device (can be 1, 2 or 3) [default: 2] -#Edition=1 - -# set display size -# Note: The size can be obtained directly from device for edition 2 & 3. -#Size=16x2 - -# Set the initial contrast [default: 480; legal: 0 - 1000] -#Contrast=200 - -# Set the initial brightness [default: 480; legal: 1 - 1000] -#Brightness=480 - -# Set the initial off-brightness [default: 100; legal: 1 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -#OffBrightness=100 - - -## SVGAlib driver ## -[svga] - -# svgalib mode to use [default: G320x240x256 ] -# legal values are supported svgalib modes -#Mode=G640x480x256 - -# set display size [default: 20x4] -Size=20x4 - -# Set the initial contrast [default: 500; legal: 0 - 1000] -# Can be set but does not change anything internally -Contrast=500 - -# Set the initial brightness [default: 1000; legal: 1 - 1000] -Brightness=1000 - -# Set the initial off-brightness [default: 500; legal: 1 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=500 - - - -## Text driver ## -[text] -# Set the display size [default: 20x4] -Size=20x4 - - - -## Toshiba T6963 driver ## -[t6963] - -# set display size in pixels [default: 128x64] -Size=128x64 - -# port to use [default: 0x378; legal: 0x200 - 0x400] -Port=0x378 - -# Use LPT port in bi-directional mode. This should work on most LPT port and -# is required for proper timing! [default: yes; legal: yes, no] -#bidirectional=yes - -# Insert additional delays into reads / writes. [default: no; legal: yes, no] -#delayBus=no - -# Clear graphic memory on start-up. [default: no; legal: yes, no] -#ClearGraphic=no - - - -## Tyan Barebones LCD driver (GS10 & GS12 series) ## -[tyan] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the communication speed [default: 9600; legal: 4800, 9600] -Speed=9600 - -# set display size [default: 16x2] -Size=16x2 - - - -## ELV ula200 driver ## -[ula200] - -# Select the LCD size [default: 20x4] -Size=20x4 - -# If you have a non standard keypad you can associate any keystrings to keys. -# There are 6 input key in the CwLnx hardware that generate characters -# from 'A' to 'F'. -# -# The following it the built-in default mapping hardcoded in the driver. -# You can leave those unchanged if you have a standard keypad. -# You can change it if you want to report other keystrings or have a non -# standard keypad. -# KeyMap_A=Up -# KeyMap_B=Down -# KeyMap_C=Left -# KeyMap_D=Right -# KeyMap_E=Enter -# KeyMap_F=Escape - - - -## Wirz SLI LCD driver ## -[sli] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200, -# 38400, 57600, 115200] -Speed=19200 - - - -## vlsys_m428 for VFD/IR combination in Moneual MonCaso 320 ## -[vlsys_m428] - -# Select the output device to use [default: /dev/ttyUSB0] -#Device=/dev/ttyUSB0 - - - -## OnScreen Display using libxosd ## -[xosd] - -# set display size [default: 20x4] -Size=20x4 - -# Offset in pixels from the top-left corner of the monitor [default: 0x0] -Offset=200x200 - -# X font to use, in XLFD format, as given by "xfontsel" -Font=-*-terminus-*-r-*-*-*-320-*-*-*-*-* - -## Y.A.R.D.2 LCD section -[yard2LCD] -Size=20x4 -# If rendering rate is too high, change in server\main.h #define RENDER_FREQ 8 to "1" - -# EOF diff --git a/data/templates/system-display/lcdproc.conf.tmpl b/data/templates/system-display/lcdproc.conf.tmpl deleted file mode 100644 index 92aee8efe..000000000 --- a/data/templates/system-display/lcdproc.conf.tmpl +++ /dev/null @@ -1,173 +0,0 @@ -### autogenerated by system-display.py ### - -# system display show host (CPU|SMP-CPU|CPU-Graph|Load|Memory|Proc-Size|Disk|Uptime) -# network interface alias -# units (bps|Bps|pps) -# clock (big|mini|date-time) - -# LCDproc client configuration file - -## general options ## -[lcdproc] -# address of the LCDd server to connect to -Server=127.0.0.1 - -# Port of the server to connect to -Port=13666 - -# set reporting level -#ReportLevel=2 - -# report to to syslog ? -ReportToSyslog=true - -# run in foreground [default: false; legal: true, false] -#Foreground=true - -# PidFile location when running as daemon [default: /var/run/lcdproc.pid] -#PidFile=/var/run/lcdproc.pid - -# slow down initial announcement of modes (in 1/100s) -#delay=2 - - -## screen specific configuration options ## -{%- if show %} -# display name for the main menu [default: LCDproc HOST] -DisplayName="{%- if show['title'] %}{{ show['title'] }}{%- else %}VyOS{%- endif %}" - -{%- if show['host'] %} - -[CPU] -# Show screen -Active={%- if 'cpu' in show['host'] %}true{%- else %}false{%- endif %} -OnTime=1 -OffTime=2 -ShowInvisible=false - -[SMP-CPU] -# Show screen -Active={%- if 'cpu-all' in show['host'] %}true{%- else %}false{%- endif %} - -[Memory] -# Show screen -Active={%- if 'memory' in show['host'] %}true{%- else %}false{%- endif %} - -[Load] -# Show screen -Active={%- if 'load-hist' in show['host'] %}true{%- else %}false{%- endif %} -# Min Load Avg at which the backlight will be turned off [default: 0.05] -LowLoad=0.05 -# Max Load Avg at which the backlight will start blinking [default: 1.3] -HighLoad=1.3 - -[Uptime] -# Show screen -Active={%- if 'uptime' in show['host'] %}true{%- else %}false{%- endif %} - -[CPUGraph] -# Show screen -Active={%- if 'cpu-hist' in show['host'] %}true{%- else %}false{%- endif %} - -[ProcSize] -# Show screen -Active={%- if 'proc' in show['host'] %}true{%- else %}false{%- endif %} - -[Disk] -# Show screen -Active={%- if 'disk' in show['host'] %}true{%- else %}false{%- endif %} -{%- else %} {# if show['host'] #} -{# Turn off sections that default active #} - -[CPU] -Active=false - -[Memory] -Active=false - -[Load] -Active=false - -{%- endif %} {# if show['host'] #} - -[TimeDate] -# Show screen -Active={%- if show['clock'] == 'date-time' %}true{%- else %}false{%- endif %} -# time format [default: %H:%M:%S; legal: see strftime(3)] -TimeFormat="%H:%M:%S" -# date format [default: %x; legal: see strftime(3)] -DateFormat="%x" - -[BigClock] -# Show screen -Active={%- if show['clock'] == 'big' %}true{%- else %}false{%- endif %} - -[MiniClock] -# Show screen -Active={%- if show['clock'] == 'mini' %}true{%- else %}false{%- endif %} -# time format [default: %H:%M; legal: see strftime(3)] -TimeFormat="%H:%M" - -{%- if show['network'] %} -[Iface] -# Show screen -Active={%- if show['network']['interface'] %}true{%- else %}false{%- endif %} -{%- for i in show['network']['interface'] %} -# Show stats for Interface {{ i }} -Interface{{ loop.index0 }}={{ i }} -{%- if show['network']['interface'][i]['alias'] %} -# Interface alias name to display [default: ] -Alias{{ loop.index0 }}={{ show['network']['interface'][i]['alias'] }} -{%- endif %} -{%- endfor %} - -# Units to display [default: byte; legal: byte, bit, packet] -{%- if show['network']['units'] == 'bps' %} -unit=bit -{%- elif show['network']['units'] == 'Bps'%} -unit=byte -{%- elif show['network']['units'] == 'pps' %} -unit=packet -{%- else %} -unit=bit -{%- endif %} -# add screen with transferred traffic -#transfer=TRUE -{%- endif %} {# if show['network'] #} - -{%- else %}{# if show #} -{# Turn off sections that default active #} - -[CPU] -Active=false - -[Memory] -Active=false - -[Load] -Active=false - -[TimeDate] -Active=false -{%- endif %}{# if show #} - -[Battery] -# Show screen -Active=false - -[About] -# Show screen -Active=false - -[OldTime] -# Show screen -Active=false -# time format [default: %H:%M:%S; legal: see strftime(3)] -TimeFormat="%H:%M:%S" -# date format [default: %x; legal: see strftime(3)] -DateFormat="%x" -# Display the title bar in two-line mode. Note that with four lines or more -# the title is always shown. [default: true; legal: true, false] -#ShowTitle=false - -# EOF diff --git a/interface-definitions/system-display.xml.in b/interface-definitions/system-display.xml.in deleted file mode 100644 index fbd897996..000000000 --- a/interface-definitions/system-display.xml.in +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - - - System display LCD/VFD/LED - 400 - - - - - - Model of the display attached to this system [REQUIRED] - - sdec ezio test - - - (sdec|ezio|test) - - Invalid system display model - - sdec - Display model for Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances with built-in SDEC LCD - - - ezio - Display model for Portwell, Caswell appliances with built-in EZIO-100 or EZIO-300 LCD - - - test - Test model for USB CrystalFonz CF533 - - - - - - - Disable sytem display - - - - - - Select the screens for the system display [REQUIRED] - - - - - - - Select host screens for the system display - - cpu cpu-all cpu-hist disk load-hist memory proc uptime - - - (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) - - Invalid host screen - - cpu - Detailed CPU usage - - - cpu-all - CPU usage overview (one line per CPU) - - - cpu-hist - CPU usage histogram - - - disk - File systems fill level - - - load-hist - Load histogram - - - memory - Memory and swap usage - - - proc - Top processes by size - - - uptime - System uptime - - - - - - - Network settings for system display - - - - - - Show network traffic on the system display [Max 3 interfaces] - - - - - - - - Interface alias - - [A-Za-z0-9]{1,10} - - Invalid alias, must be 1 to 10 char or digit - - - - - - - - Unit for network details - - bps Bps pps - - - (bps|Bps|pps) - - Invalid network detail unit - - bps - Bit(s) per second - - - Bps - Byte(s) per second - - - pps - Packet(s) per second - - - - - - - - - - Show a clock on the system display - - big mini date-time - - - (big|mini|date-time) - - Invalid clock format - - big - Multi-line clock - - - mini - Minimal clock - - - date-time - Clock with Date and Time - - - - - - - Screen title to show on the system display - - [A-Za-z0-9]{1,16} - - Invalid title, must be 1 to 16 char or digit - - - - - - - - Time in sec to show each screen on the system display - - 1-30 - Numer of seconds - - - - - - - - - - Message to show when system display first starts - - .{1,16} - - Hello message must be 1 to 16 char - - - - - - Message to show when system display stops - - .{1,16} - - Bye message must be 1 to 16 char - - - - - - - diff --git a/interface-definitions/system-lcd.xml.in b/interface-definitions/system-lcd.xml.in new file mode 100644 index 000000000..ad59acb6b --- /dev/null +++ b/interface-definitions/system-lcd.xml.in @@ -0,0 +1,62 @@ + + + + + + + System LCD display + 100 + + + + + Model of the display attached to this system [REQUIRED] + + CFA-533 CFA-631 CFA-633 CFA-635 + + + CFA-533 + Crystalfontz CFA-533 + + + CFA-631 + Crystalfontz CFA-631 + + + CFA-633 + Crystalfontz CFA-633 + + + CFA-635 + Crystalfontz CFA-635 + + + ^(CFA-533|CFA-631|CFA-633|CFA-635)$ + + + + + + Physical device used by LCD display + + + + + + ttySXX + TTY device name, regular serial port + + + usbNbXpY + TTY device name, USB based + + + ^(ttyS[0-9]+|usb[0-9]+b.*)$ + + + + + + + + diff --git a/python/vyos/util.py b/python/vyos/util.py index 7078762df..c07fef599 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -661,3 +661,15 @@ def check_kmod(k_mod): if not os.path.exists(f'/sys/module/{module}'): if call(f'modprobe {module}') != 0: raise ConfigError(f'Loading Kernel module {module} failed') + +def find_device_file(device): + """ Recurively search /dev for the given device file and return its full path. + If no device file was found 'None' is returned """ + from fnmatch import fnmatch + + for root, dirs, files in os.walk('/dev'): + for basename in files: + if fnmatch(basename, device): + return os.path.join(root, basename) + + return None diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 4081be3c9..6d168d918 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -16,7 +16,6 @@ import os -from fnmatch import fnmatch from sys import exit from vyos.config import Config @@ -25,22 +24,13 @@ from vyos.configverify import verify_vrf from vyos.template import render from vyos.util import call from vyos.util import check_kmod +from vyos.util import find_device_file from vyos import ConfigError from vyos import airbag airbag.enable() k_mod = ['option', 'usb_wwan', 'usbserial'] -def find_device_file(device): - """ Recurively search /dev for the given device file and return its full path. - If no device file was found 'None' is returned """ - for root, dirs, files in os.walk('/dev'): - for basename in files: - if fnmatch(basename, device): - return os.path.join(root, basename) - - return None - def get_config(): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py deleted file mode 100755 index 3eafc30c0..000000000 --- a/src/conf_mode/system-display.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020 Francois Mertz fireboxled at gmail.com -# -# 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 . - -import os - -from sys import exit - -from vyos.config import Config -from vyos import ConfigError -from vyos.util import run -from vyos.template import render - -from vyos import airbag -airbag.enable() - -def get_config(): - conf = Config() - base = ['system', 'display'] - display = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Return a (possibly empty) configuration dictionary - return display - -def verify(config_dict): - if not config_dict: - return None - - if 'model' not in config_dict: - raise ConfigError('Display model is [REQUIRED]') - - if ( 'show' not in config_dict - or ( 'clock' not in config_dict['show'] - and 'network' not in config_dict['show'] - and 'host' not in config_dict['show'] - ) - ): - raise ConfigError('Display show must have a clock, host or network') - - if ( 'network' in config_dict['show'] - and 'interface' not in config_dict['show']['network'] - ): - raise ConfigError('Display show network must have an interface') - - if ( 'network' in config_dict['show'] - and 'interface' in config_dict['show']['network'] - and len(config_dict['show']['network']['interface']) > 3 - ): - raise ConfigError('Display show network cannot have > 3 interfaces') - - return None - -def generate(config_dict): - if not config_dict: - return None - # Render config file for daemon LCDd - render('/run/LCDd/LCDd.lo.conf', 'system-display/LCDd.conf.tmpl', config_dict) - # Render config file for client lcdproc - render('/run/lcdproc/lcdproc.lo.conf', 'system-display/lcdproc.conf.tmpl', config_dict) - - return None - -def apply(config_dict): - # Stop client - run('systemctl stop lcdproc@lo.service') - - if not config_dict or 'disabled' in config_dict: - # Stop server - run('systemctl stop LCDd@lo.service') - return None - - # Restart server - run('systemctl restart LCDd@lo.service') - # Start client - run('systemctl start lcdproc@lo.service') - - return None - -if __name__ == '__main__': - try: - config_dict = get_config() - verify(config_dict) - generate(config_dict) - apply(config_dict) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py new file mode 100755 index 000000000..0ad1318f0 --- /dev/null +++ b/src/conf_mode/system_lcd.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# +# Copyright 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 . + +import os + +from sys import exit + +from vyos.config import Config +from vyos.util import call +from vyos.util import find_device_file +from vyos.template import render +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +lcdd_conf = '/run/LCDd/LCDd.conf' +lcdproc_conf = '/run/lcdproc/lcdproc.conf' + +def get_config(): + conf = Config() + base = ['system', 'lcd'] + lcd = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) + # Return (possibly empty) dictionary + return lcd + +def verify(lcd): + if not lcd: + return None + + if not {'device', 'model'} <= set(lcd): + raise ConfigError('Both device and driver must be set!') + + return None + +def generate(lcd): + if not lcd: + return None + + if 'device' in lcd: + lcd['device'] = find_device_file(lcd['device']) + + # Render config file for daemon LCDd + render(lcdd_conf, 'lcd/LCDd.conf.tmpl', lcd, trim_blocks=True) + # Render config file for client lcdproc + render(lcdproc_conf, 'lcd/lcdproc.conf.tmpl', lcd, trim_blocks=True) + + return None + +def apply(lcd): + if not lcd: + call('systemctl stop lcdproc.service LCDd.service') + + for file in [lcdd_conf, lcdproc_conf]: + if os.path.exists(file): + os.remove(file) + else: + # Restart server + call('systemctl restart LCDd.service lcdproc.service') + + return None + +if __name__ == '__main__': + try: + config_dict = get_config() + verify(config_dict) + generate(config_dict) + apply(config_dict) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/etc/systemd/system/LCDd.service.d/override.conf b/src/etc/systemd/system/LCDd.service.d/override.conf new file mode 100644 index 000000000..5f3f0dc95 --- /dev/null +++ b/src/etc/systemd/system/LCDd.service.d/override.conf @@ -0,0 +1,8 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +ExecStart= +ExecStart=/usr/sbin/LCDd -c /run/LCDd/LCDd.conf + diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf index bb8e81d7a..b03dbc299 100644 --- a/src/etc/systemd/system/hostapd@.service.d/override.conf +++ b/src/etc/systemd/system/hostapd@.service.d/override.conf @@ -3,8 +3,7 @@ After= After=vyos-router.service [Service] -WorkingDirectory=/run/hostapd -EnvironmentFile= +WorkingDirectory=/run/LCDd ExecStart= -ExecStart=/usr/sbin/hostapd -B -P /run/hostapd/%i.pid /run/hostapd/%i.conf -PIDFile=/run/hostapd/%i.pid +ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.conf + diff --git a/src/systemd/LCDd@.service b/src/systemd/LCDd@.service deleted file mode 100644 index a4604cf21..000000000 --- a/src/systemd/LCDd@.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=LCD display daemon on %I -Documentation=man:LCDd(8) http://www.lcdproc.org/ - -[Service] -User=root -ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.%I.conf - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/src/systemd/lcdproc.service b/src/systemd/lcdproc.service new file mode 100644 index 000000000..5aa99ec78 --- /dev/null +++ b/src/systemd/lcdproc.service @@ -0,0 +1,13 @@ +[Unit] +Description=LCDproc system status information viewer on %I +Documentation=man:lcdproc(8) http://www.lcdproc.org/ +After=vyos-router.service +After=LCDd.service + +[Service] +User=root +ExecStart=/usr/bin/lcdproc -f -c /run/lcdproc/lcdproc.conf +PIDFile=/run/lcdproc/lcdproc.pid + +[Install] +WantedBy=multi-user.target diff --git a/src/systemd/lcdproc@.service b/src/systemd/lcdproc@.service deleted file mode 100644 index 9a1723dba..000000000 --- a/src/systemd/lcdproc@.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=LCDproc system status information viewer on %I -Documentation=man:lcdproc(8) http://www.lcdproc.org/ - -[Service] -User=root -ExecStart=/usr/bin/lcdproc -f -c /run/lcdproc/lcdproc.%I.conf - -[Install] -WantedBy=multi-user.target \ No newline at end of file -- cgit v1.2.3 From 39fe36151eac3652ec9a2283974c19efdd728dc1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 18:08:34 +0200 Subject: vyos.configdict: keep is_member import as local as possible --- python/vyos/configdict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 126d6195a..010eda45c 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -23,7 +23,6 @@ from enum import Enum from copy import deepcopy from vyos import ConfigError -from vyos.validate import is_member def retrieve_config(path_hash, base_path, config): """ @@ -203,6 +202,7 @@ def get_interface_dict(config, base, ifname=''): Will return a dictionary with the necessary interface configuration """ from vyos.xml import defaults + from vyos.validate import is_member if not ifname: # determine tagNode instance -- cgit v1.2.3 From 10fd2aa6ab205ef22e0bd3eef3767cb1ab00e164 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 19:17:29 +0200 Subject: vyos.configverify: no need to call .keys() when searching dict --- python/vyos/configverify.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'python') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index bb590a514..d1519b0ac 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -29,11 +29,11 @@ def verify_vrf(config): recurring validation of VRF configuration. """ from netifaces import interfaces - if 'vrf' in config.keys(): + if 'vrf' in config: if config['vrf'] not in interfaces(): raise ConfigError('VRF "{vrf}" does not exist'.format(**config)) - if 'is_bridge_member' in config.keys(): + if 'is_bridge_member' in config: raise ConfigError( 'Interface "{ifname}" cannot be both a member of VRF "{vrf}" ' 'and bridge "{is_bridge_member}"!'.format(**config)) @@ -57,7 +57,7 @@ def verify_bridge_delete(config): perform recurring validation of IP address assignmenr when interface also is part of a bridge. """ - if 'is_bridge_member' in config.keys(): + if 'is_bridge_member' in config: raise ConfigError( 'Interface "{ifname}" cannot be deleted as it is a ' 'member of bridge "{is_bridge_member}"!'.format(**config)) @@ -101,20 +101,20 @@ def verify_vlan_config(config): recurring validation of interface VLANs """ # 802.1q VLANs - for vlan in config.get('vif', {}).keys(): + for vlan in config.get('vif', {}): vlan = config['vif'][vlan] verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) # 802.1ad (Q-in-Q) VLANs - for vlan in config.get('vif_s', {}).keys(): + for vlan in config.get('vif_s', {}): vlan = config['vif_s'][vlan] verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) - for vlan in config.get('vif_s', {}).get('vif_c', {}).keys(): + for vlan in config.get('vif_s', {}).get('vif_c', {}): vlan = config['vif_c'][vlan] verify_dhcpv6(vlan) verify_address(vlan) -- cgit v1.2.3 From b9658cf8fa85d5d2bdc503d10340dc82c9c2a8c0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 18 Aug 2020 16:39:50 +0200 Subject: ifconfig: T2653: bugfix when removing DHCP address DHCP service was not stopped when an DHCP address got removed from the interface. DHCP service is now always stopped if it is not configured explicitly. --- python/vyos/ifconfig/interface.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'python') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 36f258301..892495dec 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -901,6 +901,11 @@ class Interface(Control): if isinstance(new_addr, str): new_addr = [new_addr] + # ensure DHCP/DHCPv6 is stopped (when not configured explicitly) + for proto in ['dhcp', 'dhcpv6']: + if proto not in new_addr: + self.del_addr(proto) + # determine IP addresses which are assigned to the interface and build a # list of addresses which are no longer in the dict so they can be removed cur_addr = self.get_addr() -- cgit v1.2.3 From 0bc94e588fea31888445d182f54803598e82b615 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 19 Aug 2020 23:15:07 -0500 Subject: certbot: T2815: change config dir to /config/auth/letsencrypt --- python/vyos/defaults.py | 3 ++- src/conf_mode/le_cert.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 3062ed31c..9921e3b5f 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -21,7 +21,8 @@ directories = { "current": "/opt/vyatta/etc/config-migrate/current", "migrate": "/opt/vyatta/etc/config-migrate/migrate", "log": "/var/log/vyatta", - "templates": "/usr/share/vyos/templates/" + "templates": "/usr/share/vyos/templates/", + "certbot": "/config/auth/letsencrypt" } cfg_group = 'vyattacfg' diff --git a/src/conf_mode/le_cert.py b/src/conf_mode/le_cert.py index 5b965f95f..755c89966 100755 --- a/src/conf_mode/le_cert.py +++ b/src/conf_mode/le_cert.py @@ -27,6 +27,7 @@ from vyos import airbag airbag.enable() vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode'] +vyos_certbot_dir = vyos.defaults.directories['certbot'] dependencies = [ 'https.py', @@ -45,7 +46,7 @@ def request_certbot(cert): else: domain_flag = '' - certbot_cmd = 'certbot certonly -n --nginx --agree-tos --no-eff-email --expand {0} {1}'.format(email_flag, domain_flag) + certbot_cmd = f'certbot certonly --config-dir {vyos_certbot_dir} -n --nginx --agree-tos --no-eff-email --expand {email_flag} {domain_flag}' cmd(certbot_cmd, raising=ConfigError, -- cgit v1.2.3 From 1658ff2a8b639fe89d48c48710beea0da1a02032 Mon Sep 17 00:00:00 2001 From: erkin Date: Thu, 20 Aug 2020 16:21:43 +0300 Subject: VRRP: T2761: Extend "show vrrp" op-mode command with router priority --- python/vyos/ifconfig/vrrp.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py index 5e6387881..01a7cc7ab 100644 --- a/python/vyos/ifconfig/vrrp.py +++ b/python/vyos/ifconfig/vrrp.py @@ -96,7 +96,7 @@ class VRRP(object): pid = util.read_file(cls.location['pid']) os.kill(int(pid), cls._signal[what]) - # shoud look for file size change ? + # should look for file size change? sleep(0.2) return util.read_file(fname) except FileNotFoundError: @@ -126,8 +126,8 @@ class VRRP(object): return disabled @classmethod - def format (cls, data): - headers = ["Name", "Interface", "VRID", "State", "Last Transition"] + def format(cls, data): + headers = ["Name", "Interface", "VRID", "State", "Priority", "Last Transition"] groups = [] data = json.loads(data) @@ -138,11 +138,12 @@ class VRRP(object): intf = data['ifp_ifname'] vrid = data['vrid'] state = cls.decode_state(data["state"]) + priority = data['effective_priority'] since = int(time() - float(data['last_transition'])) last = util.seconds_to_human(since) - groups.append([name, intf, vrid, state, last]) + groups.append([name, intf, vrid, state, priority, last]) # add to the active list disabled instances groups.extend(cls.disabled()) -- cgit v1.2.3 From 98c2c5e0585e0806099a353de207f392223eff9a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 22 Aug 2020 23:15:29 +0200 Subject: dhcpv6-pd: T2677: optimize CLI interface for PD configuration The current CLI did not support multiple prefix-delegations per interface. Some ISPs only send one /64 to a client per prefix-delegation request, but they allow the customer to request multiple prefixes. The 'dhcpv6-options prefix-delegation' node has been renamed and converted to a tag node named 'dhcpv6-options pd'. The tag node specifies a PD request (>=0). In the past the user needed to know what prefix will be assigned and required to calculate the sla-len by himself. The 'sla-len' node was dropped and is now calculated in the background from the 'dhcpv6-options pd 0 length' node. It is no longer mandatory to supply the 'sla-id' node, if sla-id is not specified it is 'guessed' by counting upwards. Example configuration: ---------------------- ethernet eth1 { address dhcpv6 dhcpv6-options { pd 0 { length 56 interface eth2 { address 1 } } } } This will request a /56 assignment from the ISP and will delegate a /64 network to interface eth2. VyOS will use the interface address ::1 on the delegate interface (eth2) as its local address. --- data/templates/dhcp-client/ipv6.tmpl | 48 +++++++++------- interface-definitions/include/dhcp-options.xml.i | 2 +- interface-definitions/include/dhcpv6-options.xml.i | 40 ++++++-------- python/vyos/configdict.py | 64 ++++++++++++++++++---- src/conf_mode/interfaces-pppoe.py | 4 +- src/migration-scripts/interfaces/11-to-12 | 58 ++++++++++++++++++++ 6 files changed, 157 insertions(+), 59 deletions(-) create mode 100755 src/migration-scripts/interfaces/11-to-12 (limited to 'python') diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index 9673f302b..e9285d86b 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -8,37 +8,43 @@ interface {{ ifname }} { information-only; {% endif %} {% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} - send ia-na 1; # non-temporary address + send ia-na 0; # non-temporary address {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} - send ia-pd 2; # prefix delegation +{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} +{% for pd in dhcpv6_options.pd %} + send ia-pd {{ pd }}; # prefix delegation #{{ pd }} +{% endfor %} {% endif %} }; {% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} -id-assoc na 1 { - # Identity association NA +id-assoc na 0 { + # Identity association for non temporary address }; {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} -id-assoc pd 2 { -{% if dhcpv6_options.prefix_delegation.length is defined %} - prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; -{% endif %} -{% for interface in dhcpv6_options.prefix_delegation.interface %} +{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} +{% for pd in dhcpv6_options.pd %} +id-assoc pd {{ pd }} { +{# length got a default value #} + prefix ::/{{ dhcpv6_options.pd[pd].length }} infinity; +{% set sla_len = 64 - dhcpv6_options.pd[pd].length|int %} +{% set count = namespace(value=0) %} +{% for interface in dhcpv6_options.pd[pd].interface if dhcpv6_options.pd[pd].interface is defined %} prefix-interface {{ interface }} { -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} - sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} - sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} - ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; -{% endif %} + sla-len {{ sla_len }}; +{% if dhcpv6_options.pd[pd].interface[interface].sla_id is defined and dhcpv6_options.pd[pd].interface[interface].sla_id is not none %} + sla-id {{ dhcpv6_options.pd[pd].interface[interface].sla_id }}; +{% else %} + sla-id {{ count.value }}; +{% endif %} +{% if dhcpv6_options.pd[pd].interface[interface].address is defined and dhcpv6_options.pd[pd].interface[interface].address is not none %} + ifid {{ dhcpv6_options.pd[pd].interface[interface].address }}; +{% endif %} }; -{% endfor %} +{% set count.value = count.value + 1 %} +{% endfor %} }; +{% endfor %} {% endif %} diff --git a/interface-definitions/include/dhcp-options.xml.i b/interface-definitions/include/dhcp-options.xml.i index 0f71d9321..9989291fc 100644 --- a/interface-definitions/include/dhcp-options.xml.i +++ b/interface-definitions/include/dhcp-options.xml.i @@ -1,6 +1,6 @@ - DHCP options + DHCP client settings/options diff --git a/interface-definitions/include/dhcpv6-options.xml.i b/interface-definitions/include/dhcpv6-options.xml.i index 98a87dba2..5d088b83d 100644 --- a/interface-definitions/include/dhcpv6-options.xml.i +++ b/interface-definitions/include/dhcpv6-options.xml.i @@ -1,11 +1,24 @@ - DHCPv6 options + DHCPv6 client settings/options - + + + Acquire only config parameters, no address + + + + - DHCPv6 Prefix Delegation Options + DHCPv6 prefix delegation interface statement + + instance number + Prefix delegation instance (>= 0) + + + + @@ -19,6 +32,7 @@ + 64 @@ -52,28 +66,10 @@ - - - Site-Level aggregator (SLA) length - - 0-128 - Length of delegated prefix - - - - - - - - - - Acquire only config parameters, no address - - - + IPv6 "temporary" address diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 010eda45c..c1e93955e 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -93,7 +93,7 @@ def dict_merge(source, destination): tmp = deepcopy(destination) for key, value in source.items(): - if key not in tmp.keys(): + if key not in tmp: tmp[key] = value elif isinstance(source[key], dict): tmp[key] = dict_merge(source[key], tmp[key]) @@ -109,15 +109,15 @@ def T2665_default_dict_cleanup(dict): """ Cleanup default keys for tag nodes https://phabricator.vyos.net/T2665. """ # Cleanup for vif in ['vif', 'vif_s']: - if vif in dict.keys(): - for key in ['ip', 'mtu']: - if key in dict[vif].keys(): + if vif in dict: + for key in ['ip', 'mtu', 'dhcpv6_options']: + if key in dict[vif]: del dict[vif][key] # cleanup VIF-S defaults - if 'vif_c' in dict[vif].keys(): - for key in ['ip', 'mtu']: - if key in dict[vif]['vif_c'].keys(): + if 'vif_c' in dict[vif]: + for key in ['ip', 'mtu', 'dhcpv6_options']: + if key in dict[vif]['vif_c']: del dict[vif]['vif_c'][key] # If there is no vif-c defined and we just cleaned the default # keys - we can clean the entire vif-c dict as it's useless @@ -129,6 +129,21 @@ def T2665_default_dict_cleanup(dict): if not dict[vif]: del dict[vif] + if 'dhcpv6_options' in dict and 'pd' in dict['dhcpv6_options']: + if 'length' in dict['dhcpv6_options']['pd']: + del dict['dhcpv6_options']['pd']['length'] + + # delete empty dicts + if 'dhcpv6_options' in dict: + if 'pd' in dict['dhcpv6_options']: + # test if 'pd' is an empty node so we can remove it + if not dict['dhcpv6_options']['pd']: + del dict['dhcpv6_options']['pd'] + + # test if 'dhcpv6_options' is an empty node so we can remove it + if not dict['dhcpv6_options']: + del dict['dhcpv6_options'] + return dict def leaf_node_changed(conf, path): @@ -193,6 +208,16 @@ def get_removed_vlans(conf, dict): return dict + +def dict_add_dhcpv6pd_defaults(defaults, config_dict): + # Implant default dictionary for DHCPv6-PD instances + if 'dhcpv6_options' in config_dict and 'pd' in config_dict['dhcpv6_options']: + for pd, pd_config in config_dict['dhcpv6_options']['pd'].items(): + config_dict['dhcpv6_options']['pd'][pd] = dict_merge( + defaults, pd_config) + + return config_dict + def get_interface_dict(config, base, ifname=''): """ Common utility function to retrieve and mandgle the interfaces available @@ -257,15 +282,30 @@ def get_interface_dict(config, base, ifname=''): # remove wrongly inserted values dict = T2665_default_dict_cleanup(dict) - # The values are identical for vif, vif-s and vif-c as the all include the same - # XML definitions which hold the defaults + # Implant default dictionary for DHCPv6-PD instances + default_pd_values = defaults(base + ['dhcpv6-options', 'pd']) + dict = dict_add_dhcpv6pd_defaults(default_pd_values, dict) + + # Implant default dictionary in vif/vif-s VLAN interfaces. Values are + # identical for all types of VLAN interfaces as they all include the same + # XML definitions which hold the defaults. default_vif_values = defaults(base + ['vif']) for vif, vif_config in dict.get('vif', {}).items(): - vif_config = dict_merge(default_vif_values, vif_config) + dict['vif'][vif] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_config) + dict['vif'][vif] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_config)) + for vif_s, vif_s_config in dict.get('vif_s', {}).items(): - vif_s_config = dict_merge(default_vif_values, vif_s_config) + dict['vif_s'][vif_s] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_s_config) + dict['vif_s'][vif_s] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_s_config)) for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): - vif_c_config = dict_merge(default_vif_values, vif_c_config) + dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_c_config) + dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_c_config)) # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 928113b49..901ea769c 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -15,7 +15,6 @@ # along with this program. If not, see . import os -import jmespath from sys import exit from copy import deepcopy @@ -104,8 +103,7 @@ def generate(pppoe): render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe, trim_blocks=True, permission=0o755) - tmp = jmespath.search('dhcpv6_options.prefix_delegation.interface', pppoe) - if tmp and len(tmp) > 0: + if 'dhcpv6_options' in pppoe and 'pd' in pppoe['dhcpv6_options']: # ipv6.tmpl relies on ifname - this should be made consitent in the # future better then double key-ing the same value render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe, trim_blocks=True) diff --git a/src/migration-scripts/interfaces/11-to-12 b/src/migration-scripts/interfaces/11-to-12 new file mode 100755 index 000000000..0dad24642 --- /dev/null +++ b/src/migration-scripts/interfaces/11-to-12 @@ -0,0 +1,58 @@ +#!/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 . + +# - rename 'dhcpv6-options prefix-delegation' from single node to a new tag node +# 'dhcpv6-options pd 0' +# - delete 'sla-len' from CLI - value is calculated on demand + +from sys import exit, argv +from vyos.configtree import ConfigTree + +if __name__ == '__main__': + 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) + + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + # cache current config tree + base_path = ['interfaces', type, interface, 'dhcpv6-options'] + old_base = base_path + ['prefix-delegation'] + new_base = base_path + ['pd'] + if config.exists(old_base): + config.set(new_base) + config.set_tag(new_base) + config.copy(old_base, new_base + ['0']) + config.delete(old_base) + + for pd in config.list_nodes(new_base): + for tmp in config.list_nodes(new_base + [pd, 'interface']): + sla_config = new_base + [pd, 'interface', tmp, 'sla-len'] + if config.exists(sla_config): + config.delete(sla_config) + + 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) -- cgit v1.2.3 From 5a5974d5a00b482cabd3dee92bc365d3c9f399bc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 22 Aug 2020 23:29:18 +0200 Subject: ifconfig: T2653: drop unused vyos.ifconfig.pppoe --- python/vyos/ifconfig/__init__.py | 1 - python/vyos/ifconfig/pppoe.py | 41 ---------------------------------------- 2 files changed, 42 deletions(-) delete mode 100644 python/vyos/ifconfig/pppoe.py (limited to 'python') diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index 9cd8d44c1..f4b504ebd 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -30,7 +30,6 @@ from vyos.ifconfig.vxlan import VXLANIf from vyos.ifconfig.wireguard import WireGuardIf from vyos.ifconfig.vtun import VTunIf from vyos.ifconfig.vti import VTIIf -from vyos.ifconfig.pppoe import PPPoEIf from vyos.ifconfig.tunnel import GREIf from vyos.ifconfig.tunnel import GRETapIf from vyos.ifconfig.tunnel import IP6GREIf diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py deleted file mode 100644 index 787245696..000000000 --- a/python/vyos/ifconfig/pppoe.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2020 VyOS maintainers and contributors -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see . - - -from vyos.ifconfig.interface import Interface - - -@Interface.register -class PPPoEIf(Interface): - default = { - 'type': 'pppoe', - } - definition = { - **Interface.definition, - **{ - 'section': 'pppoe', - 'prefixes': ['pppoe', ], - }, - } - - # stub this interface is created in the configure script - - def _create(self): - # we can not create this interface as it is managed outside - pass - - def _delete(self): - # we can not create this interface as it is managed outside - pass -- cgit v1.2.3 From ec1cf7dd1508e4a84d99818c7e34d093242b3331 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 00:04:09 +0200 Subject: dhcpv6-pd: T2821: support dhcpv6-pd without "address dhcpv6" Currently DHCPv6-PD requires an interface address configured to dhcpv6 on the CLI. This is not required also sometimes there is either no dhcpv6 interface addressing available (PPPoE) or wanted. This limitation was artificial due to the old interface code. Change the implementation to spawn the DHCPv6 client and request a prefix even when there is no address request configured. --- data/templates/dhcp-client/ipv6.tmpl | 12 ++++++++---- python/vyos/ifconfig/interface.py | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) (limited to 'python') diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index e9285d86b..85841fe94 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -2,13 +2,15 @@ # man https://www.unix.com/man-page/debian/5/dhcp6c.conf/ interface {{ ifname }} { +{% if address is defined and 'dhcpv6' in address %} request domain-name-servers; request domain-name; -{% if dhcpv6_options is defined and dhcpv6_options.parameters_only is defined %} +{% if dhcpv6_options is defined and dhcpv6_options.parameters_only is defined %} information-only; -{% endif %} -{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} +{% endif %} +{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} send ia-na 0; # non-temporary address +{% endif %} {% endif %} {% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} {% for pd in dhcpv6_options.pd %} @@ -17,10 +19,12 @@ interface {{ ifname }} { {% endif %} }; -{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} +{% if address is defined and 'dhcpv6' in address %} +{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} id-assoc na 0 { # Identity association for non temporary address }; +{% endif %} {% endif %} {% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 892495dec..537c4bc2d 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -901,10 +901,15 @@ class Interface(Control): if isinstance(new_addr, str): new_addr = [new_addr] - # ensure DHCP/DHCPv6 is stopped (when not configured explicitly) - for proto in ['dhcp', 'dhcpv6']: - if proto not in new_addr: - self.del_addr(proto) + # always ensure DHCP client is stopped (when not configured explicitly) + if 'dhcp' not in new_addr: + self.del_addr('dhcp') + + # always ensure DHCPv6 client is stopped (when not configured as client + # for IPv6 address or prefix delegation + dhcpv6pd = jmespath.search('dhcpv6_options.pd', config) + if 'dhcpv6' not in new_addr or dhcpv6pd == None: + self.del_addr('dhcpv6') # determine IP addresses which are assigned to the interface and build a # list of addresses which are no longer in the dict so they can be removed @@ -915,6 +920,10 @@ class Interface(Control): for addr in new_addr: self.add_addr(addr) + # start DHCPv6 client when only PD was configured + if dhcpv6pd != None: + self.set_dhcpv6(True) + # There are some items in the configuration which can only be applied # if this instance is not bound to a bridge. This should be checked # by the caller but better save then sorry! -- cgit v1.2.3 From c8ea23cc3d3cab6e384b143d7d22030a9085f1a2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 11:47:30 +0200 Subject: Revert "ifconfig: T2653: drop unused vyos.ifconfig.pppoe" This reverts commit 5a5974d5a00b482cabd3dee92bc365d3c9f399bc. Required for operational mode "show interfaces" command. --- python/vyos/ifconfig/__init__.py | 1 + python/vyos/ifconfig/pppoe.py | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 python/vyos/ifconfig/pppoe.py (limited to 'python') diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index f4b504ebd..9cd8d44c1 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -30,6 +30,7 @@ from vyos.ifconfig.vxlan import VXLANIf from vyos.ifconfig.wireguard import WireGuardIf from vyos.ifconfig.vtun import VTunIf from vyos.ifconfig.vti import VTIIf +from vyos.ifconfig.pppoe import PPPoEIf from vyos.ifconfig.tunnel import GREIf from vyos.ifconfig.tunnel import GRETapIf from vyos.ifconfig.tunnel import IP6GREIf diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py new file mode 100644 index 000000000..787245696 --- /dev/null +++ b/python/vyos/ifconfig/pppoe.py @@ -0,0 +1,41 @@ +# Copyright 2020 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + + +from vyos.ifconfig.interface import Interface + + +@Interface.register +class PPPoEIf(Interface): + default = { + 'type': 'pppoe', + } + definition = { + **Interface.definition, + **{ + 'section': 'pppoe', + 'prefixes': ['pppoe', ], + }, + } + + # stub this interface is created in the configure script + + def _create(self): + # we can not create this interface as it is managed outside + pass + + def _delete(self): + # we can not create this interface as it is managed outside + pass -- cgit v1.2.3 From 1748ce9ca2d50e931c94d50116198365f59e5c8b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 12:14:12 +0200 Subject: ifconfig: vxlan: geneve: T2823: properly set interface state --- python/vyos/ifconfig/geneve.py | 19 +++++++++++++++++++ python/vyos/ifconfig/vxlan.py | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) (limited to 'python') diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py index 145dc268c..dd0658668 100644 --- a/python/vyos/ifconfig/geneve.py +++ b/python/vyos/ifconfig/geneve.py @@ -64,3 +64,22 @@ class GeneveIf(Interface): >> dict = GeneveIf().get_config() """ return deepcopy(cls.default) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class first + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 0dddab7b7..18a500336 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -109,3 +109,22 @@ class VXLANIf(Interface): >> dict = VXLANIf().get_config() """ return deepcopy(cls.default) + + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class first + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) -- cgit v1.2.3 From 86ed6b5ebfd43f3baac3becd0c0524577812d11d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 12:34:13 +0200 Subject: wireless: T2057: ensure interface state is properly set --- python/vyos/ifconfig/wireless.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'python') diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py index 3122ac0a3..a50346ffa 100644 --- a/python/vyos/ifconfig/wireless.py +++ b/python/vyos/ifconfig/wireless.py @@ -71,6 +71,25 @@ class WiFiIf(Interface): return config + def update(self, config): + """ General helper function which works on a dictionary retrived by + get_config_dict(). It's main intention is to consolidate the scattered + interface setup code and provide a single point of entry when workin + on any interface. """ + + # call base class first + super().update(config) + + # Enable/Disable of an interface must always be done at the end of the + # derived class to make use of the ref-counting set_admin_state() + # function. We will only enable the interface if 'up' was called as + # often as 'down'. This is required by some interface implementations + # as certain parameters can only be changed when the interface is + # in admin-down state. This ensures the link does not flap during + # reconfiguration. + state = 'down' if 'disable' in config else 'up' + self.set_admin_state(state) + @Interface.register class WiFiModemIf(WiFiIf): -- cgit v1.2.3 From 02e995265a4548aac7a990ae3e176f993b2f5a17 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 13:05:03 +0200 Subject: vyos.util: T2755: add vyos_dict_search() to traverse a dictionary This is faster implementation then using jmespath.search('foo.bar', dict). --- python/vyos/util.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'python') diff --git a/python/vyos/util.py b/python/vyos/util.py index d27a8a3e7..4cc25764b 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -672,3 +672,17 @@ def find_device_file(device): return os.path.join(root, basename) return None + +def vyos_dict_search(dict, path): + """ Traverse Python dictionary (dict) delimited by dot (.). + Return value of key if found, None otherwise. + + This is faster implementation then jmespath.search('foo.bar', dict)""" + parts = path.split('.') + inside = parts[:-1] + if not inside: + return dict[path] + c = dict + for p in parts[:-1]: + c = c.get(p, {}) + return c.get(parts[-1], None) -- cgit v1.2.3 From 20ef1aab793504cf4956dedeeadaf528933b7ccf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 13:06:37 +0200 Subject: vyos.configverify: T2677: extend verify_dhcpv6() for non duplicate sla-ids --- python/vyos/configverify.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index d1519b0ac..264dd1c30 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -91,9 +91,26 @@ def verify_dhcpv6(config): Common helper function used by interface implementations to perform recurring validation of DHCPv6 options which are mutually exclusive. """ - if {'parameters_only', 'temporary'} <= set(config.get('dhcpv6_options', {})): - raise ConfigError('DHCPv6 temporary and parameters-only options ' - 'are mutually exclusive!') + if 'dhcpv6_options' in config: + from vyos.util import vyos_dict_search + if {'parameters_only', 'temporary'} <= set(config['dhcpv6_options']): + raise ConfigError('DHCPv6 temporary and parameters-only options ' + 'are mutually exclusive!') + + # It is not allowed to have duplicate SLA-IDs as those identify an + # assigned IPv6 subnet from a delegated prefix + for pd in vyos_dict_search(config, 'dhcpv6_options.pd'): + sla_ids = [] + for interface in vyos_dict_search(config, f'dhcpv6_options.pd.{pd}.interface'): + sla_id = vyos_dict_search(config, + f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id') + sla_ids.append(sla_id) + + # Check for duplicates + duplicates = [x for n, x in enumerate(sla_ids) if x in sla_ids[:n]] + if duplicates: + raise ConfigError('Site-Level Aggregation Identifier (SLA-ID) ' + 'must be unique per prefix-delegation!') def verify_vlan_config(config): """ -- cgit v1.2.3 From aa25690c83c4812c92490d29b564dd0330b24d34 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 14:22:41 +0200 Subject: T2755: convert jmespath.search() to vyos_dict_search() for performance --- python/vyos/configdict.py | 6 +++--- python/vyos/configverify.py | 9 +++++---- python/vyos/ifconfig/bond.py | 6 +++--- python/vyos/ifconfig/bridge.py | 9 ++++----- python/vyos/ifconfig/ethernet.py | 12 ++++++------ python/vyos/ifconfig/interface.py | 33 +++++++++++++++++---------------- python/vyos/util.py | 2 +- 7 files changed, 39 insertions(+), 38 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index c1e93955e..bd8624ced 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -17,7 +17,6 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. """ import os -import jmespath from enum import Enum from copy import deepcopy @@ -226,8 +225,9 @@ def get_interface_dict(config, base, ifname=''): Will return a dictionary with the necessary interface configuration """ - from vyos.xml import defaults + from vyos.util import vyos_dict_search from vyos.validate import is_member + from vyos.xml import defaults if not ifname: # determine tagNode instance @@ -273,7 +273,7 @@ def get_interface_dict(config, base, ifname=''): # XXX: T2636 workaround: convert string to a list with one element if isinstance(eui64, str): eui64 = [eui64] - tmp = jmespath.search('ipv6.address', dict) + tmp = vyos_dict_search('ipv6.address', dict) if not tmp: dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) else: diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 264dd1c30..7e1930878 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -93,17 +93,18 @@ def verify_dhcpv6(config): """ if 'dhcpv6_options' in config: from vyos.util import vyos_dict_search + if {'parameters_only', 'temporary'} <= set(config['dhcpv6_options']): raise ConfigError('DHCPv6 temporary and parameters-only options ' 'are mutually exclusive!') # It is not allowed to have duplicate SLA-IDs as those identify an # assigned IPv6 subnet from a delegated prefix - for pd in vyos_dict_search(config, 'dhcpv6_options.pd'): + for pd in vyos_dict_search('dhcpv6_options.pd', config): sla_ids = [] - for interface in vyos_dict_search(config, f'dhcpv6_options.pd.{pd}.interface'): - sla_id = vyos_dict_search(config, - f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id') + for interface in vyos_dict_search(f'dhcpv6_options.pd.{pd}.interface', config): + sla_id = vyos_dict_search( + f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id', config) sla_ids.append(sla_id) # Check for duplicates diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 193cea321..64407401b 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -14,12 +14,12 @@ # License along with this library. If not, see . import os -import jmespath from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.util import cmd +from vyos.util import vyos_dict_search from vyos.validate import assert_list from vyos.validate import assert_positive @@ -336,7 +336,7 @@ class BondIf(Interface): self.set_arp_ip_target('-' + addr) # Add configured ARP target addresses - value = jmespath.search('arp_monitor.target', config) + value = vyos_dict_search('arp_monitor.target', config) if isinstance(value, str): value = [value] if value: @@ -359,7 +359,7 @@ class BondIf(Interface): if value: self.set_mode(value) # Add (enslave) interfaces to bond - value = jmespath.search('member.interface', config) + value = vyos_dict_search('member.interface', config) if value: for interface in value: # if we've come here we already verified the interface diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 466e6b682..4c76fe996 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -13,13 +13,12 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . -import jmespath - from vyos.ifconfig.interface import Interface from vyos.ifconfig.stp import STP from vyos.validate import assert_boolean from vyos.validate import assert_positive from vyos.util import cmd +from vyos.util import vyos_dict_search @Interface.register class BridgeIf(Interface): @@ -223,18 +222,18 @@ class BridgeIf(Interface): self.set_stp(value) # enable or disable IGMP querier - tmp = jmespath.search('igmp.querier', config) + tmp = vyos_dict_search('igmp.querier', config) value = '1' if (tmp != None) else '0' self.set_multicast_querier(value) # remove interface from bridge - tmp = jmespath.search('member.interface_remove', config) + tmp = vyos_dict_search('member.interface_remove', config) if tmp: for member in tmp: self.del_port(member) STPBridgeIf = STP.enable(BridgeIf) - tmp = jmespath.search('member.interface', config) + tmp = vyos_dict_search('member.interface', config) if tmp: for interface, interface_config in tmp.items(): # if we've come here we already verified the interface diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index b2f701e00..17c1bd64d 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -15,12 +15,12 @@ import os import re -import jmespath from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.validate import assert_list from vyos.util import run +from vyos.util import vyos_dict_search @Interface.register @VLAN.enable @@ -268,27 +268,27 @@ class EthernetIf(Interface): self.set_flow_control(value) # GRO (generic receive offload) - tmp = jmespath.search('offload_options.generic_receive', config) + tmp = vyos_dict_search('offload_options.generic_receive', config) value = tmp if (tmp != None) else 'off' self.set_gro(value) # GSO (generic segmentation offload) - tmp = jmespath.search('offload_options.generic_segmentation', config) + tmp = vyos_dict_search('offload_options.generic_segmentation', config) value = tmp if (tmp != None) else 'off' self.set_gso(value) # scatter-gather option - tmp = jmespath.search('offload_options.scatter_gather', config) + tmp = vyos_dict_search('offload_options.scatter_gather', config) value = tmp if (tmp != None) else 'off' self.set_sg(value) # TSO (TCP segmentation offloading) - tmp = jmespath.search('offload_options.udp_fragmentation', config) + tmp = vyos_dict_search('offload_options.udp_fragmentation', config) value = tmp if (tmp != None) else 'off' self.set_tso(value) # UDP fragmentation offloading - tmp = jmespath.search('offload_options.udp_fragmentation', config) + tmp = vyos_dict_search('offload_options.udp_fragmentation', config) value = tmp if (tmp != None) else 'off' self.set_ufo(value) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 537c4bc2d..67ba973c4 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -34,6 +34,7 @@ from vyos.configdict import list_diff from vyos.configdict import dict_merge from vyos.template import render from vyos.util import mac2eui64 +from vyos.util import vyos_dict_search from vyos.validate import is_ipv4 from vyos.validate import is_ipv6 from vyos.validate import is_intf_addr_assigned @@ -824,7 +825,7 @@ class Interface(Control): lease_file = f'{config_base}_{ifname}.leases' if enable and 'disable' not in self._config: - if jmespath.search('dhcp_options.host_name', self._config) == None: + if vyos_dict_search('dhcp_options.host_name', self._config) == None: # read configured system hostname. # maybe change to vyos hostd client ??? hostname = 'vyos' @@ -907,7 +908,7 @@ class Interface(Control): # always ensure DHCPv6 client is stopped (when not configured as client # for IPv6 address or prefix delegation - dhcpv6pd = jmespath.search('dhcpv6_options.pd', config) + dhcpv6pd = vyos_dict_search('dhcpv6_options.pd', config) if 'dhcpv6' not in new_addr or dhcpv6pd == None: self.del_addr('dhcpv6') @@ -935,59 +936,59 @@ class Interface(Control): self.set_vrf(config.get('vrf', '')) # Configure ARP cache timeout in milliseconds - has default value - tmp = jmespath.search('ip.arp_cache_timeout', config) + tmp = vyos_dict_search('ip.arp_cache_timeout', config) value = tmp if (tmp != None) else '30' self.set_arp_cache_tmo(value) # Configure ARP filter configuration - tmp = jmespath.search('ip.disable_arp_filter', config) + tmp = vyos_dict_search('ip.disable_arp_filter', config) value = '0' if (tmp != None) else '1' self.set_arp_filter(value) # Configure ARP accept - tmp = jmespath.search('ip.enable_arp_accept', config) + tmp = vyos_dict_search('ip.enable_arp_accept', config) value = '1' if (tmp != None) else '0' self.set_arp_accept(value) # Configure ARP announce - tmp = jmespath.search('ip.enable_arp_announce', config) + tmp = vyos_dict_search('ip.enable_arp_announce', config) value = '1' if (tmp != None) else '0' self.set_arp_announce(value) # Configure ARP ignore - tmp = jmespath.search('ip.enable_arp_ignore', config) + tmp = vyos_dict_search('ip.enable_arp_ignore', config) value = '1' if (tmp != None) else '0' self.set_arp_ignore(value) # Enable proxy-arp on this interface - tmp = jmespath.search('ip.enable_proxy_arp', config) + tmp = vyos_dict_search('ip.enable_proxy_arp', config) value = '1' if (tmp != None) else '0' self.set_proxy_arp(value) # Enable private VLAN proxy ARP on this interface - tmp = jmespath.search('ip.proxy_arp_pvlan', config) + tmp = vyos_dict_search('ip.proxy_arp_pvlan', config) value = '1' if (tmp != None) else '0' self.set_proxy_arp_pvlan(value) # IPv6 forwarding - tmp = jmespath.search('ipv6.disable_forwarding', config) + tmp = vyos_dict_search('ipv6.disable_forwarding', config) value = '0' if (tmp != None) else '1' self.set_ipv6_forwarding(value) # IPv6 router advertisements - tmp = jmespath.search('ipv6.address.autoconf', config) + tmp = vyos_dict_search('ipv6.address.autoconf', config) value = '2' if (tmp != None) else '1' if 'dhcpv6' in new_addr: value = '2' self.set_ipv6_accept_ra(value) # IPv6 address autoconfiguration - tmp = jmespath.search('ipv6.address.autoconf', config) + tmp = vyos_dict_search('ipv6.address.autoconf', config) value = '1' if (tmp != None) else '0' self.set_ipv6_autoconf(value) # IPv6 Duplicate Address Detection (DAD) tries - tmp = jmespath.search('ipv6.dup_addr_detect_transmits', config) + tmp = vyos_dict_search('ipv6.dup_addr_detect_transmits', config) value = tmp if (tmp != None) else '1' self.set_ipv6_dad_messages(value) @@ -996,7 +997,7 @@ class Interface(Control): self.set_mtu(config.get('mtu')) # Delete old IPv6 EUI64 addresses before changing MAC - tmp = jmespath.search('ipv6.address.eui64_old', config) + tmp = vyos_dict_search('ipv6.address.eui64_old', config) if tmp: for addr in tmp: self.del_ipv6_eui64_address(addr) @@ -1011,7 +1012,7 @@ class Interface(Control): self.set_mac(mac) # Manage IPv6 link-local addresses - tmp = jmespath.search('ipv6.address.no_default_link_local', config) + tmp = vyos_dict_search('ipv6.address.no_default_link_local', config) # we must check explicitly for None type as if the key is set we will # get an empty dict () if tmp is not None: @@ -1020,7 +1021,7 @@ class Interface(Control): self.add_ipv6_eui64_address('fe80::/64') # Add IPv6 EUI-based addresses - tmp = jmespath.search('ipv6.address.eui64', config) + tmp = vyos_dict_search('ipv6.address.eui64', config) if tmp: # XXX: T2636 workaround: convert string to a list with one element if isinstance(tmp, str): diff --git a/python/vyos/util.py b/python/vyos/util.py index 4cc25764b..84aa16791 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -673,7 +673,7 @@ def find_device_file(device): return None -def vyos_dict_search(dict, path): +def vyos_dict_search(path, dict): """ Traverse Python dictionary (dict) delimited by dot (.). Return value of key if found, None otherwise. -- cgit v1.2.3 From bfae3e1a439972cf17e0c8193a91e14e388367d8 Mon Sep 17 00:00:00 2001 From: Runar Borge Date: Tue, 25 Aug 2020 20:05:39 +0200 Subject: T2826: frr: frr python lib error in replace_section because of a bug in frr.py the default before_re will not be working. it is by default without a group, but will be used in a match that needs a group. The whole string could be matched in the group, so the fix is easy to implement. --- python/vyos/frr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/frr.py b/python/vyos/frr.py index e39b6a914..3fc75bbdf 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -281,7 +281,7 @@ def replace_section(config, replacement, from_re, to_re=r'!', before_re=r'line v startline and endline tags will be automatically added to the resulting from_re/to_re and before_re regex'es """ - return _replace_section(config, replacement, replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=rf'^{before_re}$') + return _replace_section(config, replacement, replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=rf'^({before_re})$') def remove_section(config, from_re, to_re='!'): -- cgit v1.2.3 From b40c52682a25664f7018ab8b4e5ba6f467f10d17 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 30 Aug 2020 08:22:32 -0500 Subject: config: T2636: get_config_dict() returns a list on multi node by default Unless no_multi_convert is True, a single valued multi node will be returned as a list by get_config_dict(). Modification of Thomas Mangin's version. --- python/vyos/config.py | 61 ++++++++++++++++++++++++++----------------- python/vyos/configdiff.py | 4 +-- python/vyos/xml/__init__.py | 4 +++ python/vyos/xml/definition.py | 17 ++++++++++++ 4 files changed, 60 insertions(+), 26 deletions(-) (limited to 'python') diff --git a/python/vyos/config.py b/python/vyos/config.py index 884d6d947..de79a3654 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -67,6 +67,7 @@ import re import json from copy import deepcopy +import vyos.xml import vyos.util import vyos.configtree from vyos.configsource import ConfigSource, ConfigSourceSession @@ -193,50 +194,62 @@ class Config(object): """ return self._config_source.show_config(path, default, effective) - def get_cached_dict(self, effective=False): + def get_cached_root_dict(self, effective=False): cached = self._dict_cache.get(effective, {}) if cached: - config_dict = cached + return cached + + if effective: + config = self._running_config else: - config_dict = {} + config = self._session_config - if effective: - if self._running_config: - config_dict = json.loads((self._running_config).to_json()) - else: - if self._session_config: - config_dict = json.loads((self._session_config).to_json()) + if config: + config_dict = json.loads(config.to_json()) + else: + config_dict = {} - self._dict_cache[effective] = config_dict + self._dict_cache[effective] = config_dict return config_dict - def get_config_dict(self, path=[], effective=False, key_mangling=None, get_first_key=False): + def get_config_dict(self, path=[], effective=False, key_mangling=None, + get_first_key=False, no_multi_convert=False): """ Args: path (str list): Configuration tree path, can be empty effective=False: effective or session config key_mangling=None: mangle dict keys according to regex and replacement get_first_key=False: if k = path[:-1], return sub-dict d[k] instead of {k: d[k]} + no_multi_convert=False: if convert, return single value of multi node as list Returns: a dict representation of the config under path """ - config_dict = self.get_cached_dict(effective) + lpath = self._make_path(path) + root_dict = self.get_cached_root_dict(effective) + conf_dict = vyos.util.get_sub_dict(root_dict, lpath, get_first_key) - config_dict = vyos.util.get_sub_dict(config_dict, self._make_path(path), get_first_key) + if not key_mangling and no_multi_convert: + return deepcopy(conf_dict) - if key_mangling: - if not (isinstance(key_mangling, tuple) and \ - (len(key_mangling) == 2) and \ - isinstance(key_mangling[0], str) and \ - isinstance(key_mangling[1], str)): - raise ValueError("key_mangling must be a tuple of two strings") - else: - config_dict = vyos.util.mangle_dict_keys(config_dict, key_mangling[0], key_mangling[1]) - else: - config_dict = deepcopy(config_dict) + xmlpath = lpath if get_first_key else lpath[:-1] - return config_dict + if not key_mangling: + conf_dict = vyos.xml.multi_to_list(xmlpath, conf_dict) + return conf_dict + + if no_multi_convert is False: + conf_dict = vyos.xml.multi_to_list(xmlpath, conf_dict) + + if not (isinstance(key_mangling, tuple) and \ + (len(key_mangling) == 2) and \ + isinstance(key_mangling[0], str) and \ + isinstance(key_mangling[1], str)): + raise ValueError("key_mangling must be a tuple of two strings") + + conf_dict = vyos.util.mangle_dict_keys(conf_dict, key_mangling[0], key_mangling[1]) + + return conf_dict def is_multi(self, path): """ diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py index b79893507..0e41fbe27 100644 --- a/python/vyos/configdiff.py +++ b/python/vyos/configdiff.py @@ -82,8 +82,8 @@ class ConfigDiff(object): """ def __init__(self, config, key_mangling=None): self._level = config.get_level() - self._session_config_dict = config.get_cached_dict() - self._effective_config_dict = config.get_cached_dict(effective=True) + self._session_config_dict = config.get_cached_root_dict(effective=False) + self._effective_config_dict = config.get_cached_root_dict(effective=True) self._key_mangling = key_mangling # mirrored from Config; allow path arguments relative to level diff --git a/python/vyos/xml/__init__.py b/python/vyos/xml/__init__.py index 0f914fed2..0ef0c85ce 100644 --- a/python/vyos/xml/__init__.py +++ b/python/vyos/xml/__init__.py @@ -51,6 +51,10 @@ def defaults(lpath, flat=False): return load_configuration().defaults(lpath, flat) +def multi_to_list(lpath, conf): + return load_configuration().multi_to_list(lpath, conf) + + if __name__ == '__main__': print(defaults(['service'], flat=True)) print(defaults(['service'], flat=False)) diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index 098e64f7e..6d6fcb5c7 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -281,6 +281,23 @@ class XML(dict): return _flatten(lpath, len(lpath), d) + def multi_to_list(self, lpath, conf): + r = {} + for k in conf: + # key mangling could also be done here + # it would prevent two parsing of the config tree + # under = k.replace('-','_') + under = k + fpath = lpath + [k] + if isinstance(conf[k],dict): + r[under] = self.multi_to_list(fpath, conf[k]) + continue + value = conf[k] + if self.is_multi(fpath) and not isinstance(value, list): + value = [value] + r[under] = value + return r + # from functools import lru_cache # @lru_cache(maxsize=100) # XXX: need to use cachetool instead - for later -- cgit v1.2.3 From eeeab0197c6e54eda5c2ec59a91f93403618a58b Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:25 -0500 Subject: configd: T2582: inject shim env variable into configsession ConfigSession needs to have required env vars manully injected; add the shim definition env var to the list, if config daemon is active. --- python/vyos/configsession.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 0994fd974..6e4214360 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -17,6 +17,8 @@ import re import sys import subprocess +from vyos.util import call + CLI_SHELL_API = '/bin/cli-shell-api' SET = '/opt/vyatta/sbin/my_set' DELETE = '/opt/vyatta/sbin/my_delete' @@ -69,6 +71,11 @@ def inject_vyos_env(env): env['vyos_sbin_dir'] = '/usr/sbin' env['vyos_validators_dir'] = '/usr/libexec/vyos/validators' + # if running the vyos-configd daemon, inject the vyshim env var + ret = call('systemctl is-active --quiet vyos-configd.service') + if not ret: + env['vyshim'] = '/usr/sbin/vyshim' + return env -- cgit v1.2.3 From 9c63731d6683f59ea784c08852ed38e3ac22794b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 31 Aug 2020 19:57:06 +0200 Subject: T2636: remove workarounds for get_config_dict() Now that b40c52682a256 ("config: T2636: get_config_dict() returns a list on multi node by default") is implemented the workarounds can be removed. --- data/templates/ntp/ntp.conf.tmpl | 16 ++++------------ data/templates/ssh/sshd_config.tmpl | 16 ++++------------ data/templates/wifi/hostapd.conf.tmpl | 12 ------------ python/vyos/configdict.py | 3 --- python/vyos/ifconfig/interface.py | 7 ------- python/vyos/ifconfig/loopback.py | 5 ----- src/conf_mode/https.py | 9 --------- 7 files changed, 8 insertions(+), 60 deletions(-) (limited to 'python') diff --git a/data/templates/ntp/ntp.conf.tmpl b/data/templates/ntp/ntp.conf.tmpl index 6ef0c0f2c..df8157a41 100644 --- a/data/templates/ntp/ntp.conf.tmpl +++ b/data/templates/ntp/ntp.conf.tmpl @@ -25,23 +25,15 @@ server {{ srv | replace('_', '-') }} iburst {{ options }} {% if allow_clients is defined and allow_clients.address is defined %} # Allowed clients configuration -{% if allow_clients.address is string %} -restrict {{ allow_clients.address|address_from_cidr }} mask {{ allow_clients.address|netmask_from_cidr }} nomodify notrap nopeer -{% else %} -{% for address in allow_clients.address %} +{% for address in allow_clients.address %} restrict {{ address|address_from_cidr }} mask {{ address|netmask_from_cidr }} nomodify notrap nopeer -{% endfor %} -{% endif %} +{% endfor %} {% endif %} {% if listen_address %} # NTP should listen on configured addresses only interface ignore wildcard -{% if listen_address is string %} -interface listen {{ listen_address }} -{% else %} -{% for address in listen_address %} +{% for address in listen_address %} interface listen {{ address }} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} diff --git a/data/templates/ssh/sshd_config.tmpl b/data/templates/ssh/sshd_config.tmpl index 4fde24255..52d537aca 100644 --- a/data/templates/ssh/sshd_config.tmpl +++ b/data/templates/ssh/sshd_config.tmpl @@ -37,13 +37,9 @@ PermitRootLogin no UseDNS {{ "no" if disable_host_validation is defined else "yes" }} # Specifies the port number that sshd(8) listens on -{% if port is string %} -Port {{ port }} -{% else %} -{% for value in port %} +{% for value in port %} Port {{ value }} -{% endfor %} -{% endif %} +{% endfor %} # Gives the verbosity level that is used when logging messages from sshd LogLevel {{ loglevel | upper }} @@ -53,13 +49,9 @@ PasswordAuthentication {{ "no" if disable_password_authentication is defined els {% if listen_address %} # Specifies the local addresses sshd should listen on -{% if listen_address is string %} -ListenAddress {{ listen_address }} -{% else %} -{% for address in listen_address %} +{% for address in listen_address %} ListenAddress {{ address }} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} {% if ciphers %} diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl index 765668c57..a7efee6d5 100644 --- a/data/templates/wifi/hostapd.conf.tmpl +++ b/data/templates/wifi/hostapd.conf.tmpl @@ -500,18 +500,10 @@ wpa=1 {% if security.wpa.mode is defined and security.wpa.mode == 'wpa2' %} # Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value) -{% if security.wpa.cipher is string %} -rsn_pairwise={{ security.wpa.cipher }} -{% else %} rsn_pairwise={{ security.wpa.cipher | join(" ") }} -{% endif %} {% else %} # Pairwise cipher for WPA (v1) (default: TKIP) -{% if security.wpa.cipher is string %} -wpa_pairwise={{ security.wpa.cipher }} -{% else %} wpa_pairwise={{ security.wpa.cipher | join(" ") }} -{% endif %} {% endif %} {% endif %} @@ -522,11 +514,7 @@ wpa_pairwise={{ security.wpa.cipher | join(" ") }} # overriding the group cipher with an unexpected value can result in # interoperability issues and in general, this parameter is mainly used for # testing purposes. -{% if security.wpa.group_cipher is string %} -group_cipher={{ security.wpa.group_cipher }} -{% else %} group_cipher={{ security.wpa.group_cipher | join(" ") }} -{% endif %} {% endif %} {% if security.wpa.passphrase is defined %} diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index bd8624ced..e8c0aa5b3 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -270,9 +270,6 @@ def get_interface_dict(config, base, ifname=''): eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64']) if eui64: - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(eui64, str): - eui64 = [eui64] tmp = vyos_dict_search('ipv6.address', dict) if not tmp: dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 67ba973c4..ef2336c17 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -898,10 +898,6 @@ class Interface(Control): # configured addresses will be removed first new_addr = config.get('address', []) - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(new_addr, str): - new_addr = [new_addr] - # always ensure DHCP client is stopped (when not configured explicitly) if 'dhcp' not in new_addr: self.del_addr('dhcp') @@ -1023,9 +1019,6 @@ class Interface(Control): # Add IPv6 EUI-based addresses tmp = vyos_dict_search('ipv6.address.eui64', config) if tmp: - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(tmp, str): - tmp = [tmp] for addr in tmp: self.add_ipv6_eui64_address(addr) diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py index 2b4ebfdcc..c70e1773f 100644 --- a/python/vyos/ifconfig/loopback.py +++ b/python/vyos/ifconfig/loopback.py @@ -64,11 +64,6 @@ class LoopbackIf(Interface): on any interface. """ addr = config.get('address', []) - # XXX workaround for T2636, convert IP address string to a list - # with one element - if isinstance(addr, str): - addr = [addr] - # We must ensure that the loopback addresses are never deleted from the system addr += self._persistent_addresses diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index dc51cb117..de228f0f8 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -74,9 +74,6 @@ def get_config(config=None): server_block['address'] = data.get('listen-address', '*') server_block['port'] = data.get('listen-port', '443') name = data.get('server-name', ['_']) - # XXX: T2636 workaround: convert string to a list with one element - if not isinstance(name, list): - name = [name] server_block['name'] = name server_block_list.append(server_block) @@ -98,9 +95,6 @@ def get_config(config=None): certbot = False cert_domains = cert_dict.get('certbot', {}).get('domain-name', []) if cert_domains: - # XXX: T2636 workaround: convert string to a list with one element - if not isinstance(cert_domains, list): - cert_domains = [cert_domains] certbot = True for domain in cert_domains: sub_list = vyos.certbot_util.choose_server_block(server_block_list, @@ -125,9 +119,6 @@ def get_config(config=None): if port: api_data['port'] = port vhosts = https_dict.get('api-restrict', {}).get('virtual-host', []) - # XXX: T2636 workaround: convert string to a list with one element - if not isinstance(vhosts, list): - vhosts = [vhosts] if vhosts: api_data['vhost'] = vhosts[:] -- cgit v1.2.3 From 0ddca9de00b7ae3969f718c2cd2f081506d5c222 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 1 Sep 2020 11:41:17 -0500 Subject: xml: T2849: vyos.xml.defaults should return a list on multi nodes --- python/vyos/xml/definition.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'python') diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index 6d6fcb5c7..a25fc50c5 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -254,6 +254,9 @@ class XML(dict): d = d.get(k, {}) if not flat: + # _flatten will make this conversion + d = self.multi_to_list(lpath, d) + r = {} for k in d: under = k.replace('-','_') -- cgit v1.2.3