summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorNataliia Solomko <natalirs1985@gmail.com>2026-09-04 16:32:01 +0300
committerNataliia Solomko <natalirs1985@gmail.com>2026-10-08 14:36:32 +0300
commitd054b7c82f239ff09a905b506943c03094d2d26b (patch)
tree8b59d7b9930bc69a06344667e101f295d904d9d9 /python
parent1ac0219e62c7b0efee9e81e7266558dffc7b14e6 (diff)
downloadvyos-1x-d054b7c82f239ff09a905b506943c03094d2d26b.tar.gz
vyos-1x-d054b7c82f239ff09a905b506943c03094d2d26b.zip
vpp: T9161: Enforce NIC MTU limit and apply MTU to VLAN sub-interfaces
On a VPP-bound interface the MTU limit is the NIC's, which only VPP knows (the kernel side is an LCP tap reporting a meaningless larger max). Reject at commit an MTU larger than that limit, read from VPP's 'show hardware-interfaces' (max mtu + driver frame overhead), so the kernel and dataplane cannot silently end up with different MTUs. Also apply a VLAN sub-interface's MTU to its VPP dataplane sub-interface and its linux-cp tap (created with MTU 0), not just the parent; a sub-interface without its own MTU inherits the parent's. Covers ethernet (vif/vif-s) and VPP bond/loopback (vif).
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ifconfig/vpp/bond.py9
-rw-r--r--python/vyos/ifconfig/vpp/interface.py48
-rw-r--r--python/vyos/ifconfig/vpp/loopback.py15
-rw-r--r--python/vyos/vpp/control_vpp.py34
4 files changed, 87 insertions, 19 deletions
diff --git a/python/vyos/ifconfig/vpp/bond.py b/python/vyos/ifconfig/vpp/bond.py
index 240657383..095cfc538 100644
--- a/python/vyos/ifconfig/vpp/bond.py
+++ b/python/vyos/ifconfig/vpp/bond.py
@@ -160,8 +160,11 @@ class VPPBondInterface(Interface, VPPInterface):
if member not in members:
self.add_member(interface=member)
+ # Apply all settings to the lcp pair (kernel) interface first: creating
+ # the kernel VLANs is what makes linux-cp create the matching VPP
+ # sub-interfaces and their taps, which the VPP-specific settings below
+ # (MTU sync to those taps) then depend on.
+ super().update(config)
+
# Apply VPP-specific interface settings
VPPInterface.update(self, config)
-
- # Apply all settings to the lcp pair (kernel) interface
- super().update(config)
diff --git a/python/vyos/ifconfig/vpp/interface.py b/python/vyos/ifconfig/vpp/interface.py
index 17be83814..99ade51d2 100644
--- a/python/vyos/ifconfig/vpp/interface.py
+++ b/python/vyos/ifconfig/vpp/interface.py
@@ -55,22 +55,44 @@ class VPPInterface:
if lcp_name:
self.vpp.iface_rxmode(lcp_name, rx_mode)
- def set_mtu_vpp(self, mtu):
- # Set MTU for the VPP interface
- self.vpp.set_iface_mtu(self.vpp_ifname, mtu)
+ def set_mtu_vpp(self, config):
+ """Set the MTU on this interface and its VLAN sub-interfaces.
- # Set MTU for the LCP pair interface
- lcp_pair = self.vpp.lcp_pair_find(vpp_name_hw=self.vpp_ifname)
- if lcp_pair:
- lcp_name = lcp_pair.get('vpp_name_kernel')
- if lcp_name:
- self.vpp.set_iface_mtu(lcp_name, mtu)
+ The MTU is set on both the dataplane interface and its LCP tap. The
+ tap is created with MTU 0, so it always needs setting. A VLAN
+ sub-interface uses its own MTU if set, otherwise it inherits the
+ parent MTU.
+ """
+ parent_mtu = config.get('mtu')
+ if not parent_mtu:
+ return
+
+ # Fetch all LCP pairs
+ lcp_tap_map = {
+ pair.get('vpp_name_hw'): pair.get('vpp_name_kernel')
+ for pair in self.vpp.lcp_pairs_list()
+ }
+
+ # Parent: always set the dataplane interface, plus its tap if present.
+ self.vpp.set_iface_mtu(self.vpp_ifname, int(parent_mtu))
+ parent_tap = lcp_tap_map.get(self.vpp_ifname)
+ if parent_tap:
+ self.vpp.set_iface_mtu(parent_tap, int(parent_mtu))
+
+ # VLAN sub-interfaces: set the sub-interface and its tap once linux-cp
+ # has created the tap.
+ for vlan_id, vlan_config in config.get('vif', {}).items():
+ vlan_ifname = f'{self.vpp_ifname}.{vlan_id}'
+ lcp_name = lcp_tap_map.get(vlan_ifname)
+ if not lcp_name:
+ continue
+ mtu = int(vlan_config.get('mtu') or parent_mtu)
+ self.vpp.set_iface_mtu(vlan_ifname, mtu)
+ self.vpp.set_iface_mtu(lcp_name, mtu)
def update(self, config):
- # Set MTU
- if 'mtu' in config:
- mtu = int(config['mtu'])
- self.set_mtu_vpp(mtu)
+ # Set MTU on the interface, its VLAN sub-interfaces, and their LCP taps
+ self.set_mtu_vpp(config)
# Set rx-mode
rx_mode = config.get('vpp_settings', {}).get('interface_rx_mode')
diff --git a/python/vyos/ifconfig/vpp/loopback.py b/python/vyos/ifconfig/vpp/loopback.py
index 9f87781c0..ee34a5348 100644
--- a/python/vyos/ifconfig/vpp/loopback.py
+++ b/python/vyos/ifconfig/vpp/loopback.py
@@ -106,7 +106,16 @@ class VPPLoopbackInterface(Interface, VPPInterface):
# Add loopback interface
self.add()
- VPPInterface.update(self, config)
-
- # Apply all settings to the lcp pair (kernel) interface
+ # Apply all settings to the lcp pair (kernel) interface first: creating
+ # the kernel VLANs is what makes linux-cp create the matching VPP
+ # sub-interfaces and their taps, which the VPP-specific settings below
+ # (MTU sync to those taps) then depend on.
super().update(config)
+
+ # Without a configured MTU the loopback keeps VPP's default; take it
+ # from the kernel so it still reaches the LCP taps and VLAN
+ # sub-interfaces. An explicit MTU is applied by VPPInterface.
+ if 'mtu' not in config:
+ config['mtu'] = self.get_mtu()
+
+ VPPInterface.update(self, config)
diff --git a/python/vyos/vpp/control_vpp.py b/python/vyos/vpp/control_vpp.py
index 5cad2a646..49c55b56e 100644
--- a/python/vyos/vpp/control_vpp.py
+++ b/python/vyos/vpp/control_vpp.py
@@ -535,6 +535,40 @@ class VPPControl:
return self.__vpp_api_client.api.sw_interface_set_mtu(**api_call_args)
@_Decorators.api_call
+ def get_iface_max_mtu(self, iface_name_vpp: str) -> int | None:
+ """Return the maximum L3 MTU an interface's NIC can accept.
+
+ Derived from the driver limits in 'show hardware-interfaces': 'max mtu'
+ (rte_eth_dev_info.max_mtu) plus 'driver frame overhead'. The NIC is owned
+ by the VPP dataplane, so only VPP knows its real limit - the kernel side
+ is an LCP tap that reports its own, much larger max MTU and does not
+ reflect the physical NIC.
+
+ Args:
+ iface_name_vpp (str): Name of an interface in VPP
+
+ Returns:
+ int | None: maximum L3 MTU or None
+ """
+ hw_info = self.cli_cmd(f'show hardware-interfaces {iface_name_vpp}').reply or ''
+ max_mtu = re.search(r'max mtu:\s*(\d+)', hw_info)
+ if not max_mtu:
+ return None
+
+ max_mtu = int(max_mtu.group(1))
+ overhead = re.search(r'driver frame overhead:\s*(\d+)', hw_info)
+ if not overhead:
+ # Without the driver frame overhead the L3 limit is unknown.
+ return None
+
+ # VPP programs the NIC with (L3 MTU + hi->frame_overhead -
+ # driver_frame_overhead); hi->frame_overhead for a DPDK Ethernet
+ # interface is Ethernet header (14) + two QinQ VLAN tags (8) = 22. So the
+ # largest settable L3 MTU is (max_mtu + driver_frame_overhead - 22).
+ l2_frame_overhead = 14 + 8
+ return max_mtu + int(overhead.group(1)) - l2_frame_overhead
+
+ @_Decorators.api_call
def get_sw_if_dev_type(self, ifname: str) -> int | None:
"""Find interface device type by interface name in VPP