Loading Cargo.lock +89 −95 Original line number Diff line number Diff line Loading @@ -105,6 +105,54 @@ dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys 0.48.0", ] [[package]] name = "anstyle-wincon" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys 0.48.0", ] [[package]] name = "anyhow" version = "1.0.68" Loading Loading @@ -478,6 +526,33 @@ dependencies = [ "libloading", ] [[package]] name = "clap" version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_lex" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "coarsetime" version = "0.1.22" Loading @@ -500,6 +575,12 @@ dependencies = [ "unicode-width", ] [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" version = "2.0.0" Loading Loading @@ -1532,15 +1613,6 @@ dependencies = [ "generic-array", ] [[package]] name = "ipnetwork" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" dependencies = [ "serde", ] [[package]] name = "itertools" version = "0.11.0" Loading Loading @@ -1775,71 +1847,6 @@ dependencies = [ "windows-sys 0.48.0", ] [[package]] name = "netlink-packet-core" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" dependencies = [ "anyhow", "byteorder", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" dependencies = [ "anyhow", "bitflags 1.3.2", "byteorder", "libc", "netlink-packet-core", "netlink-packet-utils", ] [[package]] name = "netlink-packet-utils" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" dependencies = [ "anyhow", "byteorder", "paste", "thiserror", ] [[package]] name = "netlink-proto" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "842c6770fc4bb33dd902f41829c61ef872b8e38de1405aa0b938b27b8fba12c3" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", "thiserror", "tokio", ] [[package]] name = "netlink-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "260e21fbb6f3d253a14df90eb0000a6066780a15dd901a7519ce02d77a94985b" dependencies = [ "bytes", "futures", "libc", "log", "tokio", ] [[package]] name = "nix" version = "0.26.2" Loading Loading @@ -2031,12 +2038,11 @@ name = "onionmasq" version = "0.1.0" dependencies = [ "anyhow", "clap", "futures", "ipnetwork", "libc", "log", "netlink-packet-route", "onion-tunnel", "rtnetlink", "simple-proc-net", "tokio", "tracing-subscriber", Loading Loading @@ -2592,24 +2598,6 @@ dependencies = [ "zeroize", ] [[package]] name = "rtnetlink" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" dependencies = [ "futures", "log", "netlink-packet-core", "netlink-packet-route", "netlink-packet-utils", "netlink-proto", "netlink-sys", "nix", "thiserror", "tokio", ] [[package]] name = "rusqlite" version = "0.29.0" Loading Loading @@ -4218,6 +4206,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "valuable" version = "0.1.0" Loading crates/onionmasq/Cargo.toml +2 −3 Original line number Diff line number Diff line Loading @@ -11,8 +11,7 @@ onion-tunnel = { path = "../onion-tunnel", version = "0.1.0" } tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } log = "0.4" tracing-subscriber = { version = "0.3.17", features = ["fmt", "tracing-log", "env-filter"] } rtnetlink = "0.13" netlink-packet-route = "0.17.1" ipnetwork = "*" futures = "0.3" simple-proc-net = { path = "../simple-proc-net" } clap = "4.3" libc = "0.2" crates/onionmasq/src/main.rs +190 −27 Original line number Diff line number Diff line use crate::netlink::Netlink; use log::{info, warn}; use onion_tunnel::{IpEndpoint, OnionTunnel, TunnelScaffolding}; use anyhow::Context; use clap::{Arg, Command}; use log::{info, trace, warn}; use onion_tunnel::scaffolding::{ConnectionDetails, FailedConnectionDetails}; use onion_tunnel::{CountryCode, IpEndpoint, OnionTunnel, TunnelScaffolding}; use simple_proc_net::ProcNetEntry; use std::ffi::c_void; use std::io; use std::mem::size_of; use std::net::SocketAddr; use std::os::fd::AsRawFd; use std::os::fd::RawFd; use tokio::runtime::Handle; use std::str::FromStr; use tokio::net::TcpSocket; use tracing_subscriber::FmtSubscriber; mod netlink; struct LinuxScaffolding { cc: Option<CountryCode>, can_mark: bool, log_connections: bool, } impl LinuxScaffolding { /// The fwmark to set on arti connections (so policy routing can avoid them being routed /// recursively back into the tunnel). /// /// This is just a randomly generated set of 2 bytes that hopefully won't conflict with /// anything else. pub const FWMARK: libc::c_int = 0xc185; struct NetlinkScaffolding { netlink: Netlink, rt: Handle, /// Mark the provided socket file descriptor with the `FWMARK`. #[cfg(target_os = "linux")] fn mark_fd(fd: RawFd) -> io::Result<()> { let ret = unsafe { libc::setsockopt( fd, libc::SOL_SOCKET, libc::SO_MARK, &Self::FWMARK as *const libc::c_int as *const c_void, size_of::<libc::c_int>() as _, ) }; if ret != 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } } impl TunnelScaffolding for NetlinkScaffolding { fn protect(&self, _: RawFd, addr: &SocketAddr) -> io::Result<()> { // HACK(eta): protect() isn't async, so we just block. Oh well! tokio::task::block_in_place(|| { if let Err(e) = self .rt .block_on(self.netlink.add_passthrough_route(&addr.ip().into())) { warn!("adding netlink passthrough route failed: {}", e); impl TunnelScaffolding for LinuxScaffolding { fn protect(&self, fd: RawFd, _: &SocketAddr) -> io::Result<()> { #[cfg(target_os = "linux")] if self.can_mark { Self::mark_fd(fd)?; } }); Ok(()) } fn locate(&self, _: IpEndpoint, _: IpEndpoint, _: u64) -> Option<CountryCode> { self.cc } fn on_bootstrapped(&self) { info!("Connection to Tor complete!"); } fn on_established(&self, details: ConnectionDetails<'_>) { if self.log_connections { let exit_identity = details .circuit_relays() .last() .map(|x| { let cc = match x.country_code { Some(v) => v.to_string(), None => "??".into(), }; if let Some(i) = x.ed_identity { format!("{} ({})", i, cc) } else if let Some(i) = x.rsa_identity { format!("{} ({})", i, cc) } else { "???".into() } }) .unwrap_or_else(|| "???".into()); info!( "New connection to '{}' (uid {}) via exit {}", details.tor_dst, details.isolation_key, exit_identity ); } } fn on_arti_failure(&self, details: FailedConnectionDetails) { if self.log_connections { warn!( "Failed to connect to '{}': {}", details.tor_dst, details.error ); } } fn isolate(&self, src: IpEndpoint, dst: IpEndpoint, ip_proto: u8) -> io::Result<u64> { let iter = match ip_proto { 6 => ProcNetEntry::tcp4()?.chain(ProcNetEntry::tcp6()?), Loading @@ -40,7 +113,7 @@ impl TunnelScaffolding for NetlinkScaffolding { if IpEndpoint::from(v.local_addr) == src && IpEndpoint::from(v.remote_addr) == dst { info!("isolated {src} -> {dst} proto {ip_proto} as {}", v.uid); trace!("isolated {src} -> {dst} proto {ip_proto} as {}", v.uid); return Ok(v.uid as u64); } } Loading @@ -55,22 +128,112 @@ impl TunnelScaffolding for NetlinkScaffolding { } } const ENV_FILTER_VERBOSE: &str = "info,smoltcp=debug,onion_tunnel=trace,arti_client=debug,tor_chanmgr=debug,tor_proto=debug"; const ENV_FILTER_TAME: &str = "info"; #[tokio::main] async fn main() -> anyhow::Result<()> { FmtSubscriber::builder() .with_env_filter("info,smoltcp=debug,onion_tunnel=trace,arti_client=debug,tor_chanmgr=debug,tor_proto=debug") .init(); let matches = Command::new("onionmasq") .version(env!("CARGO_PKG_VERSION")) .author("eta <eta@torproject.org>") .about("A magical TUN device that feeds traffic via Tor.") .arg( Arg::new("tun-device") .short('d') .long("device") .value_name("DEVICE") .default_value("onion0") .help( "Name of a TUN device to use. Will attempt to create one if it doesn't exist.", ), ) .arg( Arg::new("country-code") .short('c') .long("country-code") .value_name("DE|NL|etc") .help("Make traffic come from exit nodes in this country (ISO 3166-1 alpha-2 country code)"), ) /* TODO(eta): make this a thing .arg(Arg::new("whole-system") .short('s') .long("whole-system") .help("Make all system traffic go through the onionmasq TUN device. Off by default.") .action(clap::ArgAction::SetTrue) ) */ .arg(Arg::new("debug") .long("debug") .help("Enable detailed debug logging. Off by default.") .action(clap::ArgAction::SetTrue) ) .arg(Arg::new("verbose") .short('v') .long("verbose") .help("Print information about successful and failed connections. Off by default.") .action(clap::ArgAction::SetTrue)) .get_matches(); let filter = if matches.get_flag("debug") { ENV_FILTER_VERBOSE } else { ENV_FILTER_TAME }; let log_connections = matches.get_flag("verbose"); let country_code = matches .get_one("country-code") .map(|x: &String| CountryCode::from_str(x)) .transpose() .context("invalid country code provided")?; let tun_device: &String = matches.get_one("tun-device").unwrap(); FmtSubscriber::builder().with_env_filter(filter).init(); info!("Starting tunnel on interface 'onion0'..."); info!( "Starting onionmasq {} on device '{}'...", env!("CARGO_PKG_VERSION"), tun_device ); let scaffolding = NetlinkScaffolding { netlink: Netlink::new().await, rt: Handle::current(), // Check whether we can call setsockopt(). If we can't, don't bother doing so in future, since // we probably don't have the capabilities; just print an explanatory warning instead. #[cfg(target_os = "linux")] let dummy_socket = TcpSocket::new_v4()?; #[cfg(target_os = "linux")] let can_mark = match LinuxScaffolding::mark_fd(dummy_socket.as_raw_fd()) { Ok(_) => { info!( "Outgoing connections (to the Tor network) will use fwmark {:#x}.", LinuxScaffolding::FWMARK ); true } Err(e) => { warn!("Calling setsockopt() failed: {e}"); warn!("Setting fwmarks on outgoing sockets will be disabled."); warn!("Make the onionmasq binary CAP_NET_ADMIN to fix this problem."); false } }; #[cfg(not(target_os = "linux"))] let can_mark = false; let scaffolding = LinuxScaffolding { can_mark, cc: country_code, log_connections, }; #[cfg(target_os = "linux")] let mut onion_tunnel = OnionTunnel::new(scaffolding, "onion0", Default::default()).await?; info!("Connecting to Tor..."); #[cfg(target_os = "linux")] tokio::select! { _ = onion_tunnel.run() => (), Loading crates/onionmasq/src/netlink.rsdeleted 100644 → 0 +0 −69 Original line number Diff line number Diff line use std::net::{IpAddr, Ipv4Addr}; use futures::stream::TryStreamExt; use ipnetwork::IpNetwork; use netlink_packet_route::{RTN_UNICAST, RT_SCOPE_UNIVERSE}; use rtnetlink::{new_connection, Error, Handle, IpVersion}; /* async fn get_link_index_by_name(handle: &Handle, name: &String) -> Result<u32, Error> { let mut links = handle.link().get().match_name(name.clone()).execute(); if let Some(msg) = links.try_next().await? { Ok(msg.header.index) } else { Err(Error::RequestFailed) } } */ async fn get_default_gateway4(handle: &Handle) -> Result<Ipv4Addr, Error> { let mut routes = handle.route().get(IpVersion::V4).execute(); while let Some(msg) = routes.try_next().await? { if msg.header.destination_prefix_length == 0 && msg.header.scope == RT_SCOPE_UNIVERSE && msg.header.kind == RTN_UNICAST { if let IpAddr::V4(v4) = msg.gateway().unwrap() { return Ok(v4); } } } Err(Error::RequestFailed) } #[derive(Clone)] pub struct Netlink { gw4_addr: Ipv4Addr, handle: Handle, } impl Netlink { // XXX: Handle errors. pub async fn new() -> Self { let (conn, handle, _) = new_connection().unwrap(); tokio::spawn(conn); Self { gw4_addr: get_default_gateway4(&handle).await.unwrap(), handle, } } pub async fn add_passthrough_route(&self, dest: &IpNetwork) -> Result<(), Error> { match dest { IpNetwork::V4(v4) => { self.handle .route() .add() .v4() .destination_prefix(v4.ip(), v4.prefix()) .gateway(self.gw4_addr) .execute() .await?; } IpNetwork::V6(_v6) => (), } Ok(()) } } Loading
Cargo.lock +89 −95 Original line number Diff line number Diff line Loading @@ -105,6 +105,54 @@ dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys 0.48.0", ] [[package]] name = "anstyle-wincon" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys 0.48.0", ] [[package]] name = "anyhow" version = "1.0.68" Loading Loading @@ -478,6 +526,33 @@ dependencies = [ "libloading", ] [[package]] name = "clap" version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_lex" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "coarsetime" version = "0.1.22" Loading @@ -500,6 +575,12 @@ dependencies = [ "unicode-width", ] [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" version = "2.0.0" Loading Loading @@ -1532,15 +1613,6 @@ dependencies = [ "generic-array", ] [[package]] name = "ipnetwork" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" dependencies = [ "serde", ] [[package]] name = "itertools" version = "0.11.0" Loading Loading @@ -1775,71 +1847,6 @@ dependencies = [ "windows-sys 0.48.0", ] [[package]] name = "netlink-packet-core" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" dependencies = [ "anyhow", "byteorder", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" dependencies = [ "anyhow", "bitflags 1.3.2", "byteorder", "libc", "netlink-packet-core", "netlink-packet-utils", ] [[package]] name = "netlink-packet-utils" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" dependencies = [ "anyhow", "byteorder", "paste", "thiserror", ] [[package]] name = "netlink-proto" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "842c6770fc4bb33dd902f41829c61ef872b8e38de1405aa0b938b27b8fba12c3" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", "thiserror", "tokio", ] [[package]] name = "netlink-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "260e21fbb6f3d253a14df90eb0000a6066780a15dd901a7519ce02d77a94985b" dependencies = [ "bytes", "futures", "libc", "log", "tokio", ] [[package]] name = "nix" version = "0.26.2" Loading Loading @@ -2031,12 +2038,11 @@ name = "onionmasq" version = "0.1.0" dependencies = [ "anyhow", "clap", "futures", "ipnetwork", "libc", "log", "netlink-packet-route", "onion-tunnel", "rtnetlink", "simple-proc-net", "tokio", "tracing-subscriber", Loading Loading @@ -2592,24 +2598,6 @@ dependencies = [ "zeroize", ] [[package]] name = "rtnetlink" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" dependencies = [ "futures", "log", "netlink-packet-core", "netlink-packet-route", "netlink-packet-utils", "netlink-proto", "netlink-sys", "nix", "thiserror", "tokio", ] [[package]] name = "rusqlite" version = "0.29.0" Loading Loading @@ -4218,6 +4206,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "valuable" version = "0.1.0" Loading
crates/onionmasq/Cargo.toml +2 −3 Original line number Diff line number Diff line Loading @@ -11,8 +11,7 @@ onion-tunnel = { path = "../onion-tunnel", version = "0.1.0" } tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } log = "0.4" tracing-subscriber = { version = "0.3.17", features = ["fmt", "tracing-log", "env-filter"] } rtnetlink = "0.13" netlink-packet-route = "0.17.1" ipnetwork = "*" futures = "0.3" simple-proc-net = { path = "../simple-proc-net" } clap = "4.3" libc = "0.2"
crates/onionmasq/src/main.rs +190 −27 Original line number Diff line number Diff line use crate::netlink::Netlink; use log::{info, warn}; use onion_tunnel::{IpEndpoint, OnionTunnel, TunnelScaffolding}; use anyhow::Context; use clap::{Arg, Command}; use log::{info, trace, warn}; use onion_tunnel::scaffolding::{ConnectionDetails, FailedConnectionDetails}; use onion_tunnel::{CountryCode, IpEndpoint, OnionTunnel, TunnelScaffolding}; use simple_proc_net::ProcNetEntry; use std::ffi::c_void; use std::io; use std::mem::size_of; use std::net::SocketAddr; use std::os::fd::AsRawFd; use std::os::fd::RawFd; use tokio::runtime::Handle; use std::str::FromStr; use tokio::net::TcpSocket; use tracing_subscriber::FmtSubscriber; mod netlink; struct LinuxScaffolding { cc: Option<CountryCode>, can_mark: bool, log_connections: bool, } impl LinuxScaffolding { /// The fwmark to set on arti connections (so policy routing can avoid them being routed /// recursively back into the tunnel). /// /// This is just a randomly generated set of 2 bytes that hopefully won't conflict with /// anything else. pub const FWMARK: libc::c_int = 0xc185; struct NetlinkScaffolding { netlink: Netlink, rt: Handle, /// Mark the provided socket file descriptor with the `FWMARK`. #[cfg(target_os = "linux")] fn mark_fd(fd: RawFd) -> io::Result<()> { let ret = unsafe { libc::setsockopt( fd, libc::SOL_SOCKET, libc::SO_MARK, &Self::FWMARK as *const libc::c_int as *const c_void, size_of::<libc::c_int>() as _, ) }; if ret != 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } } impl TunnelScaffolding for NetlinkScaffolding { fn protect(&self, _: RawFd, addr: &SocketAddr) -> io::Result<()> { // HACK(eta): protect() isn't async, so we just block. Oh well! tokio::task::block_in_place(|| { if let Err(e) = self .rt .block_on(self.netlink.add_passthrough_route(&addr.ip().into())) { warn!("adding netlink passthrough route failed: {}", e); impl TunnelScaffolding for LinuxScaffolding { fn protect(&self, fd: RawFd, _: &SocketAddr) -> io::Result<()> { #[cfg(target_os = "linux")] if self.can_mark { Self::mark_fd(fd)?; } }); Ok(()) } fn locate(&self, _: IpEndpoint, _: IpEndpoint, _: u64) -> Option<CountryCode> { self.cc } fn on_bootstrapped(&self) { info!("Connection to Tor complete!"); } fn on_established(&self, details: ConnectionDetails<'_>) { if self.log_connections { let exit_identity = details .circuit_relays() .last() .map(|x| { let cc = match x.country_code { Some(v) => v.to_string(), None => "??".into(), }; if let Some(i) = x.ed_identity { format!("{} ({})", i, cc) } else if let Some(i) = x.rsa_identity { format!("{} ({})", i, cc) } else { "???".into() } }) .unwrap_or_else(|| "???".into()); info!( "New connection to '{}' (uid {}) via exit {}", details.tor_dst, details.isolation_key, exit_identity ); } } fn on_arti_failure(&self, details: FailedConnectionDetails) { if self.log_connections { warn!( "Failed to connect to '{}': {}", details.tor_dst, details.error ); } } fn isolate(&self, src: IpEndpoint, dst: IpEndpoint, ip_proto: u8) -> io::Result<u64> { let iter = match ip_proto { 6 => ProcNetEntry::tcp4()?.chain(ProcNetEntry::tcp6()?), Loading @@ -40,7 +113,7 @@ impl TunnelScaffolding for NetlinkScaffolding { if IpEndpoint::from(v.local_addr) == src && IpEndpoint::from(v.remote_addr) == dst { info!("isolated {src} -> {dst} proto {ip_proto} as {}", v.uid); trace!("isolated {src} -> {dst} proto {ip_proto} as {}", v.uid); return Ok(v.uid as u64); } } Loading @@ -55,22 +128,112 @@ impl TunnelScaffolding for NetlinkScaffolding { } } const ENV_FILTER_VERBOSE: &str = "info,smoltcp=debug,onion_tunnel=trace,arti_client=debug,tor_chanmgr=debug,tor_proto=debug"; const ENV_FILTER_TAME: &str = "info"; #[tokio::main] async fn main() -> anyhow::Result<()> { FmtSubscriber::builder() .with_env_filter("info,smoltcp=debug,onion_tunnel=trace,arti_client=debug,tor_chanmgr=debug,tor_proto=debug") .init(); let matches = Command::new("onionmasq") .version(env!("CARGO_PKG_VERSION")) .author("eta <eta@torproject.org>") .about("A magical TUN device that feeds traffic via Tor.") .arg( Arg::new("tun-device") .short('d') .long("device") .value_name("DEVICE") .default_value("onion0") .help( "Name of a TUN device to use. Will attempt to create one if it doesn't exist.", ), ) .arg( Arg::new("country-code") .short('c') .long("country-code") .value_name("DE|NL|etc") .help("Make traffic come from exit nodes in this country (ISO 3166-1 alpha-2 country code)"), ) /* TODO(eta): make this a thing .arg(Arg::new("whole-system") .short('s') .long("whole-system") .help("Make all system traffic go through the onionmasq TUN device. Off by default.") .action(clap::ArgAction::SetTrue) ) */ .arg(Arg::new("debug") .long("debug") .help("Enable detailed debug logging. Off by default.") .action(clap::ArgAction::SetTrue) ) .arg(Arg::new("verbose") .short('v') .long("verbose") .help("Print information about successful and failed connections. Off by default.") .action(clap::ArgAction::SetTrue)) .get_matches(); let filter = if matches.get_flag("debug") { ENV_FILTER_VERBOSE } else { ENV_FILTER_TAME }; let log_connections = matches.get_flag("verbose"); let country_code = matches .get_one("country-code") .map(|x: &String| CountryCode::from_str(x)) .transpose() .context("invalid country code provided")?; let tun_device: &String = matches.get_one("tun-device").unwrap(); FmtSubscriber::builder().with_env_filter(filter).init(); info!("Starting tunnel on interface 'onion0'..."); info!( "Starting onionmasq {} on device '{}'...", env!("CARGO_PKG_VERSION"), tun_device ); let scaffolding = NetlinkScaffolding { netlink: Netlink::new().await, rt: Handle::current(), // Check whether we can call setsockopt(). If we can't, don't bother doing so in future, since // we probably don't have the capabilities; just print an explanatory warning instead. #[cfg(target_os = "linux")] let dummy_socket = TcpSocket::new_v4()?; #[cfg(target_os = "linux")] let can_mark = match LinuxScaffolding::mark_fd(dummy_socket.as_raw_fd()) { Ok(_) => { info!( "Outgoing connections (to the Tor network) will use fwmark {:#x}.", LinuxScaffolding::FWMARK ); true } Err(e) => { warn!("Calling setsockopt() failed: {e}"); warn!("Setting fwmarks on outgoing sockets will be disabled."); warn!("Make the onionmasq binary CAP_NET_ADMIN to fix this problem."); false } }; #[cfg(not(target_os = "linux"))] let can_mark = false; let scaffolding = LinuxScaffolding { can_mark, cc: country_code, log_connections, }; #[cfg(target_os = "linux")] let mut onion_tunnel = OnionTunnel::new(scaffolding, "onion0", Default::default()).await?; info!("Connecting to Tor..."); #[cfg(target_os = "linux")] tokio::select! { _ = onion_tunnel.run() => (), Loading
crates/onionmasq/src/netlink.rsdeleted 100644 → 0 +0 −69 Original line number Diff line number Diff line use std::net::{IpAddr, Ipv4Addr}; use futures::stream::TryStreamExt; use ipnetwork::IpNetwork; use netlink_packet_route::{RTN_UNICAST, RT_SCOPE_UNIVERSE}; use rtnetlink::{new_connection, Error, Handle, IpVersion}; /* async fn get_link_index_by_name(handle: &Handle, name: &String) -> Result<u32, Error> { let mut links = handle.link().get().match_name(name.clone()).execute(); if let Some(msg) = links.try_next().await? { Ok(msg.header.index) } else { Err(Error::RequestFailed) } } */ async fn get_default_gateway4(handle: &Handle) -> Result<Ipv4Addr, Error> { let mut routes = handle.route().get(IpVersion::V4).execute(); while let Some(msg) = routes.try_next().await? { if msg.header.destination_prefix_length == 0 && msg.header.scope == RT_SCOPE_UNIVERSE && msg.header.kind == RTN_UNICAST { if let IpAddr::V4(v4) = msg.gateway().unwrap() { return Ok(v4); } } } Err(Error::RequestFailed) } #[derive(Clone)] pub struct Netlink { gw4_addr: Ipv4Addr, handle: Handle, } impl Netlink { // XXX: Handle errors. pub async fn new() -> Self { let (conn, handle, _) = new_connection().unwrap(); tokio::spawn(conn); Self { gw4_addr: get_default_gateway4(&handle).await.unwrap(), handle, } } pub async fn add_passthrough_route(&self, dest: &IpNetwork) -> Result<(), Error> { match dest { IpNetwork::V4(v4) => { self.handle .route() .add() .v4() .destination_prefix(v4.ip(), v4.prefix()) .gateway(self.gw4_addr) .execute() .await?; } IpNetwork::V6(_v6) => (), } Ok(()) } }