summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ifconfig/vxlan.py25
-rw-r--r--python/vyos/vpp.py315
2 files changed, 24 insertions, 316 deletions
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index 1fe5db7cd..8c5a0220e 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -56,6 +56,10 @@ class VXLANIf(Interface):
}
_command_set = {**Interface._command_set, **{
+ 'neigh_suppress': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'shellcmd': 'bridge link set dev {ifname} neigh_suppress {value} learning off',
+ },
'vlan_tunnel': {
'validate': lambda v: assert_list(v, ['on', 'off']),
'shellcmd': 'bridge link set dev {ifname} vlan_tunnel {value}',
@@ -68,8 +72,8 @@ class VXLANIf(Interface):
# - https://man7.org/linux/man-pages/man8/ip-link.8.html
mapping = {
'group' : 'group',
- 'external' : 'external',
'gpe' : 'gpe',
+ 'parameters.external' : 'external',
'parameters.ip.df' : 'df',
'parameters.ip.tos' : 'tos',
'parameters.ip.ttl' : 'ttl',
@@ -113,6 +117,19 @@ class VXLANIf(Interface):
'port {port} dev {ifname}'
self._cmd(cmd.format(**self.config))
+ def set_neigh_suppress(self, state):
+ """
+ Controls whether neigh discovery (arp and nd) proxy and suppression
+ is enabled on the port. By default this flag is off.
+ """
+
+ # Determine current OS Kernel neigh_suppress setting - only adjust when needed
+ tmp = get_interface_config(self.ifname)
+ cur_state = 'on' if dict_search(f'linkinfo.info_slave_data.neigh_suppress', tmp) == True else 'off'
+ new_state = 'on' if state else 'off'
+ if cur_state != new_state:
+ self.set_interface('neigh_suppress', state)
+
def set_vlan_vni_mapping(self, state):
"""
Controls whether vlan to tunnel mapping is enabled on the port.
@@ -163,3 +180,9 @@ class VXLANIf(Interface):
# Enable/Disable VLAN tunnel mapping
# This is only possible after the interface was assigned to the bridge
self.set_vlan_vni_mapping(dict_search('vlan_to_vni', config) != None)
+
+ # Enable/Disable neighbor suppression and learning, there is no need to
+ # explicitly "disable" it, as VXLAN interface will be recreated if anything
+ # under "parameters" changes.
+ if dict_search('parameters.neighbor_suppress', config) != None:
+ self.set_neigh_suppress('on')
diff --git a/python/vyos/vpp.py b/python/vyos/vpp.py
deleted file mode 100644
index 76e5d29c3..000000000
--- a/python/vyos/vpp.py
+++ /dev/null
@@ -1,315 +0,0 @@
-# Copyright 2023 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
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# 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/>.
-
-from functools import wraps
-from pathlib import Path
-from re import search as re_search, fullmatch as re_fullmatch, MULTILINE as re_M
-from subprocess import run
-from time import sleep
-
-from vpp_papi import VPPApiClient
-from vpp_papi import VPPIOError, VPPValueError
-
-
-class VPPControl:
- """Control VPP network stack
- """
-
- class _Decorators:
- """Decorators for VPPControl
- """
-
- @classmethod
- def api_call(cls, decorated_func):
- """Check if API is connected before API call
-
- Args:
- decorated_func: function to decorate
-
- Raises:
- VPPIOError: Connection to API is not established
- """
-
- @wraps(decorated_func)
- def api_safe_wrapper(cls, *args, **kwargs):
- if not cls.vpp_api_client.transport.connected:
- raise VPPIOError(2, 'VPP API is not connected')
- return decorated_func(cls, *args, **kwargs)
-
- return api_safe_wrapper
-
- @classmethod
- def check_retval(cls, decorated_func):
- """Check retval from API response
-
- Args:
- decorated_func: function to decorate
-
- Raises:
- VPPValueError: raised when retval is not 0
- """
-
- @wraps(decorated_func)
- def check_retval_wrapper(cls, *args, **kwargs):
- return_value = decorated_func(cls, *args, **kwargs)
- if not return_value.retval == 0:
- raise VPPValueError(
- f'VPP API call failed: {return_value.retval}')
- return return_value
-
- return check_retval_wrapper
-
- def __init__(self, attempts: int = 5, interval: int = 1000) -> None:
- """Create VPP API connection
-
- Args:
- attempts (int, optional): attempts to connect. Defaults to 5.
- interval (int, optional): interval between attempts in ms. Defaults to 1000.
-
- Raises:
- VPPIOError: Connection to API cannot be established
- """
- self.vpp_api_client = VPPApiClient()
- # connect with interval
- while attempts:
- try:
- attempts -= 1
- self.vpp_api_client.connect('vpp-vyos')
- break
- except (ConnectionRefusedError, FileNotFoundError) as err:
- print(f'VPP API connection timeout: {err}')
- sleep(interval / 1000)
- # raise exception if connection was not successful in the end
- if not self.vpp_api_client.transport.connected:
- raise VPPIOError(2, 'Cannot connect to VPP API')
-
- def __del__(self) -> None:
- """Disconnect from VPP API (destructor)
- """
- self.disconnect()
-
- def disconnect(self) -> None:
- """Disconnect from VPP API
- """
- if self.vpp_api_client.transport.connected:
- self.vpp_api_client.disconnect()
-
- @_Decorators.check_retval
- @_Decorators.api_call
- def cli_cmd(self, command: str):
- """Send raw CLI command
-
- Args:
- command (str): command to send
-
- Returns:
- vpp_papi.vpp_serializer.cli_inband_reply: CLI reply class
- """
- return self.vpp_api_client.api.cli_inband(cmd=command)
-
- @_Decorators.api_call
- def get_mac(self, ifname: str) -> str:
- """Find MAC address by interface name in VPP
-
- Args:
- ifname (str): interface name inside VPP
-
- Returns:
- str: MAC address
- """
- for iface in self.vpp_api_client.api.sw_interface_dump():
- if iface.interface_name == ifname:
- return iface.l2_address.mac_string
- return ''
-
- @_Decorators.api_call
- def get_sw_if_index(self, ifname: str) -> int | None:
- """Find interface index by interface name in VPP
-
- Args:
- ifname (str): interface name inside VPP
-
- Returns:
- int | None: Interface index or None (if was not fount)
- """
- for iface in self.vpp_api_client.api.sw_interface_dump():
- if iface.interface_name == ifname:
- return iface.sw_if_index
- return None
-
- @_Decorators.check_retval
- @_Decorators.api_call
- def lcp_pair_add(self, iface_name_vpp: str, iface_name_kernel: str) -> None:
- """Create LCP interface pair between VPP and kernel
-
- Args:
- iface_name_vpp (str): interface name in VPP
- iface_name_kernel (str): interface name in kernel
- """
- iface_index = self.get_sw_if_index(iface_name_vpp)
- if iface_index:
- return self.vpp_api_client.api.lcp_itf_pair_add_del(
- is_add=True,
- sw_if_index=iface_index,
- host_if_name=iface_name_kernel)
-
- @_Decorators.check_retval
- @_Decorators.api_call
- def lcp_pair_del(self, iface_name_vpp: str, iface_name_kernel: str) -> None:
- """Delete LCP interface pair between VPP and kernel
-
- Args:
- iface_name_vpp (str): interface name in VPP
- iface_name_kernel (str): interface name in kernel
- """
- iface_index = self.get_sw_if_index(iface_name_vpp)
- if iface_index:
- return self.vpp_api_client.api.lcp_itf_pair_add_del(
- is_add=False,
- sw_if_index=iface_index,
- host_if_name=iface_name_kernel)
-
- @_Decorators.check_retval
- @_Decorators.api_call
- def iface_rxmode(self, iface_name: str, rx_mode: str) -> None:
- """Set interface rx-mode in VPP
-
- Args:
- iface_name (str): interface name in VPP
- rx_mode (str): mode (polling, interrupt, adaptive)
- """
- modes_dict: dict[str, int] = {
- 'polling': 1,
- 'interrupt': 2,
- 'adaptive': 3
- }
- if rx_mode not in modes_dict:
- raise VPPValueError(f'Mode {rx_mode} is not known')
- iface_index = self.get_sw_if_index(iface_name)
- return self.vpp_api_client.api.sw_interface_set_rx_mode(
- sw_if_index=iface_index, mode=modes_dict[rx_mode])
-
- @_Decorators.api_call
- def get_pci_addr(self, ifname: str) -> str:
- """Find PCI address of interface by interface name in VPP
-
- Args:
- ifname (str): interface name inside VPP
-
- Returns:
- str: PCI address
- """
- hw_info = self.cli_cmd(f'show hardware-interfaces {ifname}').reply
-
- regex_filter = r'^\s+pci: device (?P<device>\w+:\w+) subsystem (?P<subsystem>\w+:\w+) address (?P<address>\w+:\w+:\w+\.\w+) numa (?P<numa>\w+)$'
- re_obj = re_search(regex_filter, hw_info, re_M)
-
- # return empty string if no interface or no PCI info was found
- if not hw_info or not re_obj:
- return ''
-
- address = re_obj.groupdict().get('address', '')
-
- # we need to modify address to math kernel style
- # for example: 0000:06:14.00 -> 0000:06:14.0
- address_chunks: list[str] = address.split('.')
- address_normalized: str = f'{address_chunks[0]}.{int(address_chunks[1])}'
-
- return address_normalized
-
-
-class HostControl:
- """Control Linux host
- """
-
- @staticmethod
- def pci_rescan(pci_addr: str = '') -> None:
- """Rescan PCI device by removing it and rescan PCI bus
-
- If PCI address is not defined - just rescan PCI bus
-
- Args:
- address (str, optional): PCI address of device. Defaults to ''.
- """
- if pci_addr:
- device_file = Path(f'/sys/bus/pci/devices/{pci_addr}/remove')
- if device_file.exists():
- device_file.write_text('1')
- # wait 10 seconds max until device will be removed
- attempts = 100
- while device_file.exists() and attempts:
- attempts -= 1
- sleep(0.1)
- if device_file.exists():
- raise TimeoutError(
- f'Timeout was reached for removing PCI device {pci_addr}'
- )
- else:
- raise FileNotFoundError(f'PCI device {pci_addr} does not exist')
- rescan_file = Path('/sys/bus/pci/rescan')
- rescan_file.write_text('1')
- if pci_addr:
- # wait 10 seconds max until device will be installed
- attempts = 100
- while not device_file.exists() and attempts:
- attempts -= 1
- sleep(0.1)
- if not device_file.exists():
- raise TimeoutError(
- f'Timeout was reached for installing PCI device {pci_addr}')
-
- @staticmethod
- def get_eth_name(pci_addr: str) -> str:
- """Find Ethernet interface name by PCI address
-
- Args:
- pci_addr (str): PCI address
-
- Raises:
- FileNotFoundError: no Ethernet interface was found
-
- Returns:
- str: Ethernet interface name
- """
- # find all PCI devices with eth* names
- net_devs: dict[str, str] = {}
- net_devs_dir = Path('/sys/class/net')
- regex_filter = r'^/sys/devices/pci[\w/:\.]+/(?P<pci_addr>\w+:\w+:\w+\.\w+)/[\w/:\.]+/(?P<iface_name>eth\d+)$'
- for dir in net_devs_dir.iterdir():
- real_dir: str = dir.resolve().as_posix()
- re_obj = re_fullmatch(regex_filter, real_dir)
- if re_obj:
- iface_name: str = re_obj.group('iface_name')
- iface_addr: str = re_obj.group('pci_addr')
- net_devs.update({iface_addr: iface_name})
- # match to provided PCI address and return a name if found
- if pci_addr in net_devs:
- return net_devs[pci_addr]
- # raise error if device was not found
- raise FileNotFoundError(
- f'PCI device {pci_addr} not found in ethernet interfaces')
-
- @staticmethod
- def rename_iface(name_old: str, name_new: str) -> None:
- """Rename interface
-
- Args:
- name_old (str): old name
- name_new (str): new name
- """
- rename_cmd: list[str] = [
- 'ip', 'link', 'set', name_old, 'name', name_new
- ]
- run(rename_cmd)