Skip to content

Commit 5881da9

Browse files
committed
Test leaking TCP/UDP/ICMP packets in split tunnel
1 parent 4219b70 commit 5881da9

File tree

16 files changed

+725
-162
lines changed

16 files changed

+725
-162
lines changed

mullvad-management-interface/src/client.rs

-4
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,6 @@ impl MullvadProxyClient {
631631
Ok(())
632632
}
633633

634-
//#[cfg(target_os = "windows")]
635634
pub async fn add_split_tunnel_app<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
636635
let path = path.as_ref().to_str().ok_or(Error::PathMustBeUtf8)?;
637636
self.0
@@ -641,7 +640,6 @@ impl MullvadProxyClient {
641640
Ok(())
642641
}
643642

644-
//#[cfg(target_os = "windows")]
645643
pub async fn remove_split_tunnel_app<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
646644
let path = path.as_ref().to_str().ok_or(Error::PathMustBeUtf8)?;
647645
self.0
@@ -651,7 +649,6 @@ impl MullvadProxyClient {
651649
Ok(())
652650
}
653651

654-
//#[cfg(target_os = "windows")]
655652
pub async fn clear_split_tunnel_apps(&mut self) -> Result<()> {
656653
self.0
657654
.clear_split_tunnel_apps(())
@@ -660,7 +657,6 @@ impl MullvadProxyClient {
660657
Ok(())
661658
}
662659

663-
//#[cfg(target_os = "windows")]
664660
pub async fn set_split_tunnel_state(&mut self, state: bool) -> Result<()> {
665661
self.0
666662
.set_split_tunnel_state(state)

test/Cargo.lock

+34-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ members = [
1212
"test-runner",
1313
"test-rpc",
1414
"socks-server",
15-
"am-i-mullvad",
15+
"connection-checker",
1616
]
1717

1818
[workspace.lints.rust]

test/am-i-mullvad/src/main.rs

-33
This file was deleted.

test/build.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ if [[ $TARGET == x86_64-unknown-linux-gnu ]]; then
1717
-e CARGO_HOME=/root/.cargo/registry \
1818
-e CARGO_TARGET_DIR=/src/test/target \
1919
mullvadvpn-app-tests \
20-
/bin/bash -c "cd /src/test/; cargo build --bin test-runner --release --target ${TARGET}"
20+
/bin/bash -c "cd /src/test/; cargo build --bin test-runner --bin connection-checker --release --target ${TARGET}"
2121
else
2222
cargo build \
2323
--bin test-runner \
24-
--bin am-i-mullvad \
24+
--bin connection-checker \
2525
--release --target "${TARGET}"
2626
fi
2727

test/am-i-mullvad/Cargo.toml test/connection-checker/Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "am-i-mullvad"
2+
name = "connection-checker"
33
description = "Simple cli for testing Mullvad VPN connections"
44
authors.workspace = true
55
repository.workspace = true
@@ -11,7 +11,10 @@ rust-version.workspace = true
1111
workspace = true
1212

1313
[dependencies]
14+
clap = { workspace = true, features = ["derive"] }
1415
color-eyre = "0.6.2"
1516
eyre = "0.6.12"
17+
ping = "0.5.2"
1618
reqwest = { version = "0.11.24", default-features = false, features = ["blocking", "rustls-tls", "json"] }
1719
serde = { version = "1.0.197", features = ["derive"] }
20+
socket2 = { version = "0.5.3", features = ["all"] }

test/connection-checker/src/cli.rs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use std::net::SocketAddr;
2+
3+
use clap::Parser;
4+
5+
/// CLI tool that queries <https://am.i.mullvad.net> to check if the machine is connected to
6+
/// Mullvad VPN.
7+
#[derive(Parser)]
8+
pub struct Opt {
9+
/// Interactive mode, press enter to check if you are Mullvad.
10+
#[clap(short, long)]
11+
pub interactive: bool,
12+
13+
/// Timeout for network connections (in millis).
14+
#[clap(short, long, default_value = "2000")]
15+
pub timeout: u64,
16+
17+
/// Try to send some junk data over TCP to <leak>.
18+
#[clap(long, requires = "leak")]
19+
pub leak_tcp: bool,
20+
21+
/// Try to send some junk data over UDP to <leak>.
22+
#[clap(long, requires = "leak")]
23+
pub leak_udp: bool,
24+
25+
/// Try to send ICMP request to <leak>.
26+
#[clap(long, requires = "leak")]
27+
pub leak_icmp: bool,
28+
29+
/// Target of <leak_tcp>, <leak_udp> or <leak_icmp>.
30+
#[clap(long)]
31+
pub leak: Option<SocketAddr>,
32+
}

test/connection-checker/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod cli;
2+
pub mod net;

test/connection-checker/src/main.rs

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use clap::Parser;
2+
use eyre::{eyre, Context};
3+
use reqwest::blocking::Client;
4+
use serde::Deserialize;
5+
use std::{io::stdin, time::Duration};
6+
7+
use connection_checker::cli::Opt;
8+
use connection_checker::net::{send_ping, send_tcp, send_udp};
9+
10+
fn main() -> eyre::Result<()> {
11+
let opt = Opt::parse();
12+
color_eyre::install()?;
13+
14+
if opt.interactive {
15+
let stdin = stdin();
16+
for line in stdin.lines() {
17+
let _ = line.wrap_err("Failed to read from stdin")?;
18+
test_connection(&opt)?;
19+
}
20+
} else {
21+
test_connection(&opt)?;
22+
}
23+
24+
Ok(())
25+
}
26+
27+
fn test_connection(opt: &Opt) -> eyre::Result<bool> {
28+
if let Some(destination) = opt.leak {
29+
if opt.leak_tcp {
30+
let _ = send_tcp(opt, destination);
31+
}
32+
if opt.leak_udp {
33+
let _ = send_udp(opt, destination);
34+
}
35+
if opt.leak_icmp {
36+
let _ = send_ping(opt, destination.ip());
37+
}
38+
}
39+
am_i_mullvad(opt)
40+
}
41+
42+
/// Check if connected to Mullvad and print the result to stdout
43+
fn am_i_mullvad(opt: &Opt) -> eyre::Result<bool> {
44+
#[derive(Debug, Deserialize)]
45+
struct Response {
46+
ip: String,
47+
mullvad_exit_ip_hostname: Option<String>,
48+
}
49+
50+
let url = "https://am.i.mullvad.net/json";
51+
52+
let client = Client::new();
53+
let response: Response = client
54+
.get(url)
55+
.timeout(Duration::from_millis(opt.timeout))
56+
.send()
57+
.and_then(|r| r.json())
58+
.wrap_err_with(|| eyre!("Failed to GET {url}"))?;
59+
60+
if let Some(server) = &response.mullvad_exit_ip_hostname {
61+
println!(
62+
"You are connected to Mullvad (server {}). Your IP address is {}",
63+
server, response.ip
64+
);
65+
Ok(true)
66+
} else {
67+
println!(
68+
"You are not connected to Mullvad. Your IP address is {}",
69+
response.ip
70+
);
71+
Ok(false)
72+
}
73+
}

0 commit comments

Comments
 (0)