diff --git a/.unreleased/start_telio_with_tun_natlab_test b/.unreleased/start_telio_with_tun_natlab_test new file mode 100644 index 000000000..e69de29bb diff --git a/Cargo.lock b/Cargo.lock index f3b4fd61a..6bfc249de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2602,7 +2602,7 @@ dependencies = [ [[package]] name = "neptun" version = "0.6.0" -source = "git+https://github.com/NordSecurity/neptun.git?tag=v1.0.0#db16cadc28efa3c915435d85e5b834834a867a69" +source = "git+https://github.com/NordSecurity/neptun.git?tag=v1.0.2#b3b66795d748aa1720352a7b92204cc56d1c4daa" dependencies = [ "aead", "base64 0.13.1", diff --git a/Cargo.toml b/Cargo.toml index 447e129b8..9851fc321 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,7 +173,7 @@ windows = { version = "0.56", features = [ "Win32_NetworkManagement_IpHelper", ] } -neptun = { git = "https://github.com/NordSecurity/neptun.git", tag = "v1.0.0", features = ["device"] } +neptun = { git = "https://github.com/NordSecurity/neptun.git", tag = "v1.0.2", features = ["device"] } x25519-dalek = { version = "2.0.1", features = ["reusable_secrets", "static_secrets"] } diff --git a/crates/telio-wg/src/adapter/neptun.rs b/crates/telio-wg/src/adapter/neptun.rs index 891d0d27c..b3ee54d14 100644 --- a/crates/telio-wg/src/adapter/neptun.rs +++ b/crates/telio-wg/src/adapter/neptun.rs @@ -63,8 +63,6 @@ impl NepTUN { protect: socket_pool, firewall_process_inbound_callback, firewall_process_outbound_callback, - #[cfg(target_os = "linux")] - uapi_fd: -1, }; let device = match tun { diff --git a/nat-lab/tests/telio.py b/nat-lab/tests/telio.py index 858f00b47..71f96850e 100644 --- a/nat-lab/tests/telio.py +++ b/nat-lab/tests/telio.py @@ -519,6 +519,19 @@ async def simple_start(self): if isinstance(self.get_router(), LinuxRouter): await self.get_proxy().set_fwmark(int(LINUX_FWMARK_VALUE)) + async def create_tun(self, tun_name: str) -> int: + return await self.get_proxy().create_tun(tun_name) + + async def start_with_tun(self, tun: int, tun_name): + await self.get_proxy().start_with_tun( + private_key=self._node.private_key, + adapter=self._adapter_type, + tun=tun, + ) + if isinstance(self.get_router(), LinuxRouter): + self.get_router().set_interface_name(tun_name) + await self.get_proxy().set_fwmark(int(LINUX_FWMARK_VALUE)) + async def wait_for_state_peer( self, public_key, diff --git a/nat-lab/tests/test_start_with_tun.py b/nat-lab/tests/test_start_with_tun.py new file mode 100644 index 000000000..21f74e6ce --- /dev/null +++ b/nat-lab/tests/test_start_with_tun.py @@ -0,0 +1,38 @@ +from contextlib import AsyncExitStack +from helpers import SetupParameters, ping_between_all_nodes, setup_mesh_nodes +from typing import List +from utils.bindings import default_features, TelioAdapterType +from utils.connection_util import ConnectionTag + + +def _generate_setup_parameters( + conn_tags: List[ConnectionTag], +) -> List[SetupParameters]: + return [ + SetupParameters( + connection_tag=conn_tag, + adapter_type_override=TelioAdapterType.BORING_TUN, + features=default_features(), + fingerprint=f"{conn_tag}", + ) + for conn_tag in conn_tags + ] + + +async def test_start_with_tun() -> None: + setup_params = _generate_setup_parameters([ + ConnectionTag.DOCKER_CONE_CLIENT_1, + ConnectionTag.DOCKER_CONE_CLIENT_2, + ]) + + async with AsyncExitStack() as exit_stack: + env = await setup_mesh_nodes(exit_stack, setup_params) + alpha_client, _ = env.clients + alpha, _ = env.nodes + + await alpha_client.stop_device() + tun = await alpha_client.create_tun("tun11") + await alpha_client.start_with_tun(tun, "tun11") + await alpha_client.set_meshnet_config(env.api.get_meshnet_config(alpha.id)) + await ping_between_all_nodes(env) + await alpha_client.stop_device() diff --git a/nat-lab/tests/uniffi/libtelio_proxy.py b/nat-lab/tests/uniffi/libtelio_proxy.py index 3737e8820..f93c4ecac 100644 --- a/nat-lab/tests/uniffi/libtelio_proxy.py +++ b/nat-lab/tests/uniffi/libtelio_proxy.py @@ -99,6 +99,16 @@ def start_named(self, private_key, adapter, name: str): lambda r: r.start_named(private_key, adapter.value, name) ) + @move_to_async_thread + def create_tun(self, tun_name: str) -> int: + return self._handle_remote_error(lambda r: r.create_tun(tun_name)) + + @move_to_async_thread + def start_with_tun(self, private_key, adapter, tun: int): + self._handle_remote_error( + lambda r: r.start_with_tun(private_key, adapter.value, tun) + ) + @move_to_async_thread def set_fwmark(self, fwmark: int): self._handle_remote_error(lambda r: r.set_fwmark(fwmark)) diff --git a/nat-lab/tests/uniffi/libtelio_remote.py b/nat-lab/tests/uniffi/libtelio_remote.py index be4404627..e921341f5 100644 --- a/nat-lab/tests/uniffi/libtelio_remote.py +++ b/nat-lab/tests/uniffi/libtelio_remote.py @@ -1,8 +1,10 @@ import datetime +import fcntl import os import Pyro5.api # type: ignore import Pyro5.server # type: ignore import shutil +import struct import sys import telio_bindings as libtelio # type: ignore # pylint: disable=import-error import time @@ -109,6 +111,27 @@ def start_named(self, private_key, adapter, name: str): private_key, libtelio.TelioAdapterType(adapter), name ) + @serialize_error + def create_tun(self, tun_name: str) -> int: + # Constants for TUN/TAP interface creation (from Linux's if_tun.h) + TUNSETIFF = 0x400454CA + IFF_TUN = 0x0001 + IFF_MULTI_QUEUE = 0x0100 + IFF_NO_PI = 0x1000 + tun_name_bytes = tun_name.encode("ascii") + tun_fd = os.open("/dev/net/tun", os.O_RDWR) + # '16sH' means we need to pass 16-byte string (interface name) and 2-byte short (flags) + ifr = struct.pack("16sH", tun_name_bytes, IFF_TUN | IFF_MULTI_QUEUE | IFF_NO_PI) + fcntl.ioctl(tun_fd, TUNSETIFF, ifr) + + return tun_fd + + @serialize_error + def start_with_tun(self, private_key, adapter, tun: int): + self._libtelio.start_with_tun( + private_key, libtelio.TelioAdapterType(adapter), tun + ) + @serialize_error def set_fwmark(self, fwmark: int): self._libtelio.set_fwmark(fwmark) diff --git a/nat-lab/tests/utils/router/linux_router.py b/nat-lab/tests/utils/router/linux_router.py index 2dd8de2c1..205e2666d 100644 --- a/nat-lab/tests/utils/router/linux_router.py +++ b/nat-lab/tests/utils/router/linux_router.py @@ -61,6 +61,9 @@ async def setup_interface(self, addresses: List[str]) -> None: ["ip", "link", "set", "up", "dev", self._interface_name] ).execute() + def set_interface_name(self, new_interface_name: str) -> None: + self._interface_name = new_interface_name + async def deconfigure_interface(self, addresses: List[str]) -> None: for address in addresses: await self._connection.create_process( diff --git a/nat-lab/tests/utils/router/mac_router.py b/nat-lab/tests/utils/router/mac_router.py index 9d1a0cb66..c7e8e7d3b 100644 --- a/nat-lab/tests/utils/router/mac_router.py +++ b/nat-lab/tests/utils/router/mac_router.py @@ -180,3 +180,8 @@ async def block_udp_port( @asynccontextmanager async def reset_upnpd(self) -> AsyncIterator: yield + + def set_interface_name( + self, new_interface_name: str # pylint: disable=unused-argument + ) -> None: + pass diff --git a/nat-lab/tests/utils/router/router.py b/nat-lab/tests/utils/router/router.py index 96e38ac64..7b685d25f 100644 --- a/nat-lab/tests/utils/router/router.py +++ b/nat-lab/tests/utils/router/router.py @@ -70,6 +70,10 @@ def check_ip_address(self, address: str) -> Optional[IPProto]: def get_interface_name(self) -> str: pass + @abstractmethod + def set_interface_name(self, new_interface_name: str) -> None: + pass + @abstractmethod async def setup_interface(self, addresses: List[str]) -> None: pass diff --git a/nat-lab/tests/utils/router/windows_router.py b/nat-lab/tests/utils/router/windows_router.py index 926afabb8..0a58e15b9 100644 --- a/nat-lab/tests/utils/router/windows_router.py +++ b/nat-lab/tests/utils/router/windows_router.py @@ -324,3 +324,8 @@ async def block_udp_port( @asynccontextmanager async def reset_upnpd(self) -> AsyncIterator: yield + + def set_interface_name( + self, new_interface_name: str # pylint: disable=unused-argument + ) -> None: + pass