diff --git a/.unreleased/LLT-5771 b/.unreleased/LLT-5771 new file mode 100644 index 000000000..e69de29bb diff --git a/Cargo.lock b/Cargo.lock index 61793f4d6..a69ba2d52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6094,7 +6094,7 @@ dependencies = [ [[package]] name = "wireguard-nt" version = "1.0.0" -source = "git+https://github.com/NordSecurity/wireguard-nt-rust-wrapper?tag=v1.0.5#8ec27423762f110ed4aa243e4b0bd8f7fe599834" +source = "git+https://github.com/NordSecurity/wireguard-nt-rust-wrapper?branch=unplugged_adapter_on_windows_experiment#b0774c0629ea0b63836da4462ce43f7b73d83f2e" dependencies = [ "bitflags 1.3.2", "ipnet", diff --git a/crates/telio-wg/Cargo.toml b/crates/telio-wg/Cargo.toml index d147f35a6..251007276 100644 --- a/crates/telio-wg/Cargo.toml +++ b/crates/telio-wg/Cargo.toml @@ -56,7 +56,7 @@ cc.workspace = true sha2.workspace = true winapi = { workspace = true, features = ["nldef"] } -wireguard-nt = { git = "https://github.com/NordSecurity/wireguard-nt-rust-wrapper", tag = "v1.0.5" } +wireguard-nt = { git = "https://github.com/NordSecurity/wireguard-nt-rust-wrapper", branch = "unplugged_adapter_on_windows_experiment" } wg-go-rust-wrapper = { path = "../../wireguard-go-rust-wrapper" } diff --git a/crates/telio-wg/src/adapter/windows_native_wg.rs b/crates/telio-wg/src/adapter/windows_native_wg.rs index 8dc78bc4b..85e09fbe6 100644 --- a/crates/telio-wg/src/adapter/windows_native_wg.rs +++ b/crates/telio-wg/src/adapter/windows_native_wg.rs @@ -159,19 +159,21 @@ impl WindowsNativeWg { )))); } - let mut os_error = IOError::from_raw_os_error(0); - for _ in 0..5 { - if wg_dev.adapter.up() { - telio_log_debug!("Adapter state set to up"); - return Ok(wg_dev); - } - os_error = IOError::last_os_error(); - telio_log_warn!("Failed to set adapter state to up, last error: {os_error:?}"); - sleep(Duration::from_millis(200)); - } - Err(AdapterError::WindowsNativeWg(Error::Fail(format!( - "Failed to set adapter's state to up, last error: {os_error:?}", - )))) + Ok(wg_dev) + + //let mut os_error = IOError::from_raw_os_error(0); + //for _ in 0..5 { + // if wg_dev.adapter.up() { + // telio_log_debug!("Adapter state set to up"); + // return Ok(wg_dev); + // } + // os_error = IOError::last_os_error(); + // telio_log_warn!("Failed to set adapter state to up, last error: {os_error:?}"); + // sleep(Duration::from_millis(200)); + //} + //Err(AdapterError::WindowsNativeWg(Error::Fail(format!( + // "Failed to set adapter's state to up, last error: {os_error:?}", + //)))) } fn get_config_uapi(&self) -> Response { @@ -208,21 +210,50 @@ impl Adapter for WindowsNativeWg { async fn send_uapi_cmd(&self, cmd: &Cmd) -> Result { match cmd { Get => Ok(self.get_config_uapi()), - Set(set_cfg) => match self.adapter.set_config_uapi(set_cfg) { - Ok(()) => { - // Remember last successfully set configuration - if let Ok(mut interface_watcher) = - (self as &WindowsNativeWg).watcher.clone().lock() - { - interface_watcher.set_last_known_configuration(set_cfg); + Set(set_cfg) => { + // If we have any peers added -> bring the adapter up + if set_cfg.peers.len() > 0 && !self.adapter.is_up() { + if self.adapter.up() { + telio_log_info!("Adapter brought up succesfully"); + } else { + let os_error = IOError::last_os_error(); + telio_log_warn!("Failed to set adapter state to up, last error: {os_error:?}"); + return Err(os_error.into()); } - - Ok(self.get_config_uapi()) } - Err(_err) => Ok(Response { - errno: 1, - interface: None, - }), + + let (resp, peer_cnt) = match self.adapter.set_config_uapi(set_cfg) { + Ok(()) => { + // Remember last successfully set configuration + if let Ok(mut interface_watcher) = + (self as &WindowsNativeWg).watcher.clone().lock() + { + interface_watcher.set_last_known_configuration(set_cfg); + } + + let resp = self.get_config_uapi(); + let peer_cnt = resp.interface.as_ref().map(|i| i.peers.len()); + (Ok(resp), peer_cnt) + } + Err(_err) => (Ok(Response { + errno: 1, + interface: None, + }), + None) + }; + + // If all of the peers has been removed -> bring the adapter down + if peer_cnt.map(|p| p == 0).unwrap_or(false) && self.adapter.is_up() { + if self.adapter.down() { + telio_log_info!("Adapter brought down succesfully"); + } else { + let os_error = IOError::last_os_error(); + telio_log_warn!("Failed to set adapter state to down, last error: {os_error:?}"); + return Err(os_error.into()); + } + }; + + resp }, } } diff --git a/crates/telio-wg/src/wg.rs b/crates/telio-wg/src/wg.rs index 451ec56d3..e28449c62 100644 --- a/crates/telio-wg/src/wg.rs +++ b/crates/telio-wg/src/wg.rs @@ -656,25 +656,25 @@ impl State { // but will properly resume work after that. In order to determine a non-recoverable failure // such as a malicious removal, we need to count the successive failed calls. // If a certain threshold is reached, cleanup the network config and notify the app about connection loss. - if 0 == ret.errno { - self.uapi_fail_counter = 0; - } else { - self.uapi_fail_counter += 1; - } - - if self.uapi_fail_counter >= MAX_UAPI_FAIL_COUNT && ret.interface.is_none() { - if let Some(libtelio_event) = &self.libtelio_event { - let err_event = LibtelioEvent::builder::() - .set(EventMsg::from("Interface gone")) - .set(ErrorCode::Unknown) - .set(ErrorLevel::Critical) - .build(); - if let Some(err_event) = err_event { - let _ = libtelio_event.send(Box::new(err_event)); - } - } - return Err(Error::InternalError("Interface gone")); - } + //if 0 == ret.errno { + // self.uapi_fail_counter = 0; + //} else { + // self.uapi_fail_counter += 1; + //} + + //if self.uapi_fail_counter >= MAX_UAPI_FAIL_COUNT && ret.interface.is_none() { + // if let Some(libtelio_event) = &self.libtelio_event { + // let err_event = LibtelioEvent::builder::() + // .set(EventMsg::from("Interface gone")) + // .set(ErrorCode::Unknown) + // .set(ErrorLevel::Critical) + // .build(); + // if let Some(err_event) = err_event { + // let _ = libtelio_event.send(Box::new(err_event)); + // } + // } + // return Err(Error::InternalError("Interface gone")); + //} Ok(ret) } diff --git a/nat-lab/tests/test_dns.py b/nat-lab/tests/test_dns.py index 0a391e066..cf60e880a 100644 --- a/nat-lab/tests/test_dns.py +++ b/nat-lab/tests/test_dns.py @@ -4,15 +4,22 @@ import config import itertools import pytest -import re import timeouts from config import LIBTELIO_DNS_IPV4, LIBTELIO_DNS_IPV6 from contextlib import AsyncExitStack from helpers import SetupParameters, setup_api, setup_environment, setup_mesh_nodes from typing import List from utils.bindings import default_features, FeatureDns, TelioAdapterType -from utils.connection_tracker import ConnectionLimits -from utils.connection_util import ConnectionTag, generate_connection_tracker_config +from utils.connection_tracker import ( + ConnectionLimits, + ConnectionTrackerConfig, + FiveTuple, +) +from utils.connection_util import ( + ConnectionTag, + generate_connection_tracker_config, + LAN_ADDR_MAP, +) from utils.dns import query_dns, query_dns_port from utils.process import ProcessExecError from utils.router import IPStack @@ -325,7 +332,7 @@ async def test_vpn_dns(alpha_ip_stack: IPStack) -> None: "www.microsoft.com", ["canonical name"], dns_server_address, - "-q=CNAME", + ["-q=CNAME"], ) # Turn off the module and see if it worked @@ -634,9 +641,19 @@ async def test_dns_duplicate_requests_on_multiple_forward_servers() -> None: SetupParameters( connection_tag=ConnectionTag.DOCKER_CONE_CLIENT_1, ip_stack=IPStack.IPv4v6, - connection_tracker_config=generate_connection_tracker_config( - ConnectionTag.DOCKER_CONE_CLIENT_1 - ), + connection_tracker_config=[ + ConnectionTrackerConfig( + key="dns-limiter", + limits=ConnectionLimits( + 1, 1 + ), # Require strictly one DNS connection + target=FiveTuple( + protocol="udp", + src_ip=LAN_ADDR_MAP[ConnectionTag.DOCKER_CONE_CLIENT_1], + dst_port=53, + ), + ) + ], derp_servers=[], ) ], @@ -644,38 +661,10 @@ async def test_dns_duplicate_requests_on_multiple_forward_servers() -> None: connection_alpha, *_ = [conn.connection for conn in env.connections] client_alpha, *_ = env.clients - process = await exit_stack.enter_async_context( - connection_alpha.create_process([ - "tcpdump", - "--immediate-mode", - "-ni", - "eth0", - "udp", - "and", - "port", - "53", - "-l", - ]).run() - ) - await asyncio.sleep(1) - await client_alpha.enable_magic_dns([FIRST_DNS_SERVER, SECOND_DNS_SERVER]) - await asyncio.sleep(1) - - await query_dns(connection_alpha, "google.com") - await asyncio.sleep(1) - - tcpdump_stdout = process.get_stdout() - tcpdump_stderr = process.get_stderr() - results = set(re.findall( - r".* IP .* > (?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\.\d{1,5}: .* A\?.*", - tcpdump_stdout, - )) # fmt: skip - - assert results in ( - {FIRST_DNS_SERVER}, - {SECOND_DNS_SERVER}, - ), f"tcpdump stdout:\n{tcpdump_stdout}\ntcpdump stderr:\n{tcpdump_stderr}" + await query_dns( + connection_alpha, "google.com", options=["-timeout=1", "-type=a"] + ) @pytest.mark.asyncio diff --git a/nat-lab/tests/test_dns_through_exit.py b/nat-lab/tests/test_dns_through_exit.py index 54d63dda5..c5416c1e8 100644 --- a/nat-lab/tests/test_dns_through_exit.py +++ b/nat-lab/tests/test_dns_through_exit.py @@ -169,7 +169,7 @@ async def disable_path(addr): "google.com", dns_server=dns_server_address_local, expected_output=["Name:.*google.com.*Address"], - options="-timeout=5", + options=["-timeout=5"], ) await client_alpha.disconnect_from_exit_node(exit_node.public_key) diff --git a/nat-lab/tests/utils/dns.py b/nat-lab/tests/utils/dns.py index fe8eb46a9..75703ba14 100644 --- a/nat-lab/tests/utils/dns.py +++ b/nat-lab/tests/utils/dns.py @@ -9,14 +9,17 @@ async def query_dns( host_name: str, expected_output: Optional[List[str]] = None, dns_server: Optional[str] = None, - options: Optional[str] = None, + options: Optional[List[str]] = None, ) -> None: - response = await connection.create_process([ - "nslookup", - options if options else "-timeout=1", - host_name, - dns_server if dns_server else LIBTELIO_DNS_IPV4, - ]).execute() + args = ["nslookup"] + if options: + args += options + else: + args.append("-timeout=1") + args.append(host_name) + args.append(dns_server if dns_server else LIBTELIO_DNS_IPV4) + + response = await connection.create_process(args).execute() dns_output = response.get_stdout() if expected_output: for expected_str in expected_output: