Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unplugged adapter on windows experiment #940

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .unreleased/LLT-5771
Empty file.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/telio-wg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand Down
83 changes: 57 additions & 26 deletions crates/telio-wg/src/adapter/windows_native_wg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -208,21 +210,50 @@ impl Adapter for WindowsNativeWg {
async fn send_uapi_cmd(&self, cmd: &Cmd) -> Result<Response, AdapterError> {
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
},
}
}
Expand Down
38 changes: 19 additions & 19 deletions crates/telio-wg/src/wg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<LibtelioError>()
.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::<LibtelioError>()
// .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)
}
Expand Down
65 changes: 27 additions & 38 deletions nat-lab/tests/test_dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -634,48 +641,30 @@ 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=[],
)
],
)
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<dest_ip>\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
Expand Down
2 changes: 1 addition & 1 deletion nat-lab/tests/test_dns_through_exit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
17 changes: 10 additions & 7 deletions nat-lab/tests/utils/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading