Skip to content

Commit

Permalink
Finalize leak checker and implement in daemon
Browse files Browse the repository at this point in the history
  • Loading branch information
hulthe committed Jan 24, 2025
1 parent ba7f0aa commit 5a70db0
Show file tree
Hide file tree
Showing 33 changed files with 1,829 additions and 886 deletions.
27 changes: 8 additions & 19 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ hickory-server = { version = "0.24.2", features = ["resolver"] }
tokio = { version = "1.42" }
parity-tokio-ipc = "0.9"
futures = "0.3.15"

# Tonic and related crates
tonic = "0.12.3"
tonic-build = { version = "0.10.0", default-features = false }
Expand All @@ -94,6 +95,7 @@ hyper-util = {version = "0.1.8", features = ["client", "client-legacy", "http2",

env_logger = "0.10.0"
thiserror = "2.0"
anyhow = "1.0"
log = "0.4"

shadowsocks = "1.20.3"
Expand All @@ -107,8 +109,10 @@ once_cell = "1.16"
serde = "1.0.204"
serde_json = "1.0.122"

pnet_packet = "0.35.0"
ipnetwork = "0.20"
tun = { version = "0.7", features = ["async"] }
socket2 = "0.5.7"

# Test dependencies
proptest = "1.4"
Expand Down
20 changes: 12 additions & 8 deletions leak-checker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,28 @@ license.workspace = true
edition.workspace = true
rust-version.workspace = true

[features]
default = ["am-i-mullvad"]
am-i-mullvad = ["dep:reqwest"]

[dependencies]
log.workspace = true
eyre = "0.6.12"
socket2 = { version = "0.5.7", features = ["all"] }
match_cfg = "0.1.0"
pnet_packet = "0.35.0"
anyhow.workspace = true
socket2 = { workspace = true, features = ["all"] }
pnet_packet.workspace = true
pretty_env_logger = "0.5.0"
tokio = { workspace = true, features = ["macros", "time", "rt", "sync", "net"] }
tokio = { workspace = true, features = ["macros", "time", "rt", "sync", "net", "process"] }
futures.workspace = true
serde = { workspace = true, features = ["derive"] }
reqwest = { version = "0.12.9", default-features = false, features = ["json", "rustls-tls"] }
clap = { version = "*", features = ["derive"] }
clap = { workspace = true, features = ["derive"] }

reqwest = { version = "0.12.9", optional = true, default-features = false, features = ["json", "rustls-tls"] }

[dev-dependencies]
tokio = { workspace = true, features = ["full"] }

[target.'cfg(unix)'.dependencies]
nix = { version = "0.29.0", features = ["net"] }
nix = { version = "0.29.0", features = ["net", "socket", "uio"] }

[target.'cfg(windows)'.dependencies]
windows-sys.workspace = true
Expand Down
8 changes: 5 additions & 3 deletions leak-checker/examples/leaker-cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::{Parser, Subcommand};
use leak_checker::{am_i_mullvad::AmIMullvadOpt, traceroute::TracerouteOpt};
use leak_checker::traceroute::TracerouteOpt;

#[derive(Parser)]
pub struct Opt {
Expand All @@ -13,11 +13,12 @@ pub enum LeakMethod {
Traceroute(#[clap(flatten)] TracerouteOpt),

/// Ask `am.i.mullvad.net` whether you are leaking.
AmIMullvad(#[clap(flatten)] AmIMullvadOpt),
#[cfg(feature = "am-i-mullvad")]
AmIMullvad(#[clap(flatten)] leak_checker::am_i_mullvad::AmIMullvadOpt),
}

#[tokio::main]
async fn main() -> eyre::Result<()> {
async fn main() -> anyhow::Result<()> {
pretty_env_logger::formatted_builder()
.filter_level(log::LevelFilter::Debug)
.parse_default_env()
Expand All @@ -27,6 +28,7 @@ async fn main() -> eyre::Result<()> {

let leak_status = match &opt.method {
LeakMethod::Traceroute(opt) => leak_checker::traceroute::run_leak_test(opt).await,
#[cfg(feature = "am-i-mullvad")]
LeakMethod::AmIMullvad(opt) => leak_checker::am_i_mullvad::run_leak_test(opt).await,
};

Expand Down
16 changes: 0 additions & 16 deletions leak-checker/notes.md

This file was deleted.

53 changes: 26 additions & 27 deletions leak-checker/src/am_i_mullvad.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use eyre::{eyre, Context};
use anyhow::{anyhow, Context};
use futures::TryFutureExt;
use match_cfg::match_cfg;
use reqwest::{Client, ClientBuilder};
use serde::Deserialize;

Expand All @@ -24,7 +23,7 @@ pub async fn run_leak_test(opt: &AmIMullvadOpt) -> LeakStatus {
}

/// Check if connected to Mullvad and print the result to stdout
pub async fn try_run_leak_test(opt: &AmIMullvadOpt) -> eyre::Result<LeakStatus> {
pub async fn try_run_leak_test(opt: &AmIMullvadOpt) -> anyhow::Result<LeakStatus> {
#[derive(Debug, Deserialize)]
struct Response {
ip: String,
Expand All @@ -37,14 +36,14 @@ pub async fn try_run_leak_test(opt: &AmIMullvadOpt) -> eyre::Result<LeakStatus>
client = bind_client_to_interface(client, interface)?;
}

let client = client.build().wrap_err("Failed to create HTTP client")?;
let client = client.build().context("Failed to create HTTP client")?;
let response: Response = client
.get(AM_I_MULLVAD_URL)
//.timeout(Duration::from_secs(opt.timeout))
.send()
.and_then(|r| r.json())
.await
.wrap_err_with(|| eyre!("Failed to GET {AM_I_MULLVAD_URL}"))?;
.with_context(|| anyhow!("Failed to GET {AM_I_MULLVAD_URL}"))?;

if let Some(server) = &response.mullvad_exit_ip_hostname {
log::debug!(
Expand All @@ -59,32 +58,32 @@ pub async fn try_run_leak_test(opt: &AmIMullvadOpt) -> eyre::Result<LeakStatus>
response.ip
);
Ok(LeakStatus::LeakDetected(LeakInfo::AmIMullvad {
ip: response.ip.parse().wrap_err("Malformed IP")?,
ip: response.ip.parse().context("Malformed IP")?,
}))
}
}

match_cfg! {
#[cfg(target_os = "linux")] => {
fn bind_client_to_interface(
builder: ClientBuilder,
interface: &str
) -> eyre::Result<ClientBuilder> {
log::debug!("Binding HTTP client to {interface}");
Ok(builder.interface(interface))
}
}
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "android"))] => {
fn bind_client_to_interface(
builder: ClientBuilder,
interface: &str
) -> eyre::Result<ClientBuilder> {
use crate::util::get_interface_ip;
#[cfg(target_os = "linux")]
fn bind_client_to_interface(
builder: ClientBuilder,
interface: &str,
) -> anyhow::Result<ClientBuilder> {
log::debug!("Binding HTTP client to {interface}");
Ok(builder.interface(interface))
}

let ip = get_interface_ip(interface)?;
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "android"))]
fn bind_client_to_interface(
builder: ClientBuilder,
interface: &str,
) -> anyhow::Result<ClientBuilder> {
use crate::util::{get_interface_ip, Ip};
use crate::Interface;

log::debug!("Binding HTTP client to {ip} ({interface})");
Ok(builder.local_address(ip))
}
}
let interface = Interface::Name(interface.to_string());
let ip = get_interface_ip(&interface, Ip::v6())
.or_else(|_| get_interface_ip(&interface, Ip::v4()))?;

log::debug!("Binding HTTP client to {ip} ({interface:?})");
Ok(builder.local_address(ip))
}
38 changes: 36 additions & 2 deletions leak-checker/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::net::IpAddr;
use std::{fmt, net::IpAddr};

#[cfg(feature = "am-i-mullvad")]
pub mod am_i_mullvad;
pub mod traceroute;
mod util;
Expand All @@ -16,9 +17,42 @@ pub enum LeakInfo {
/// Managed to reach another network node on the physical interface, bypassing firewall rules.
NodeReachableOnInterface {
reachable_nodes: Vec<IpAddr>,
interface: String,
interface: Interface,
},

/// Queried a <https://am.i.mullvad.net>, and was not mullvad.
#[cfg(feature = "am-i-mullvad")]
AmIMullvad { ip: IpAddr },
}

#[derive(Clone)]
pub enum Interface {
Name(String),

#[cfg(target_os = "windows")]
Luid(windows_sys::Win32::NetworkManagement::Ndis::NET_LUID_LH),

#[cfg(target_os = "macos")]
Index(std::num::NonZeroU32),
}

impl From<String> for Interface {
fn from(name: String) -> Self {
Interface::Name(name)
}
}

impl fmt::Debug for Interface {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Name(arg0) => f.debug_tuple("Name").field(arg0).finish(),

// SAFETY: u64 is valid for all bit patterns, so reading the union as a u64 is safe.
#[cfg(target_os = "windows")]
Self::Luid(arg0) => f.debug_tuple("Luid").field(&unsafe { arg0.Value }).finish(),

#[cfg(target_os = "macos")]
Self::Index(arg0) => f.debug_tuple("Luid").field(arg0).finish(),
}
}
}
Loading

0 comments on commit 5a70db0

Please sign in to comment.