From 3a1a7c40a13ee9f5561823a79876d88d3f5bf053 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 20 Feb 2022 10:36:33 +0100 Subject: interface: T4203: prevent DHCP client restart if not necessary In the past whenever a change happened to any interface and it was configured as a DHCP client, VyOS always had a breif outage as DHCP released the old lease and re-aquired a new one - bad! This commit changes the behavior that DHCP client is only restarted if any one of the possible options one can set for DHCP client under the "dhcp-options" node is altered. --- python/vyos/ifconfig/interface.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'python/vyos/ifconfig/interface.py') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 91c7f0c33..cf1887bf6 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1228,12 +1228,11 @@ class Interface(Control): options_file = f'{config_base}_{ifname}.options' pid_file = f'{config_base}_{ifname}.pid' lease_file = f'{config_base}_{ifname}.leases' - - # Stop client with old config files to get the right IF_METRIC. systemd_service = f'dhclient@{ifname}.service' - if is_systemd_service_active(systemd_service): - self._cmd(f'systemctl stop {systemd_service}') + # '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. if enable and 'disable' not in self._config: if dict_search('dhcp_options.host_name', self._config) == None: # read configured system hostname. @@ -1244,16 +1243,19 @@ class Interface(Control): tmp = {'dhcp_options' : { 'host_name' : hostname}} self._config = dict_merge(tmp, self._config) - render(options_file, 'dhcp-client/daemon-options.tmpl', - self._config) - render(config_file, 'dhcp-client/ipv4.tmpl', - self._config) + render(options_file, 'dhcp-client/daemon-options.tmpl', self._config) + render(config_file, 'dhcp-client/ipv4.tmpl', self._config) - # '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 {systemd_service}') + # When the DHCP client is restarted a brief outage will occur, as + # the old lease is released a new one is acquired (T4203). We will + # only restart DHCP client if it's option changed, or if it's not + # running, but it should be running (e.g. on system startup) + if 'dhcp_options_old' in self._config or not is_systemd_service_active(systemd_service): + return self._cmd(f'systemctl restart {systemd_service}') + return None else: + if is_systemd_service_active(systemd_service): + self._cmd(f'systemctl stop {systemd_service}') # cleanup old config files for file in [config_file, options_file, pid_file, lease_file]: if os.path.isfile(file): -- cgit v1.2.3 From 0e23fc10581cde7b31101990566f59eded826233 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 3 Mar 2022 18:15:32 +0100 Subject: interface: T4203: switch to new recursive node_changed() implementation --- python/vyos/configdict.py | 42 ++++++++++----------------------------- python/vyos/ifconfig/interface.py | 4 ++-- 2 files changed, 13 insertions(+), 33 deletions(-) (limited to 'python/vyos/ifconfig/interface.py') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index aea22c0c9..b8f428c1c 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -136,7 +136,7 @@ def leaf_node_changed(conf, path): return None -def node_changed(conf, path, key_mangling=None): +def node_changed(conf, path, key_mangling=None, recursive=False): """ 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 @@ -146,7 +146,7 @@ def node_changed(conf, path, key_mangling=None): 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() + keys = D.get_child_nodes_diff(path, expand_nodes=Diff.DELETE, recursive=recursive)['delete'].keys() return list(keys) def get_removed_vlans(conf, dict): @@ -444,13 +444,8 @@ def get_interface_dict(config, base, ifname=''): if bond: dict.update({'is_bond_member' : bond}) # Check if any DHCP options changed which require a client restat - for leaf_node in ['client-id', 'default-route-distance', 'host-name', - 'no-default-route', 'vendor-class-id']: - dhcp = leaf_node_changed(config, ['dhcp-options', leaf_node]) - if dhcp: - dict.update({'dhcp_options_old' : dhcp}) - # one option is suffiecient to set 'dhcp_options_old' key - break + dhcp = node_changed(config, ['dhcp-options'], recursive=True) + if dhcp: dict.update({'dhcp_options_changed' : ''}) # Some interfaces come with a source_interface which must also not be part # of any other bond or bridge interface as it is exclusivly assigned as the @@ -506,13 +501,8 @@ def get_interface_dict(config, base, ifname=''): if bridge: dict['vif'][vif].update({'is_bridge_member' : bridge}) # Check if any DHCP options changed which require a client restat - for leaf_node in ['client-id', 'default-route-distance', 'host-name', - 'no-default-route', 'vendor-class-id']: - dhcp = leaf_node_changed(config, ['vif', vif, 'dhcp-options', leaf_node]) - if dhcp: - dict['vif'][vif].update({'dhcp_options_old' : dhcp}) - # one option is suffiecient to set 'dhcp_options_old' key - break + dhcp = node_changed(config, ['vif', vif, 'dhcp-options'], recursive=True) + if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : ''}) for vif_s, vif_s_config in dict.get('vif_s', {}).items(): default_vif_s_values = defaults(base + ['vif-s']) @@ -547,13 +537,8 @@ def get_interface_dict(config, base, ifname=''): if bridge: dict['vif_s'][vif_s].update({'is_bridge_member' : bridge}) # Check if any DHCP options changed which require a client restat - for leaf_node in ['client-id', 'default-route-distance', 'host-name', - 'no-default-route', 'vendor-class-id']: - dhcp = leaf_node_changed(config, ['vif-s', vif_s, 'dhcp-options', leaf_node]) - if dhcp: - dict['vif_s'][vif_s].update({'dhcp_options_old' : dhcp}) - # one option is suffiecient to set 'dhcp_options_old' key - break + dhcp = node_changed(config, ['vif-s', vif_s, 'dhcp-options'], recursive=True) + if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : ''}) for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): default_vif_c_values = defaults(base + ['vif-s', 'vif-c']) @@ -589,14 +574,9 @@ def get_interface_dict(config, base, ifname=''): {'is_bridge_member' : bridge}) # Check if any DHCP options changed which require a client restat - for leaf_node in ['client-id', 'default-route-distance', 'host-name', - 'no-default-route', 'vendor-class-id']: - dhcp = leaf_node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, - 'dhcp-options', leaf_node]) - if dhcp: - dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_old' : dhcp}) - # one option is suffiecient to set 'dhcp_options_old' key - break + dhcp = leaf_node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, + 'dhcp-options'], recursive=True) + if dhcp: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_changed' : ''}) # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index cf1887bf6..8c5ff576e 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors +# Copyright 2019-2022 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 @@ -1250,7 +1250,7 @@ class Interface(Control): # the old lease is released a new one is acquired (T4203). We will # only restart DHCP client if it's option changed, or if it's not # running, but it should be running (e.g. on system startup) - if 'dhcp_options_old' in self._config or not is_systemd_service_active(systemd_service): + if 'dhcp_options_changed' in self._config or not is_systemd_service_active(systemd_service): return self._cmd(f'systemctl restart {systemd_service}') return None else: -- cgit v1.2.3 From f8b3d8999cbea988ce8e7d303957558308ddc1bc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Mar 2022 18:56:06 +0100 Subject: ipv6: T4319: do not configure IPv6 related settings if it's disabled --- python/vyos/ifconfig/interface.py | 93 ++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 45 deletions(-) (limited to 'python/vyos/ifconfig/interface.py') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 8c5ff576e..8ce851cdb 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -39,6 +39,7 @@ from vyos.util import read_file from vyos.util import get_interface_config from vyos.util import get_interface_namespace from vyos.util import is_systemd_service_active +from vyos.util import sysctl_read from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.validate import is_intf_addr_assigned @@ -1448,11 +1449,6 @@ class Interface(Control): value = tmp if (tmp != None) else '0' self.set_tcp_ipv4_mss(value) - # Configure MSS value for IPv6 TCP connections - tmp = dict_search('ipv6.adjust_mss', config) - value = tmp if (tmp != None) else '0' - self.set_tcp_ipv6_mss(value) - # Configure ARP cache timeout in milliseconds - has default value tmp = dict_search('ip.arp_cache_timeout', config) value = tmp if (tmp != None) else '30' @@ -1498,47 +1494,54 @@ class Interface(Control): value = tmp if (tmp != None) else '0' self.set_ipv4_source_validation(value) - # IPv6 forwarding - tmp = dict_search('ipv6.disable_forwarding', config) - value = '0' if (tmp != None) else '1' - self.set_ipv6_forwarding(value) - - # IPv6 router advertisements - tmp = 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 = 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 = dict_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')) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in (dict_search('ipv6.address.eui64_old', config) or []): - self.del_ipv6_eui64_address(addr) - - # Manage IPv6 link-local addresses - if dict_search('ipv6.address.no_default_link_local', config) != None: - self.del_ipv6_eui64_address('fe80::/64') - else: - self.add_ipv6_eui64_address('fe80::/64') + # Only change IPv6 parameters if IPv6 was not explicitly disabled + if sysctl_read('net.ipv6.conf.all.disable_ipv6') == '0': + # Configure MSS value for IPv6 TCP connections + tmp = dict_search('ipv6.adjust_mss', config) + value = tmp if (tmp != None) else '0' + self.set_tcp_ipv6_mss(value) + + # IPv6 forwarding + tmp = dict_search('ipv6.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + self.set_ipv6_forwarding(value) + + # IPv6 router advertisements + tmp = 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 = 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 = dict_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')) + + # Delete old IPv6 EUI64 addresses before changing MAC + for addr in (dict_search('ipv6.address.eui64_old', config) or []): + self.del_ipv6_eui64_address(addr) + + # Manage IPv6 link-local addresses + if dict_search('ipv6.address.no_default_link_local', config) != None: + self.del_ipv6_eui64_address('fe80::/64') + else: + self.add_ipv6_eui64_address('fe80::/64') - # Add IPv6 EUI-based addresses - tmp = dict_search('ipv6.address.eui64', config) - if tmp: - for addr in tmp: - self.add_ipv6_eui64_address(addr) + # Add IPv6 EUI-based addresses + tmp = dict_search('ipv6.address.eui64', config) + if 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: -- cgit v1.2.3 From bafb1973d906707cb571385e994a949d0d90b645 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 30 Mar 2022 19:16:52 +0200 Subject: vyos.ifconfig: make add_addr() method more reader firendly --- python/vyos/ifconfig/interface.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'python/vyos/ifconfig/interface.py') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 8ce851cdb..4fda1c0a9 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1089,8 +1089,11 @@ class Interface(Control): elif addr == 'dhcpv6': 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}"') + tmp = f'ip addr add {addr} dev {self.ifname}' + # Add broadcast address for IPv4 + if is_ipv4(addr): tmp += ' brd +' + + self._cmd(tmp) else: return False -- cgit v1.2.3