diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/configdict.py | 92 | ||||
| -rwxr-xr-x | python/vyos/ifconfig/interface.py | 26 | ||||
| -rw-r--r-- | python/vyos/ifconfig/vxlan.py | 19 | ||||
| -rw-r--r-- | python/vyos/util.py | 8 | 
4 files changed, 121 insertions, 24 deletions
| diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index e7f515ea9..f2ec93520 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -319,34 +319,42 @@ def is_source_interface(conf, interface, intftype=None):  def get_dhcp_interfaces(conf, vrf=None):      """ Common helper functions to retrieve all interfaces from current CLI      sessions that have DHCP configured. """ -    dhcp_interfaces = [] +    dhcp_interfaces = {}      dict = conf.get_config_dict(['interfaces'], get_first_key=True)      if not dict:          return dhcp_interfaces      def check_dhcp(config, ifname): -        out = [] +        tmp = {}          if 'address' in config and 'dhcp' in config['address']: +            options = {} +            if 'dhcp_options' in config and 'default_route_distance' in config['dhcp_options']: +                options.update({'distance' : config['dhcp_options']['default_route_distance']})              if 'vrf' in config: -                if vrf is config['vrf']: out.append(ifname) -            else: out.append(ifname) -        return out +                if vrf is config['vrf']: tmp.update({ifname : options}) +            else: tmp.update({ifname : options}) +        return tmp      for section, interface in dict.items(): -        for ifname, ifconfig in interface.items(): +        for ifname in interface: +            # we already have a dict representation of the config from get_config_dict(), +            # but with the extended information from get_interface_dict() we also +            # get the DHCP client default-route-distance default option if not specified. +            ifconfig = get_interface_dict(conf, ['interfaces', section], ifname) +              tmp = check_dhcp(ifconfig, ifname) -            dhcp_interfaces.extend(tmp) +            dhcp_interfaces.update(tmp)              # check per VLAN interfaces              for vif, vif_config in ifconfig.get('vif', {}).items():                  tmp = check_dhcp(vif_config, f'{ifname}.{vif}') -                dhcp_interfaces.extend(tmp) +                dhcp_interfaces.update(tmp)              # check QinQ VLAN interfaces              for vif_s, vif_s_config in ifconfig.get('vif-s', {}).items():                  tmp = check_dhcp(vif_s_config, f'{ifname}.{vif_s}') -                dhcp_interfaces.extend(tmp) +                dhcp_interfaces.update(tmp)                  for vif_c, vif_c_config in vif_s_config.get('vif-c', {}).items():                      tmp = check_dhcp(vif_c_config, f'{ifname}.{vif_s}.{vif_c}') -                    dhcp_interfaces.extend(tmp) +                    dhcp_interfaces.update(tmp)      return dhcp_interfaces @@ -405,6 +413,12 @@ def get_interface_dict(config, base, ifname=''):      if 'deleted' not in dict:          dict = dict_merge(default_values, dict) +        # If interface does not request an IPv4 DHCP address there is no need +        # to keep the dhcp-options key +        if 'address' not in dict or 'dhcp' not in dict['address']: +            if 'dhcp_options' in dict: +                del dict['dhcp_options'] +      # XXX: T2665: blend in proper DHCPv6-PD default values      dict = T2665_set_dhcpv6pd_defaults(dict) @@ -423,6 +437,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. @@ -466,10 +489,25 @@ def get_interface_dict(config, base, ifname=''):              # XXX: T2665: blend in proper DHCPv6-PD default values              dict['vif'][vif] = T2665_set_dhcpv6pd_defaults(dict['vif'][vif]) +            # If interface does not request an IPv4 DHCP address there is no need +            # to keep the dhcp-options key +            if 'address' not in dict['vif'][vif] or 'dhcp' not in dict['vif'][vif]['address']: +                if 'dhcp_options' in dict['vif'][vif]: +                    del dict['vif'][vif]['dhcp_options'] +          # Check if we are a member of a bridge device          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 @@ -491,10 +529,26 @@ def get_interface_dict(config, base, ifname=''):              # XXX: T2665: blend in proper DHCPv6-PD default values              dict['vif_s'][vif_s] = T2665_set_dhcpv6pd_defaults(dict['vif_s'][vif_s]) +            # If interface does not request an IPv4 DHCP address there is no need +            # to keep the dhcp-options key +            if 'address' not in dict['vif_s'][vif_s] or 'dhcp' not in \ +                dict['vif_s'][vif_s]['address']: +                if 'dhcp_options' in dict['vif_s'][vif_s]: +                    del dict['vif_s'][vif_s]['dhcp_options'] +          # Check if we are a member of a bridge device          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']) @@ -516,11 +570,28 @@ def get_interface_dict(config, base, ifname=''):                  dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_set_dhcpv6pd_defaults(                      dict['vif_s'][vif_s]['vif_c'][vif_c]) +                # If interface does not request an IPv4 DHCP address there is no need +                # to keep the dhcp-options key +                if 'address' not in dict['vif_s'][vif_s]['vif_c'][vif_c] or 'dhcp' \ +                    not in dict['vif_s'][vif_s]['vif_c'][vif_c]['address']: +                    if 'dhcp_options' in dict['vif_s'][vif_s]['vif_c'][vif_c]: +                        del dict['vif_s'][vif_s]['vif_c'][vif_c]['dhcp_options'] +              # Check if we are a member of a bridge device              bridge = is_member(config, f'{ifname}.{vif_s}.{vif_c}', 'bridge')              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 @@ -545,7 +616,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 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): diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 0c5282db4..516a19f24 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 @@ -68,6 +68,16 @@ class VXLANIf(Interface):              'vni'                        : 'id',          } +        # 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} dstport {port}'          for vyos_key, iproute2_key in mapping.items():              # dict_search will return an empty dict "{}" for valueless nodes like @@ -82,3 +92,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/util.py b/python/vyos/util.py index 1767ff9d3..4526375df 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -774,6 +774,14 @@ def dict_search_recursive(dict_object, key):              for x in dict_search_recursive(j, key):                  yield x +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. | 
