diff options
35 files changed, 346 insertions, 623 deletions
@@ -7,7 +7,7 @@ LIBS := -lzmq CFLAGS := BUILD_ARCH := $(shell dpkg-architecture -q DEB_BUILD_ARCH) J2LINT := $(shell command -v j2lint 2> /dev/null) -PYLINT_FILES := $(shell git ls-files *.py src/migration-scripts) +PYLINT_FILES := $(shell git ls-files *.py src/migration-scripts src/services) LIBVYOSCONFIG_BUILD_PATH := /tmp/libvyosconfig/_build/libvyosconfig.so LIBVYOSCONFIG_STATUS := $(shell git submodule status) diff --git a/data/templates/ids/fastnetmon.j2 b/data/templates/ids/fastnetmon.j2 deleted file mode 100644 index f6f03d0db..000000000 --- a/data/templates/ids/fastnetmon.j2 +++ /dev/null @@ -1,121 +0,0 @@ -# enable this option if you want to send logs to local syslog facility -logging:logging_level = debug -logging:local_syslog_logging = on - -# list of all your networks in CIDR format -networks_list_path = /run/fastnetmon/networks_list - -# list networks in CIDR format which will be not monitored for attacks -white_list_path = /run/fastnetmon/excluded_networks_list - -# Enable/Disable any actions in case of attack -enable_ban = on -enable_ban_ipv6 = on - -## How many packets will be collected from attack traffic -ban_details_records_count = 500 - -## How long (in seconds) we should keep an IP in blocked state -## If you set 0 here it completely disables unban capability -{% if ban_time is vyos_defined %} -ban_time = {{ ban_time }} -{% endif %} - -# Check if the attack is still active, before triggering an unban callback with this option -# If the attack is still active, check each run of the unban watchdog -unban_only_if_attack_finished = on - -# enable per subnet speed meters -# For each subnet, list track speed in bps and pps for both directions -enable_subnet_counters = off - -{% if mode is vyos_defined('mirror') %} -mirror_afpacket = on -{% elif mode is vyos_defined('sflow') %} -sflow = on -{% if sflow.port is vyos_defined %} -sflow_port = {{ sflow.port }} -{% endif %} -{% if sflow.listen_address is vyos_defined %} -sflow_host = {{ sflow.listen_address }} -{% endif %} -{% endif %} - - -process_incoming_traffic = {{ 'on' if direction is vyos_defined and 'in' in direction else 'off' }} -process_outgoing_traffic = {{ 'on' if direction is vyos_defined and 'out' in direction else 'off' }} - -{% if threshold is vyos_defined %} -{% if threshold.general is vyos_defined %} -# General threshold -{% for thr, thr_value in threshold.general.items() %} -{% if thr is vyos_defined('fps') %} -ban_for_flows = on -threshold_flows = {{ thr_value }} -{% elif thr is vyos_defined('mbps') %} -ban_for_bandwidth = on -threshold_mbps = {{ thr_value }} -{% elif thr is vyos_defined('pps') %} -ban_for_pps = on -threshold_pps = {{ thr_value }} -{% endif %} -{% endfor %} -{% endif %} - -{% if threshold.tcp is vyos_defined %} -# TCP threshold -{% for thr, thr_value in threshold.tcp.items() %} -{% if thr is vyos_defined('fps') %} -ban_for_tcp_flows = on -threshold_tcp_flows = {{ thr_value }} -{% elif thr is vyos_defined('mbps') %} -ban_for_tcp_bandwidth = on -threshold_tcp_mbps = {{ thr_value }} -{% elif thr is vyos_defined('pps') %} -ban_for_tcp_pps = on -threshold_tcp_pps = {{ thr_value }} -{% endif %} -{% endfor %} -{% endif %} - -{% if threshold.udp is vyos_defined %} -# UDP threshold -{% for thr, thr_value in threshold.udp.items() %} -{% if thr is vyos_defined('fps') %} -ban_for_udp_flows = on -threshold_udp_flows = {{ thr_value }} -{% elif thr is vyos_defined('mbps') %} -ban_for_udp_bandwidth = on -threshold_udp_mbps = {{ thr_value }} -{% elif thr is vyos_defined('pps') %} -ban_for_udp_pps = on -threshold_udp_pps = {{ thr_value }} -{% endif %} -{% endfor %} -{% endif %} - -{% if threshold.icmp is vyos_defined %} -# ICMP threshold -{% for thr, thr_value in threshold.icmp.items() %} -{% if thr is vyos_defined('fps') %} -ban_for_icmp_flows = on -threshold_icmp_flows = {{ thr_value }} -{% elif thr is vyos_defined('mbps') %} -ban_for_icmp_bandwidth = on -threshold_icmp_mbps = {{ thr_value }} -{% elif thr is vyos_defined('pps') %} -ban_for_icmp_pps = on -threshold_icmp_pps = {{ thr_value }} -{% endif %} -{% endfor %} -{% endif %} - -{% endif %} - -{% if listen_interface is vyos_defined %} -interfaces = {{ listen_interface | join(',') }} -{% endif %} - -{% if alert_script is vyos_defined %} -notify_script_path = {{ alert_script }} -{% endif %} diff --git a/data/templates/ids/fastnetmon_excluded_networks_list.j2 b/data/templates/ids/fastnetmon_excluded_networks_list.j2 deleted file mode 100644 index c88a1c527..000000000 --- a/data/templates/ids/fastnetmon_excluded_networks_list.j2 +++ /dev/null @@ -1,5 +0,0 @@ -{% if excluded_network is vyos_defined %} -{% for net in excluded_network %} -{{ net }} -{% endfor %} -{% endif %} diff --git a/data/templates/ids/fastnetmon_networks_list.j2 b/data/templates/ids/fastnetmon_networks_list.j2 deleted file mode 100644 index 0a0576d2a..000000000 --- a/data/templates/ids/fastnetmon_networks_list.j2 +++ /dev/null @@ -1,5 +0,0 @@ -{% if network is vyos_defined %} -{% for net in network %} -{{ net }} -{% endfor %} -{% endif %} diff --git a/debian/control b/debian/control index c40b0fb04..ffa21f840 100644 --- a/debian/control +++ b/debian/control @@ -196,7 +196,6 @@ Depends: ddclient (>= 3.11.1), # End "service dns dynamic" # # For "service ids" - fastnetmon [amd64], suricata, suricata-update, # End "service ids" diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index fde58651a..798ecaa1b 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -221,11 +221,9 @@ fi # Remove unwanted daemon files from /etc # conntackd # pmacct -# fastnetmon # ntp DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd /etc/default/pmacctd /etc/pmacct - /etc/networks_list /etc/networks_whitelist /etc/fastnetmon.conf /etc/ntp.conf /etc/default/ssh /etc/avahi/avahi-daemon.conf /etc/avahi/hosts /etc/powerdns /etc/default/pdns-recursor /etc/ppp/ip-up.d/0000usepeerdns /etc/ppp/ip-down.d/0000usepeerdns" diff --git a/interface-definitions/include/dhcp/option-v4.xml.i b/interface-definitions/include/dhcp/option-v4.xml.i index bd6fc6043..08fbcca4a 100644 --- a/interface-definitions/include/dhcp/option-v4.xml.i +++ b/interface-definitions/include/dhcp/option-v4.xml.i @@ -59,6 +59,18 @@ <constraintErrorMessage>DHCP client prefix length must be 0 to 32</constraintErrorMessage> </properties> </leafNode> + <leafNode name="capwap-controller"> + <properties> + <help>IP address of CAPWAP access controller (Option 138)</help> + <valueHelp> + <format>ipv4</format> + <description>CAPWAP AC controller</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> <leafNode name="default-router"> <properties> <help>IP address of default router</help> diff --git a/interface-definitions/include/dhcp/option-v6.xml.i b/interface-definitions/include/dhcp/option-v6.xml.i index e1897f52d..202843ddf 100644 --- a/interface-definitions/include/dhcp/option-v6.xml.i +++ b/interface-definitions/include/dhcp/option-v6.xml.i @@ -7,6 +7,18 @@ #include <include/dhcp/captive-portal.xml.i> #include <include/dhcp/domain-search.xml.i> #include <include/name-server-ipv6.xml.i> + <leafNode name="capwap-controller"> + <properties> + <help>IP address of CAPWAP access controller (Option 52)</help> + <valueHelp> + <format>ipv6</format> + <description>CAPWAP AC controller</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> <leafNode name="nis-domain"> <properties> <help>NIS domain name for client to use</help> diff --git a/interface-definitions/include/version/ids-version.xml.i b/interface-definitions/include/version/ids-version.xml.i index 9133be02b..6d4e92c21 100644 --- a/interface-definitions/include/version/ids-version.xml.i +++ b/interface-definitions/include/version/ids-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/ids-version.xml.i --> -<syntaxVersion component='ids' version='1'></syntaxVersion> +<syntaxVersion component='ids' version='2'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/service_ids_ddos-protection.xml.in b/interface-definitions/service_ids_ddos-protection.xml.in deleted file mode 100644 index 3ef2640b3..000000000 --- a/interface-definitions/service_ids_ddos-protection.xml.in +++ /dev/null @@ -1,167 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> - <node name="service"> - <children> - <node name="ids"> - <properties> - <help>Intrusion Detection System</help> - </properties> - <children> - <node name="ddos-protection" owner="${vyos_conf_scripts_dir}/service_ids_ddos-protection.py"> - <properties> - <help>FastNetMon detection and protection parameters</help> - <priority>731</priority> - </properties> - <children> - <leafNode name="alert-script"> - <properties> - <help>Path to fastnetmon alert script</help> - </properties> - </leafNode> - <leafNode name="ban-time"> - <properties> - <help>How long we should keep an IP in blocked state</help> - <valueHelp> - <format>u32:1-4294967294</format> - <description>Time in seconds</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-4294967294"/> - </constraint> - </properties> - <defaultValue>1900</defaultValue> - </leafNode> - <leafNode name="direction"> - <properties> - <help>Direction for processing traffic</help> - <completionHelp> - <list>in out</list> - </completionHelp> - <constraint> - <regex>(in|out)</regex> - </constraint> - <multi/> - </properties> - </leafNode> - <leafNode name="excluded-network"> - <properties> - <help>Specify IPv4 and IPv6 networks which are going to be excluded from protection</help> - <valueHelp> - <format>ipv4net</format> - <description>IPv4 prefix(es) to exclude</description> - </valueHelp> - <valueHelp> - <format>ipv6net</format> - <description>IPv6 prefix(es) to exclude</description> - </valueHelp> - <constraint> - <validator name="ipv4-prefix"/> - <validator name="ipv6-prefix"/> - </constraint> - <multi/> - </properties> - </leafNode> - <leafNode name="listen-interface"> - <properties> - <help>Listen interface for mirroring traffic</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - <multi/> - </properties> - </leafNode> - <leafNode name="mode"> - <properties> - <help>Traffic capture mode</help> - <completionHelp> - <list>mirror sflow</list> - </completionHelp> - <valueHelp> - <format>mirror</format> - <description>Listen to mirrored traffic</description> - </valueHelp> - <valueHelp> - <format>sflow</format> - <description>Capture sFlow flows</description> - </valueHelp> - <constraint> - <regex>(mirror|sflow)</regex> - </constraint> - </properties> - </leafNode> - <node name="sflow"> - <properties> - <help>Sflow settings</help> - </properties> - <children> - #include <include/listen-address-ipv4-single.xml.i> - #include <include/port-number.xml.i> - <leafNode name="port"> - <defaultValue>6343</defaultValue> - </leafNode> - </children> - </node> - <leafNode name="network"> - <properties> - <help>Specify IPv4 and IPv6 networks which belong to you</help> - <valueHelp> - <format>ipv4net</format> - <description>Your IPv4 prefix(es)</description> - </valueHelp> - <valueHelp> - <format>ipv6net</format> - <description>Your IPv6 prefix(es)</description> - </valueHelp> - <constraint> - <validator name="ipv4-prefix"/> - <validator name="ipv6-prefix"/> - </constraint> - <multi/> - </properties> - </leafNode> - <node name="threshold"> - <properties> - <help>Attack limits thresholds</help> - </properties> - <children> - <node name="general"> - <properties> - <help>General threshold</help> - </properties> - <children> - #include <include/ids/threshold.xml.i> - </children> - </node> - <node name="tcp"> - <properties> - <help>TCP threshold</help> - </properties> - <children> - #include <include/ids/threshold.xml.i> - </children> - </node> - <node name="udp"> - <properties> - <help>UDP threshold</help> - </properties> - <children> - #include <include/ids/threshold.xml.i> - </children> - </node> - <node name="icmp"> - <properties> - <help>ICMP threshold</help> - </properties> - <children> - #include <include/ids/threshold.xml.i> - </children> - </node> - </children> - </node> - </children> - </node> - </children> - </node> - </children> - </node> -</interfaceDefinition> diff --git a/libvyosconfig b/libvyosconfig -Subproject 58dbb42e827e3d326c6e0e9470334d4d5c7c396 +Subproject 1dedc69476d707718031c45b53b626da8badf86 diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index b9ef8f48e..91e1c93ef 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -17,19 +17,6 @@ </properties> <command>SYSTEMD_COLORS=false grc journalctl --no-hostname --follow --boot</command> </node> - <node name="ids"> - <properties> - <help>Monitor Intrusion Detection System log</help> - </properties> - <children> - <leafNode name="ddos-protection"> - <properties> - <help>Monitor last lines of DDOS protection</help> - </properties> - <command>journalctl --no-hostname --follow --boot --unit fastnetmon.service</command> - </leafNode> - </children> - </node> <leafNode name="certbot"> <properties> <help>Monitor last lines of certbot log</help> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 5ee7c973f..c43ceaf32 100755 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -62,19 +62,6 @@ </properties> <command>journalctl --no-hostname --boot --unit conserver-server.service</command> </leafNode> - <node name="ids"> - <properties> - <help>Show log for for Intrusion Detection System</help> - </properties> - <children> - <leafNode name="ddos-protection"> - <properties> - <help>Show log for DDOS protection</help> - </properties> - <command>journalctl --no-hostname --boot --unit fastnetmon.service</command> - </leafNode> - </children> - </node> <node name="dhcp"> <properties> <help>Show log for Dynamic Host Control Protocol (DHCP)</help> diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py index 94215531d..81d986658 100644 --- a/python/vyos/component_version.py +++ b/python/vyos/component_version.py @@ -49,7 +49,9 @@ DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot') REGEX_WARN_VYOS = r'(// Warning: Do not remove the following line.)' REGEX_WARN_VYATTA = r'(/\* Warning: Do not remove the following line. \*/)' REGEX_COMPONENT_VERSION_VYOS = r'// vyos-config-version:\s+"([\w@:-]+)"\s*' -REGEX_COMPONENT_VERSION_VYATTA = r'/\* === vyatta-config-version:\s+"([\w@:-]+)"\s+=== \*/' +REGEX_COMPONENT_VERSION_VYATTA = ( + r'/\* === vyatta-config-version:\s+"([\w@:-]+)"\s+=== \*/' +) REGEX_RELEASE_VERSION_VYOS = r'// Release version:\s+(\S*)\s*' REGEX_RELEASE_VERSION_VYATTA = r'/\* Release version:\s+(\S*)\s*\*/' @@ -62,16 +64,31 @@ CONFIG_FILE_VERSION = """\ warn_filter_vyos = re.compile(REGEX_WARN_VYOS) warn_filter_vyatta = re.compile(REGEX_WARN_VYATTA) -regex_filter = { 'vyos': dict(zip(['component', 'release'], - [re.compile(REGEX_COMPONENT_VERSION_VYOS), - re.compile(REGEX_RELEASE_VERSION_VYOS)])), - 'vyatta': dict(zip(['component', 'release'], - [re.compile(REGEX_COMPONENT_VERSION_VYATTA), - re.compile(REGEX_RELEASE_VERSION_VYATTA)])) } +regex_filter = { + 'vyos': dict( + zip( + ['component', 'release'], + [ + re.compile(REGEX_COMPONENT_VERSION_VYOS), + re.compile(REGEX_RELEASE_VERSION_VYOS), + ], + ) + ), + 'vyatta': dict( + zip( + ['component', 'release'], + [ + re.compile(REGEX_COMPONENT_VERSION_VYATTA), + re.compile(REGEX_RELEASE_VERSION_VYATTA), + ], + ) + ), +} + @dataclass class VersionInfo: - component: Optional[dict[str,int]] = None + component: Optional[dict[str, int]] = None release: str = get_version() vintage: str = 'vyos' config_body: Optional[str] = None @@ -84,8 +101,9 @@ class VersionInfo: return bool(self.config_body is None) def update_footer(self): - f = CONFIG_FILE_VERSION.format(component_to_string(self.component), - self.release) + f = CONFIG_FILE_VERSION.format( + component_to_string(self.component), self.release + ) self.footer_lines = f.splitlines() def update_syntax(self): @@ -121,13 +139,16 @@ class VersionInfo: except Exception as e: raise ValueError(e) from e + def component_to_string(component: dict) -> str: - l = [f'{k}@{v}' for k, v in sorted(component.items(), key=lambda x: x[0])] + l = [f'{k}@{v}' for k, v in sorted(component.items(), key=lambda x: x[0])] # noqa: E741 return ':'.join(l) + def component_from_string(string: str) -> dict: return {k: int(v) for k, v in re.findall(r'([\w,-]+)@(\d+)', string)} + def version_info_from_file(config_file) -> VersionInfo: """Return config file component and release version info.""" version_info = VersionInfo() @@ -166,27 +187,27 @@ def version_info_from_file(config_file) -> VersionInfo: return version_info + def version_info_from_system() -> VersionInfo: """Return system component and release version info.""" d = component_version() sort_d = dict(sorted(d.items(), key=lambda x: x[0])) - version_info = VersionInfo( - component = sort_d, - release = get_version(), - vintage = 'vyos' - ) + version_info = VersionInfo(component=sort_d, release=get_version(), vintage='vyos') return version_info + def version_info_copy(v: VersionInfo) -> VersionInfo: """Make a copy of dataclass.""" return replace(v) + def version_info_prune_component(x: VersionInfo, y: VersionInfo) -> VersionInfo: """In place pruning of component keys of x not in y.""" if x.component is None or y.component is None: return - x.component = { k: v for k,v in x.component.items() if k in y.component } + x.component = {k: v for k, v in x.component.items() if k in y.component} + def add_system_version(config_str: str = None, out_file: str = None): """Wrap config string with system version and write to out_file. @@ -202,3 +223,11 @@ def add_system_version(config_str: str = None, out_file: str = None): version_info.write(out_file) else: sys.stdout.write(version_info.write_string()) + + +def append_system_version(file: str): + """Append system version data to existing file""" + version_info = version_info_from_system() + version_info.update_footer() + with open(file, 'a') as f: + f.write(version_info.write_string()) diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 90b96b88c..a3be29881 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -21,6 +21,10 @@ import subprocess from vyos.defaults import directories from vyos.utils.process import is_systemd_service_running from vyos.utils.dict import dict_to_paths +from vyos.utils.boot import boot_configuration_complete +from vyos.vyconf_session import VyconfSession + +vyconf_backend = False CLI_SHELL_API = '/bin/cli-shell-api' SET = '/opt/vyatta/sbin/my_set' @@ -165,6 +169,11 @@ class ConfigSession(object): self.__run_command([CLI_SHELL_API, 'setupSession']) + if vyconf_backend and boot_configuration_complete(): + self._vyconf_session = VyconfSession(on_error=ConfigSessionError) + else: + self._vyconf_session = None + def __del__(self): try: output = ( @@ -209,7 +218,10 @@ class ConfigSession(object): value = [] else: value = [value] - self.__run_command([SET] + path + value) + if self._vyconf_session is None: + self.__run_command([SET] + path + value) + else: + self._vyconf_session.set(path + value) def set_section(self, path: list, d: dict): try: @@ -223,7 +235,10 @@ class ConfigSession(object): value = [] else: value = [value] - self.__run_command([DELETE] + path + value) + if self._vyconf_session is None: + self.__run_command([DELETE] + path + value) + else: + self._vyconf_session.delete(path + value) def load_section(self, path: list, d: dict): try: @@ -261,20 +276,34 @@ class ConfigSession(object): self.__run_command([COMMENT] + path + value) def commit(self): - out = self.__run_command([COMMIT]) + if self._vyconf_session is None: + out = self.__run_command([COMMIT]) + else: + out, _ = self._vyconf_session.commit() + return out def discard(self): - self.__run_command([DISCARD]) + if self._vyconf_session is None: + self.__run_command([DISCARD]) + else: + out, _ = self._vyconf_session.discard() def show_config(self, path, format='raw'): - config_data = self.__run_command(SHOW_CONFIG + path) + if self._vyconf_session is None: + config_data = self.__run_command(SHOW_CONFIG + path) + else: + config_data, _ = self._vyconf_session.show_config() if format == 'raw': return config_data def load_config(self, file_path): - out = self.__run_command(LOAD_CONFIG + [file_path]) + if self._vyconf_session is None: + out = self.__run_command(LOAD_CONFIG + [file_path]) + else: + out, _ = self._vyconf_session.load_config(file=file_path) + return out def load_explicit(self, file_path): @@ -287,11 +316,21 @@ class ConfigSession(object): raise ConfigSessionError(e) from e def migrate_and_load_config(self, file_path): - out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path]) + if self._vyconf_session is None: + out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path]) + else: + out, _ = self._vyconf_session.load_config(file=file_path, migrate=True) + return out def save_config(self, file_path): - out = self.__run_command(SAVE_CONFIG + [file_path]) + if self._vyconf_session is None: + out = self.__run_command(SAVE_CONFIG + [file_path]) + else: + out, _ = self._vyconf_session.save_config( + file=file_path, append_version=True + ) + return out def install_image(self, url): diff --git a/python/vyos/kea.py b/python/vyos/kea.py index a2a35cf65..2b0cac7e6 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -43,6 +43,7 @@ kea4_options = { 'wpad_url': 'wpad-url', 'ipv6_only_preferred': 'v6-only-preferred', 'captive_portal': 'v4-captive-portal', + 'capwap_controller': 'capwap-ac-v4', } kea6_options = { @@ -55,6 +56,7 @@ kea6_options = { 'nisplus_server': 'nisp-servers', 'sntp_server': 'sntp-servers', 'captive_portal': 'v6-captive-portal', + 'capwap_controller': 'capwap-ac-v6', } kea_ctrl_socket = '/run/kea/dhcp{inet}-ctrl-socket' diff --git a/python/vyos/proto/vyconf_client.py b/python/vyos/proto/vyconf_client.py index f34549309..b385f0951 100644 --- a/python/vyos/proto/vyconf_client.py +++ b/python/vyos/proto/vyconf_client.py @@ -52,7 +52,9 @@ def request_to_msg(req: vyconf_proto.RequestEnvelope) -> vyconf_pb2.RequestEnvel def msg_to_response(msg: vyconf_pb2.Response) -> vyconf_proto.Response: # pylint: disable=no-member - d = MessageToDict(msg, preserving_proto_field_name=True) + d = MessageToDict( + msg, preserving_proto_field_name=True, use_integers_for_enums=True + ) response = vyconf_proto.Response(**d) return response diff --git a/python/vyos/vyconf_session.py b/python/vyos/vyconf_session.py new file mode 100644 index 000000000..506095625 --- /dev/null +++ b/python/vyos/vyconf_session.py @@ -0,0 +1,123 @@ +# Copyright 2025 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. +# +# + +import tempfile +import shutil +from functools import wraps +from typing import Type + +from vyos.proto import vyconf_client +from vyos.migrate import ConfigMigrate +from vyos.migrate import ConfigMigrateError +from vyos.component_version import append_system_version + + +def output(o): + out = '' + for res in (o.output, o.error, o.warning): + if res is not None: + out = out + res + return out + + +class VyconfSession: + def __init__(self, token: str = None, on_error: Type[Exception] = None): + if token is None: + out = vyconf_client.send_request('setup_session') + self.__token = out.output + else: + self.__token = token + + self.on_error = on_error + + @staticmethod + def raise_exception(f): + @wraps(f) + def wrapped(self, *args, **kwargs): + if self.on_error is None: + return f(self, *args, **kwargs) + o, e = f(self, *args, **kwargs) + if e: + raise self.on_error(o) + return o, e + + return wrapped + + @raise_exception + def set(self, path: list[str]) -> tuple[str, int]: + out = vyconf_client.send_request('set', token=self.__token, path=path) + return output(out), out.status + + @raise_exception + def delete(self, path: list[str]) -> tuple[str, int]: + out = vyconf_client.send_request('delete', token=self.__token, path=path) + return output(out), out.status + + @raise_exception + def commit(self) -> tuple[str, int]: + out = vyconf_client.send_request('commit', token=self.__token) + return output(out), out.status + + @raise_exception + def discard(self) -> tuple[str, int]: + out = vyconf_client.send_request('discard', token=self.__token) + return output(out), out.status + + def session_changed(self) -> bool: + out = vyconf_client.send_request('session_changed', token=self.__token) + return not bool(out.status) + + @raise_exception + def load_config(self, file: str, migrate: bool = False) -> tuple[str, int]: + # pylint: disable=consider-using-with + if migrate: + tmp = tempfile.NamedTemporaryFile() + shutil.copy2(file, tmp.name) + config_migrate = ConfigMigrate(tmp.name) + try: + config_migrate.run() + except ConfigMigrateError as e: + tmp.close() + return repr(e), 1 + file = tmp.name + else: + tmp = '' + + out = vyconf_client.send_request('load', token=self.__token, location=file) + if tmp: + tmp.close() + + return output(out), out.status + + @raise_exception + def save_config(self, file: str, append_version: bool = False) -> tuple[str, int]: + out = vyconf_client.send_request('save', token=self.__token, location=file) + if append_version: + append_system_version(file) + return output(out), out.status + + @raise_exception + def show_config(self, path: list[str] = None) -> tuple[str, int]: + if path is None: + path = [] + out = vyconf_client.send_request('show_config', token=self.__token, path=path) + return output(out), out.status + + def __del__(self): + out = vyconf_client.send_request('teardown', token=self.__token) + if out.status: + print(f'Could not tear down session {self.__token}: {output(out)}') diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index 05900a4ba..694c24e4d 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2023 VyOS maintainers and contributors +# Copyright (C) 2020-2025 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 @@ -25,7 +25,6 @@ from vyos.utils.network import interface_exists from vyos.utils.network import get_vxlan_vlan_tunnels from vyos.utils.network import get_vxlan_vni_filter from vyos.template import is_ipv6 -from vyos import ConfigError from base_interfaces_test import BasicInterfaceTest def convert_to_list(ranges_to_convert): diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index a8f08dc6a..935be6227 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -218,6 +218,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): wpad = 'http://wpad.vyos.io/foo/bar' server_identifier = bootfile_server ipv6_only_preferred = '300' + capwap_access_controller = '192.168.2.125' pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] self.cli_set(pool + ['subnet-id', '1']) @@ -237,6 +238,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.cli_set(pool + ['option', 'bootfile-server', bootfile_server]) self.cli_set(pool + ['option', 'wpad-url', wpad]) self.cli_set(pool + ['option', 'server-identifier', server_identifier]) + self.cli_set( + pool + ['option', 'capwap-controller', capwap_access_controller] + ) static_route = '10.0.0.0/24' static_route_nexthop = '192.0.2.1' @@ -325,6 +329,11 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.verify_config_object( obj, ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'capwap-ac-v4', 'data': capwap_access_controller}, + ) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], {'name': 'tftp-server-name', 'data': tftp_server}, ) self.verify_config_object( diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py index 6ecf6c1cf..6535ca72d 100755 --- a/smoketest/scripts/cli/test_service_dhcpv6-server.py +++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py @@ -108,6 +108,7 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): self.cli_set(pool + ['lease-time', 'default', lease_time]) self.cli_set(pool + ['lease-time', 'maximum', max_lease_time]) self.cli_set(pool + ['lease-time', 'minimum', min_lease_time]) + self.cli_set(pool + ['option', 'capwap-controller', dns_1]) self.cli_set(pool + ['option', 'name-server', dns_1]) self.cli_set(pool + ['option', 'name-server', dns_2]) self.cli_set(pool + ['option', 'name-server', dns_2]) @@ -157,6 +158,10 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): self.verify_config_object( obj, ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'capwap-ac-v6', 'data': dns_1}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], {'name': 'dns-servers', 'data': f'{dns_1}, {dns_2}'}) self.verify_config_object( obj, diff --git a/smoketest/scripts/cli/test_service_ids_ddos-protection.py b/smoketest/scripts/cli/test_service_ids_ddos-protection.py deleted file mode 100755 index 91b056eea..000000000 --- a/smoketest/scripts/cli/test_service_ids_ddos-protection.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file - -PROCESS_NAME = 'fastnetmon' -FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf' -NETWORKS_CONF = '/run/fastnetmon/networks_list' -EXCLUDED_NETWORKS_CONF = '/run/fastnetmon/excluded_networks_list' -base_path = ['service', 'ids', 'ddos-protection'] - -class TestServiceIDS(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - super(TestServiceIDS, cls).setUpClass() - - # ensure we can also run this test on a live system - so lets clean - # out the current configuration :) - cls.cli_delete(cls, base_path) - - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - # delete test config - self.cli_delete(base_path) - self.cli_commit() - - self.assertFalse(os.path.exists(FASTNETMON_CONF)) - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_fastnetmon(self): - networks = ['10.0.0.0/24', '10.5.5.0/24', '2001:db8:10::/64', '2001:db8:20::/64'] - excluded_networks = ['10.0.0.1/32', '2001:db8:10::1/128'] - interfaces = ['eth0', 'eth1'] - fps = '3500' - mbps = '300' - pps = '60000' - - self.cli_set(base_path + ['mode', 'mirror']) - # Required network! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in networks: - self.cli_set(base_path + ['network', tmp]) - - # optional excluded-network! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in excluded_networks: - self.cli_set(base_path + ['excluded-network', tmp]) - - # Required interface(s)! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in interfaces: - self.cli_set(base_path + ['listen-interface', tmp]) - - self.cli_set(base_path + ['direction', 'in']) - self.cli_set(base_path + ['threshold', 'general', 'fps', fps]) - self.cli_set(base_path + ['threshold', 'general', 'pps', pps]) - self.cli_set(base_path + ['threshold', 'general', 'mbps', mbps]) - - # commit changes - self.cli_commit() - - # Check configured port - config = read_file(FASTNETMON_CONF) - self.assertIn(f'mirror_afpacket = on', config) - self.assertIn(f'process_incoming_traffic = on', config) - self.assertIn(f'process_outgoing_traffic = off', config) - self.assertIn(f'ban_for_flows = on', config) - self.assertIn(f'threshold_flows = {fps}', config) - self.assertIn(f'ban_for_bandwidth = on', config) - self.assertIn(f'threshold_mbps = {mbps}', config) - self.assertIn(f'ban_for_pps = on', config) - self.assertIn(f'threshold_pps = {pps}', config) - # default - self.assertIn(f'enable_ban = on', config) - self.assertIn(f'enable_ban_ipv6 = on', config) - self.assertIn(f'ban_time = 1900', config) - - tmp = ','.join(interfaces) - self.assertIn(f'interfaces = {tmp}', config) - - - network_config = read_file(NETWORKS_CONF) - for tmp in networks: - self.assertIn(f'{tmp}', network_config) - - excluded_network_config = read_file(EXCLUDED_NETWORKS_CONF) - for tmp in excluded_networks: - self.assertIn(f'{tmp}', excluded_network_config) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index ed72f378e..71dec68d8 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -25,9 +25,7 @@ import shutil from base_vyostest_shim import VyOSUnitTestSHIM -from contextlib import redirect_stdout from gzip import GzipFile -from io import StringIO, TextIOWrapper from subprocess import Popen from subprocess import PIPE from pwd import getpwall diff --git a/src/conf_mode/service_ids_ddos-protection.py b/src/conf_mode/service_ids_ddos-protection.py deleted file mode 100755 index 276a71fcb..000000000 --- a/src/conf_mode/service_ids_ddos-protection.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2023 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os - -from sys import exit - -from vyos.config import Config -from vyos.template import render -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = r'/run/fastnetmon/fastnetmon.conf' -networks_list = r'/run/fastnetmon/networks_list' -excluded_networks_list = r'/run/fastnetmon/excluded_networks_list' -attack_dir = '/var/log/fastnetmon_attacks' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'ids', 'ddos-protection'] - if not conf.exists(base): - return None - - fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - return fastnetmon - -def verify(fastnetmon): - if not fastnetmon: - return None - - if 'mode' not in fastnetmon: - raise ConfigError('Specify operating mode!') - - if fastnetmon.get('mode') == 'mirror' and 'listen_interface' not in fastnetmon: - raise ConfigError("Incorrect settings for 'mode mirror': must specify interface(s) for traffic mirroring") - - if fastnetmon.get('mode') == 'sflow' and 'listen_address' not in fastnetmon.get('sflow', {}): - raise ConfigError("Incorrect settings for 'mode sflow': must specify sFlow 'listen-address'") - - if 'alert_script' in fastnetmon: - if os.path.isfile(fastnetmon['alert_script']): - # Check script permissions - if not os.access(fastnetmon['alert_script'], os.X_OK): - raise ConfigError('Script "{alert_script}" is not executable!'.format(fastnetmon['alert_script'])) - else: - raise ConfigError('File "{alert_script}" does not exists!'.format(fastnetmon)) - -def generate(fastnetmon): - if not fastnetmon: - for file in [config_file, networks_list]: - if os.path.isfile(file): - os.unlink(file) - - return None - - # Create dir for log attack details - if not os.path.exists(attack_dir): - os.mkdir(attack_dir) - - render(config_file, 'ids/fastnetmon.j2', fastnetmon) - render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon) - render(excluded_networks_list, 'ids/fastnetmon_excluded_networks_list.j2', fastnetmon) - return None - -def apply(fastnetmon): - systemd_service = 'fastnetmon.service' - if not fastnetmon: - # Stop fastnetmon service if removed - call(f'systemctl stop {systemd_service}') - else: - call(f'systemctl reload-or-restart {systemd_service}') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/system_login.py b/src/conf_mode/system_login.py index 3fed6d273..4febb6494 100755 --- a/src/conf_mode/system_login.py +++ b/src/conf_mode/system_login.py @@ -15,7 +15,6 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -import warnings from passlib.hosts import linux_context from psutil import users @@ -30,12 +29,9 @@ from vyos.config import Config from vyos.configverify import verify_vrf from vyos.template import render from vyos.template import is_ipv4 -from vyos.utils.auth import ( - DEFAULT_PASSWORD, - EPasswdStrength, - evaluate_strength, - get_current_user -) +from vyos.utils.auth import EPasswdStrength +from vyos.utils.auth import evaluate_strength +from vyos.utils.auth import get_current_user from vyos.utils.configfs import delete_cli_node from vyos.utils.configfs import add_cli_node from vyos.utils.dict import dict_search diff --git a/src/etc/netplug/vyos-netplug-dhcp-client b/src/etc/netplug/vyos-netplug-dhcp-client index 7fe6cda75..a230fe900 100755 --- a/src/etc/netplug/vyos-netplug-dhcp-client +++ b/src/etc/netplug/vyos-netplug-dhcp-client @@ -20,10 +20,10 @@ import sys from time import sleep from vyos.config import Config -from vyos.configdict import get_interface_dict -from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.utils.boot import boot_configuration_complete +from vyos.utils.process import cmd +from vyos.utils.process import is_systemd_service_active from vyos.utils.commit import commit_in_progress from vyos import airbag @@ -38,21 +38,34 @@ if not boot_configuration_complete(): sys.exit(1) interface = sys.argv[1] -# helper scripts should only work on physical interfaces not on individual -# sub-interfaces. Moving e.g. a VLAN interface in/out a VRF will also trigger -# this script which should be prohibited - bail out early -if '.' in interface: - sys.exit(0) while commit_in_progress(): - sleep(1) + sleep(0.250) in_out = sys.argv[2] config = Config() interface_path = ['interfaces'] + Section.get_config_path(interface).split() -_, interface_config = get_interface_dict( - config, interface_path[:-1], ifname=interface, with_pki=True -) -if 'deleted' not in interface_config: - Interface(interface).update(interface_config) + +systemdV4_service = f'dhclient@{interface}.service' +systemdV6_service = f'dhcp6c@{interface}.service' +if in_out == 'out': + # Interface moved state to down + if is_systemd_service_active(systemdV4_service): + cmd(f'systemctl stop {systemdV4_service}') + if is_systemd_service_active(systemdV6_service): + cmd(f'systemctl stop {systemdV6_service}') +elif in_out == 'in': + if config.exists_effective(interface_path + ['address']): + tmp = config.return_effective_values(interface_path + ['address']) + # Always (re-)start the DHCP(v6) client service. If the DHCP(v6) client + # is already running - which could happen if the interface is re- + # configured in operational down state, it will have a backoff + # time increasing while not receiving a DHCP(v6) reply. + # + # To make the interface instantly available, and as for a DHCP(v6) lease + # we will re-start the service and thus cancel the backoff time. + if 'dhcp' in tmp: + cmd(f'systemctl restart {systemdV4_service}') + if 'dhcpv6' in tmp: + cmd(f'systemctl restart {systemdV6_service}') diff --git a/src/etc/systemd/system/fastnetmon.service.d/override.conf b/src/etc/systemd/system/fastnetmon.service.d/override.conf deleted file mode 100644 index 841666070..000000000 --- a/src/etc/systemd/system/fastnetmon.service.d/override.conf +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -RequiresMountsFor=/run -ConditionPathExists=/run/fastnetmon/fastnetmon.conf -After= -After=vyos-router.service - -[Service] -Type=simple -WorkingDirectory=/run/fastnetmon -PIDFile=/run/fastnetmon.pid -ExecStart= -ExecStart=/usr/sbin/fastnetmon --configuration_file /run/fastnetmon/fastnetmon.conf diff --git a/src/init/vyos-router b/src/init/vyos-router index ab3cc42cb..081adf214 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -417,6 +417,7 @@ gen_duid () start () { + echo -e "Initializing VyOS router\033[0m" # reset and clean config files security_reset || log_failure_msg "security reset failed" @@ -517,7 +518,6 @@ start () cleanup_post_commit_hooks - log_daemon_msg "Starting VyOS router" disabled migrate || migrate_bootfile restore_if_missing_preconfig_script @@ -557,6 +557,9 @@ start () if [[ ! -z "$tmp" ]]; then vtysh -c "rpki start" fi + + # Start netplug daemon + systemctl start netplug.service } stop() @@ -574,8 +577,8 @@ stop() umount ${vyatta_configdir} log_action_end_msg $? + systemctl stop netplug.service systemctl stop vyconfd.service - systemctl stop frr.service unmount_encrypted_config diff --git a/src/migration-scripts/ids/1-to-2 b/src/migration-scripts/ids/1-to-2 new file mode 100644 index 000000000..4c0333c88 --- /dev/null +++ b/src/migration-scripts/ids/1-to-2 @@ -0,0 +1,30 @@ +# Copyright 2025 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. + +# T: Migrate threshold and add new threshold types + +from vyos.configtree import ConfigTree + +# The old 'service ids' path was only used for FastNetMon +# Suricata is in 'service suricata', +# so this isn't an overreach +base = ['service', 'ids'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + else: + config.delete(base) diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py index 9c17d0229..179913f15 100755 --- a/src/op_mode/image_installer.py +++ b/src/op_mode/image_installer.py @@ -74,6 +74,7 @@ MSG_INPUT_CONFIG_FOUND: str = 'An active configuration was found. Would you like MSG_INPUT_CONFIG_CHOICE: str = 'The following config files are available for boot:' MSG_INPUT_CONFIG_CHOOSE: str = 'Which file would you like as boot config?' MSG_INPUT_IMAGE_NAME: str = 'What would you like to name this image?' +MSG_INPUT_IMAGE_NAME_TAKEN: str = 'There is already an installed image by that name; please choose again' MSG_INPUT_IMAGE_DEFAULT: str = 'Would you like to set the new image as the default one for boot?' MSG_INPUT_PASSWORD: str = 'Please enter a password for the "vyos" user:' MSG_INPUT_PASSWORD_CONFIRM: str = 'Please confirm password for the "vyos" user:' @@ -984,8 +985,12 @@ def add_image(image_path: str, vrf: str = None, username: str = '', f'Adding image would downgrade image tools to v.{cfg_ver}; disallowed') if not no_prompt: + versions = grub.version_list() while True: image_name: str = ask_input(MSG_INPUT_IMAGE_NAME, version_name) + if image_name in versions: + print(MSG_INPUT_IMAGE_NAME_TAKEN) + continue if image.validate_name(image_name): break print(MSG_WARN_IMAGE_NAME_WRONG) diff --git a/src/services/vyos-conntrack-logger b/src/services/vyos-conntrack-logger index 9c31b465f..ec0e1f717 100755 --- a/src/services/vyos-conntrack-logger +++ b/src/services/vyos-conntrack-logger @@ -15,10 +15,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import argparse -import grp import logging import multiprocessing -import os import queue import signal import socket diff --git a/src/services/vyos-domain-resolver b/src/services/vyos-domain-resolver index aba5ba9db..4419fc4a7 100755 --- a/src/services/vyos-domain-resolver +++ b/src/services/vyos-domain-resolver @@ -92,12 +92,14 @@ def resolve(domains, ipv6=False): for domain in domains: resolved = fqdn_resolve(domain, ipv6=ipv6) + cache_key = f'{domain}_ipv6' if ipv6 else domain + if resolved and cache: - domain_state[domain] = resolved + domain_state[cache_key] = resolved elif not resolved: - if domain not in domain_state: + if cache_key not in domain_state: continue - resolved = domain_state[domain] + resolved = domain_state[cache_key] ip_list = ip_list | resolved return ip_list diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 1ba90471e..44f03586c 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -233,10 +233,7 @@ # } import os -import sys -import time import json -import signal import traceback import re import logging @@ -245,7 +242,6 @@ import zmq from voluptuous import Schema, MultipleInvalid, Required, Any from collections import OrderedDict from vyos.utils.file import makedir -from vyos.utils.permission import chown from vyos.utils.permission import chmod_755 from vyos.utils.process import popen from vyos.utils.process import process_named_running diff --git a/src/systemd/netplug.service b/src/systemd/netplug.service new file mode 100644 index 000000000..928c553e8 --- /dev/null +++ b/src/systemd/netplug.service @@ -0,0 +1,9 @@ +[Unit] +Description=Network cable hotplug management daemon +Documentation=man:netplugd(8) +After=vyos-router.service + +[Service] +Type=forking +PIDFile=/run/netplugd.pid +ExecStart=/sbin/netplugd -c /etc/netplug/netplugd.conf -p /run/netplugd.pid diff --git a/src/systemd/vyos.target b/src/systemd/vyos.target index 47c91c1cc..c5d04891d 100644 --- a/src/systemd/vyos.target +++ b/src/systemd/vyos.target @@ -1,3 +1,3 @@ [Unit] Description=VyOS target -After=multi-user.target +After=multi-user.target vyos-grub-update.service |