summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/configdict.py92
-rwxr-xr-xpython/vyos/ifconfig/interface.py26
-rw-r--r--python/vyos/ifconfig/vxlan.py19
-rw-r--r--python/vyos/util.py8
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.