summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2025-11-04 16:16:25 +0100
committerChristian Breunig <christian@breunig.cc>2025-11-04 16:24:29 +0100
commit2f5ced44d12f3e92a22cc43eb5f4e7df4b7f062f (patch)
tree6886802c6e999ff78068bf91ff2983216bb75773
parente1b7b865a63454ae4b8f840c94ab599633faaf7f (diff)
downloadvyos-1x-2f5ced44d12f3e92a22cc43eb5f4e7df4b7f062f.tar.gz
vyos-1x-2f5ced44d12f3e92a22cc43eb5f4e7df4b7f062f.zip
veth: T7990: fix stale DHCP clients when removing virtual Ethernet pairs
When removing a veth interface, the kernel automatically deletes its peer interface, since veth devices always exist in pairs. However, this automatic removal does not trigger the remove() helper in vyos.ifconfig.interfaces, which can leave associated DHCP(v6) clients running indefinitely. For example: ip link add veth0 type veth peer name veth1 ip link del dev veth1 Removing veth1 will also delete veth0 in the kernel, but the VyOS cleanup routines are never called for veth0. This patch ensures that peer removal correctly purges both interfaces and their associated state. This drops the workarounds in veth smoketest and falls back to the common interface smoketests from the inherited base class.
-rw-r--r--python/vyos/ifconfig/interface.py12
-rw-r--r--python/vyos/ifconfig/veth.py18
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_virtual-ethernet.py27
3 files changed, 27 insertions, 30 deletions
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 761ba1799..491651aac 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -371,7 +371,7 @@ class Interface(Control):
if 'netns' in self.config: cmd = f'ip netns exec {netns} {cmd}'
self._cmd(cmd)
- def remove(self):
+ def remove(self, skip_delete=False):
"""
Remove interface from operating system. Removing the interface
deconfigures all assigned IP addresses and clear possible DHCP(v6)
@@ -388,12 +388,20 @@ class Interface(Control):
# remove all assigned IP addresses from interface - this is a bit redundant
# as the kernel will remove all addresses on interface deletion, but we
- # can not delete ALL interfaces, see below
+ # can not delete ALL interfaces, see below.
+ #
+ # This will internally stop DHCP(v6) if running
self.flush_addrs()
# remove interface from conntrack VRF interface map
self._del_interface_from_ct_iface_map()
+ # Some interfaces - mainly veth pairs - should be properly de-configured
+ # but not deleted. Deleting one veth pair member will delete the other,
+ # we need need a way to skip the deletion.
+ if skip_delete:
+ return
+
# ---------------------------------------------------------------------
# Any class can define an eternal regex in its definition
# interface matching the regex will not be deleted
diff --git a/python/vyos/ifconfig/veth.py b/python/vyos/ifconfig/veth.py
index f4075fa02..9868ea526 100644
--- a/python/vyos/ifconfig/veth.py
+++ b/python/vyos/ifconfig/veth.py
@@ -14,7 +14,8 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
from vyos.ifconfig.interface import Interface
-
+from vyos.utils.dict import dict_search
+from vyos.utils.network import get_interface_config
@Interface.register
class VethIf(Interface):
@@ -51,3 +52,18 @@ class VethIf(Interface):
# interface is always A/D down. It needs to be enabled explicitly
self.set_admin_state('down')
+
+ def remove(self):
+ # The oddity with virtual-ethernet pairs is, if you delete one - the OS
+ # Kernel will immediately delete the other interface, leaving no
+ # possibility to properly shutdown the peer-interface.
+ #
+ # We deconfigure the second veth pair interface during deletion, but
+ # skip it's actual call to "ip link del dev ..."
+ tmp = get_interface_config(self.ifname)
+ peer = tmp.get('link', dict_search('linkinfo.info_kind', tmp))
+ if peer != None:
+ Interface(peer).remove(skip_delete=True)
+
+ # always forward to the base class
+ super().remove()
diff --git a/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py b/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py
index 0a581951d..9bcc1cb1f 100755
--- a/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py
@@ -15,14 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
-from netifaces import interfaces # pylint: disable = no-name-in-module
from base_interfaces_test import BasicInterfaceTest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.frrender import mgmt_daemon
-from vyos.utils.process import process_named_running
-
class VEthInterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
def setUpClass(cls):
@@ -36,28 +32,5 @@ class VEthInterfaceTest(BasicInterfaceTest.TestCase):
# call base-classes classmethod
super(VEthInterfaceTest, cls).setUpClass()
- # As we always need a pair of veth interfaces, we can not rely on the base
- # class check to determine if there is a dhcp6c or dhclient instance
- # running. This test will always fail as there is an instance still running
- # on the peer interface.
- def tearDown(self):
- self.cli_delete(self._base_path)
- self.cli_commit()
-
- # Verify that no previously interface remained on the system
- for intf in self._interfaces:
- self.assertNotIn(intf, interfaces())
-
- # check process health and continuity
- self.assertEqual(self.mgmt_daemon_pid, process_named_running(mgmt_daemon))
-
- @classmethod
- def tearDownClass(cls):
- # No daemon started during tests should remain running
- for daemon in ['dhcp6c', 'dhclient']:
- cls.assertFalse(cls, process_named_running(daemon))
-
- super(VEthInterfaceTest, cls).tearDownClass()
-
if __name__ == '__main__':
unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())