From 4271bc19d152da493f62a1f5b1707ff8cf8e1f10 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. (cherry picked from commit 3a1a7c40a13ee9f5561823a79876d88d3f5bf053) --- python/vyos/configdict.py | 38 +++++++++++++++++++++++++++++++++++++- python/vyos/ifconfig/interface.py | 26 ++++++++++++++------------ 2 files changed, 51 insertions(+), 13 deletions(-) (limited to 'python/vyos') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 486260152..b18db1fb8 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -387,6 +387,15 @@ def get_interface_dict(config, base, ifname=''): bond = is_member(config, ifname, 'bonding') 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 + # 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 # Kernels "lower" interface to this new "virtual/upper" interface. @@ -431,6 +440,15 @@ def get_interface_dict(config, base, ifname=''): bridge = is_member(config, f'{ifname}.{vif}', 'bridge') 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 + for vif_s, vif_s_config in dict.get('vif_s', {}).items(): default_vif_s_values = defaults(base + ['vif-s']) # XXX: T2665: we only wan't the vif-s defaults - do not care about vif-c @@ -454,6 +472,15 @@ def get_interface_dict(config, base, ifname=''): bridge = is_member(config, f'{ifname}.{vif_s}', 'bridge') 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 + for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): default_vif_c_values = defaults(base + ['vif-s', 'vif-c']) @@ -477,6 +504,16 @@ def get_interface_dict(config, base, ifname=''): if bridge: dict['vif_s'][vif_s]['vif_c'][vif_c].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, '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 + # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) return dict @@ -501,7 +538,6 @@ def get_vlan_ids(interface): return vlan_ids - def get_accel_dict(config, base, chap_secrets): """ Common utility function to retrieve and mangle the Accel-PPP configuration diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 036ca1413..430940c57 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1135,12 +1135,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. @@ -1151,16 +1150,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