summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/config.py1
-rw-r--r--python/vyos/configdep.py49
-rw-r--r--python/vyos/configtree.py9
-rw-r--r--python/vyos/defaults.py10
-rw-r--r--python/vyos/firewall.py22
-rw-r--r--python/vyos/ifconfig/interface.py59
-rw-r--r--python/vyos/ifconfig/l2tpv3.py12
-rw-r--r--python/vyos/ifconfig/vxlan.py20
-rw-r--r--python/vyos/ipsec.py136
-rw-r--r--python/vyos/opmode.py12
-rw-r--r--python/vyos/template.py4
-rw-r--r--python/vyos/utils/network.py51
-rw-r--r--python/vyos/utils/process.py2
13 files changed, 284 insertions, 103 deletions
diff --git a/python/vyos/config.py b/python/vyos/config.py
index cca65f0eb..b7ee606a9 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -140,6 +140,7 @@ class Config(object):
self._level = []
self._dict_cache = {}
+ self.dependency_list = []
(self._running_config,
self._session_config) = self._config_source.get_configtree_tuple()
diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py
index 73bd9ea96..e0fe1ddac 100644
--- a/python/vyos/configdep.py
+++ b/python/vyos/configdep.py
@@ -33,10 +33,9 @@ if typing.TYPE_CHECKING:
dependency_dir = os.path.join(directories['data'],
'config-mode-dependencies')
-local_dependent_func: dict[str, list[typing.Callable]] = {}
+dependency_list: list[typing.Callable] = []
DEBUG = False
-FORCE_LOCAL = False
def debug_print(s: str):
if DEBUG:
@@ -50,7 +49,8 @@ def canon_name_of_path(path: str) -> str:
return canon_name(script)
def caller_name() -> str:
- return stack()[2].filename
+ filename = stack()[2].filename
+ return canon_name_of_path(filename)
def name_of(f: typing.Callable) -> str:
return f.__name__
@@ -107,46 +107,47 @@ def run_config_mode_script(script: str, config: 'Config'):
mod.generate(c)
mod.apply(c)
except (VyOSError, ConfigError) as e:
- raise ConfigError(repr(e))
+ raise ConfigError(str(e)) from e
def def_closure(target: str, config: 'Config',
tagnode: typing.Optional[str] = None) -> typing.Callable:
script = target + '.py'
def func_impl():
- if tagnode:
+ if tagnode is not None:
os.environ['VYOS_TAGNODE_VALUE'] = tagnode
run_config_mode_script(script, config)
+ tag_ext = f'_{tagnode}' if tagnode is not None else ''
+ func_impl.__name__ = f'{target}{tag_ext}'
return func_impl
def set_dependents(case: str, config: 'Config',
tagnode: typing.Optional[str] = None):
+ global dependency_list
+
+ dependency_list = config.dependency_list
+
d = get_dependency_dict(config)
- k = canon_name_of_path(caller_name())
- tag_ext = f'_{tagnode}' if tagnode is not None else ''
- if hasattr(config, 'dependent_func') and not FORCE_LOCAL:
- dependent_func = getattr(config, 'dependent_func')
- l = dependent_func.setdefault('vyos_configd', [])
- else:
- dependent_func = local_dependent_func
- l = dependent_func.setdefault(k, [])
+ k = caller_name()
+ l = dependency_list
+
for target in d[k][case]:
func = def_closure(target, config, tagnode)
- func.__name__ = f'{target}{tag_ext}'
append_uniq(l, func)
- debug_print(f'set_dependents: caller {k}, dependents {names_of(l)}')
-def call_dependents(dependent_func: dict = None):
- k = canon_name_of_path(caller_name())
- if dependent_func is None or FORCE_LOCAL:
- dependent_func = local_dependent_func
- l = dependent_func.get(k, [])
- else:
- l = dependent_func.get('vyos_configd', [])
- debug_print(f'call_dependents: caller {k}, dependents {names_of(l)}')
+ debug_print(f'set_dependents: caller {k}, current dependents {names_of(l)}')
+
+def call_dependents():
+ k = caller_name()
+ l = dependency_list
+ debug_print(f'call_dependents: caller {k}, remaining dependents {names_of(l)}')
while l:
f = l.pop(0)
debug_print(f'calling: {f.__name__}')
- f()
+ try:
+ f()
+ except ConfigError as e:
+ s = f'dependent {f.__name__}: {str(e)}'
+ raise ConfigError(s) from e
def called_as_dependent() -> bool:
st = stack()[1:]
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index 5775070e2..bd77ab899 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -1,5 +1,5 @@
# configtree -- a standalone VyOS config file manipulation library (Python bindings)
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# Copyright (C) 2018-2024 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 License as published by the Free Software Foundation;
@@ -290,7 +290,7 @@ class ConfigTree(object):
else:
return True
- def list_nodes(self, path):
+ def list_nodes(self, path, path_must_exist=True):
check_path(path)
path_str = " ".join(map(str, path)).encode()
@@ -298,7 +298,10 @@ class ConfigTree(object):
res = json.loads(res_json)
if res is None:
- raise ConfigTreeError("Path [{}] doesn't exist".format(path_str))
+ if path_must_exist:
+ raise ConfigTreeError("Path [{}] doesn't exist".format(path_str))
+ else:
+ return []
else:
return res
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 9ccd925ce..25ee45391 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -50,3 +50,13 @@ commit_lock = os.path.join(directories['vyos_configdir'], '.lock')
component_version_json = os.path.join(directories['data'], 'component-versions.json')
config_default = os.path.join(directories['data'], 'config.boot.default')
+
+rt_symbolic_names = {
+ # Standard routing tables for Linux & reserved IDs for VyOS
+ 'default': 253, # Confusingly, a final fallthru, not the default.
+ 'main': 254, # The actual global table used by iproute2 unless told otherwise.
+ 'local': 255, # Special kernel loopback table.
+}
+
+rt_global_vrf = rt_symbolic_names['main']
+rt_global_table = rt_symbolic_names['main']
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 664df28cc..facd498ca 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -30,6 +30,9 @@ from vyos.utils.dict import dict_search_args
from vyos.utils.dict import dict_search_recursive
from vyos.utils.process import cmd
from vyos.utils.process import run
+from vyos.utils.network import get_vrf_tableid
+from vyos.defaults import rt_global_table
+from vyos.defaults import rt_global_vrf
# Conntrack
def conntrack_required(conf):
@@ -366,10 +369,14 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
output.append(f'ip{def_suffix} dscp != {{{negated_dscp_str}}}')
if 'ipsec' in rule_conf:
- if 'match_ipsec' in rule_conf['ipsec']:
+ if 'match_ipsec_in' in rule_conf['ipsec']:
output.append('meta ipsec == 1')
- if 'match_none' in rule_conf['ipsec']:
+ if 'match_none_in' in rule_conf['ipsec']:
output.append('meta ipsec == 0')
+ if 'match_ipsec_out' in rule_conf['ipsec']:
+ output.append('rt ipsec exists')
+ if 'match_none_out' in rule_conf['ipsec']:
+ output.append('rt ipsec missing')
if 'fragment' in rule_conf:
# Checking for fragmentation after priority -400 is not possible,
@@ -469,11 +476,20 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if 'mark' in rule_conf['set']:
mark = rule_conf['set']['mark']
output.append(f'meta mark set {mark}')
+ if 'vrf' in rule_conf['set']:
+ set_table = True
+ vrf_name = rule_conf['set']['vrf']
+ if vrf_name == 'default':
+ table = rt_global_vrf
+ else:
+ # NOTE: VRF->table ID lookup depends on the VRF iface already existing.
+ table = get_vrf_tableid(vrf_name)
if 'table' in rule_conf['set']:
set_table = True
table = rule_conf['set']['table']
if table == 'main':
- table = '254'
+ table = rt_global_table
+ if set_table:
mark = 0x7FFFFFFF - int(table)
output.append(f'meta mark set {mark}')
if 'tcp_mss' in rule_conf['set']:
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 117479ade..72d3d3afe 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -37,6 +37,7 @@ from vyos.utils.network import mac2eui64
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
from vyos.utils.network import get_interface_namespace
+from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import is_netns_interface
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import run
@@ -382,6 +383,9 @@ class Interface(Control):
# can not delete ALL interfaces, see below
self.flush_addrs()
+ # remove interface from conntrack VRF interface map
+ self._del_interface_from_ct_iface_map()
+
# ---------------------------------------------------------------------
# Any class can define an eternal regex in its definition
# interface matching the regex will not be deleted
@@ -402,29 +406,20 @@ class Interface(Control):
if netns: cmd = f'ip netns exec {netns} {cmd}'
return self._cmd(cmd)
- def _set_vrf_ct_zone(self, vrf):
- """
- Add/Remove rules in nftables to associate traffic in VRF to an
- individual conntack zone
- """
- # Don't allow for netns yet
- if 'netns' in self.config:
- return None
+ def _nft_check_and_run(self, nft_command):
+ # Check if deleting is possible first to avoid raising errors
+ _, err = self._popen(f'nft --check {nft_command}')
+ if not err:
+ # Remove map element
+ self._cmd(f'nft {nft_command}')
- if vrf:
- # Get routing table ID for VRF
- vrf_table_id = get_interface_config(vrf).get('linkinfo', {}).get(
- 'info_data', {}).get('table')
- # Add map element with interface and zone ID
- if vrf_table_id:
- self._cmd(f'nft add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}')
- else:
- nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}'
- # Check if deleting is possible first to avoid raising errors
- _, err = self._popen(f'nft --check {nft_del_element}')
- if not err:
- # Remove map element
- self._cmd(f'nft {nft_del_element}')
+ def _del_interface_from_ct_iface_map(self):
+ nft_command = f'delete element inet vrf_zones ct_iface_map {{ "{self.ifname}" }}'
+ self._nft_check_and_run(nft_command)
+
+ def _add_interface_to_ct_iface_map(self, vrf_table_id: int):
+ nft_command = f'add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}'
+ self._nft_check_and_run(nft_command)
def get_min_mtu(self):
"""
@@ -597,12 +592,30 @@ class Interface(Control):
>>> Interface('eth0').set_vrf()
"""
+ # Don't allow for netns yet
+ if 'netns' in self.config:
+ return False
+
tmp = self.get_interface('vrf')
if tmp == vrf:
return False
+ # Get current VRF table ID
+ old_vrf_tableid = get_vrf_tableid(self.ifname)
self.set_interface('vrf', vrf)
- self._set_vrf_ct_zone(vrf)
+
+ if vrf:
+ # Get routing table ID number for VRF
+ vrf_table_id = get_vrf_tableid(vrf)
+ # Add map element with interface and zone ID
+ if vrf_table_id:
+ # delete old table ID from nftables if it has changed, e.g. interface moved to a different VRF
+ if old_vrf_tableid and old_vrf_tableid != int(vrf_table_id):
+ self._del_interface_from_ct_iface_map()
+ self._add_interface_to_ct_iface_map(vrf_table_id)
+ else:
+ self._del_interface_from_ct_iface_map()
+
return True
def set_arp_cache_tmo(self, tmo):
diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py
index 85a89ef8b..c1f2803ee 100644
--- a/python/vyos/ifconfig/l2tpv3.py
+++ b/python/vyos/ifconfig/l2tpv3.py
@@ -90,9 +90,17 @@ class L2TPv3If(Interface):
"""
if self.exists(self.ifname):
- # interface is always A/D down. It needs to be enabled explicitly
self.set_admin_state('down')
+ # remove all assigned IP addresses from interface - this is a bit redundant
+ # as the kernel will remove all addresses on interface deletion
+ self.flush_addrs()
+
+ # remove interface from conntrack VRF interface map, here explicitly and do not
+ # rely on the base class implementation as the interface will
+ # vanish as soon as the l2tp session is deleted
+ self._del_interface_from_ct_iface_map()
+
if {'tunnel_id', 'session_id'} <= set(self.config):
cmd = 'ip l2tp del session tunnel_id {tunnel_id}'
cmd += ' session_id {session_id}'
@@ -101,3 +109,5 @@ class L2TPv3If(Interface):
if 'tunnel_id' in self.config:
cmd = 'ip l2tp del tunnel tunnel_id {tunnel_id}'
self._cmd(cmd.format(**self.config))
+
+ # No need to call the baseclass as the interface is now already gone
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index 918aea202..1023c58d1 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -134,6 +134,19 @@ class VXLANIf(Interface):
Controls whether vlan to tunnel mapping is enabled on the port.
By default this flag is off.
"""
+ def range_to_dict(vlan_to_vni):
+ """ Converts dict of ranges to dict """
+ result_dict = {}
+ for vlan, vlan_conf in vlan_to_vni.items():
+ vni = vlan_conf['vni']
+ vlan_range, vni_range = vlan.split('-'), vni.split('-')
+ if len(vlan_range) > 1:
+ vlan_range = range(int(vlan_range[0]), int(vlan_range[1]) + 1)
+ vni_range = range(int(vni_range[0]), int(vni_range[1]) + 1)
+ dict_to_add = {str(k): {'vni': str(v)} for k, v in zip(vlan_range, vni_range)}
+ result_dict.update(dict_to_add)
+ return result_dict
+
if not isinstance(state, bool):
raise ValueError('Value out of range')
@@ -142,7 +155,7 @@ class VXLANIf(Interface):
if dict_search('parameters.vni_filter', self.config) != None:
cur_vni_filter = get_vxlan_vni_filter(self.ifname)
- for vlan, vlan_config in self.config['vlan_to_vni_removed'].items():
+ for vlan, vlan_config in range_to_dict(self.config['vlan_to_vni_removed']).items():
# If VNI filtering is enabled, remove matching VNI filter
if cur_vni_filter != None:
vni = vlan_config['vni']
@@ -159,10 +172,11 @@ class VXLANIf(Interface):
if 'vlan_to_vni' in self.config:
# Determine current OS Kernel configured VLANs
+ vlan_vni_mapping = range_to_dict(self.config['vlan_to_vni'])
os_configured_vlan_ids = get_vxlan_vlan_tunnels(self.ifname)
- add_vlan = list_diff(list(self.config['vlan_to_vni'].keys()), os_configured_vlan_ids)
+ add_vlan = list_diff(list(vlan_vni_mapping.keys()), os_configured_vlan_ids)
- for vlan, vlan_config in self.config['vlan_to_vni'].items():
+ for vlan, vlan_config in vlan_vni_mapping.items():
# VLAN mapping already exists - skip
if vlan not in add_vlan:
continue
diff --git a/python/vyos/ipsec.py b/python/vyos/ipsec.py
index 4603aab22..28f77565a 100644
--- a/python/vyos/ipsec.py
+++ b/python/vyos/ipsec.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2020-2024 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
@@ -13,31 +13,38 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-#Package to communicate with Strongswan VICI
+# Package to communicate with Strongswan VICI
+
class ViciInitiateError(Exception):
"""
- VICI can't initiate a session.
+ VICI can't initiate a session.
"""
+
pass
+
+
class ViciCommandError(Exception):
"""
- VICI can't execute a command by any reason.
+ VICI can't execute a command by any reason.
"""
+
pass
+
def get_vici_sas():
from vici import Session as vici_session
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec not initialized')
try:
sas = list(session.list_sas())
return sas
except Exception:
- raise ViciCommandError(f'Failed to get SAs')
+ raise ViciCommandError('Failed to get SAs')
+
def get_vici_connections():
from vici import Session as vici_session
@@ -45,18 +52,19 @@ def get_vici_connections():
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec not initialized')
try:
connections = list(session.list_conns())
return connections
except Exception:
- raise ViciCommandError(f'Failed to get connections')
+ raise ViciCommandError('Failed to get connections')
+
def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list:
"""
- Find sas by IKE_SA name and/or CHILD_SA name
- and return list of OrdinaryDicts with SASs info
- If tunnel is not None return value is list of OrdenaryDicts contained only
+ Find installed SAs by IKE_SA name and/or CHILD_SA name
+ and return list with SASs info.
+ If tunnel is not None return a list contained only
CHILD_SAs wich names equal tunnel value.
:param ike_name: IKE SA name
:type ike_name: str
@@ -70,7 +78,7 @@ def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list:
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec not initialized')
vici_dict = {}
if ike_name:
vici_dict['ike'] = ike_name
@@ -80,7 +88,31 @@ def get_vici_sas_by_name(ike_name: str, tunnel: str) -> list:
sas = list(session.list_sas(vici_dict))
return sas
except Exception:
- raise ViciCommandError(f'Failed to get SAs')
+ raise ViciCommandError('Failed to get SAs')
+
+
+def get_vici_connection_by_name(ike_name: str) -> list:
+ """
+ Find loaded SAs by IKE_SA name and return list with SASs info
+ :param ike_name: IKE SA name
+ :type ike_name: str
+ :return: list of Ordinary Dicts with SASs
+ :rtype: list
+ """
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise ViciInitiateError('IPsec is not initialized')
+ vici_dict = {}
+ if ike_name:
+ vici_dict['ike'] = ike_name
+ try:
+ sas = list(session.list_conns(vici_dict))
+ return sas
+ except Exception:
+ raise ViciCommandError('Failed to get SAs')
def terminate_vici_ikeid_list(ike_id_list: list) -> None:
@@ -94,19 +126,17 @@ def terminate_vici_ikeid_list(ike_id_list: list) -> None:
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec is not initialized')
try:
for ikeid in ike_id_list:
- session_generator = session.terminate(
- {'ike-id': ikeid, 'timeout': '-1'})
+ session_generator = session.terminate({'ike-id': ikeid, 'timeout': '-1'})
# a dummy `for` loop is required because of requirements
# from vici. Without a full iteration on the output, the
# command to vici may not be executed completely
for _ in session_generator:
pass
except Exception:
- raise ViciCommandError(
- f'Failed to terminate SA for IKE ids {ike_id_list}')
+ raise ViciCommandError(f'Failed to terminate SA for IKE ids {ike_id_list}')
def terminate_vici_by_name(ike_name: str, child_name: str) -> None:
@@ -123,9 +153,9 @@ def terminate_vici_by_name(ike_name: str, child_name: str) -> None:
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec is not initialized')
try:
- vici_dict: dict= {}
+ vici_dict: dict = {}
if ike_name:
vici_dict['ike'] = ike_name
if child_name:
@@ -138,16 +168,48 @@ def terminate_vici_by_name(ike_name: str, child_name: str) -> None:
pass
except Exception:
if child_name:
- raise ViciCommandError(
- f'Failed to terminate SA for IPSEC {child_name}')
+ raise ViciCommandError(f'Failed to terminate SA for IPSEC {child_name}')
else:
- raise ViciCommandError(
- f'Failed to terminate SA for IKE {ike_name}')
+ raise ViciCommandError(f'Failed to terminate SA for IKE {ike_name}')
+
+
+def vici_initiate_all_child_sa_by_ike(ike_sa_name: str, child_sa_list: list) -> bool:
+ """
+ Initiate IKE SA with scpecified CHILD_SAs in list
+
+ Args:
+ ike_sa_name (str): an IKE SA connection name
+ child_sa_list (list): a list of child SA names
+
+ Returns:
+ bool: a result of initiation command
+ """
+ from vici import Session as vici_session
+
+ try:
+ session = vici_session()
+ except Exception:
+ raise ViciInitiateError('IPsec is not initialized')
+
+ try:
+ for child_sa_name in child_sa_list:
+ session_generator = session.initiate(
+ {'ike': ike_sa_name, 'child': child_sa_name, 'timeout': '-1'}
+ )
+ # a dummy `for` loop is required because of requirements
+ # from vici. Without a full iteration on the output, the
+ # command to vici may not be executed completely
+ for _ in session_generator:
+ pass
+ return True
+ except Exception:
+ raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}')
-def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str,
- dst_addr: str) -> bool:
- """Initiate IKE SA connection with specific peer
+def vici_initiate(
+ ike_sa_name: str, child_sa_name: str, src_addr: str, dst_addr: str
+) -> bool:
+ """Initiate IKE SA with one child_sa connection with specific peer
Args:
ike_sa_name (str): an IKE SA connection name
@@ -163,16 +225,18 @@ def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str,
try:
session = vici_session()
except Exception:
- raise ViciInitiateError("IPsec not initialized")
+ raise ViciInitiateError('IPsec is not initialized')
try:
- session_generator = session.initiate({
- 'ike': ike_sa_name,
- 'child': child_sa_name,
- 'timeout': '-1',
- 'my-host': src_addr,
- 'other-host': dst_addr
- })
+ session_generator = session.initiate(
+ {
+ 'ike': ike_sa_name,
+ 'child': child_sa_name,
+ 'timeout': '-1',
+ 'my-host': src_addr,
+ 'other-host': dst_addr,
+ }
+ )
# a dummy `for` loop is required because of requirements
# from vici. Without a full iteration on the output, the
# command to vici may not be executed completely
@@ -180,4 +244,4 @@ def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str,
pass
return True
except Exception:
- raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}') \ No newline at end of file
+ raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}')
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index 8dab9a4ca..a6c64adfb 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -31,7 +31,15 @@ class Error(Exception):
class UnconfiguredSubsystem(Error):
""" Requested operation is valid, but cannot be completed
- because corresponding subsystem is not configured and running.
+ because corresponding subsystem is not configured
+ and thus is not running.
+ """
+ pass
+
+class UnconfiguredObject(UnconfiguredSubsystem):
+ """ Requested operation is valid but cannot be completed
+ because its parameter refers to an object that does not exist
+ in the system configuration.
"""
pass
@@ -81,7 +89,7 @@ class InternalError(Error):
def _is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew)", name):
+ if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew|release)", name):
return True
else:
return False
diff --git a/python/vyos/template.py b/python/vyos/template.py
index e8d7ba669..3507e0940 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -556,8 +556,8 @@ def get_openvpn_cipher(cipher):
return openvpn_translate[cipher].upper()
return cipher.upper()
-@register_filter('openvpn_ncp_ciphers')
-def get_openvpn_ncp_ciphers(ciphers):
+@register_filter('openvpn_data_ciphers')
+def get_openvpn_data_ciphers(ciphers):
out = []
for cipher in ciphers:
if cipher in openvpn_translate:
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index 829124b57..8fce08de0 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -83,6 +83,19 @@ def get_interface_vrf(interface):
return tmp['master']
return 'default'
+def get_vrf_tableid(interface: str):
+ """ Return VRF table ID for given interface name or None """
+ from vyos.utils.dict import dict_search
+ table = None
+ tmp = get_interface_config(interface)
+ # Check if we are "the" VRF interface
+ if dict_search('linkinfo.info_kind', tmp) == 'vrf':
+ table = tmp['linkinfo']['info_data']['table']
+ # or an interface bound to a VRF
+ elif dict_search('linkinfo.info_slave_kind', tmp) == 'vrf':
+ table = tmp['linkinfo']['info_slave_data']['table']
+ return table
+
def get_interface_config(interface):
""" Returns the used encapsulation protocol for given interface.
If interface does not exist, None is returned.
@@ -537,22 +550,50 @@ def ipv6_prefix_length(low, high):
return None
xor = bytearray(a ^ b for a, b in zip(lo, hi))
-
+
plen = 0
while plen < 128 and xor[plen // 8] == 0:
plen += 8
-
+
if plen == 128:
return plen
-
+
for i in range((plen // 8) + 1, 16):
if xor[i] != 0:
return None
-
+
for i in range(8):
msk = ~xor[plen // 8] & 0xff
-
+
if msk == bytemasks[i]:
return plen + i + 1
return None
+
+def get_nft_vrf_zone_mapping() -> dict:
+ """
+ Retrieve current nftables conntrack mapping list from Kernel
+
+ returns: [{'interface': 'red', 'vrf_tableid': 1000},
+ {'interface': 'eth2', 'vrf_tableid': 1000},
+ {'interface': 'blue', 'vrf_tableid': 2000}]
+ """
+ from json import loads
+ from jmespath import search
+ from vyos.utils.process import cmd
+ output = []
+ tmp = loads(cmd('sudo nft -j list table inet vrf_zones'))
+ # {'nftables': [{'metainfo': {'json_schema_version': 1,
+ # 'release_name': 'Old Doc Yak #3',
+ # 'version': '1.0.9'}},
+ # {'table': {'family': 'inet', 'handle': 6, 'name': 'vrf_zones'}},
+ # {'map': {'elem': [['eth0', 666],
+ # ['dum0', 666],
+ # ['wg500', 666],
+ # ['bond10.666', 666]],
+ vrf_list = search('nftables[].map.elem | [0]', tmp)
+ if not vrf_list:
+ return output
+ for (vrf_name, vrf_id) in vrf_list:
+ output.append({'interface' : vrf_name, 'vrf_tableid' : vrf_id})
+ return output
diff --git a/python/vyos/utils/process.py b/python/vyos/utils/process.py
index 60ef87a51..ce880f4a4 100644
--- a/python/vyos/utils/process.py
+++ b/python/vyos/utils/process.py
@@ -225,7 +225,7 @@ def process_named_running(name: str, cmdline: str=None, timeout: int=0):
if not tmp:
if time.time() > time_expire:
break
- time.sleep(0.100) # wait 250ms
+ time.sleep(0.100) # wait 100ms
continue
return tmp
else: