diff options
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/configdict.py | 44 | ||||
-rw-r--r-- | python/vyos/configverify.py | 7 | ||||
-rw-r--r-- | python/vyos/ethtool.py | 9 | ||||
-rw-r--r-- | python/vyos/ifconfig/bridge.py | 12 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 26 | ||||
-rw-r--r-- | python/vyos/ifconfig/vxlan.py | 19 | ||||
-rw-r--r-- | python/vyos/ifconfig/wireless.py | 6 | ||||
-rw-r--r-- | python/vyos/util.py | 17 |
8 files changed, 112 insertions, 28 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 486260152..1f245f3d2 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -117,6 +117,12 @@ def leaf_node_changed(conf, path): D.set_level(conf.get_level()) (new, old) = D.get_value_diff(path) if new != old: + if isinstance(old, dict): + # valueLess nodes return {} if node is deleted + return True + if old is None and isinstance(new, dict): + # valueLess nodes return {} if node was added + return True if old is None: return [] if isinstance(old, str): @@ -387,6 +393,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 +446,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 +478,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 +510,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 +544,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/configverify.py b/python/vyos/configverify.py index 3aece499e..6566e8863 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -196,9 +196,10 @@ def verify_bridge_delete(config): when interface also is part of a bridge. """ 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)) + interface = config['ifname'] + for bridge in config['is_bridge_member']: + raise ConfigError(f'Interface "{interface}" cannot be deleted as it ' + f'is a member of bridge "{bridge}"!') def verify_interface_exists(ifname): """ diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index e45b0f041..672ee2cc9 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -18,6 +18,9 @@ import re from vyos.util import popen +# These drivers do not support using ethtool to change the speed, duplex, or flow control settings +_drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront', 'iavf', 'ice', 'i40e'] + class Ethtool: """ Class is used to retrive and cache information about an ethernet adapter @@ -41,7 +44,7 @@ class Ethtool: # '100' : {'full': '', 'half': ''}, # '1000': {'full': ''} # } - _speed_duplex = { } + _speed_duplex = {'auto': {'auto': ''}} _ring_buffers = { } _ring_buffers_max = { } _driver_name = None @@ -188,7 +191,7 @@ class Ethtool: if duplex not in ['auto', 'full', 'half']: raise ValueError(f'Value "{duplex}" for duplex is invalid!') - if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']: + if self.get_driver_name() in _drivers_without_speed_duplex_flow: return False if speed in self._speed_duplex: @@ -198,7 +201,7 @@ class Ethtool: def check_flow_control(self): """ Check if the NIC supports flow-control """ - if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']: + if self.get_driver_name() in _drivers_without_speed_duplex_flow: return False return self._flow_control diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 27073b266..ffd9c590f 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -298,7 +298,6 @@ class BridgeIf(Interface): tmp = dict_search('member.interface', config) if tmp: - for interface, interface_config in tmp.items(): # if interface does yet not exist bail out early and # add it later @@ -316,10 +315,13 @@ class BridgeIf(Interface): # enslave interface port to bridge self.add_port(interface) - # always set private-vlan/port isolation - tmp = dict_search('isolated', interface_config) - value = 'on' if (tmp != None) else 'off' - lower.set_port_isolation(value) + if not interface.startswith('wlan'): + # always set private-vlan/port isolation - this can not be + # done when lower link is a wifi link, as it will trigger: + # RTNETLINK answers: Operation not supported + tmp = dict_search('isolated', interface_config) + value = 'on' if (tmp != None) else 'off' + lower.set_port_isolation(value) # set bridge port path cost if 'cost' in interface_config: 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): diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index d73fb47b8..ec5fd167d 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2022 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 @@ -65,6 +65,16 @@ class VXLANIf(Interface): 'parameters.nolearning' : 'nolearning', } + # IPv6 flowlabels can only be used on IPv6 tunnels, thus we need to + # ensure that at least the first remote IP address is passed to the + # tunnel creation command. Subsequent tunnel remote addresses can later + # be added to the FDB + remote_list = None + if 'remote' in self.config: + # skip first element as this is already configured as remote + remote_list = self.config['remote'][1:] + self.config['remote'] = self.config['remote'][0] + cmd = 'ip link add {ifname} type {type} id {vni} dstport {port}' for vyos_key, iproute2_key in mapping.items(): # dict_search will return an empty dict "{}" for valueless nodes like @@ -79,3 +89,10 @@ class VXLANIf(Interface): self._cmd(cmd.format(**self.config)) # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') + + # VXLAN tunnel is always recreated on any change - see interfaces-vxlan.py + if remote_list: + for remote in remote_list: + cmd = f'bridge fdb append to 00:00:00:00:00:00 dst {remote} ' \ + 'port {port} dev {ifname}' + self._cmd(cmd.format(**self.config)) diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py index d897715db..11120a3d3 100644 --- a/python/vyos/ifconfig/wireless.py +++ b/python/vyos/ifconfig/wireless.py @@ -54,10 +54,10 @@ class WiFiIf(Interface): on any interface. """ # We can not call add_to_bridge() until wpa_supplicant is running, thus - # we will remove the key from the config dict and react to this specal - # case in thie derived class. + # we will remove the key from the config dict and react to this special + # case in this derived class. # re-add ourselves to any bridge we might have fallen out of - bridge_member = '' + bridge_member = None if 'is_bridge_member' in config: bridge_member = config['is_bridge_member'] del config['is_bridge_member'] diff --git a/python/vyos/util.py b/python/vyos/util.py index f2f302559..76ba0fb60 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -694,6 +694,14 @@ def dict_search(path, dict_object): c = c.get(p, {}) return c.get(parts[-1], None) +def get_bridge_fdb(interface): + """ Returns the forwarding database entries for a given interface """ + if not os.path.exists(f'/sys/class/net/{interface}'): + return None + from json import loads + tmp = loads(cmd(f'bridge -j fdb show dev {interface}')) + return tmp + def get_interface_config(interface): """ Returns the used encapsulation protocol for given interface. If interface does not exist, None is returned. @@ -805,3 +813,12 @@ def boot_configuration_complete() -> bool: if os.path.isfile(config_status): return True return False + +def sysctl(name, value): + """ Change value via sysctl() - return True if changed, False otherwise """ + tmp = cmd(f'sysctl {name}') + # last list index contains the actual value - only write if value differs + if tmp.split()[-1] != str(value): + call(f'sysctl -wq {name}={value}') + return True + return False |