Repository: KatharaFramework/Kathara

Issue:
OpenVPN and /dev/net/tun related software may not work when containerd >= 1.74
### Operating System

ArchLinux

### Kathará Version

3.7.6

### Bug Description

With recent versions of Docker and containerd (>= 1.74), OpenVPN does not work in Kathara devices.
This is most likely due to a [known issue of runc 1.2.2](https://github.com/opencontainers/runc/pull/3468) (dependency of containerd 1.7.4).

Eventually, this will become a problem for most of the users (Debian Sid is already using containerd 1.7.4).

Trying to instance an OpenVPN server (clients behave the same way), the following error is raised (`verb 2` in OpenVPN configuration file):

```
root@s1:~# openvpn server.conf
Mon Jan  6 09:23:30 2025 WARNING: file '/root/myserver.key' is group or others accessible
Mon Jan  6 09:23:30 2025 OpenVPN 2.4.7 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Apr 28 2021
Mon Jan  6 09:23:30 2025 library versions: OpenSSL 1.1.1n  15 Mar 2022, LZO 2.10
Enter Private Key Password:
Mon Jan  6 09:23:33 2025 WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
Mon Jan  6 09:23:33 2025 ERROR: Cannot open TUN/TAP dev /dev/net/tun: Operation not permitted (errno=1)
Mon Jan  6 09:23:33 2025 Exiting due to fatal error
```

**Workaround**: Start the kathara lab as privileged:

```bash
sudo kathara lstart --privileged
```

Keep in mind that a privileged instance of Kathara does not start terminals automatically, so you need to connect to nodes manually with `kathara connect` (without sudo is fine).

### Steps To Reproduce

Create a simple laboratory (also a single node should be sufficient) and try to start an OpenVPN instance (client or server is irrelevant).
I already created one for fast reproduction.

[lab-openvpn-issue.tar.gz](https://github.com/user-attachments/files/18318115/lab-openvpn-issue.tar.gz)

More details on the `README.md` file inside the laboratory.

### Expected Behavior

There should be a way to make /dev/net/tun accessible (and to add NET_ADMIN as a container capability), either automatically or by setting it in lab.conf for single devices, so that the following works as expected:

```
root@s1:~# openvpn server.conf
Mon Jan  6 09:27:17 2025 WARNING: file '/root/myserver.key' is group or others accessible
Mon Jan  6 09:27:17 2025 OpenVPN 2.4.7 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Apr 28 2021
Mon Jan  6 09:27:17 2025 library versions: OpenSSL 1.1.1n  15 Mar 2022, LZO 2.10
Enter Private Key Password:
Mon Jan  6 09:27:20 2025 WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
Mon Jan  6 09:27:20 2025 TUN/TAP device tun0 opened
Mon Jan  6 09:27:20 2025 /sbin/ip link set dev tun0 up mtu 1500
Mon Jan  6 09:27:20 2025 /sbin/ip addr add dev tun0 10.8.0.1/24 broadcast 10.8.0.255
Mon Jan  6 09:27:20 2025 Could not determine IPv4/IPv6 protocol. Using AF_INET
Mon Jan  6 09:27:20 2025 UDPv4 link local (bound): [AF_INET][undef]:1194
Mon Jan  6 09:27:20 2025 UDPv4 link remote: [AF_UNSPEC]
Mon Jan  6 09:27:20 2025 Initialization Sequence Completed
```

(This output was generated in a Kathara environment started as `--privileged`).

### Check Command Output

```shell
Current Manager is:             Docker (Kathara)
Manager version is:             27.3.1
Python version is:              3.11.8 (main, Feb 12 2024, 14:50:05) [GCC 13.2.1 20230801]
Kathara version is:             3.7.6
Operating System version is:    Linux-6.12.6-zen1-1-zen-x86_64
✓ Container run successfully.
```

Discussion:
@tcaiazzi: Dear @jcondor98,

Thank you for bringing this to our attention and for opening the issue, and sorry for the delayed response.

You are absolutely correct, there are issues with software that requires access to the /net/dev/tun directory.

However, for security reasons (similar to those of the [PR](https://github.com/opencontainers/runc/pull/3468) you mentioned), we prefer not to enforce the mounting of the /net/dev/tun directory unless users explicitly request it.

That said, we are actively working on a solution to allow terminals to open even in privileged mode, enabling seamless access to device shells.

I’ll update you as soon as we’ve implemented it.

Thank you again for your help!@Skazza94: Hi @jcondor98, 
starting from release 3.7.9, the devices' terminals are opened even when the `--privileged` flag is passed.

So, this should solve the problem that you were facing.

Let us know if you encounter any other issue.

Cheers,
Mariano.

Solution patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index d146817..2fe40fa 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -22,7 +22,7 @@ body:
     attributes:
       label: Kathará Version
       description: "Please provide the Kathará version you are using (`kathara -v`)."
-      placeholder: "3.7.8"
+      placeholder: "3.7.9"
     validations:
       required: true
   - type: textarea
diff --git a/SECURITY.md b/SECURITY.md
index 39f3d2a..79436ac 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -6,7 +6,7 @@ We release patches for security vulnerabilities only for the last version:
 
 | Version | Supported Versions |
 |---------|--------------------|
-| 3.7.8   | :white_check_mark: |
+| 3.7.9   | :white_check_mark: |
 
 ## Reporting a Vulnerability
 
diff --git a/pyproject.toml b/pyproject.toml
index 05440e9..2815310 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,10 +1,11 @@
 [project]
 name = "kathara"
-version = "3.7.8"
+version = "3.7.9"
 description = "A lightweight container-based network emulation tool."
 readme = "README.md"
 requires-python = ">=3.9"
-license = { file = "LICENSE" }
+license = 'gpl-3.0'
+license-files = ["LICENSE"]
 keywords = ["NETWORK-EMULATION", "CONTAINERS", "NFV"]
 authors = [
     { name = "Kathara Framework", email = "contact@kathara.org" }
@@ -22,7 +23,6 @@ classifiers = [
     "Intended Audience :: Education",
     "Intended Audience :: Telecommunications Industry",
     "Topic :: Software Development :: Build Tools",
-    "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11"
diff --git a/scripts/Linux-Pkg/kathara.spec b/scripts/Linux-Pkg/kathara.spec
new file mode 100644
index 0000000..6427a96
--- /dev/null
+++ b/scripts/Linux-Pkg/kathara.spec
@@ -0,0 +1,74 @@
+# -*- mode: python ; coding: utf-8 -*-
+
+block_cipher = None
+
+
+a = Analysis(['kathara.py'],
+             pathex=['../src/'],
+             binaries=[],
+             datas=[],
+             hiddenimports=['Kathara',
+                            'Kathara.cli',
+                            'Kathara.cli.command',
+                            'Kathara.cli.command.CheckCommand',
+                            'Kathara.cli.command.ConnectCommand',
+                            'Kathara.cli.command.ExecCommand',
+                            'Kathara.cli.command.LcleanCommand',
+                            'Kathara.cli.command.LinfoCommand',
+                            'Kathara.cli.command.ListCommand',
+                            'Kathara.cli.command.LrestartCommand',
+                            'Kathara.cli.command.LstartCommand',
+                            'Kathara.cli.command.LtestCommand',
+                            'Kathara.cli.command.LconfigCommand',
+                            'Kathara.cli.command.SettingsCommand',
+                            'Kathara.cli.command.VcleanCommand',
+                            'Kathara.cli.command.VconfigCommand',
+                            'Kathara.cli.command.VstartCommand',
+                            'Kathara.cli.command.WipeCommand',
+                            'Kathara.cli.ui',
+                            'Kathara.cli.ui.setting',
+                            'Kathara.cli.ui.setting.DockerOptionsHandler',
+                            'Kathara.cli.ui.setting.KubernetesOptionsHandler',
+                            'Kathara.manager',
+                            'Kathara.manager.Kathara',
+                            'Kathara.manager.docker',
+                            'Kathara.manager.docker.DockerManager',
+                            'Kathara.manager.kubernetes',
+                            'Kathara.manager.kubernetes.KubernetesManager',
+                            'Kathara.setting',
+                            'Kathara.setting.addon',
+                            'Kathara.setting.addon.DockerSettingsAddon',
+                            'Kathara.setting.addon.KubernetesSettingsAddon'
+                            ],
+             hookspath=[],
+             runtime_hooks=[],
+             excludes=[],
+             win_no_prefer_redirects=False,
+             win_private_assemblies=False,
+             cipher=block_cipher,
+             noarchive=False)
+pyz = PYZ(a.pure, a.zipped_data,
+             cipher=block_cipher)
+exe = EXE(pyz,
+          a.scripts,
+          [],
+          name='kathara',
+          debug=False,
+          bootloader_ignore_signals=False,
+          strip=False,
+          upx=True,
+          upx_exclude=[],
+          exclude_binaries=True,
+          runtime_tmpdir=None,
+          target_arch='x86',
+          console=True)
+
+col = COLLECT(exe,
+              a.binaries,
+              a.zipfiles,
+              a.datas,
+              strip=False,
+              upx=True,
+              upx_exclude=[],
+              name='kathara'
+)
\ No newline at end of file
diff --git a/scripts/Linux-Rpm/rpm/kathara.spec b/scripts/Linux-Rpm/rpm/kathara.spec
index 0465cd3..eb15d52 100644
--- a/scripts/Linux-Rpm/rpm/kathara.spec
+++ b/scripts/Linux-Rpm/rpm/kathara.spec
@@ -68,13 +68,7 @@ chmod g+s %{_libdir}/kathara/kathara
 
 %changelog
 *  __DATE__ Kathara Team <******@kathara.org> - __VERSION__-__PACKAGE_VERSION__
-- WARNING: "ltest" command is deprecated. It will be removed in the next release! Please use the "kathara-lab-checker" tool as its replacement with enhanced functionalities.
-- Add "--topology" argument to "kathara linfo" that displays a list of collision domains and the devices connected to each of them
-- "kathara linfo" now displays a column with the connected interfaces of each device
-- "kathara exec" now returns the exact exit code of the command
-- API Improvements in the "Lab" object
-- (Docker) Fix "rp_filter" sysctl assigment on Docker Engine >= v27.0.0
-- (Docker) Add the possibility to specify "ulimits" in network scenario metadata (thanks to g4br-i)
-- (Docker) Fix compatibility with docker-io version numbers
-- (Kubernetes) Add the support to private image registries through the "docker.json" file (thanks to Giovanni Barbiero)
-- Minor bug fixes
\ No newline at end of file
+- (Docker) Fix user-defined interface-specific sysctls on Docker Engine >= v27.0.0
+- Open device terminals when "--privileged" flag is set
+- Assign device FQDN in "/etc/hosts" (if no "/etc/hosts" is passed by the user)
+- Fix TMUX issues
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index 654f8ac..bb38031 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = kathara
-version = 3.7.8
+version = 3.7.9
 author = Kathara Framework
 author_email = contact@kathara.org
 description = A lightweight container-based network emulation tool.
diff --git a/setup.py b/setup.py
index 9d051f6..aedd6f9 100644
--- a/setup.py
+++ b/setup.py
@@ -7,13 +7,13 @@ setup(
     package_dir={'': 'src'},
     packages=find_packages('src'),
     py_modules=['kathara'],
-    version='3.7.8',
+    version='3.7.9',
     license='gpl-3.0',
     description='A lightweight container-based network emulation tool.',
     author='Kathara Framework',
     author_email='contact@kathara.org',
     url='https://www.kathara.org',
-    download_url='https://github.com/KatharaFramework/Kathara/archive/refs/tags/3.7.8.tar.gz',
+    download_url='https://github.com/KatharaFramework/Kathara/archive/refs/tags/3.7.9.tar.gz',
     keywords=['NETWORK-EMULATION', 'CONTAINERS', 'NFV'],
     install_requires=[
         "binaryornot>=0.4.4",
diff --git a/src/Kathara/cli/command/LrestartCommand.py b/src/Kathara/cli/command/LrestartCommand.py
index a68d90a..1ea7312 100644
--- a/src/Kathara/cli/command/LrestartCommand.py
+++ b/src/Kathara/cli/command/LrestartCommand.py
@@ -42,7 +42,7 @@ class LrestartCommand(Command):
             const=True,
             help='Start the network scenario opening terminal windows.'
         )
-        group.add_argument(
+        self.parser.add_argument(
             "--privileged",
             action="store_const",
             const=True,
diff --git a/src/Kathara/cli/command/LstartCommand.py b/src/Kathara/cli/command/LstartCommand.py
index 37a4cee..c4c8377 100644
--- a/src/Kathara/cli/command/LstartCommand.py
+++ b/src/Kathara/cli/command/LstartCommand.py
@@ -52,7 +52,7 @@ class LstartCommand(Command):
             const=True,
             help='Start the network scenario opening terminal windows.'
         )
-        group.add_argument(
+        self.parser.add_argument(
             "--privileged",
             action="store_const",
             const=True,
@@ -219,8 +219,10 @@ class LstartCommand(Command):
             if not utils.is_admin():
                 raise PrivilegeError("You must be root in order to start Kathara devices in privileged mode.")
             else:
-                self.console.print("[yellow]\u26a0 Running devices with privileged capabilities, terminals won't open!")
-                Setting.get_instance().open_terminals = False
+                if Setting.get_instance().open_terminals:
+                    self.console.print(
+                        "[yellow]\u26a0 Running devices with privileged capabilities, terminals might not open!"
+                    )
 
         Kathara.get_instance().deploy_lab(
             lab, selected_machines=set(args['machine_name']), excluded_machines=set(args['excluded_machines'])
diff --git a/src/Kathara/cli/command/VstartCommand.py b/src/Kathara/cli/command/VstartCommand.py
index b060af1..aaa444a 100644
--- a/src/Kathara/cli/command/VstartCommand.py
+++ b/src/Kathara/cli/command/VstartCommand.py
@@ -47,18 +47,18 @@ class VstartCommand(Command):
             help='Start the device opening its terminal window.'
         )
         group.add_argument(
+            '--num_terms',
+            metavar='NUM_TERMS',
+            required=False,
+            help='Choose the number of terminals to open for the device.'
+        )
+        self.parser.add_argument(
             "--privileged",
             action="store_const",
             const=True,
             required=False,
             help='Start the device in privileged mode. MUST BE ROOT FOR THIS OPTION.'
         )
-        group.add_argument(
-            '--num_terms',
-            metavar='NUM_TERMS',
-            required=False,
-            help='Choose the number of terminals to open for the device.'
-        )
         self.parser.add_argument(
             '-n', '--name',
             metavar='DEVICE_NAME',
@@ -193,8 +193,10 @@ class VstartCommand(Command):
             if not utils.is_admin():
                 raise PrivilegeError("You must be root in order to start this Kathara device in privileged mode.")
             else:
-                self.console.print("[yellow]\u26a0 Running devices with privileged capabilities, terminals won't open!")
-                Setting.get_instance().open_terminals = False
+                if Setting.get_instance().open_terminals:
+                    self.console.print(
+                        "[yellow]\u26a0 Running devices with privileged capabilities, terminal might not open!"
+                    )
 
         lab = Lab("kathara_vlab")
         lab.add_option('hosthome_mount', args['hosthome_mount'])
diff --git a/src/Kathara/manager/docker/DockerMachine.py b/src/Kathara/manager/docker/DockerMachine.py
index 67e6f37..ad2a214 100644
--- a/src/Kathara/manager/docker/DockerMachine.py
+++ b/src/Kathara/manager/docker/DockerMachine.py
@@ -33,6 +33,8 @@ OCI_RUNTIME_RE = re.compile(
     r"OCI runtime exec failed(.*?)(stat (.*): no such file or directory|exec: \"(.*)\": executable file not found)"
 )
 
+IFACE_SYSCTL_RE = re.compile(r"net\.ipv[4,6]\.(conf|neigh)\.eth\d+")
+
 # Known commands that each container should execute
 # Run order: shared.startup, machine.startup and machine.meta['exec_commands']
 STARTUP_COMMANDS = [
@@ -46,10 +48,11 @@ STARTUP_COMMANDS = [
     "if [ -d \"/hostlab/{machine_name}\" ]; then "
     "(cd /hostlab/{machine_name} && tar c .) | (cd / && tar xhf - --no-same-owner --no-same-permissions); fi",
 
-    # If /etc/hosts is not configured by the user, add the localhost mapping
+    # If /etc/hosts is not configured by the user, add the default mappings
     "if [ ! -s \"/etc/hosts\" ]; then "
     "echo '127.0.0.1 localhost' > /etc/hosts",
     "echo '::1 localhost' >> /etc/hosts",
+    "echo '127.0.1.1 {machine_name}' >> /etc/hosts",
     "fi",
 
     # Give proper permissions to /var/www
@@ -279,6 +282,9 @@ class DockerMachine(object):
 
         # Merge machine sysctls
         sysctl_parameters = {**sysctl_parameters, **machine.meta['sysctls'], **sysctl_first_interface}
+        # Remove interface-related sysctls on newer Docker Engine versions (these are added as driver_opts)
+        if version_gte(self._engine_version, "27.0.0"):
+            sysctl_parameters = dict(filter(lambda x: not IFACE_SYSCTL_RE.match(x[0]), sysctl_parameters.items()))
 
         volumes = {}
 
@@ -300,12 +306,12 @@ class DockerMachine(object):
 
         networking_config = None
         if first_machine_iface:
-            driver_opt = self._create_driver_opt(machine, first_machine_iface)
+            driver_opt = self._create_driver_opt(
+                machine, first_machine_iface, self._get_iface_sysctls(machine.get_sysctls(), first_machine_iface.num)
+            )
 
             networking_config = {
-                first_network.name: self.client.api.create_endpoint_config(
-                    driver_opt=driver_opt
-                )
+                first_network.name: self.client.api.create_endpoint_config(driver_opt=driver_opt)
             }
 
         container_name = self.get_container_name(machine.name, machine.lab.hash)
@@ -369,7 +375,10 @@ class DockerMachine(object):
         attached_networks = machine.api_object.attrs["NetworkSettings"]["Networks"]
 
         if interface.link.api_object.name not in attached_networks:
-            driver_opt = self._create_driver_opt(machine, interface)
+            driver_opt = self._create_driver_opt(
+                machine, interface, self._get_iface_sysctls(machine.get_sysctls(), interface.num)
+            )
+
             try:
                 interface.link.api_object.connect(
                     machine.api_object,
@@ -383,24 +392,46 @@ class DockerMachine(object):
                 else:
                     raise e
 
-    def _create_driver_opt(self, machine: Machine, interface: Interface) -> dict[str, str]:
-        """Create a dict containing the default network driver options for a device.
+    @staticmethod
+    def _get_iface_sysctls(sysctls: Dict[str, int], interface_num: int) -> Set[str]:
+        """Extract and transform sysctl settings specific to a particular network interface.
+
+            Args:
+                sysctls (Dict[str, int]): The sysctls for the device.
+                interface_num (int): The interface number for which to match and transform sysctl settings.
+
+            Returns:
+                Set[str]: A set containing transformed sysctl settings strings for the specified interface.
+                    Each string includes the setting in "key=value" format with the interface name replaced by 'IFNAME'.
+        """
+        iface_sysctls = set()
+        for sysctl_name, value in sysctls.items():
+            if re.match(rf"net\.ipv[4,6]\.(conf|neigh)\.eth{interface_num}", sysctl_name):
+                sysctl = f"{re.sub(rf'eth{interface_num}', 'IFNAME', sysctl_name)}={value}"
+                iface_sysctls.add(sysctl)
+        return iface_sysctls
+
+    def _create_driver_opt(self, machine: Machine, interface: Interface, iface_sysctls: Set[str]) -> Dict[str, str]:
+        """Create a dict containing the network driver options for a device.
 
         Args:
             machine (Kathara.model.Machine.Machine): The Kathara device to be attached to the interface.
             interface (Kathara.model.Interface.Interface): The interface to be attached to the device.
+            iface_sysctls (Set[str]): Sysctls in Docker driver_opt format for the interface.
 
         Returns:
-            dict[str, str]: A dict containing the default network driver options for a device.
+            Dict[str, str]: A dict containing the network driver options for a device.
         """
         driver_opt = {'kathara.iface': str(interface.num), 'kathara.link': interface.link.name}
         if version_gte(self._engine_version, "27.0.0"):
-            sysctl_opts = ["net.ipv4.conf.IFNAME.rp_filter=0"]
+            sysctl_opts = {"net.ipv4.conf.IFNAME.rp_filter=0"}
 
             if machine.is_ipv6_enabled():
-                sysctl_opts.extend(["net.ipv6.conf.IFNAME.disable_ipv6=0", "net.ipv6.conf.IFNAME.forwarding=1"])
+                sysctl_opts.update({"net.ipv6.conf.IFNAME.disable_ipv6=0", "net.ipv6.conf.IFNAME.forwarding=1"})
             else:
-                sysctl_opts.append("net.ipv6.conf.IFNAME.disable_ipv6=1")
+                sysctl_opts.add("net.ipv6.conf.IFNAME.disable_ipv6=1")
+
+            sysctl_opts.update(iface_sysctls)
 
             driver_opt["com.docker.network.endpoint.sysctls"] = ",".join(sysctl_opts)
 
diff --git a/src/Kathara/manager/docker/DockerManager.py b/src/Kathara/manager/docker/DockerManager.py
index 9f97c5f..ab19259 100644
--- a/src/Kathara/manager/docker/DockerManager.py
+++ b/src/Kathara/manager/docker/DockerManager.py
@@ -730,14 +730,17 @@ class DockerManager(IManager):
                     network = lab_networks[network_name]
                     link = reconstructed_lab.get_or_new_link(network.attrs["Labels"]["name"])
                     link.api_object = network
+                    iface_number = int(network_options["DriverOpts"]["kathara.iface"])
 
                     iface_mac_addr = None
                     if network_options["DriverOpts"] is not None:
                         if "kathara.mac_addr" in network_options["DriverOpts"]:
                             iface_mac_addr = network_options["DriverOpts"]["kathara.mac_addr"]
+                        if "com.docker.network.endpoint.sysctls" in network_options["DriverOpts"]:
+                            for s in network_options["DriverOpts"]["com.docker.network.endpoint.sysctls"].split(","):
+                                device.add_meta("sysctl", s.replace("IFNAME", f"eth{iface_number}"))
 
-                    device.add_interface(link, mac_address=iface_mac_addr,
-                                         number=int(network_options["DriverOpts"]["kathara.iface"]))
+                    device.add_interface(link, mac_address=iface_mac_addr, number=iface_number)
 
         return reconstructed_lab
 
@@ -800,12 +803,16 @@ class DockerManager(IManager):
                 link.api_object = deployed_networks_by_link_name[link.name]
                 iface_options = current_ifaces[link.name]
                 iface_mac_addr = None
+                iface_number = int(iface_options["DriverOpts"]["kathara.iface"])
+
                 if iface_options["DriverOpts"] is not None:
                     if "kathara.mac_addr" in iface_options["DriverOpts"]:
                         iface_mac_addr = iface_options["DriverOpts"]["kathara.mac_addr"]
+                    if "com.docker.network.endpoint.sysctls" in iface_options["DriverOpts"]:
+                        for s in iface_options["DriverOpts"]["com.docker.network.endpoint.sysctls"].split(","):
+                            device.add_meta("sysctl", s.replace("IFNAME", f"eth{iface_number}"))
 
-                device.add_interface(link, mac_address=iface_mac_addr,
-                                     number=int(iface_options["DriverOpts"]["kathara.iface"]))
+                device.add_interface(link, mac_address=iface_mac_addr, number=iface_number)
 
             for link in deleted_links:
                 device.remove_interface(link)
diff --git a/src/Kathara/manager/kubernetes/KubernetesMachine.py b/src/Kathara/manager/kubernetes/KubernetesMachine.py
index 9922a18..740cf5b 100644
--- a/src/Kathara/manager/kubernetes/KubernetesMachine.py
+++ b/src/Kathara/manager/kubernetes/KubernetesMachine.py
@@ -68,10 +68,11 @@ STARTUP_COMMANDS = [
     "if [ -d \"/hostlab/{machine_name}\" ]; then "
     "(cd /hostlab/{machine_name} && tar c .) | (cd / && tar xhf - --no-same-owner --no-same-permissions); fi",
 
-    # If /etc/hosts is not configured by the user, add the localhost mapping
+    # If /etc/hosts is not configured by the user, add the default mappings
     "if [ ! -s \"/etc/hosts\" ]; then "
     "echo '127.0.0.1 localhost' > /etc/hosts",
     "echo '::1 localhost' >> /etc/hosts",
+    "echo '127.0.1.1 {machine_name}' >> /etc/hosts",
     "fi",
 
     # Give proper permissions to /var/www
diff --git a/src/Kathara/version.py b/src/Kathara/version.py
index fb76ce6..ef2afc4 100644
--- a/src/Kathara/version.py
+++ b/src/Kathara/version.py
@@ -1,6 +1,6 @@
 from typing import Tuple
 
-CURRENT_VERSION = "3.7.8"
+CURRENT_VERSION = "3.7.9"
 
 
 def parse(version: str) -> Tuple:


Test patch:
diff --git a/tests/cli/lstart_command_test.py b/tests/cli/lstart_command_test.py
index 5b7f5ae..30aed07 100644
--- a/tests/cli/lstart_command_test.py
+++ b/tests/cli/lstart_command_test.py
@@ -178,7 +178,6 @@ def test_run_with_privileged(mock_setting_get_instance, mock_is_admin, mock_pars
     command = LstartCommand()
     with mock.patch.object(Lab, "add_option") as mock_add_option:
         command.run('.', ['--privileged'])
-        assert not mock_setting.open_terminals
         assert mock_setting.terminal == '/usr/bin/xterm'
         mock_parse_lab.assert_called_once_with(os.getcwd())
         mock_parse_dep.assert_called_once_with(os.getcwd())
diff --git a/tests/cli/vstart_command_test.py b/tests/cli/vstart_command_test.py
index 2770f8f..bc6bdb9 100644
--- a/tests/cli/vstart_command_test.py
+++ b/tests/cli/vstart_command_test.py
@@ -130,7 +130,6 @@ def test_run_with_privileged(mock_docker_manager, mock_manager_get_instance, moc
     command = VstartCommand()
     with mock.patch.object(Lab, "add_option") as mock_add_option:
         command.run('.', ['-n', 'pc1', '--privileged'])
-        assert not mock_setting.open_terminals
         assert mock_setting.terminal == '/usr/bin/xterm'
         assert mock_setting.device_shell == '/usr/bin/bash'
         mock_add_option.assert_any_call('hosthome_mount', None)
@@ -151,7 +150,6 @@ def test_run_with_privileged_no_root(mock_setting_get_instance, mock_is_admin,
     command = VstartCommand()
     with pytest.raises(PrivilegeError):
         command.run('.', ['-n', 'pc1', '--privileged'])
-        assert not mock_setting.open_terminals
         assert mock_setting.terminal == '/usr/bin/xterm'
         assert mock_setting.device_shell == '/usr/bin/bash'
 
diff --git a/tests/manager/docker/docker_link_test.py b/tests/manager/docker/docker_link_test.py
index d0b711b..5ab66aa 100644
--- a/tests/manager/docker/docker_link_test.py
+++ b/tests/manager/docker/docker_link_test.py
@@ -327,7 +327,7 @@ def test_create_shared_cds_between_labs(mock_get_current_user_name, mock_setting
 @mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink.create")
 def test_deploy_link(mock_create, docker_link, default_link):
     docker_link._deploy_link(("", default_link))
-    mock_create.called_once_with(default_link)
+    mock_create.assert_called_once_with(default_link)
 
 
 def test_deploy_link_bridge(docker_link, bridged_link):
@@ -407,45 +407,40 @@ def test_delete_link(docker_network, docker_link):
 @mock.patch("src.Kathara.manager.docker.DockerLink.DockerLink._undeploy_link")
 def test_undeploy_link(mock_undeploy_link, docker_link, docker_network):
     docker_link._undeploy_link(docker_network)
-    mock_undeploy_link.called_once_with(docker_network)
+    mock_undeploy_link.assert_called_once_with(docker_network)
 
 
 #
 # TEST: test_get_links_by_filters
 #
-@mock.patch("docker.models.networks.list")
-def test_get_links_by_filters(mock_network_list, docker_link):
+def test_get_links_by_filters(docker_link):
     docker_link.get_links_api_objects_by_filters("lab_hash_value", "link_name_value", "user_name_value")
-    filters = {"label": ["app=kathara", "lab_hash=lab_hash_value", "user=user_name_value", "name=link_name_value"]}
-    mock_network_list.called_once_with(filters=filters)
+    filters = {"label": ["app=kathara", "user=user_name_value", "lab_hash=lab_hash_value", "name=link_name_value"]}
+    docker_link.client.networks.list.assert_called_once_with(filters=filters, greedy=True)
 
 
-@mock.patch("docker.models.networks.list")
-def test_get_links_by_filters_empty_filters(mock_network_list, docker_link):
+def test_get_links_by_filters_empty_filters(docker_link):
     docker_link.get_links_api_objects_by_filters()
     filters = {"label": ["app=kathara"]}
-    mock_network_list.called_once_with(filters=filters)
+    docker_link.client.networks.list.assert_called_once_with(filters=filters, greedy=True)
 
 
-@mock.patch("docker.models.networks.list")
-def test_get_links_by_filters_only_lab_hash(mock_network_list, docker_link):
+def test_get_links_by_filters_only_lab_hash(docker_link):
     docker_link.get_links_api_objects_by_filters("lab_hash_value")
     filters = {"label": ["app=kathara", "lab_hash=lab_hash_value"]}
-    mock_network_list.called_once_with(filters=filters)
+    docker_link.client.networks.list.assert_called_once_with(filters=filters, greedy=True)
 
 
-@mock.patch("docker.models.networks.list")
-def test_get_links_by_filters_only_link_name(mock_network_list, docker_link):
+def test_get_links_by_filters_only_link_name(docker_link):
     docker_link.get_links_api_objects_by_filters(None, "link_name_value")
     filters = {"label": ["app=kathara", "name=link_name_value"]}
-    mock_network_list.called_once_with(filters=filters)
+    docker_link.client.networks.list.assert_called_once_with(filters=filters, greedy=True)
 
 
-@mock.patch("docker.models.networks.list")
-def test_get_links_by_filters_only_user_name(mock_network_list, docker_link):
+def test_get_links_by_filters_only_user_name(docker_link):
     docker_link.get_links_api_objects_by_filters(None, None, "user_name_value")
     filters = {"label": ["app=kathara", "user=user_name_value"]}
-    mock_network_list.called_once_with(filters=filters)
+    docker_link.client.networks.list.assert_called_once_with(filters=filters, greedy=True)
 
 
 #
@@ -464,7 +459,7 @@ def test_undeploy(mock_get_links_by_filters, mock_undeploy_link, mock_net1, mock
     lab.get_or_new_link("C")
     mock_get_links_by_filters.return_value = [mock_net1, mock_net2, mock_net3]
     docker_link.undeploy("lab_hash")
-    mock_get_links_by_filters.called_once_with(lab.hash)
+    mock_get_links_by_filters.assert_called_once_with(lab_hash="lab_hash")
     assert mock_net1.reload.call_count == 1
     assert mock_net2.reload.call_count == 1
     assert mock_net3.reload.call_count == 1
@@ -477,7 +472,7 @@ def test_undeploy_empty_lab(mock_get_links_by_filters, mock_undeploy_link, docke
     lab = Lab("Default scenario")
     mock_get_links_by_filters.return_value = []
     docker_link.undeploy("lab_hash")
-    mock_get_links_by_filters.called_once_with(lab.hash)
+    mock_get_links_by_filters.assert_called_once_with(lab_hash="lab_hash")
     assert not mock_undeploy_link.called
 
 
@@ -496,7 +491,7 @@ def test_undeploy_selected_links(mock_get_links_by_filters, mock_undeploy_link,
     mock_get_links_by_filters.return_value = [mock_net1, mock_net2]
 
     docker_link.undeploy("lab_hash", selected_links={"B"})
-    mock_get_links_by_filters.called_once_with(lab.hash)
+    mock_get_links_by_filters.assert_called_once_with(lab_hash="lab_hash")
     assert mock_net1.reload.call_count == 0
     assert mock_net2.reload.call_count == 1
     assert mock_undeploy_link.call_count == 1
@@ -517,7 +512,7 @@ def test_wipe(mock_get_links_by_filters, mock_net1, mock_net2, mock_net3, mock_u
     lab.get_or_new_link("C")
     mock_get_links_by_filters.return_value = [mock_net1, mock_net2, mock_net3]
     docker_link.wipe()
-    mock_get_links_by_filters.called_once_with(lab.hash)
+    mock_get_links_by_filters.assert_called_once_with(user=None)
     assert mock_net1.reload.call_count == 1
     assert mock_net2.reload.call_count == 1
     assert mock_net3.reload.call_count == 1
diff --git a/tests/manager/docker/docker_machine_test.py b/tests/manager/docker/docker_machine_test.py
index d43a9bd..9286260 100644
--- a/tests/manager/docker/docker_machine_test.py
+++ b/tests/manager/docker/docker_machine_test.py
@@ -599,14 +599,7 @@ def test_start_one_mac_addr(docker_machine, default_device, default_link, defaul
     docker_machine.client.api.exec_create.assert_called_once()
     docker_machine.client.api.exec_start.assert_called_once()
     docker_machine.client.api.exec_inspect.assert_called_once()
-    default_link_b.api_object.connect.assert_called_once_with(
-        default_device.api_object,
-        driver_opt={
-            'kathara.iface': str(iface_b.num), 'kathara.link': iface_b.link.name,
-            'com.docker.network.endpoint.sysctls': 'net.ipv4.conf.IFNAME.rp_filter=0,net.ipv6.conf.IFNAME.disable_ipv6=1',
-            'kathara.mac_addr': expected_mac_addr
-        }
-    )
+    default_link_b.api_object.connect.assert_called_once()
 
 
 def test_start_two_mac_addr(docker_machine, default_device, default_link, default_link_b, default_link_c):
@@ -628,22 +621,8 @@ def test_start_two_mac_addr(docker_machine, default_device, default_link, defaul
     docker_machine.client.api.exec_create.assert_called_once()
     docker_machine.client.api.exec_start.assert_called_once()
     docker_machine.client.api.exec_inspect.assert_called_once()
-    default_link_b.api_object.connect.assert_called_once_with(
-        default_device.api_object,
-        driver_opt={
-            'kathara.iface': str(iface_b.num), 'kathara.link': iface_b.link.name,
-            'com.docker.network.endpoint.sysctls': 'net.ipv4.conf.IFNAME.rp_filter=0,net.ipv6.conf.IFNAME.disable_ipv6=1',
-            'kathara.mac_addr': expected_mac_addr_1
-        }
-    )
-    default_link_c.api_object.connect.assert_called_once_with(
-        default_device.api_object,
-        driver_opt={
-            'kathara.iface': str(iface_c.num), 'kathara.link': iface_c.link.name,
-            'com.docker.network.endpoint.sysctls': 'net.ipv4.conf.IFNAME.rp_filter=0,net.ipv6.conf.IFNAME.disable_ipv6=1',
-            'kathara.mac_addr': expected_mac_addr_2
-        }
-    )
+    default_link_b.api_object.connect.assert_called_once()
+    default_link_c.api_object.connect.assert_called_once()
 
 
 def test_start_plugin_error_endpoint_start(default_device, docker_machine):
@@ -856,14 +835,7 @@ def test_connect_interface_mac_addr(docker_machine, default_device, default_link
     docker_machine.connect_interface(default_device, interface)
 
     assert not default_link.api_object.connect.called
-    default_link_b.api_object.connect.assert_called_once_with(
-        default_device.api_object,
-        driver_opt={
-            'kathara.iface': str(interface.num), 'kathara.link': interface.link.name,
-            'com.docker.network.endpoint.sysctls': 'net.ipv4.conf.IFNAME.rp_filter=0,net.ipv6.conf.IFNAME.disable_ipv6=1',
-            'kathara.mac_addr': expected_mac_addr
-        }
-    )
+    default_link_b.api_object.connect.assert_called_once()
 
 
 def test_connect_interface_plugin_error_network(default_device, default_link, docker_machine):
@@ -904,10 +876,18 @@ def test_connect_interface_plugin_api_error(default_device, default_link, docker
 #
 # TEST:_create_driver_opt
 #
+def order_driver_opt(driver_opt: dict[str, str]) -> dict[str, str]:
+    if "com.docker.network.endpoint.sysctls" in driver_opt:
+        sysctls = sorted(driver_opt["com.docker.network.endpoint.sysctls"].split(","))
+        driver_opt["com.docker.network.endpoint.sysctls"] = ",".join(sysctls)
+    return driver_opt
+
+
 def test_create_driver_opt_no_ipv6(docker_machine, default_device, default_link):
     interface = default_device.add_interface(default_link)
-    driver_opt = docker_machine._create_driver_opt(default_device, interface)
-    assert driver_opt == {
+    driver_opt = docker_machine._create_driver_opt(
+        default_device, interface, docker_machine._get_iface_sysctls(default_device.get_sysctls(), interface.num))
+    assert order_driver_opt(driver_opt) == {
         'kathara.iface': str(interface.num), 'kathara.link': interface.link.name,
         'com.docker.network.endpoint.sysctls': 'net.ipv4.conf.IFNAME.rp_filter=0,net.ipv6.conf.IFNAME.disable_ipv6=1',
     }
@@ -916,8 +896,10 @@ def test_create_driver_opt_no_ipv6(docker_machine, default_device, default_link)
 def test_create_driver_opt_mac_address(docker_machine, default_device, default_link):
     interface = default_device.add_interface(default_link)
     interface.mac_address = '00:00:00:00:00:01'
-    driver_opt = docker_machine._create_driver_opt(default_device, interface)
-    assert driver_opt == {
+    driver_opt = docker_machine._create_driver_opt(
+        default_device, interface, docker_machine._get_iface_sysctls(default_device.get_sysctls(), interface.num)
+    )
+    assert order_driver_opt(driver_opt) == {
         'kathara.iface': str(interface.num), 'kathara.link': interface.link.name,
         'com.docker.network.endpoint.sysctls': 'net.ipv4.conf.IFNAME.rp_filter=0,net.ipv6.conf.IFNAME.disable_ipv6=1',
         'kathara.mac_addr': '00:00:00:00:00:01'
@@ -927,18 +909,31 @@ def test_create_driver_opt_mac_address(docker_machine, default_device, default_l
 def test_create_driver_opt_ipv6(docker_machine, default_device, default_link):
     interface = default_device.add_interface(default_link)
     default_device.add_meta('ipv6', True)
-    driver_opt = docker_machine._create_driver_opt(default_device, interface)
-    assert driver_opt == {
+    driver_opt = docker_machine._create_driver_opt(
+        default_device, interface, docker_machine._get_iface_sysctls(default_device.get_sysctls(), interface.num)
+    )
+    assert order_driver_opt(driver_opt) == {
         'kathara.iface': str(interface.num), 'kathara.link': interface.link.name,
         'com.docker.network.endpoint.sysctls': 'net.ipv4.conf.IFNAME.rp_filter=0,net.ipv6.conf.IFNAME.disable_ipv6=0,net.ipv6.conf.IFNAME.forwarding=1',
     }
 
+def test_create_driver_opt_complete(docker_machine, default_device, default_link):
+    interface = default_device.add_interface(default_link)
+    default_device.add_meta('sysctl', "net.ipv6.neigh.eth0.anycast_delay=50")
+    driver_opt = docker_machine._create_driver_opt(
+        default_device, interface, docker_machine._get_iface_sysctls(default_device.get_sysctls(), interface.num))
+    assert order_driver_opt(driver_opt) == {
+        'kathara.iface': str(interface.num), 'kathara.link': interface.link.name,
+        'com.docker.network.endpoint.sysctls': 'net.ipv4.conf.IFNAME.rp_filter=0,net.ipv6.conf.IFNAME.disable_ipv6=1,net.ipv6.neigh.IFNAME.anycast_delay=50',
+    }
 
 def test_create_driver_old_docker(docker_machine, default_device, default_link):
     docker_machine._engine_version = '25.0.0'
     interface = default_device.add_interface(default_link)
-    driver_opt = docker_machine._create_driver_opt(default_device, interface)
-    assert driver_opt == {'kathara.iface': str(interface.num), 'kathara.link': interface.link.name}
+    driver_opt = docker_machine._create_driver_opt(
+        default_device, interface, docker_machine._get_iface_sysctls(default_device.get_sysctls(), interface.num)
+    )
+    assert order_driver_opt(driver_opt) == {'kathara.iface': str(interface.num), 'kathara.link': interface.link.name}
 
 
 #
diff --git a/tests/manager/kubernetes/kubernetes_link_test.py b/tests/manager/kubernetes/kubernetes_link_test.py
index 7bc85af..f7c6896 100644
--- a/tests/manager/kubernetes/kubernetes_link_test.py
+++ b/tests/manager/kubernetes/kubernetes_link_test.py
@@ -1,5 +1,6 @@
 import copy
 import sys
+from collections import namedtuple
 from unittest import mock
 from unittest.mock import Mock, call
 
@@ -38,14 +39,10 @@ class FakeManager(object):
         return True
 
 
-EXPECTED_NETWORK_ID = 4694369
-NETWORK_IDS_SINGLE = {
-    4694369: 1
-}
-NETWORK_IDS_DOUBLE = {
-    4694369: 1,
-    4694370: 1
-}
+FakeLinkData = namedtuple('FakeLinkData', ['metadata'])
+FakeLinkMetadata = namedtuple('FakeLinkMetadata', ['name'])
+
+EXPECTED_NETWORK_ID = 1362434
 
 
 #
@@ -57,6 +54,21 @@ def default_link():
     return Link(Lab("default_scenario"), "A")
 
 
+@pytest.fixture()
+def NETWORK_IDS_SINGLE():
+    return {
+        1362434: 1
+    }
+
+
+@pytest.fixture()
+def NETWORK_IDS_DOUBLE():
+    return {
+        1362434: 1,
+        1362435: 1
+    }
+
+
 @pytest.fixture()
 @mock.patch("kubernetes.client.api.custom_objects_api.CustomObjectsApi")
 @mock.patch("kubernetes.client.Configuration")
@@ -177,14 +189,14 @@ def test_build_definition(mock_setting_get_instance, default_link, kubernetes_li
 # TEST: _get_network_id
 #
 def test_get_network_id(kubernetes_link):
-    actual_id = kubernetes_link._get_network_id("a", 0)
+    actual_id = kubernetes_link._get_network_id("A", 0)
 
     assert actual_id == EXPECTED_NETWORK_ID
 
 
 def test_get_network_id_offset(kubernetes_link):
     expected_id = EXPECTED_NETWORK_ID + 1
-    actual_id = kubernetes_link._get_network_id("a", 1)
+    actual_id = kubernetes_link._get_network_id("A", 1)
 
     assert actual_id == expected_id
 
@@ -195,26 +207,26 @@ def test_get_network_id_offset(kubernetes_link):
 def test_get_unique_network_id(kubernetes_link):
     network_ids = {}
 
-    actual_network_id = kubernetes_link._get_unique_network_id("a", network_ids)
+    actual_network_id = kubernetes_link._get_unique_network_id("A", network_ids)
 
     assert actual_network_id == EXPECTED_NETWORK_ID
-    assert network_ids == {4694369: 1}
+    assert network_ids == {1362434: 1}
 
 
-def test_get_unique_network_id_collision(kubernetes_link):
+def test_get_unique_network_id_collision(kubernetes_link, NETWORK_IDS_SINGLE):
     expected_network_id = EXPECTED_NETWORK_ID + 1
-    actual_network_id = kubernetes_link._get_unique_network_id("a", NETWORK_IDS_SINGLE)
+    actual_network_id = kubernetes_link._get_unique_network_id("A", NETWORK_IDS_SINGLE)
 
     assert actual_network_id == expected_network_id
-    assert NETWORK_IDS_SINGLE == {4694369: 1, expected_network_id: 1}
+    assert NETWORK_IDS_SINGLE == {1362434: 1, expected_network_id: 1}
 
 
-def test_get_unique_network_id_double_collision(kubernetes_link):
+def test_get_unique_network_id_double_collision(kubernetes_link, NETWORK_IDS_DOUBLE):
     expected_network_id = EXPECTED_NETWORK_ID + 2
-    actual_network_id = kubernetes_link._get_unique_network_id("a", NETWORK_IDS_DOUBLE)
+    actual_network_id = kubernetes_link._get_unique_network_id("A", NETWORK_IDS_DOUBLE)
 
     assert actual_network_id == expected_network_id
-    assert NETWORK_IDS_DOUBLE == {4694369: 1, 4694370: 1, expected_network_id: 1}
+    assert NETWORK_IDS_DOUBLE == {1362434: 1, 1362435: 1, expected_network_id: 1}
 
 
 #
@@ -292,13 +304,13 @@ def test_create(mock_setting_get_instance, kubernetes_link, default_link):
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesLink.KubernetesLink.create")
 def test_deploy_link(mock_create, kubernetes_link, default_link):
     kubernetes_link._deploy_link({}, ("", default_link))
-    mock_create.called_once_with(default_link, EXPECTED_NETWORK_ID)
+    mock_create.assert_called_once_with(default_link, EXPECTED_NETWORK_ID)
 
 
 @mock.patch("src.Kathara.manager.kubernetes.KubernetesLink.KubernetesLink.create")
-def test_deploy_link_collision(mock_create, kubernetes_link, default_link):
+def test_deploy_link_collision(mock_create, kubernetes_link, default_link, NETWORK_IDS_SINGLE):
     kubernetes_link._deploy_link(NETWORK_IDS_SINGLE, ("", default_link))
-    mock_create.called_once_with(default_link, EXPECTED_NETWORK_ID + 1)
+    mock_create.assert_called_once_with(default_link, EXPECTED_NETWORK_ID + 1)
 
 
 #
@@ -438,7 +450,7 @@ def test_get_links_by_filters(kubernetes_link):
     kubernetes_link.get_links_api_objects_by_filters("lab_hash_value", "link_name_value")
     filters = ["app=kathara", "name=link_name_value"]
 
-    kubernetes_link.client.list_namespaced_custom_object.called_once_with(
+    kubernetes_link.client.list_namespaced_custom_object.assert_called_once_with(
         group="k8s.cni.cncf.io",
         version="v1",
         namespace="lab_hash_value",
@@ -449,14 +461,27 @@ def test_get_links_by_filters(kubernetes_link):
 
 
 def test_get_links_by_filters_empty_filters(kubernetes_link):
+    ld1 = FakeLinkData(metadata=FakeLinkMetadata(name='lab_hash_value1'))
+    ld2 = FakeLinkData(metadata=FakeLinkMetadata(name='lab_hash_value2'))
+    kubernetes_link.kubernetes_namespace.get_all.return_value = [ld1, ld2]
+
     kubernetes_link.get_links_api_objects_by_filters()
 
     filters = ["app=kathara"]
 
-    kubernetes_link.client.list_namespaced_custom_object.called_once_with(
+    kubernetes_link.client.list_namespaced_custom_object.assert_any_call(
         group="k8s.cni.cncf.io",
         version="v1",
-        namespace="lab_hash_value",
+        namespace="lab_hash_value1",
+        plural="network-attachment-definitions",
+        label_selector=",".join(filters),
+        timeout_seconds=9999
+    )
+
+    kubernetes_link.client.list_namespaced_custom_object.assert_any_call(
+        group="k8s.cni.cncf.io",
+        version="v1",
+        namespace="lab_hash_value2",
         plural="network-attachment-definitions",
         label_selector=",".join(filters),
         timeout_seconds=9999
@@ -466,9 +491,9 @@ def test_get_links_by_filters_empty_filters(kubernetes_link):
 def test_get_links_by_filters_only_lab_hash(kubernetes_link):
     kubernetes_link.get_links_api_objects_by_filters("lab_hash_value")
 
-    filters = ["app=kathara", "lab_hash=lab_hash_value"]
+    filters = ["app=kathara"]
 
-    kubernetes_link.client.list_namespaced_custom_object.called_once_with(
+    kubernetes_link.client.list_namespaced_custom_object.assert_called_once_with(
         group="k8s.cni.cncf.io",
         version="v1",
         namespace="lab_hash_value",
@@ -479,11 +504,14 @@ def test_get_links_by_filters_only_lab_hash(kubernetes_link):
 
 
 def test_get_links_by_filters_only_link_name(kubernetes_link):
+    ld = FakeLinkData(metadata=FakeLinkMetadata(name='lab_hash_value'))
+    kubernetes_link.kubernetes_namespace.get_all.return_value = [ld]
+
     kubernetes_link.get_links_api_objects_by_filters(None, "link_name_value")
 
     filters = ["app=kathara", "name=link_name_value"]
 
-    kubernetes_link.client.list_namespaced_custom_object.called_once_with(
+    kubernetes_link.client.list_namespaced_custom_object.assert_called_once_with(
         group="k8s.cni.cncf.io",
         version="v1",
         namespace="lab_hash_value",
@@ -507,7 +535,7 @@ def test_undeploy(mock_get_links_by_filters, mock_undeploy_link, kubernetes_netw
 
     kubernetes_link.undeploy("lab_hash")
 
-    mock_get_links_by_filters.called_once_with(lab.hash)
+    mock_get_links_by_filters.assert_called_once_with(lab_hash="lab_hash")
     assert mock_undeploy_link.call_count == 3
 
 
@@ -520,7 +548,7 @@ def test_undeploy_empty_lab(mock_get_links_by_filters, mock_undeploy_link, kuber
 
     kubernetes_link.undeploy("lab_hash")
 
-    mock_get_links_by_filters.called_once_with(lab.hash)
+    mock_get_links_by_filters.assert_called_once_with(lab_hash="lab_hash")
     assert not mock_undeploy_link.called
 
 
@@ -541,7 +569,7 @@ def test_undeploy_selected_links(mock_get_links_by_filters, mock_undeploy_link,
 
     kubernetes_link.undeploy("lab_hash", selected_links={"netprefix-b"})
 
-    mock_get_links_by_filters.called_once_with(lab.hash)
+    mock_get_links_by_filters.assert_called_once_with(lab_hash="lab_hash")
     assert mock_undeploy_link.call_count == 1
 
 
@@ -560,7 +588,7 @@ def test_wipe(mock_get_links_by_filters, mock_undeploy_link, kubernetes_link, ku
 
     kubernetes_link.wipe()
 
-    mock_get_links_by_filters.called_once_with(lab.hash)
+    mock_get_links_by_filters.assert_called_once_with()
     assert mock_undeploy_link.call_count == 3