diff --git a/Cargo.toml b/Cargo.toml index fa6ace85d..5ddd57b89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,21 @@ [workspace] -members = [ - "leaf", - "leaf-cli", - "leaf-ffi", - "leaf-plugins/shadowsocks", -] +members = ["leaf", "leaf-cli", "leaf-ffi", "leaf-plugins/shadowsocks"] default-members = ["leaf-cli"] -resolver = "2" +resolver = "3" + +[workspace.package] +edition = "2024" +rust-version = "1.94.0" + +[workspace.dependencies] +async-ffi = { version = "0.5" } + +thiserror = { version = "^2.0" } +anyhow = { version = "^1.0" } + +tracing = { version = "^0.1" } +tracing-appender = { version = "^0.2" } +tracing-subscriber = { version = "^0.3" } [profile.release] opt-level = 3 diff --git a/leaf-cli/Cargo.toml b/leaf-cli/Cargo.toml index 908ec6a43..8a4910e3c 100644 --- a/leaf-cli/Cargo.toml +++ b/leaf-cli/Cargo.toml @@ -2,7 +2,9 @@ name = "leaf-cli" version = "0.14.2" authors = ["eycorsican "] -edition = "2021" + +edition.workspace = true +rust-version.workspace = true [[bin]] name = "leaf" @@ -15,11 +17,7 @@ argh = "0.1" [target.'cfg(not(windows))'.dependencies.leaf] path = "../leaf" default-features = false -features = [ - "default-aws-lc", - "ctrlc", - "auto-reload" -] +features = ["default-aws-lc", "ctrlc", "auto-reload"] [target.'cfg(windows)'.dependencies.leaf] path = "../leaf" @@ -29,5 +27,5 @@ features = [ "ctrlc", "auto-reload", "inbound-nf", - "rule-process-name" + "rule-process-name", ] diff --git a/leaf-cli/src/main.rs b/leaf-cli/src/main.rs index 834cfc661..ddc191bea 100644 --- a/leaf-cli/src/main.rs +++ b/leaf-cli/src/main.rs @@ -76,18 +76,22 @@ fn main() { } if args.test { - if let Err(e) = leaf::test_config(&args.config) { - println!("{}", e); - exit(1); - } else { - println!("ok"); - exit(0); + match leaf::test_config(&args.config) { + Err(e) => { + println!("{}", e); + exit(1); + } + _ => { + println!("ok"); + exit(0); + } } } #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] if let Some(iface) = args.boundif { - std::env::set_var("OUTBOUND_INTERFACE", iface); + // TODO: Audit that the environment access only happens in single-threaded code. + unsafe { std::env::set_var("OUTBOUND_INTERFACE", iface) }; } if let Some(tag) = args.test_outbound { diff --git a/leaf-ffi/Cargo.toml b/leaf-ffi/Cargo.toml index 28344a9d9..8fa0ea3a5 100644 --- a/leaf-ffi/Cargo.toml +++ b/leaf-ffi/Cargo.toml @@ -2,7 +2,9 @@ name = "leaf-ffi" version = "0.1.0" authors = ["eycorsican "] -edition = "2021" + +edition.workspace = true +rust-version.workspace = true [lib] name = "leaf" @@ -10,27 +12,19 @@ path = "src/lib.rs" crate-type = ["staticlib", "dylib"] [features] -default = [ - "default-aws-lc", - "auto-reload", -] +default = ["default-aws-lc", "auto-reload"] -default-aws-lc= [ - "leaf/default-aws-lc", -] +default-aws-lc = ["leaf/default-aws-lc"] -default-ring = [ - "leaf/default-ring", -] +default-ring = ["leaf/default-ring"] -default-openssl = [ - "leaf/default-openssl", -] +default-openssl = ["leaf/default-openssl"] auto-reload = ["leaf/auto-reload"] [dependencies] leaf = { path = "../leaf", default-features = false, optional = true } tokio = { version = "1", features = ["rt"] } -anyhow = "1.0" futures = "0.3" + +anyhow.workspace = true diff --git a/leaf-ffi/src/lib.rs b/leaf-ffi/src/lib.rs index 77927e0f8..8b8a1cd92 100644 --- a/leaf-ffi/src/lib.rs +++ b/leaf-ffi/src/lib.rs @@ -54,7 +54,7 @@ fn to_errno(e: leaf::Error) -> i32 { /// @param stack_size Sets stack size of the runtime worker threads, takes effect when /// multi_thread is true. /// @return ERR_OK on finish running, any other errors means a startup failure. -#[no_mangle] +#[unsafe(no_mangle)] #[allow(unused_variables)] pub unsafe extern "C" fn leaf_run_with_options( rt_id: u16, @@ -92,7 +92,7 @@ pub unsafe extern "C" fn leaf_run_with_options( /// @param config_path The path of the config file, must be a file with suffix .conf /// or .json, according to the enabled features. /// @return ERR_OK on finish running, any other errors means a startup failure. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn leaf_run(rt_id: u16, config_path: *const c_char) -> i32 { if let Ok(config_path) = unsafe { CStr::from_ptr(config_path).to_str() } { let opts = leaf::StartOptions { @@ -110,7 +110,7 @@ pub unsafe extern "C" fn leaf_run(rt_id: u16, config_path: *const c_char) -> i32 } } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn leaf_run_with_config_string(rt_id: u16, config: *const c_char) -> i32 { if let Ok(config) = unsafe { CStr::from_ptr(config).to_str() } { let opts = leaf::StartOptions { @@ -133,7 +133,7 @@ pub unsafe extern "C" fn leaf_run_with_config_string(rt_id: u16, config: *const /// @param rt_id The ID of the leaf instance to reload. /// /// @return Returns ERR_OK on success. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn leaf_reload(rt_id: u16) -> i32 { if let Err(e) = leaf::reload(rt_id) { return to_errno(e); @@ -146,7 +146,7 @@ pub extern "C" fn leaf_reload(rt_id: u16) -> i32 { /// @param rt_id The ID of the leaf instance to reload. /// /// @return Returns true on success, false otherwise. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn leaf_shutdown(rt_id: u16) -> bool { leaf::shutdown(rt_id) } @@ -156,7 +156,7 @@ pub extern "C" fn leaf_shutdown(rt_id: u16) -> bool { /// @param config_path The path of the config file, must be a file with suffix .conf /// or .json, according to the enabled features. /// @return Returns ERR_OK on success, i.e no syntax error. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn leaf_test_config(config_path: *const c_char) -> i32 { if let Ok(config_path) = unsafe { CStr::from_ptr(config_path).to_str() } { if let Err(e) = leaf::test_config(config_path) { @@ -177,7 +177,7 @@ pub unsafe extern "C" fn leaf_test_config(config_path: *const c_char) -> i32 { /// @param callback The callback function to receive results. /// Arguments: tag (string), tcp_latency (ms, -1 if failed), udp_latency (ms, -1 if failed), context. /// @return Returns ERR_OK on success. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn leaf_test_outbounds( config: *const c_char, concurrency: u32, @@ -262,7 +262,7 @@ pub unsafe extern "C" fn leaf_test_outbounds( /// @param outbound_tag The tag of the outbound to test. /// @param timeout_ms Timeout in milliseconds (0 for default 4 seconds). /// @return Returns ERR_OK if either TCP or UDP health check succeeds, error code otherwise. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn leaf_health_check( rt_id: u16, outbound_tag: *const c_char, @@ -309,7 +309,7 @@ pub unsafe extern "C" fn leaf_health_check( /// @param outbound_tag The tag of the outbound. /// @param timestamp_s Pointer to store the timestamp in seconds since epoch. /// @return Returns ERR_OK on success, ERR_NO_DATA if no active time found, error code otherwise. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn leaf_get_last_active( rt_id: u16, outbound_tag: *const c_char, @@ -348,7 +348,7 @@ pub unsafe extern "C" fn leaf_get_last_active( /// @param outbound_tag The tag of the outbound. /// @param since_s Pointer to store the seconds since last active. /// @return Returns ERR_OK on success, ERR_NO_DATA if no active time found, error code otherwise. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn leaf_get_since_last_active( rt_id: u16, outbound_tag: *const c_char, diff --git a/leaf-plugins/shadowsocks/Cargo.toml b/leaf-plugins/shadowsocks/Cargo.toml index e9093bc6d..252bc4d2c 100644 --- a/leaf-plugins/shadowsocks/Cargo.toml +++ b/leaf-plugins/shadowsocks/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "shadowsocks" version = "0.1.0" -edition = "2018" + +edition.workspace = true +rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -9,8 +11,12 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -leaf = { path = "../../leaf", features = ["outbound-shadowsocks", "inbound-shadowsocks", "plugin"] } +leaf = { path = "../../leaf", features = [ + "outbound-shadowsocks", + "inbound-shadowsocks", + "plugin", +] } async-trait = "0.1" tokio = { version = "1", features = ["net"] } -async-ffi = "0.2" +async-ffi.workspace = true bytes = "1" diff --git a/leaf-plugins/shadowsocks/src/lib.rs b/leaf-plugins/shadowsocks/src/lib.rs index 737669d1b..a26683556 100644 --- a/leaf-plugins/shadowsocks/src/lib.rs +++ b/leaf-plugins/shadowsocks/src/lib.rs @@ -17,10 +17,10 @@ use leaf::{ }; use tokio::io::AsyncWriteExt; -#[no_mangle] +#[unsafe(no_mangle)] pub static plugin_spec: PluginSpec = PluginSpec { add_handler_fn }; -#[no_mangle] +#[unsafe(no_mangle)] pub fn add_handler_fn(registrar: &mut dyn PluginRegistrar, tag: &str, args: &str) { let mut args = args.split(';'); let address: String = args.next().unwrap().to_string(); @@ -68,11 +68,13 @@ impl ExternalOutboundStreamHandler for TcpHandler { async move { let mut stream = ShadowedStream::new(stream.unwrap(), &self.cipher, &self.password, None)?; - let mut buf = BytesMut::new(); - sess.destination - .write_buf(&mut buf, SocksAddrWireType::PortLast); - // FIXME combine header and first payload - stream.write_all(&buf).await?; + { + let mut buf = BytesMut::new(); + sess.destination + .write_buf(&mut buf, SocksAddrWireType::PortLast); + // FIXME combine header and first payload + stream.write_all(&buf).await?; + } Ok(Box::new(stream) as Box) } .into_ffi() @@ -114,7 +116,7 @@ impl ExternalOutboundDatagramHandler for UdpHandler { let socket = if let Some(AnyOutboundTransport::Datagram(socket)) = transport { socket } else { - return Err(io::Error::new(io::ErrorKind::Other, "invalid input")); + return Err(io::Error::other("invalid input")); }; let dgram = ShadowedDatagram::new(&self.cipher, &self.password)?; @@ -185,9 +187,9 @@ impl OutboundDatagramRecvHalf for DatagramRecvHalf { println!("truncated udp packet, please report this issue"); } buf[..to_write].copy_from_slice(&plaintext[src_addr.size()..src_addr.size() + to_write]); - if self.2.is_some() { + if let Some(dest) = self.2.as_ref() { // must be a domain destination - Ok((to_write, self.2.as_ref().unwrap().clone())) + Ok((to_write, dest.clone())) } else { Ok((to_write, src_addr)) } @@ -208,10 +210,12 @@ impl OutboundDatagramSendHalf for DatagramSendHalf { buf2.put_slice(buf); let ciphertext = self.dgram.encrypt(buf2).map_err(|_| shadow::crypto_err())?; - match self.send_half.send_to(&ciphertext, &self.server_addr).await { - Ok(_) => Ok(buf.len()), - Err(err) => Err(err), - } + let len = self + .send_half + .send_to(&ciphertext, &self.server_addr) + .await + .map(|_| buf.len())?; + Ok(len) } async fn close(&mut self) -> io::Result<()> { diff --git a/leaf/Cargo.toml b/leaf/Cargo.toml index 3734ae498..8ba7bb946 100644 --- a/leaf/Cargo.toml +++ b/leaf/Cargo.toml @@ -2,17 +2,17 @@ name = "leaf" version = "0.1.2" authors = ["eycorsican "] -edition = "2021" build = "build.rs" +edition.workspace = true +rust-version.workspace = true + [lib] name = "leaf" path = "src/lib.rs" [features] -default = [ - "default-aws-lc" -] +default = ["default-aws-lc"] default-ring = [ "all-configs", @@ -49,10 +49,7 @@ quinn-ring = ["quinn/rustls-ring", "quinn/runtime-tokio"] quinn-aws-lc = ["quinn/rustls-aws-lc-rs", "quinn/runtime-tokio"] # Grouping all features -all-configs = [ - "config-conf", - "config-json", -] +all-configs = ["config-conf", "config-json"] all-endpoints = [ # inbounds "inbound-chain", @@ -111,23 +108,49 @@ rule-process-name = ["regex"] outbound-direct = [] outbound-drop = [] outbound-redirect = [] -outbound-shadowsocks = ["hkdf", "sha-1", "md-5", "percent-encoding", "tokio-util"] +outbound-shadowsocks = [ + "hkdf", + "sha-1", + "md-5", + "percent-encoding", + "tokio-util", +] outbound-obfs = ["base64", "memchr"] outbound-socks = ["async-socks5"] outbound-trojan = ["sha2", "hex"] outbound-tls = [] outbound-ws = ["tungstenite", "tokio-tungstenite", "url", "http"] outbound-failover = ["lru_time_cache"] -outbound-static= [] +outbound-static = [] outbound-tryall = [] outbound-chain = [] outbound-vless = ["hex"] -outbound-reality = ["reality", "reality-rustls", "webpki-roots", "rustls-pemfile", "hex", "base64"] -outbound-amux= ["tokio-util"] +outbound-reality = [ + "reality", + "reality-rustls", + "webpki-roots", + "rustls-pemfile", + "hex", + "base64", +] +outbound-amux = ["tokio-util"] outbound-quic = ["rustls", "webpki-roots-old", "rustls-pemfile-old"] outbound-mptp = [] outbound-select = ["directories", "axum/query"] -outbound-vmess = ["lz_fnv", "cfb-mode", "hmac", "sha2", "aes", "aes-gcm", "sha3", "digest", "md-5", "tokio-util", "byteorder", "crc32fast"] +outbound-vmess = [ + "lz_fnv", + "cfb-mode", + "hmac", + "sha2", + "aes", + "aes-gcm", + "sha3", + "digest", + "md-5", + "tokio-util", + "byteorder", + "crc32fast", +] # Inbounds inbound-trojan = ["sha2", "hex"] @@ -152,28 +175,35 @@ ctrlc = ["tokio/signal"] [dependencies] # Common -tokio = { version = "1", features = ["sync", "io-util", "net", "time", "rt", "rt-multi-thread"] } +tokio = { version = "1", features = [ + "sync", + "io-util", + "net", + "time", + "rt", + "rt-multi-thread", +] } protobuf = "3.6" -thiserror = "1.0" futures = "0.3" async-trait = "0.1" bytes = "1" -lazy_static = "1.5" -anyhow = "1.0" rand = "0.8" socket2 = "0.5" async-recursion = "1.1" parking_lot = "0.12" uuid = { version = "1", features = ["v4"] } +anyhow.workspace = true +thiserror.workspace = true + # DNS hickory-proto = { version = "0.24", default-features = false } lru = "0.12" # Logging -tracing = "0.1" -tracing-appender = "0.2" -tracing-subscriber = "0.3" +tracing.workspace = true +tracing-appender.workspace = true +tracing-subscriber.workspace = true chrono = "0.4" # Router @@ -186,7 +216,7 @@ regex = { version = "1.11", optional = true } directories = { version = "4.0", optional = true } # plugin -async-ffi = { version = "0.2", optional = true } +async-ffi = { workspace = true, optional = true } libloading = { version = "0.7", optional = true } # config-json @@ -203,7 +233,10 @@ ring = { version = "0.17", optional = true } aws-lc-rs = { version = "1.16", optional = true } # TLS/rustls/QUIC -tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12"], optional = true } +tokio-rustls = { version = "0.26", default-features = false, features = [ + "logging", + "tls12", +], optional = true } webpki-roots = { version = "0.26", optional = true } webpki-roots-old = { package = "webpki-roots", version = "0.25", optional = true } rustls-pemfile = { version = "2.1", optional = true } @@ -253,24 +286,39 @@ crc32fast = { version = "1", optional = true } lru_time_cache = { version = "0.11", optional = true } # amux -tokio-util = { version = "0.7", default-features = false, features = ["io", "codec", "compat"], optional = true } +tokio-util = { version = "0.7", default-features = false, features = [ + "io", + "codec", + "compat", +], optional = true } # QUIC quinn = { version = "0.11", default-features = false, optional = true } -rustls = { version = "0.23", default-features = false, features = ["std"], optional = true } +rustls = { version = "0.23", default-features = false, features = [ + "std", +], optional = true } # Reality reality = { git = "https://github.com/eycorsican/reality-rs.git", optional = true } -reality-rustls = { package = "rustls", git = "https://github.com/eycorsican/reality-rustls.git", branch = "reality-rebase-0.23.36", optional = true, default-features = false, features = ["ring", "std", "tls12", "logging"] } +reality-rustls = { package = "rustls", git = "https://github.com/eycorsican/reality-rustls.git", branch = "reality-rebase-0.23.36", optional = true, default-features = false, features = [ + "ring", + "std", + "tls12", + "logging", +] } # API -axum = { version = "0.7", default-features = false, features = ["http1", "tokio", "json"], optional = true } +axum = { version = "0.7", default-features = false, features = [ + "http1", + "tokio", + "json", +], optional = true } # Auto reload notify = { version = "6", optional = true } # TUN -tun = { version = "0.7", features = ["async"], optional = true } +tun = { version = "0.7", features = ["async"], optional = true } netstack-lwip = { git = "https://github.com/eycorsican/netstack-lwip", rev = "4cc3162", optional = true } netstack-smoltcp = { git = "https://github.com/eycorsican/netstack-smoltcp", branch = "pr-16-initialize-unfilled", optional = true } @@ -291,7 +339,15 @@ memchr = { version = "2" } [dev-dependencies] rcgen = "0.13" sha2 = "0.10" -tokio = { version = "1", features = ["fs", "sync", "io-util", "net", "time", "rt", "rt-multi-thread"] } +tokio = { version = "1", features = [ + "fs", + "sync", + "io-util", + "net", + "time", + "rt", + "rt-multi-thread", +] } [build-dependencies] cc = "1.2" diff --git a/leaf/src/app/api/api_server.rs b/leaf/src/app/api/api_server.rs index b6fe11879..a99902862 100644 --- a/leaf/src/app/api/api_server.rs +++ b/leaf/src/app/api/api_server.rs @@ -7,11 +7,11 @@ use std::sync::Arc; use chrono::{Local, TimeZone}; use axum::{ + Router, extract::{Path, State}, http::StatusCode, response::{Html, Json}, routing::{get, post}, - Router, }; use tracing::info; @@ -267,8 +267,7 @@ table, th, td { Local .timestamp_opt(c.start_time() as i64, 0) .unwrap() - .format("%H:%M:%S") - .to_string(), + .format("%H:%M:%S"), )); } body.push_str(""); @@ -316,7 +315,6 @@ table, th, td { .timestamp_opt(c.start_time() as i64, 0) .unwrap() .format("%H:%M:%S") - .to_string(), )); } body.push_str(""); diff --git a/leaf/src/app/dispatcher.rs b/leaf/src/app/dispatcher.rs index bff2d6296..cdbd7e736 100644 --- a/leaf/src/app/dispatcher.rs +++ b/leaf/src/app/dispatcher.rs @@ -5,7 +5,7 @@ use std::time::Duration; use async_recursion::async_recursion; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::sync::RwLock; -use tracing::{debug, info, warn, Instrument}; +use tracing::{Instrument, debug, info, warn}; use crate::{ app::SyncDnsClient, @@ -169,22 +169,19 @@ impl Dispatcher { &sess.network, &sess.inbound_tag, &sess.source, &sess.destination ); - if option::DNS_DOMAIN_SNIFFING.load(std::sync::atomic::Ordering::Relaxed) { - if let Some(ip) = sess.destination.ip() { - if let Some(domain) = self.dns_sniffer.get(&ip).await { - debug!("dns sniffed domain={}", &domain); - sess.dns_sniffed_domain = Some(domain); - } - } + if option::DNS_DOMAIN_SNIFFING.load(std::sync::atomic::Ordering::Relaxed) + && let Some(ip) = sess.destination.ip() + && let Some(domain) = self.dns_sniffer.get(&ip).await + { + debug!("dns sniffed domain={}", &domain); + sess.dns_sniffed_domain = Some(domain); } - if let Some(domain) = sess.destination.domain() { - if domain == "healthcheck.leaf" { - if let Err(e) = healthcheck_respond_simple(&mut lhs).await { - debug!("healthcheck response failed: {}", e); - } - return; + if let Some("healthcheck.leaf") = sess.destination.domain().map(String::as_str) { + if let Err(e) = healthcheck_respond_simple(&mut lhs).await { + debug!("healthcheck response failed: {}", e); } + return; } let tls_sniff = option::TLS_DOMAIN_SNIFFING.load(std::sync::atomic::Ordering::Relaxed); @@ -219,12 +216,11 @@ impl Dispatcher { } } - if option::DOMAIN_OVERRIDE.load(std::sync::atomic::Ordering::Relaxed) { - if let Ok(dest) = SocksAddr::try_from((domain, sess.destination.port())) - { - debug!("override destination with sniffed domain={}", dest); - sess.destination = dest; - } + if option::DOMAIN_OVERRIDE.load(std::sync::atomic::Ordering::Relaxed) + && let Ok(dest) = SocksAddr::try_from((domain, sess.destination.port())) + { + debug!("override destination with sniffed domain={}", dest); + sess.destination = dest; } } } @@ -387,26 +383,24 @@ impl Dispatcher { &sess.network, &sess.inbound_tag, &sess.source, &sess.destination ); - if let Some(ip) = sess.destination.ip() { - if let Some(domain) = self.dns_sniffer.get(&ip).await { - debug!("dns sniffed domain={}", &domain); - sess.dns_sniffed_domain = Some(domain); - } + if let Some(ip) = sess.destination.ip() + && let Some(domain) = self.dns_sniffer.get(&ip).await + { + debug!("dns sniffed domain={}", &domain); + sess.dns_sniffed_domain = Some(domain); } - if let Some(domain) = sess.destination.domain() { - if domain == "healthcheck.leaf" { - let recv = HealthcheckUdpRecvHalf { - responded: false, - src_addr: sess.destination.clone(), - }; - let d = HealthcheckUdpDatagram { - recv, - send: HealthcheckUdpSendHalf, - }; - let d: Box = Box::new(d); - return Ok(d); - } + if let Some("healthcheck.leaf") = sess.destination.domain().map(String::as_str) { + let recv = HealthcheckUdpRecvHalf { + responded: false, + src_addr: sess.destination.clone(), + }; + let d = HealthcheckUdpDatagram { + recv, + send: HealthcheckUdpSendHalf, + }; + let d: Box = Box::new(d); + return Ok(d); } let outbound = { diff --git a/leaf/src/app/dns/client.rs b/leaf/src/app/dns/client.rs index ef290345a..b3c75946d 100644 --- a/leaf/src/app/dns/client.rs +++ b/leaf/src/app/dns/client.rs @@ -6,29 +6,29 @@ use std::str::FromStr; use std::sync::{Arc, Mutex, Weak}; use std::time::{Duration, Instant}; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use async_recursion::async_recursion; use futures::future::select_ok; use hickory_proto::{ op::{ - header::MessageType, op_code::OpCode, query::Query, response_code::ResponseCode, Message, + Message, header::MessageType, op_code::OpCode, query::Query, response_code::ResponseCode, }, - rr::{record_data::RData, record_type::RecordType, Name}, + rr::{Name, record_data::RData, record_type::RecordType}, }; use lru::LruCache; -use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand::{Rng, SeedableRng, rngs::StdRng}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tokio::sync::Mutex as TokioMutex; use tokio::time::timeout; -use tracing::{debug, trace, warn, Instrument}; +use tracing::{Instrument, debug, trace, warn}; #[cfg(feature = "rustls-tls")] use { std::sync::Arc as SyncArc, tokio_rustls::{ - rustls::{pki_types::ServerName, ClientConfig, RootCertStore}, TlsConnector, + rustls::{ClientConfig, RootCertStore, pki_types::ServerName}, }, }; @@ -145,7 +145,7 @@ impl DnsClient { ) -> Result { if doh.is_direct { let stream = TcpStream::connect(bootstrap_addr).await?; - return Ok(Box::new(stream)); + return Ok(Box::new(stream) as AnyStream); } if let Some(dispatcher_weak) = self.dispatcher.as_ref() { if let Some(dispatcher) = dispatcher_weak.upgrade() { @@ -852,9 +852,7 @@ impl DnsClient { if last_ttl.is_some() { trace!( "ech parameter missing in record host={} type={} server={}", - host, - ty, - resolver + host, ty, resolver ); return Err(anyhow!( "missing ech parameter in {} record for {} from {}", @@ -1127,20 +1125,19 @@ impl DnsClient { async fn is_direct_outbound(&self, host: &str) -> Result { let mut is_direct_outbound = false; - if let Some(dispatcher_weak) = self.dispatcher.as_ref() { - if let Some(dispatcher) = dispatcher_weak.upgrade() { - let dest = match SocksAddr::try_from((host.to_owned(), 0)) { - Ok(d) => d, - Err(e) => return Err(anyhow!("invalid host {}: {}", host, e)), - }; - let sess = Session { - destination: dest, - skip_resolve: true, - ..Default::default() - }; - if let Ok(Some(tag)) = dispatcher.router.read().await.pick_route(&sess).await { - is_direct_outbound = dispatcher.is_direct_outbound(tag).await; - } + if let Some(dispatcher_weak) = self.dispatcher.as_ref() + && let Some(dispatcher) = dispatcher_weak.upgrade() + { + let dest = SocksAddr::try_from((host.to_owned(), 0)) + .map_err(|e| anyhow!("invalid host {}: {}", host, e))?; + + let sess = Session { + destination: dest, + skip_resolve: true, + ..Default::default() + }; + if let Ok(Some(tag)) = dispatcher.router.read().await.pick_route(&sess).await { + is_direct_outbound = dispatcher.is_direct_outbound(tag).await; } } Ok(is_direct_outbound) @@ -1421,7 +1418,7 @@ impl DnsClient { let mut msg = Message::new(); msg.add_query(Query::query(name, ty)); let mut rng = StdRng::from_entropy(); - let id: u16 = rng.gen(); + let id: u16 = rng.r#gen(); msg.set_id(id); msg.set_op_code(OpCode::Query); msg.set_message_type(MessageType::Query); @@ -1441,14 +1438,13 @@ impl DnsClient { async fn get_cached_ech(&self, host: &str) -> Option { let mut cache = self.ech_cache.lock().await; - if let Some(entry) = cache.get(host) { - if entry + if let Some(entry) = cache.get(host) + && entry .deadline .checked_duration_since(Instant::now()) .is_some() - { - return Some(entry.ech_config_list.clone()); - } + { + return Some(entry.ech_config_list.clone()); } cache.pop(host); None @@ -1505,10 +1501,10 @@ impl DnsClient { }; { let mut locks = self.ech_query_locks.lock().await; - if let Some(current) = locks.get(host) { - if Arc::ptr_eq(current, &host_lock) { - locks.remove(host); - } + if let Some(current) = locks.get(host) + && Arc::ptr_eq(current, &host_lock) + { + locks.remove(host); } } result @@ -1571,25 +1567,24 @@ impl DnsClient { // Making cache lookup a priority rather than static hosts lookup // and insert the static IPs to the cache because there's a chance // for the IPs in the cache to be re-ordered. - if !self.hosts.is_empty() { - if let Some(ips) = self.hosts.get(host) { - if !ips.is_empty() { - if ips.len() > 1 { - let deadline = Instant::now() - .checked_add(Duration::from_secs(6000)) - .unwrap(); - self.cache_insert( - host, - CacheEntry { - ips: ips.clone(), - deadline, - }, - ) - .await; - } - return Ok(ips.to_vec()); - } + if !self.hosts.is_empty() + && let Some(ips) = self.hosts.get(host) + && !ips.is_empty() + { + if ips.len() > 1 { + let deadline = Instant::now() + .checked_add(Duration::from_secs(6000)) + .unwrap(); + self.cache_insert( + host, + CacheEntry { + ips: ips.clone(), + deadline, + }, + ) + .await; } + return Ok(ips.to_vec()); } let mut fqdn = host.to_owned(); diff --git a/leaf/src/app/fake_dns.rs b/leaf/src/app/fake_dns.rs index e6b286e06..189b31e7a 100644 --- a/leaf/src/app/fake_dns.rs +++ b/leaf/src/app/fake_dns.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr}; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use hickory_proto::op::{ - header::MessageType, op_code::OpCode, response_code::ResponseCode, Message, + Message, header::MessageType, op_code::OpCode, response_code::ResponseCode, }; use hickory_proto::rr::{ dns_class::DNSClass, rdata, record_data::RData, record_type::RecordType, resource::Record, diff --git a/leaf/src/app/inbound/cat_listener.rs b/leaf/src/app/inbound/cat_listener.rs index d2a3729ba..1398154f7 100644 --- a/leaf/src/app/inbound/cat_listener.rs +++ b/leaf/src/app/inbound/cat_listener.rs @@ -2,22 +2,22 @@ use std::net::SocketAddr; use std::sync::Arc; use std::{io, pin::Pin}; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use async_trait::async_trait; -use futures::task::{Context, Poll}; use futures::TryFutureExt; +use futures::task::{Context, Poll}; use protobuf::Message; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}; use tokio::sync::mpsc::channel as tokio_channel; use tokio::sync::mpsc::{Receiver as TokioReceiver, Sender as TokioSender}; use tracing::{debug, info}; +use crate::Runner; use crate::app::dispatcher::Dispatcher; use crate::app::nat_manager::{NatManager, UdpPacket}; use crate::config::{CatInboundSettings, Inbound}; use crate::proxy::*; use crate::session::*; -use crate::Runner; struct Stream { input: tokio::io::Stdin, diff --git a/leaf/src/app/inbound/manager.rs b/leaf/src/app/inbound/manager.rs index d35acbdc3..95ff8a063 100644 --- a/leaf/src/app/inbound/manager.rs +++ b/leaf/src/app/inbound/manager.rs @@ -1,15 +1,15 @@ use std::collections::HashMap; use std::sync::Arc; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use protobuf::Message; +use crate::Runner; use crate::app::dispatcher::Dispatcher; use crate::app::nat_manager::NatManager; use crate::config; use crate::proxy; use crate::proxy::AnyInboundHandler; -use crate::Runner; #[cfg(feature = "inbound-amux")] use crate::proxy::amux; diff --git a/leaf/src/app/inbound/network_listener.rs b/leaf/src/app/inbound/network_listener.rs index 9a8c958ee..f361d36d9 100644 --- a/leaf/src/app/inbound/network_listener.rs +++ b/leaf/src/app/inbound/network_listener.rs @@ -10,21 +10,22 @@ use tokio::net::{TcpStream, UdpSocket}; use tokio::sync::mpsc::channel as tokio_channel; use tokio::sync::mpsc::{Receiver as TokioReceiver, Sender as TokioSender}; use tokio::time::timeout; -use tracing::{debug, info, trace, warn, Instrument}; +use tracing::{Instrument, debug, info, trace, warn}; +use crate::Runner; use crate::app::dispatcher::Dispatcher; use crate::app::nat_manager::{NatManager, UdpPacket}; use crate::proxy::*; use crate::session::{Network, Session, SocksAddr}; -use crate::Runner; #[cfg(feature = "inbound-nf")] -lazy_static::lazy_static! { - pub static ref TCP_LISTENING_ADDRESSES: std::sync::RwLock> = - std::sync::RwLock::new(std::collections::HashMap::new()); - pub static ref UDP_LISTENING_ADDRESSES: std::sync::RwLock> = - std::sync::RwLock::new(std::collections::HashMap::new()); -} +pub static TCP_LISTENING_ADDRESSES: std::sync::LazyLock< + std::sync::RwLock>, +> = std::sync::LazyLock::new(|| std::sync::RwLock::new(std::collections::HashMap::new())); +#[cfg(feature = "inbound-nf")] +pub static UDP_LISTENING_ADDRESSES: std::sync::LazyLock< + std::sync::RwLock>, +> = std::sync::LazyLock::new(|| std::sync::RwLock::new(std::collections::HashMap::new())); #[cfg(feature = "inbound-nf")] pub fn get_network_listen_addr(tag: &str, kind: Network) -> Option { diff --git a/leaf/src/app/inbound/tun_listener.rs b/leaf/src/app/inbound/tun_listener.rs index 67e82dc9b..1018ac839 100644 --- a/leaf/src/app/inbound/tun_listener.rs +++ b/leaf/src/app/inbound/tun_listener.rs @@ -2,11 +2,11 @@ use std::sync::Arc; use anyhow::Result; +use crate::Runner; use crate::app::dispatcher::Dispatcher; use crate::app::nat_manager::NatManager; use crate::config::Inbound; use crate::proxy::tun; -use crate::Runner; pub struct TunInboundListener { pub inbound: Inbound, diff --git a/leaf/src/app/logger.rs b/leaf/src/app/logger.rs index 9186f9eda..f6c133294 100644 --- a/leaf/src/app/logger.rs +++ b/leaf/src/app/logger.rs @@ -6,7 +6,7 @@ use anyhow::Result; use tracing::field::Visit; use tracing_appender::non_blocking::WorkerGuard; use tracing_subscriber::{ - filter::{filter_fn, LevelFilter}, + filter::{LevelFilter, filter_fn}, fmt, layer::{Layer, Layered}, prelude::*, diff --git a/leaf/src/app/nat_manager.rs b/leaf/src/app/nat_manager.rs index 24c7e996f..cc5a4e4c3 100644 --- a/leaf/src/app/nat_manager.rs +++ b/leaf/src/app/nat_manager.rs @@ -2,12 +2,13 @@ use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, Instant}; -use futures::future::{abortable, BoxFuture}; +use futures::future::{BoxFuture, abortable}; use tokio::sync::{ + Mutex, MutexGuard, mpsc::{self, Sender}, - oneshot, Mutex, MutexGuard, + oneshot, }; -use tracing::{debug, error, trace, Instrument}; +use tracing::{Instrument, debug, error, trace}; use crate::app::dispatcher::Dispatcher; use crate::option; diff --git a/leaf/src/app/outbound/manager.rs b/leaf/src/app/outbound/manager.rs index 68ca17424..060825dab 100644 --- a/leaf/src/app/outbound/manager.rs +++ b/leaf/src/app/outbound/manager.rs @@ -1,5 +1,5 @@ use std::{ - collections::{hash_map, HashMap}, + collections::{HashMap, hash_map}, convert::From, sync::Arc, }; @@ -7,7 +7,7 @@ use std::{ #[cfg(feature = "outbound-select")] use tokio::sync::RwLock; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use futures::future::AbortHandle; use protobuf::Message; use tracing::{debug, trace}; @@ -18,10 +18,10 @@ use crate::proxy::chain; use crate::proxy::failover; #[cfg(feature = "outbound-mptp")] use crate::proxy::mptp; -#[cfg(feature = "outbound-static")] -use crate::proxy::r#static; #[cfg(feature = "outbound-select")] use crate::proxy::select; +#[cfg(feature = "outbound-static")] +use crate::proxy::r#static; #[cfg(feature = "outbound-tryall")] use crate::proxy::tryall; @@ -213,7 +213,7 @@ impl OutboundManager { "invalid [{}] outbound settings: unknown obfs method {}", &tag, method - )) + )); } }; HandlerBuilder::default() diff --git a/leaf/src/app/outbound/plugin.rs b/leaf/src/app/outbound/plugin.rs index 1bb637513..10ecc9152 100644 --- a/leaf/src/app/outbound/plugin.rs +++ b/leaf/src/app/outbound/plugin.rs @@ -149,6 +149,9 @@ impl ExternalHandlers { Self::default() } + /// # SAFETY + /// + /// Path must be a valid path to a shared library, that exports a valid plugin_spec pub unsafe fn new_handler

(&mut self, path: P, tag: &str, args: &str) -> io::Result<()> where P: AsRef + ToString + Clone, @@ -156,14 +159,14 @@ impl ExternalHandlers { let lib = if let Some(lib) = self.libraries.get(&path.to_string()) { lib.clone() } else { - let lib = Arc::new(Library::new(path.clone()).unwrap()); + let lib = Arc::new(unsafe { Library::new(path.clone()) }.unwrap()); self.libraries.insert(path.to_string(), lib.clone()); lib }; - let plugin = lib.get::<*mut PluginSpec>(b"plugin_spec\0").unwrap().read(); + let plugin = unsafe { lib.get::<*mut PluginSpec>(b"plugin_spec\0").unwrap().read() }; let mut registrar = PluginRegistrarImpl::new(Arc::clone(&lib)); - (plugin.add_handler_fn)(&mut registrar, tag, args); + unsafe { (plugin.add_handler_fn)(&mut registrar, tag, args) }; self.stream_handlers.extend(registrar.stream_handlers); self.datagram_handlers.extend(registrar.datagram_handlers); Ok(()) diff --git a/leaf/src/app/outbound/selector.rs b/leaf/src/app/outbound/selector.rs index 03fcb2387..770feaae0 100644 --- a/leaf/src/app/outbound/selector.rs +++ b/leaf/src/app/outbound/selector.rs @@ -1,11 +1,11 @@ use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; use protobuf::Message; use tracing::warn; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; fn get_cache_file_path() -> Result { let cache_loc = if !(&*crate::option::CACHE_LOCATION).is_empty() { diff --git a/leaf/src/app/router.rs b/leaf/src/app/router.rs index c1f25b41a..e1b887244 100644 --- a/leaf/src/app/router.rs +++ b/leaf/src/app/router.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; use std::sync::Arc; -use anyhow::anyhow; use anyhow::Result; +use anyhow::anyhow; use async_recursion::async_recursion; use cidr::IpCidr; use futures::TryFutureExt; -use maxminddb::geoip2::Country; use maxminddb::Mmap; +use maxminddb::geoip2::Country; #[cfg(feature = "rule-process-name")] use regex::Regex; use tracing::{debug, warn}; @@ -55,20 +55,16 @@ impl Condition for MmdbMatcher { fn apply(&self, sess: &Session) -> bool { let destination = sess .destination_for_routing() - .unwrap_or_else(|_| std::borrow::Cow::Borrowed(&sess.destination)); - if !destination.is_domain() { - if let Some(ip) = destination.ip() { - if let Ok(country) = self.reader.lookup::(ip) { - if let Some(country) = country.country { - if let Some(iso_code) = country.iso_code { - if iso_code.to_lowercase() == self.country_code.to_lowercase() { - debug!("[{}] matches geoip code [{}]", ip, &self.country_code); - return true; - } - } - } - } - } + .unwrap_or(std::borrow::Cow::Borrowed(&sess.destination)); + if !destination.is_domain() + && let Some(ip) = destination.ip() + && let Ok(country) = self.reader.lookup::(ip) + && let Some(country) = country.country + && let Some(iso_code) = country.iso_code + && iso_code.to_lowercase() == self.country_code.to_lowercase() + { + debug!("[{}] matches geoip code [{}]", ip, &self.country_code); + return true; } false } @@ -99,14 +95,14 @@ impl Condition for IpCidrMatcher { fn apply(&self, sess: &Session) -> bool { let destination = sess .destination_for_routing() - .unwrap_or_else(|_| std::borrow::Cow::Borrowed(&sess.destination)); + .unwrap_or(std::borrow::Cow::Borrowed(&sess.destination)); if !destination.is_domain() { for cidr in &self.values { - if let Some(ip) = destination.ip() { - if cidr.contains(&ip) { - debug!("[{}] matches ip-cidr [{}]", ip, &cidr); - return true; - } + if let Some(ip) = destination.ip() + && cidr.contains(&ip) + { + debug!("[{}] matches ip-cidr [{}]", ip, &cidr); + return true; } } } @@ -227,7 +223,7 @@ impl Condition for PortRangeMatcher { fn apply(&self, sess: &Session) -> bool { let port = sess .destination_for_routing() - .unwrap_or_else(|_| std::borrow::Cow::Borrowed(&sess.destination)) + .unwrap_or(std::borrow::Cow::Borrowed(&sess.destination)) .port(); if port >= self.start && port <= self.end { debug!( @@ -255,14 +251,13 @@ impl Condition for DomainKeywordMatcher { fn apply(&self, sess: &Session) -> bool { let destination = sess .destination_for_routing() - .unwrap_or_else(|_| std::borrow::Cow::Borrowed(&sess.destination)); - if destination.is_domain() { - if let Some(domain) = destination.domain() { - if domain.contains(&self.value) { - debug!("[{}] matches domain keyword [{}]", domain, &self.value); - return true; - } - } + .unwrap_or(std::borrow::Cow::Borrowed(&sess.destination)); + if destination.is_domain() + && let Some(domain) = destination.domain() + && domain.contains(&self.value) + { + debug!("[{}] matches domain keyword [{}]", domain, &self.value); + return true; } false } @@ -302,14 +297,13 @@ impl Condition for DomainSuffixMatcher { fn apply(&self, sess: &Session) -> bool { let destination = sess .destination_for_routing() - .unwrap_or_else(|_| std::borrow::Cow::Borrowed(&sess.destination)); - if destination.is_domain() { - if let Some(domain) = destination.domain() { - if is_sub_domain(domain, &self.value) { - debug!("[{}] matches domain suffix [{}]", domain, &self.value); - return true; - } - } + .unwrap_or(std::borrow::Cow::Borrowed(&sess.destination)); + if destination.is_domain() + && let Some(domain) = destination.domain() + && is_sub_domain(domain, &self.value) + { + debug!("[{}] matches domain suffix [{}]", domain, &self.value); + return true; } false } @@ -329,14 +323,13 @@ impl Condition for DomainFullMatcher { fn apply(&self, sess: &Session) -> bool { let destination = sess .destination_for_routing() - .unwrap_or_else(|_| std::borrow::Cow::Borrowed(&sess.destination)); - if destination.is_domain() { - if let Some(domain) = destination.domain() { - if domain == &self.value { - debug!("{} matches domain [{}]", domain, &self.value); - return true; - } - } + .unwrap_or(std::borrow::Cow::Borrowed(&sess.destination)); + if destination.is_domain() + && let Some(domain) = destination.domain() + && domain == &self.value + { + debug!("{} matches domain [{}]", domain, &self.value); + return true; } false } @@ -629,7 +622,7 @@ mod tests { }; // test port range - let m = PortMatcher::new(&vec!["1024-5000".to_string(), "6000-7000".to_string()]); + let m = PortMatcher::new(&["1024-5000".to_string(), "6000-7000".to_string()]); sess.destination = SocksAddr::Domain("www.google.com".to_string(), 2000); assert!(m.apply(&sess)); sess.destination = SocksAddr::Domain("www.google.com".to_string(), 5001); @@ -638,7 +631,7 @@ mod tests { assert!(m.apply(&sess)); // test single port range - let m = PortMatcher::new(&vec!["22-22".to_string()]); + let m = PortMatcher::new(&["22-22".to_string()]); sess.destination = SocksAddr::Domain("www.google.com".to_string(), 22); assert!(m.apply(&sess)); diff --git a/leaf/src/app/stat_manager.rs b/leaf/src/app/stat_manager.rs index dbde65af4..f155292ed 100644 --- a/leaf/src/app/stat_manager.rs +++ b/leaf/src/app/stat_manager.rs @@ -1,6 +1,6 @@ use std::collections::{HashMap, VecDeque}; -use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::{io, pin::Pin}; use async_trait::async_trait; @@ -9,7 +9,7 @@ use futures::{ task::{Context, Poll}, }; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -use tokio::sync::{mpsc, RwLock}; +use tokio::sync::{RwLock, mpsc}; use tracing::debug; use crate::{option, proxy::*, session::*}; diff --git a/leaf/src/common/crypto.rs b/leaf/src/common/crypto.rs index 74842a026..fdc29423f 100644 --- a/leaf/src/common/crypto.rs +++ b/leaf/src/common/crypto.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; -use anyhow::{anyhow, Result}; -use lazy_static::lazy_static; +use anyhow::{Result, anyhow}; pub trait Cipher: Sync + Send + Unpin where @@ -46,16 +45,15 @@ pub mod aead { use super::*; - lazy_static! { - static ref AEAD_LIST: HashMap<&'static str, symm::Cipher> = { + static AEAD_LIST: std::sync::LazyLock> = + std::sync::LazyLock::new(|| { let mut m = HashMap::new(); m.insert("chacha20-poly1305", symm::Cipher::chacha20_poly1305()); m.insert("chacha20-ietf-poly1305", symm::Cipher::chacha20_poly1305()); m.insert("aes-256-gcm", symm::Cipher::aes_256_gcm()); m.insert("aes-128-gcm", symm::Cipher::aes_128_gcm()); m - }; - } + }); pub struct AeadCipher { cipher: symm::Cipher, @@ -209,6 +207,8 @@ pub mod aead { not(feature = "openssl-aead") ))] pub mod aead { + use std::sync::LazyLock; + #[cfg(feature = "aws-lc-aead")] use aws_lc_rs::aead::{self, Aad, Algorithm, LessSafeKey, Nonce, UnboundKey}; #[cfg(all(feature = "ring-aead", not(feature = "aws-lc-aead")))] @@ -216,16 +216,14 @@ pub mod aead { use super::*; - lazy_static! { - static ref AEAD_LIST: HashMap<&'static str, &'static Algorithm> = { - let mut m = HashMap::new(); - m.insert("chacha20-poly1305", &aead::CHACHA20_POLY1305); - m.insert("chacha20-ietf-poly1305", &aead::CHACHA20_POLY1305); - m.insert("aes-256-gcm", &aead::AES_256_GCM); - m.insert("aes-128-gcm", &aead::AES_128_GCM); - m - }; - } + static AEAD_LIST: LazyLock> = LazyLock::new(|| { + let mut m = HashMap::new(); + m.insert("chacha20-poly1305", &aead::CHACHA20_POLY1305); + m.insert("chacha20-ietf-poly1305", &aead::CHACHA20_POLY1305); + m.insert("aes-256-gcm", &aead::AES_256_GCM); + m.insert("aes-128-gcm", &aead::AES_128_GCM); + m + }); pub struct AeadCipher { algorithm: &'static Algorithm, @@ -365,11 +363,7 @@ mod tests { impl ShadowsocksNonceSequence { fn new(size: usize) -> Self { - let mut c = Vec::new(); - for _ in 0..size { - c.push(0xff); - } - ShadowsocksNonceSequence(c) + ShadowsocksNonceSequence(std::iter::repeat_n(0xff, size).collect()) } fn inc(&mut self) { diff --git a/leaf/src/common/dns_sniff.rs b/leaf/src/common/dns_sniff.rs index b69a8119d..e775eb120 100644 --- a/leaf/src/common/dns_sniff.rs +++ b/leaf/src/common/dns_sniff.rs @@ -72,31 +72,31 @@ impl OutboundDatagramRecvHalf for SniffingDatagramRecvHalf { async fn recv_from(&mut self, buf: &mut [u8]) -> io::Result<(usize, SocksAddr)> { let (len, src_addr) = self.inner.recv_from(buf).await?; - if let Ok(msg) = Message::from_vec(&buf[..len]) { - if msg.message_type() == MessageType::Response { - // Extract domain from the first query in the response - let domain = if let Some(query) = msg.queries().first() { - let mut name = query.name().to_string(); - if name.ends_with('.') { - name.pop(); - } - Some(name) - } else { - None - }; - - if let Some(domain) = domain { - for answer in msg.answers() { - if let Some(rdata) = answer.data() { - match rdata { - RData::A(ip) => { - self.sniffer.add(IpAddr::V4(ip.0), domain.clone()).await; - } - RData::AAAA(ip) => { - self.sniffer.add(IpAddr::V6(ip.0), domain.clone()).await; - } - _ => {} + if let Ok(msg) = Message::from_vec(&buf[..len]) + && msg.message_type() == MessageType::Response + { + // Extract domain from the first query in the response + let domain = if let Some(query) = msg.queries().first() { + let mut name = query.name().to_string(); + if name.ends_with('.') { + name.pop(); + } + Some(name) + } else { + None + }; + + if let Some(domain) = domain { + for answer in msg.answers() { + if let Some(rdata) = answer.data() { + match rdata { + RData::A(ip) => { + self.sniffer.add(IpAddr::V4(ip.0), domain.clone()).await; + } + RData::AAAA(ip) => { + self.sniffer.add(IpAddr::V6(ip.0), domain.clone()).await; } + _ => {} } } } diff --git a/leaf/src/common/net.rs b/leaf/src/common/net.rs index 9511b53eb..67adbf89d 100644 --- a/leaf/src/common/net.rs +++ b/leaf/src/common/net.rs @@ -1,6 +1,6 @@ use std::net::{SocketAddr, SocketAddrV6}; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; pub fn parse_bind_addr(bind: &str) -> Result { let mut split = bind.split('%'); diff --git a/leaf/src/common/resolver.rs b/leaf/src/common/resolver.rs index 1c824849b..5f0deb31e 100644 --- a/leaf/src/common/resolver.rs +++ b/leaf/src/common/resolver.rs @@ -1,10 +1,10 @@ use std::net::SocketAddr; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use futures::TryFutureExt; +use rand::SeedableRng; use rand::prelude::SliceRandom; use rand::rngs::StdRng; -use rand::SeedableRng; use crate::app::SyncDnsClient; use crate::proxy::DialOrder; diff --git a/leaf/src/common/sniff.rs b/leaf/src/common/sniff.rs index 8f88f9e0e..73c847c93 100644 --- a/leaf/src/common/sniff.rs +++ b/leaf/src/common/sniff.rs @@ -210,14 +210,14 @@ where SniffResult::NotEnoughData => continue, SniffResult::NotMatch => (), SniffResult::Domain(domain) => { - return Ok(Some((SniffKind::Tls, domain))) + return Ok(Some((SniffKind::Tls, domain))); } } match self.sniff_http_host(&self.buf[..]) { SniffResult::NotEnoughData => continue, SniffResult::NotMatch => (), SniffResult::Domain(domain) => { - return Ok(Some((SniffKind::Http, domain))) + return Ok(Some((SniffKind::Http, domain))); } } return Ok(None); diff --git a/leaf/src/config/common.rs b/leaf/src/config/common.rs index d386b6934..0f2e20b18 100644 --- a/leaf/src/config/common.rs +++ b/leaf/src/config/common.rs @@ -896,7 +896,7 @@ pub fn to_internal(mut config: Config) -> Result { _ => { return Err(anyhow::anyhow!( "invalid [tls inbound] settings: echConfig and echKey must be set together" - )) + )); } } } @@ -1486,21 +1486,19 @@ pub fn to_internal(mut config: Config) -> Result { rule.target_tag = target_tag; // handle FINAL rule first - if let Some(type_field) = &ext_rule.type_field { - if type_field == "FINAL" { - // reorder outbounds to make the FINAL one first - let mut idx = None; - for (i, v) in outbounds.iter().enumerate() { - if v.tag == rule.target_tag { - idx = Some(i); - } + if let Some("FINAL") = ext_rule.type_field.as_deref() { + // reorder outbounds to make the FINAL one first + let mut idx = None; + for (i, v) in outbounds.iter().enumerate() { + if v.tag == rule.target_tag { + idx = Some(i); } - if let Some(idx) = idx { - let final_ob = outbounds.remove(idx); - outbounds.insert(0, final_ob); - } - continue; } + if let Some(idx) = idx { + let final_ob = outbounds.remove(idx); + outbounds.insert(0, final_ob); + } + continue; } if let Some(ext_ips) = ext_rule.ip.as_mut() { diff --git a/leaf/src/config/conf/config.rs b/leaf/src/config/conf/config.rs index ed1f2c068..ab65d63a0 100644 --- a/leaf/src/config/conf/config.rs +++ b/leaf/src/config/conf/config.rs @@ -244,7 +244,7 @@ fn get_section(text: &str) -> Option<&str> { } fn normalize_section(s: &str) -> String { - s.to_lowercase().replace(' ', "").replace('_', "") + s.to_lowercase().replace([' ', '_'], "") } fn get_certificate_sections<'a, I>(lines: I) -> HashMap @@ -311,12 +311,12 @@ where } } - if let Some(name) = current_name.take() { - if !current_lines.is_empty() { - let mut content = current_lines.join("\n"); - content.push('\n'); - certificates.insert(name, content); - } + if let Some(name) = current_name.take() + && !current_lines.is_empty() + { + let mut content = current_lines.join("\n"); + content.push('\n'); + certificates.insert(name, content); } certificates @@ -386,12 +386,12 @@ where } } - if let Some(name) = current_name.take() { - if !current_lines.is_empty() { - let mut content = current_lines.join("\n"); - content.push('\n'); - ech_configs.insert(name, content); - } + if let Some(name) = current_name.take() + && !current_lines.is_empty() + { + let mut content = current_lines.join("\n"); + content.push('\n'); + ech_configs.insert(name, content); } ech_configs @@ -426,11 +426,7 @@ where items.push(item.to_string()); } } - if !items.is_empty() { - Some(items) - } else { - None - } + if !items.is_empty() { Some(items) } else { None } } fn get_string(text: &str) -> Option { @@ -445,12 +441,13 @@ fn get_value(text: &str) -> Option where T: std::str::FromStr, { - if !text.is_empty() { - if let Ok(v) = text.parse::() { - return Some(v); - } + if !text.is_empty() + && let Ok(v) = text.parse::() + { + Some(v) + } else { + None } - None } pub fn from_lines(lines: Vec>) -> Result { @@ -466,7 +463,7 @@ pub fn from_lines(lines: Vec>) -> Result { if parts.len() != 2 { continue; } - std::env::set_var(parts[0], parts[1]); + unsafe { std::env::set_var(parts[0], parts[1]) }; } let mut general = General::default(); @@ -482,7 +479,7 @@ pub fn from_lines(lines: Vec>) -> Result { } "tun" => { if let Some(items) = get_char_sep_slice(parts[1], ',') { - if items.len() >= 1 && items[0] == "auto" { + if let Some("auto") = items.first().map(String::as_str) { general.tun_auto = Some(true); continue; } @@ -1108,19 +1105,19 @@ pub fn to_common(conf: &Config) -> Result { let ech_configs = conf.ech_configs.as_ref(); let resolve_cert = |value: &Option| -> Option { let value = value.as_ref()?; - if let Some(certificates) = certificates { - if let Some(content) = certificates.get(value) { - return Some(content.clone()); - } + if let Some(certificates) = certificates + && let Some(content) = certificates.get(value) + { + return Some(content.clone()); } Some(value.clone()) }; let resolve_ech = |value: &Option| -> Option { let value = value.as_ref()?; - if let Some(ech_configs) = ech_configs { - if let Some(content) = ech_configs.get(value) { - return Some(content.clone()); - } + if let Some(ech_configs) = ech_configs + && let Some(content) = ech_configs.get(value) + { + return Some(content.clone()); } Some(value.clone()) }; @@ -1584,6 +1581,15 @@ pub fn from_string(s: &str) -> Result { to_internal(&config) } +pub fn from_file

(path: P) -> Result +where + P: AsRef, +{ + let lines = read_lines(path)?.collect(); + let config = from_lines(lines)?; + to_internal(&config) +} + #[cfg(test)] mod tests { use super::*; @@ -1729,15 +1735,18 @@ AQI= #[test] fn test_trojan_tls_ech_validation() { - let mut proxy = Proxy::default(); - proxy.tag = "Trojan".to_string(); - proxy.protocol = "trojan".to_string(); - proxy.address = Some("1.2.3.4".to_string()); - proxy.port = Some(443); - proxy.password = Some("password".to_string()); - proxy.sni = Some("www.google.com".to_string()); - proxy.tls_ech = Some(true); - proxy.tls_ech_config_list = Some(" ".to_string()); + // let mut proxy = Proxy::default(); + let proxy = Proxy { + tag: "Trojan".into(), + protocol: "trojan".into(), + address: Some("1.2.3.4".into()), + port: Some(443), + password: Some("password".into()), + sni: Some("www.google.com".into()), + tls_ech: Some(true), + tls_ech_config_list: Some(" ".into()), + ..Default::default() + }; let config = Config { general: None, @@ -1971,12 +1980,3 @@ CERT4 assert_eq!(certs.get("NoSpaceCert").unwrap(), "CERT4\n"); } } - -pub fn from_file

(path: P) -> Result -where - P: AsRef, -{ - let lines = read_lines(path)?.collect(); - let config = from_lines(lines)?; - to_internal(&config) -} diff --git a/leaf/src/config/external_rule.rs b/leaf/src/config/external_rule.rs index 2cdcd45d8..abd8a30a1 100644 --- a/leaf/src/config/external_rule.rs +++ b/leaf/src/config/external_rule.rs @@ -2,8 +2,8 @@ use std::fs::File; use std::io::BufReader; use std::path::Path; -use anyhow::anyhow; use anyhow::Result; +use anyhow::anyhow; use super::{geosite, internal}; diff --git a/leaf/src/config/json/config.rs b/leaf/src/config/json/config.rs index 20dc1bd51..994528def 100644 --- a/leaf/src/config/json/config.rs +++ b/leaf/src/config/json/config.rs @@ -1,6 +1,6 @@ use std::path::Path; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use crate::config::{common, internal}; @@ -24,7 +24,7 @@ fn apply_env(config: &common::Config) { if let Some(env) = &config.env { for (k, v) in env { if !k.trim().is_empty() { - std::env::set_var(k, v); + unsafe { std::env::set_var(k, v) }; } } } diff --git a/leaf/src/config/json/tests/test_config.rs b/leaf/src/config/json/tests/test_config.rs index e8dec1fae..2c71415c0 100644 --- a/leaf/src/config/json/tests/test_config.rs +++ b/leaf/src/config/json/tests/test_config.rs @@ -146,7 +146,9 @@ fn test_dns_config() { #[test] fn test_env_config_sets_process_env() { let key = "LEAF_JSON_ENV_TEST_KEY"; - std::env::remove_var(key); + unsafe { + std::env::remove_var(key); + } let json_str = r#" { "env": { @@ -156,7 +158,9 @@ fn test_env_config_sets_process_env() { "#; let _ = crate::config::json::json_from_string(json_str).unwrap(); assert_eq!(std::env::var(key).unwrap(), "json-env-value"); - std::env::remove_var(key); + unsafe { + std::env::remove_var(key); + } } #[test] diff --git a/leaf/src/config/mod.rs b/leaf/src/config/mod.rs index 1f44c601b..840799915 100644 --- a/leaf/src/config/mod.rs +++ b/leaf/src/config/mod.rs @@ -1,7 +1,8 @@ +use std::ffi::OsStr; use std::path::Path; -use anyhow::anyhow; use anyhow::Result; +use anyhow::anyhow; pub mod common; pub mod external_rule; @@ -32,15 +33,13 @@ pub fn from_string(s: &str) -> Result { } pub fn from_file(path: &str) -> Result { - if let Some(ext) = Path::new(path).extension() { - if let Some(ext) = ext.to_str() { - match ext { - #[cfg(feature = "config-json")] - "json" => return json::from_file(path), - #[cfg(feature = "config-conf")] - "conf" => return conf::from_file(path), - _ => (), - } + if let Some(ext) = Path::new(path).extension().and_then(OsStr::to_str) { + match ext { + #[cfg(feature = "config-json")] + "json" => return json::from_file(path), + #[cfg(feature = "config-conf")] + "conf" => return conf::from_file(path), + _ => (), } } Err(anyhow!("config files use extension .json or .conf")) diff --git a/leaf/src/lib.rs b/leaf/src/lib.rs index 5fe526623..663a41ccd 100644 --- a/leaf/src/lib.rs +++ b/leaf/src/lib.rs @@ -1,20 +1,21 @@ use std::collections::HashMap; use std::io; -use std::sync::mpsc::sync_channel; use std::sync::Arc; +use std::sync::LazyLock; use std::sync::Mutex; +use std::sync::mpsc::sync_channel; use anyhow::anyhow; -use lazy_static::lazy_static; + use thiserror::Error; -use tokio::sync::mpsc; use tokio::sync::RwLock; -use tokio::time::{timeout, Duration}; +use tokio::sync::mpsc; +use tokio::time::{Duration, timeout}; use tracing::{info, trace, warn}; #[cfg(feature = "auto-reload")] use notify::{ - event, Error as NotifyError, RecommendedWatcher, RecursiveMode, Result as NotifyResult, Watcher, + Error as NotifyError, RecommendedWatcher, RecursiveMode, Result as NotifyResult, Watcher, event, }; use app::{ @@ -22,7 +23,7 @@ use app::{ nat_manager::NatManager, outbound::manager::OutboundManager, router::Router, }; -use crate::app::{stat_manager::StatManager, SyncStatManager}; +use crate::app::{SyncStatManager, stat_manager::StatManager}; #[cfg(feature = "api")] use crate::app::api::api_server::ApiServer; @@ -320,10 +321,10 @@ impl RuntimeManager { // The config file could somehow be removed and re-created // by an editor, in that case create a new watcher to watch // the new file. - if let event::EventKind::Remove(event::RemoveKind::File) = ev.kind { - if let Some(m) = RUNTIME_MANAGER.lock().unwrap().get(&rt_id) { - let _ = m.new_watcher(); - } + if let event::EventKind::Remove(event::RemoveKind::File) = ev.kind + && let Some(m) = RUNTIME_MANAGER.lock().unwrap().get(&rt_id) + { + let _ = m.new_watcher(); } } Err(e) => { @@ -347,10 +348,8 @@ impl RuntimeManager { pub type RuntimeId = u16; -lazy_static! { - pub static ref RUNTIME_MANAGER: Mutex>> = - Mutex::new(HashMap::new()); -} +pub static RUNTIME_MANAGER: LazyLock>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); pub fn reload(key: RuntimeId) -> Result<(), Error> { if let Some(m) = RUNTIME_MANAGER @@ -511,12 +510,12 @@ pub fn start(rt_id: RuntimeId, opts: StartOptions) -> Result<(), Error> { } else { iface.clone() }; - std::env::set_var("OUTBOUND_INTERFACE", binds); + unsafe { std::env::set_var("OUTBOUND_INTERFACE", binds) }; } } #[cfg(all(feature = "inbound-tun", target_os = "windows"))] - { + unsafe { std::env::set_var("OUTBOUND_INTERFACE", winsys::get_default_interface_ips()); } diff --git a/leaf/src/mobile/callback.rs b/leaf/src/mobile/callback.rs index 02a98fd37..2ad4aadf1 100644 --- a/leaf/src/mobile/callback.rs +++ b/leaf/src/mobile/callback.rs @@ -42,8 +42,8 @@ pub mod android { use std::os::unix::io::RawFd; - use anyhow::{anyhow, Result}; - use jni::{objects::*, JavaVM}; + use anyhow::{Result, anyhow}; + use jni::{JavaVM, objects::*}; use std::sync::RwLock; static JVM: RwLock> = RwLock::new(None); diff --git a/leaf/src/mobile/logger.rs b/leaf/src/mobile/logger.rs index 584954660..77df6f229 100644 --- a/leaf/src/mobile/logger.rs +++ b/leaf/src/mobile/logger.rs @@ -6,7 +6,7 @@ use std::{ use bytes::BytesMut; #[cfg(any(target_os = "ios", target_os = "macos"))] -use super::bindings::{asl_log, ASL_LEVEL_NOTICE}; +use super::bindings::{ASL_LEVEL_NOTICE, asl_log}; #[cfg(target_os = "android")] use super::bindings::{__android_log_print, android_LogPriority_ANDROID_LOG_VERBOSE}; diff --git a/leaf/src/option/mod.rs b/leaf/src/option/mod.rs index 4e4a24382..c696bafc8 100644 --- a/leaf/src/option/mod.rs +++ b/leaf/src/option/mod.rs @@ -1,22 +1,22 @@ use std::env; use std::net::SocketAddr; use std::str::FromStr; +use std::sync::LazyLock; use std::sync::atomic::AtomicBool; -use lazy_static::lazy_static; - // Gets an environment variable by a key and parses as type `T` or returns // the provided default value. fn get_env_var_or(key: &str, default: T) -> T where T: FromStr, { - if let Ok(v) = env::var(key) { - if let Ok(v) = v.parse::() { - return v; - } + if let Ok(v) = env::var(key) + && let Ok(v) = v.parse::() + { + v + } else { + default } - default } fn get_env_var_or_else(key: &str, f: F) -> T @@ -24,348 +24,296 @@ where T: FromStr, F: FnOnce() -> T, { - if let Ok(v) = env::var(key) { - if let Ok(v) = v.parse::() { - return v; - } + if let Ok(v) = env::var(key) + && let Ok(v) = v.parse::() + { + return v; } f() } #[cfg(target_os = "ios")] -lazy_static! { - /// Maximum number of proxy outbound TCP connections allowed at the same time. - pub static ref ENDPOINT_TCP_CONCURRENCY: usize = { - get_env_var_or("ENDPOINT_TCP_CONCURRENCY", 45) - }; - - /// Maximum number of direct outbound TCP connections allowed at the same time. - pub static ref DIRECT_TCP_CONCURRENCY: usize = { - get_env_var_or("DIRECT_TCP_CONCURRENCY", 64) - }; - - /// DNS cache size in the built-in DNS client. - pub static ref DNS_CACHE_SIZE: usize = { - get_env_var_or("DNS_CACHE_SIZE", 64) - }; -} +/// Maximum number of proxy outbound TCP connections allowed at the same time. +pub static ENDPOINT_TCP_CONCURRENCY: LazyLock = + LazyLock::new(|| get_env_var_or("ENDPOINT_TCP_CONCURRENCY", 45)); +#[cfg(target_os = "ios")] +/// Maximum number of direct outbound TCP connections allowed at the same time. +pub static DIRECT_TCP_CONCURRENCY: LazyLock = + LazyLock::new(|| get_env_var_or("DIRECT_TCP_CONCURRENCY", 64)); +#[cfg(target_os = "ios")] +/// DNS cache size in the built-in DNS client. +pub static DNS_CACHE_SIZE: LazyLock = LazyLock::new(|| get_env_var_or("DNS_CACHE_SIZE", 64)); #[cfg(not(target_os = "ios"))] -lazy_static! { - /// Maximum number of proxy outbound TCP connections allowed at the same time. - pub static ref ENDPOINT_TCP_CONCURRENCY: usize = { - get_env_var_or("ENDPOINT_TCP_CONCURRENCY", 1024) - }; - - /// Maximum number of direct outbound TCP connections allowed at the same time. - pub static ref DIRECT_TCP_CONCURRENCY: usize = { - get_env_var_or("DIRECT_TCP_CONCURRENCY", 1024) - }; - - /// DNS cache size in the built-in DNS client. - pub static ref DNS_CACHE_SIZE: usize = { - get_env_var_or("DNS_CACHE_SIZE", 512) - }; -} - -lazy_static! { - /// Maximum number of recent connections stored in StatManager. - pub static ref MAX_RECENT_CONNECTIONS: usize = { - get_env_var_or("MAX_RECENT_CONNECTIONS", 0) - }; - - pub static ref HTTP_USER_AGENT: String = { - get_env_var_or_else( - "HTTP_USER_AGENT", - || get_env_var_or("USER_AGENT", "".to_string()), // legacy support - ) - }; - - // The purpose is not to propagate the header, but to extract the forwarded - // source IP. Expects only comma separated IP list and only the first IP is - // taken as the forwarded source. Having this value customizable would benefit - // in case you don't trust the X-Forwarded-For header but there is another header - // which you can trust, for example the CF-Connecting-IP provided by Cloudflare. - pub static ref HTTP_FORWARDED_HEADER: String = { - get_env_var_or("HTTP_FORWARDED_HEADER", "X-Forwarded-For".to_string()) - }; - - pub static ref LOG_CONSOLE_OUT: bool = { - get_env_var_or("LOG_CONSOLE_OUT", false) - }; - - /// Turn on TLS SNI sniffing, the sniffed SNI would override the original - /// destination address, by default the sniffing would perform only on - /// connections with destination port 443, set also TLS_DOMAIN_SNIFFING_ALL - /// to make the sniffing work on all connections. - pub static ref TLS_DOMAIN_SNIFFING: AtomicBool = { - let v: bool = get_env_var_or_else( - "TLS_DOMAIN_SNIFFING", - || get_env_var_or("DOMAIN_SNIFFING", false), // deprecated env var - ); - AtomicBool::new(v) - }; - - /// Turn on TLS SNI sniffing for all TCP connections, this may slow down the - /// connections a little bit, depending on whether the sniff can make an early - /// return. - pub static ref TLS_DOMAIN_SNIFFING_ALL: AtomicBool = { - let v: bool = get_env_var_or("TLS_DOMAIN_SNIFFING_ALL", false); - AtomicBool::new(v) - }; - - /// Turn on HTTP host sniffing, by default only perform on connections with - /// destination port 80. - pub static ref HTTP_DOMAIN_SNIFFING: AtomicBool = { - let v: bool = get_env_var_or("HTTP_DOMAIN_SNIFFING", false); - AtomicBool::new(v) - }; - - /// Turn on HTTP host sniffing for all TCP connections, this may slow down the - /// connections a little bit, depending on whether the sniff can make an early - /// return. - pub static ref HTTP_DOMAIN_SNIFFING_ALL: AtomicBool = { - let v: bool = get_env_var_or("HTTP_DOMAIN_SNIFFING_ALL", false); - AtomicBool::new(v) - }; - - /// Override the original destination with the sniffed domain. - pub static ref DOMAIN_OVERRIDE: AtomicBool = { - let v: bool = get_env_var_or("DOMAIN_OVERRIDE", false); - AtomicBool::new(v) - }; - - /// Turn on DNS sniffing, if the destination is an IP, we try to find the - /// domain from the DNS cache. - pub static ref DNS_DOMAIN_SNIFFING: AtomicBool = { - let v: bool = get_env_var_or("DNS_DOMAIN_SNIFFING", false); - AtomicBool::new(v) - }; - - /// Uplink timeout after downlink EOF. - pub static ref TCP_UPLINK_TIMEOUT: u64 = { - get_env_var_or("TCP_UPLINK_TIMEOUT", 10) - }; - - /// Downlink timeout after uplink EOF. - pub static ref TCP_DOWNLINK_TIMEOUT: u64 = { - get_env_var_or("TCP_DOWNLINK_TIMEOUT", 10) - }; - - /// Buffer size for uplink and downlink connections, in KB. - pub static ref LINK_BUFFER_SIZE: usize = { - get_env_var_or("LINK_BUFFER_SIZE", 2) - }; - - pub static ref NETSTACK_OUTPUT_CHANNEL_SIZE: usize = { - get_env_var_or("NETSTACK_OUTPUT_CHANNEL_SIZE", 512) - }; - - pub static ref NETSTACK_UDP_UPLINK_CHANNEL_SIZE: usize = { - get_env_var_or("NETSTACK_UDP_UPLINK_CHANNEL_SIZE", 256) - }; - - pub static ref UDP_UPLINK_CHANNEL_SIZE: usize = { - get_env_var_or("UDP_UPLINK_CHANNEL_SIZE", 256) - }; - - pub static ref UDP_DOWNLINK_CHANNEL_SIZE: usize = { - get_env_var_or("UDP_DOWNLINK_CHANNEL_SIZE", 256) - }; - - pub static ref QUIC_ACCEPT_CHANNEL_SIZE: usize = { - get_env_var_or("QUIC_ACCEPT_CHANNEL_SIZE", 1024) - }; - - pub static ref AMUX_ACCEPT_CHANNEL_SIZE: usize = { - get_env_var_or("AMUX_ACCEPT_CHANNEL_SIZE", 1024) - }; - - pub static ref AMUX_STREAM_CHANNEL_SIZE: usize = { - get_env_var_or("AMUX_STREAM_CHANNEL_SIZE", 16) - }; - - pub static ref AMUX_FRAME_CHANNEL_SIZE: usize = { - get_env_var_or("AMUX_FRAME_CHANNEL_SIZE", 32) - }; - - /// Buffer size for UDP datagrams receiving/sending, in KB. - pub static ref DATAGRAM_BUFFER_SIZE: usize = { - get_env_var_or("DATAGRAM_BUFFER_SIZE", 2) - }; - - /// The timeout for an accepted inbound TCP connection to finish the proxy - /// protocol handshake. - pub static ref INBOUND_ACCEPT_TIMEOUT: u64 = { - get_env_var_or("INBOUND_ACCEPT_TIMEOUT", 60) - }; - - pub static ref OUTBOUND_DIAL_TIMEOUT: u64 = { - get_env_var_or("OUTBOUND_DIAL_TIMEOUT", 4) - }; - - pub static ref OUTBOUND_DIAL_ORDER: crate::proxy::DialOrder = { - match get_env_var_or("OUTBOUND_DIAL_ORDER", "ordered".to_string()).as_str() { +/// Maximum number of proxy outbound TCP connections allowed at the same time. +pub static ENDPOINT_TCP_CONCURRENCY: LazyLock = + LazyLock::new(|| get_env_var_or("ENDPOINT_TCP_CONCURRENCY", 1024)); +#[cfg(not(target_os = "ios"))] +/// Maximum number of direct outbound TCP connections allowed at the same time. +pub static DIRECT_TCP_CONCURRENCY: LazyLock = + LazyLock::new(|| get_env_var_or("DIRECT_TCP_CONCURRENCY", 1024)); +#[cfg(not(target_os = "ios"))] +/// DNS cache size in the built-in DNS client. +pub static DNS_CACHE_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("DNS_CACHE_SIZE", 512)); + +/// Maximum number of recent connections stored in StatManager. +pub static MAX_RECENT_CONNECTIONS: LazyLock = + LazyLock::new(|| get_env_var_or("MAX_RECENT_CONNECTIONS", 0)); + +pub static HTTP_USER_AGENT: LazyLock = LazyLock::new(|| { + get_env_var_or_else( + "HTTP_USER_AGENT", + || get_env_var_or("USER_AGENT", "".to_string()), // legacy support + ) +}); + +// The purpose is not to propagate the header, but to extract the forwarded +// source IP. Expects only comma separated IP list and only the first IP is +// taken as the forwarded source. Having this value customizable would benefit +// in case you don't trust the X-Forwarded-For header but there is another header +// which you can trust, for example the CF-Connecting-IP provided by Cloudflare. +pub static HTTP_FORWARDED_HEADER: LazyLock = + LazyLock::new(|| get_env_var_or("HTTP_FORWARDED_HEADER", "X-Forwarded-For".to_string())); + +pub static LOG_CONSOLE_OUT: LazyLock = + LazyLock::new(|| get_env_var_or("LOG_CONSOLE_OUT", false)); + +/// Turn on TLS SNI sniffing, the sniffed SNI would override the original +/// destination address, by default the sniffing would perform only on +/// connections with destination port 443, set also TLS_DOMAIN_SNIFFING_ALL +/// to make the sniffing work on all connections. +pub static TLS_DOMAIN_SNIFFING: LazyLock = LazyLock::new(|| { + let v: bool = get_env_var_or_else( + "TLS_DOMAIN_SNIFFING", + || get_env_var_or("DOMAIN_SNIFFING", false), // deprecated env var + ); + AtomicBool::new(v) +}); + +/// Turn on TLS SNI sniffing for all TCP connections, this may slow down the +/// connections a little bit, depending on whether the sniff can make an early +/// return. +pub static TLS_DOMAIN_SNIFFING_ALL: LazyLock = LazyLock::new(|| { + let v: bool = get_env_var_or("TLS_DOMAIN_SNIFFING_ALL", false); + AtomicBool::new(v) +}); + +/// Turn on HTTP host sniffing, by default only perform on connections with +/// destination port 80. +pub static HTTP_DOMAIN_SNIFFING: LazyLock = LazyLock::new(|| { + let v: bool = get_env_var_or("HTTP_DOMAIN_SNIFFING", false); + AtomicBool::new(v) +}); + +/// Turn on HTTP host sniffing for all TCP connections, this may slow down the +/// connections a little bit, depending on whether the sniff can make an early +/// return. +pub static HTTP_DOMAIN_SNIFFING_ALL: LazyLock = LazyLock::new(|| { + let v: bool = get_env_var_or("HTTP_DOMAIN_SNIFFING_ALL", false); + AtomicBool::new(v) +}); + +/// Override the original destination with the sniffed domain. +pub static DOMAIN_OVERRIDE: LazyLock = LazyLock::new(|| { + let v: bool = get_env_var_or("DOMAIN_OVERRIDE", false); + AtomicBool::new(v) +}); + +/// Turn on DNS sniffing, if the destination is an IP, we try to find the +/// domain from the DNS cache. +pub static DNS_DOMAIN_SNIFFING: LazyLock = LazyLock::new(|| { + let v: bool = get_env_var_or("DNS_DOMAIN_SNIFFING", false); + AtomicBool::new(v) +}); + +/// Uplink timeout after downlink EOF. +pub static TCP_UPLINK_TIMEOUT: LazyLock = + LazyLock::new(|| get_env_var_or("TCP_UPLINK_TIMEOUT", 10)); + +/// Downlink timeout after uplink EOF. +pub static TCP_DOWNLINK_TIMEOUT: LazyLock = + LazyLock::new(|| get_env_var_or("TCP_DOWNLINK_TIMEOUT", 10)); + +/// Buffer size for uplink and downlink connections, in KB. +pub static LINK_BUFFER_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("LINK_BUFFER_SIZE", 2)); + +pub static NETSTACK_OUTPUT_CHANNEL_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("NETSTACK_OUTPUT_CHANNEL_SIZE", 512)); + +pub static NETSTACK_UDP_UPLINK_CHANNEL_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("NETSTACK_UDP_UPLINK_CHANNEL_SIZE", 256)); + +pub static UDP_UPLINK_CHANNEL_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("UDP_UPLINK_CHANNEL_SIZE", 256)); + +pub static UDP_DOWNLINK_CHANNEL_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("UDP_DOWNLINK_CHANNEL_SIZE", 256)); + +pub static QUIC_ACCEPT_CHANNEL_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("QUIC_ACCEPT_CHANNEL_SIZE", 1024)); + +pub static AMUX_ACCEPT_CHANNEL_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("AMUX_ACCEPT_CHANNEL_SIZE", 1024)); + +pub static AMUX_STREAM_CHANNEL_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("AMUX_STREAM_CHANNEL_SIZE", 16)); + +pub static AMUX_FRAME_CHANNEL_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("AMUX_FRAME_CHANNEL_SIZE", 32)); + +/// Buffer size for UDP datagrams receiving/sending, in KB. +pub static DATAGRAM_BUFFER_SIZE: LazyLock = + LazyLock::new(|| get_env_var_or("DATAGRAM_BUFFER_SIZE", 2)); + +/// The timeout for an accepted inbound TCP connection to finish the proxy +/// protocol handshake. +pub static INBOUND_ACCEPT_TIMEOUT: LazyLock = + LazyLock::new(|| get_env_var_or("INBOUND_ACCEPT_TIMEOUT", 60)); + +pub static OUTBOUND_DIAL_TIMEOUT: LazyLock = + LazyLock::new(|| get_env_var_or("OUTBOUND_DIAL_TIMEOUT", 4)); + +pub static OUTBOUND_DIAL_ORDER: LazyLock = + LazyLock::new( + || match get_env_var_or("OUTBOUND_DIAL_ORDER", "ordered".to_string()).as_str() { "random" => crate::proxy::DialOrder::Random, "partial-random" => crate::proxy::DialOrder::PartialRandom, _ => crate::proxy::DialOrder::Ordered, - } - }; - - /// Maximum outbound dial concurrency. - pub static ref OUTBOUND_DIAL_CONCURRENCY: usize = { - get_env_var_or("OUTBOUND_DIAL_CONCURRENCY", 1) - }; - - pub static ref ASSET_LOCATION: String = { - get_env_var_or_else("ASSET_LOCATION", || { - let mut file = std::env::current_exe().unwrap(); - file.pop(); - file.to_str().unwrap().to_string() - }) - }; - - pub static ref CACHE_LOCATION: String = { - get_env_var_or("CACHE_LOCATION", "".to_string()) - }; - - pub static ref API_LISTEN: String = { - get_env_var_or("API_LISTEN", "".to_string()) - }; - - pub static ref ENABLE_IPV6: bool = { - get_env_var_or("ENABLE_IPV6", false) - }; - - pub static ref PREFER_IPV6: bool = { - get_env_var_or("PREFER_IPV6", false) - }; - - pub static ref UNSPECIFIED_BIND_ADDR: SocketAddr = { - get_env_var_or_else("UNSPECIFIED_BIND_ADDR", || { - if *ENABLE_IPV6 { - "[::]:0".to_string().parse().unwrap() - } else { - "0.0.0.0:0".to_string().parse().unwrap() - } - }) - }; - - pub static ref OUTBOUND_BINDS: Vec = { - let binds = get_env_var_or("OUTBOUND_INTERFACE", "".to_string()); - if binds.is_empty() { - return Vec::new(); - } - let mut outbound_binds = Vec::new(); - for item in binds.split(',').map(str::trim) { - if let Ok(addr) = crate::common::net::parse_bind_addr(item) { - outbound_binds.push(crate::proxy::OutboundBind::Ip(addr)); - } else { - outbound_binds.push(crate::proxy::OutboundBind::Interface(item.to_owned())); - } - } - outbound_binds - }; - - /// Sets the RPC service endpoint for protecting outbound sockets on Android to - /// avoid infinite loop. The `path` is treated as a Unix domain socket endpoint. - /// The RPC service simply listens for incoming connections, reads an int32 on - /// each connection, treats it as the file descriptor to protect, writes back 0 - /// on success. - pub static ref SOCKET_PROTECT_PATH: String = { - get_env_var_or("SOCKET_PROTECT_PATH", "".to_string()) - }; - - pub static ref SOCKET_PROTECT_SERVER: Option = { - get_env_var_or("SOCKET_PROTECT_SERVER", "".to_string()).parse().ok() - }; - - pub static ref GATEWAY_MODE: bool = { - get_env_var_or("GATEWAY_MODE", false) - }; - - /// UDP session timeout. A UDP session shall be terminated if there are no - /// activities in this period. The timeouts are observed only when a check - /// is happened. - pub static ref UDP_SESSION_TIMEOUT: u64 = { - get_env_var_or("UDP_SESSION_TIMEOUT", 30) - }; - - /// UDP session timeout check interval. The interval to check for UDP session - /// timeouts. - pub static ref UDP_SESSION_TIMEOUT_CHECK_INTERVAL: u64 = { - get_env_var_or("UDP_SESSION_TIMEOUT_CHECK_INTERVAL", 10) - }; - - /// Maximum retries for a specific DNS query for the built-in DNS client. - pub static ref MAX_DNS_RETRIES: usize = { - get_env_var_or("MAX_DNS_RETRIES", 4) - }; - - /// Timeout for a DNS query for the built-in DNS client. - pub static ref DNS_TIMEOUT: u64 = { - get_env_var_or("DNS_TIMEOUT", 4) - }; - - pub static ref DNS_SERVER_RESELECT_INTERVAL_SECS: u64 = { - get_env_var_or("DNS_SERVER_RESELECT_INTERVAL_SECS", 30) - }; - - pub static ref DNS_SERVER_SLOW_RESPONSE_MS: u64 = { - get_env_var_or("DNS_SERVER_SLOW_RESPONSE_MS", 800) - }; - - pub static ref DNS_SERVER_SWITCH_THRESHOLD: usize = { - get_env_var_or("DNS_SERVER_SWITCH_THRESHOLD", 3) - }; - - pub static ref DNS_SERVER_FALLBACK_CONCURRENCY: usize = { - get_env_var_or("DNS_SERVER_FALLBACK_CONCURRENCY", 1) - }; - - pub static ref DNS_DUALSTACK_DELAY_MS: u64 = { - get_env_var_or("DNS_DUALSTACK_DELAY_MS", 250) - }; - - pub static ref DEFAULT_TUN_NAME: String = { - get_env_var_or("DEFAULT_TUN_NAME", "utun233".to_string()) - }; - - pub static ref DEFAULT_TUN_IPV4_ADDR: String = { - #[cfg(windows)] - { - get_env_var_or("DEFAULT_TUN_IPV4_ADDR", "10.7.7.2".to_string()) - } - #[cfg(not(windows))] - { - get_env_var_or("DEFAULT_TUN_IPV4_ADDR", "192.168.233.2".to_string()) - } - }; + }, + ); + +/// Maximum outbound dial concurrency. +pub static OUTBOUND_DIAL_CONCURRENCY: LazyLock = + LazyLock::new(|| get_env_var_or("OUTBOUND_DIAL_CONCURRENCY", 1)); + +pub static ASSET_LOCATION: LazyLock = LazyLock::new(|| { + get_env_var_or_else("ASSET_LOCATION", || { + let mut file = std::env::current_exe().unwrap(); + file.pop(); + file.to_str().unwrap().to_string() + }) +}); - pub static ref DEFAULT_TUN_IPV4_GW: String = { - #[cfg(windows)] - { - get_env_var_or("DEFAULT_TUN_IPV4_GW", "10.7.7.1".to_string()) +pub static CACHE_LOCATION: LazyLock = + LazyLock::new(|| get_env_var_or("CACHE_LOCATION", "".to_string())); + +pub static API_LISTEN: LazyLock = + LazyLock::new(|| get_env_var_or("API_LISTEN", "".to_string())); + +pub static ENABLE_IPV6: LazyLock = LazyLock::new(|| get_env_var_or("ENABLE_IPV6", false)); + +pub static PREFER_IPV6: LazyLock = LazyLock::new(|| get_env_var_or("PREFER_IPV6", false)); + +pub static UNSPECIFIED_BIND_ADDR: LazyLock = LazyLock::new(|| { + get_env_var_or_else("UNSPECIFIED_BIND_ADDR", || { + if *ENABLE_IPV6 { + "[::]:0".to_string().parse().unwrap() + } else { + "0.0.0.0:0".to_string().parse().unwrap() } - #[cfg(not(windows))] - { - get_env_var_or("DEFAULT_TUN_IPV4_GW", "192.168.233.1".to_string()) + }) +}); + +pub static OUTBOUND_BINDS: LazyLock> = LazyLock::new(|| { + let binds = get_env_var_or("OUTBOUND_INTERFACE", "".to_string()); + if binds.is_empty() { + return Vec::new(); + } + let mut outbound_binds = Vec::new(); + for item in binds.split(',').map(str::trim) { + if let Ok(addr) = crate::common::net::parse_bind_addr(item) { + outbound_binds.push(crate::proxy::OutboundBind::Ip(addr)); + } else { + outbound_binds.push(crate::proxy::OutboundBind::Interface(item.to_owned())); } - }; + } + outbound_binds +}); - pub static ref DEFAULT_TUN_IPV4_MASK: String = { - get_env_var_or("DEFAULT_TUN_IPV4_MASK", "255.255.255.0".to_string()) - }; +/// Sets the RPC service endpoint for protecting outbound sockets on Android to +/// avoid infinite loop. The `path` is treated as a Unix domain socket endpoint. +/// The RPC service simply listens for incoming connections, reads an int32 on +/// each connection, treats it as the file descriptor to protect, writes back 0 +/// on success. +pub static SOCKET_PROTECT_PATH: LazyLock = + LazyLock::new(|| get_env_var_or("SOCKET_PROTECT_PATH", "".to_string())); - pub static ref DEFAULT_TUN_IPV6_ADDR: String = { - get_env_var_or("DEFAULT_TUN_IPV6_ADDR", "2001:2::2".to_string()) - }; +pub static SOCKET_PROTECT_SERVER: LazyLock> = LazyLock::new(|| { + get_env_var_or("SOCKET_PROTECT_SERVER", "".to_string()) + .parse() + .ok() +}); - pub static ref DEFAULT_TUN_IPV6_GW: String = { - get_env_var_or("DEFAULT_TUN_IPV6_GW", "2001:2::1".to_string()) - }; +pub static GATEWAY_MODE: LazyLock = LazyLock::new(|| get_env_var_or("GATEWAY_MODE", false)); - pub static ref DEFAULT_TUN_IPV6_PREFIXLEN: i32 = { - get_env_var_or("DEFAULT_TUN_IPV6_PREFIXLEN", 64) - }; -} +/// UDP session timeout. A UDP session shall be terminated if there are no +/// activities in this period. The timeouts are observed only when a check +/// is happened. +pub static UDP_SESSION_TIMEOUT: LazyLock = + LazyLock::new(|| get_env_var_or("UDP_SESSION_TIMEOUT", 30)); + +/// UDP session timeout check interval. The interval to check for UDP session +/// timeouts. +pub static UDP_SESSION_TIMEOUT_CHECK_INTERVAL: LazyLock = + LazyLock::new(|| get_env_var_or("UDP_SESSION_TIMEOUT_CHECK_INTERVAL", 10)); + +/// Maximum retries for a specific DNS query for the built-in DNS client. +pub static MAX_DNS_RETRIES: LazyLock = + LazyLock::new(|| get_env_var_or("MAX_DNS_RETRIES", 4)); + +/// Timeout for a DNS query for the built-in DNS client. +pub static DNS_TIMEOUT: LazyLock = LazyLock::new(|| get_env_var_or("DNS_TIMEOUT", 4)); + +pub static DNS_SERVER_RESELECT_INTERVAL_SECS: LazyLock = + LazyLock::new(|| get_env_var_or("DNS_SERVER_RESELECT_INTERVAL_SECS", 30)); + +pub static DNS_SERVER_SLOW_RESPONSE_MS: LazyLock = + LazyLock::new(|| get_env_var_or("DNS_SERVER_SLOW_RESPONSE_MS", 800)); + +pub static DNS_SERVER_SWITCH_THRESHOLD: LazyLock = + LazyLock::new(|| get_env_var_or("DNS_SERVER_SWITCH_THRESHOLD", 3)); + +pub static DNS_SERVER_FALLBACK_CONCURRENCY: LazyLock = + LazyLock::new(|| get_env_var_or("DNS_SERVER_FALLBACK_CONCURRENCY", 1)); + +pub static DNS_DUALSTACK_DELAY_MS: LazyLock = + LazyLock::new(|| get_env_var_or("DNS_DUALSTACK_DELAY_MS", 250)); + +pub static DEFAULT_TUN_NAME: LazyLock = + LazyLock::new(|| get_env_var_or("DEFAULT_TUN_NAME", "utun233".to_string())); + +pub static DEFAULT_TUN_IPV4_ADDR: LazyLock = LazyLock::new(|| { + #[cfg(windows)] + { + get_env_var_or("DEFAULT_TUN_IPV4_ADDR", "10.7.7.2".to_string()) + } + #[cfg(not(windows))] + { + get_env_var_or("DEFAULT_TUN_IPV4_ADDR", "192.168.233.2".to_string()) + } +}); + +pub static DEFAULT_TUN_IPV4_GW: LazyLock = LazyLock::new(|| { + #[cfg(windows)] + { + get_env_var_or("DEFAULT_TUN_IPV4_GW", "10.7.7.1".to_string()) + } + #[cfg(not(windows))] + { + get_env_var_or("DEFAULT_TUN_IPV4_GW", "192.168.233.1".to_string()) + } +}); + +pub static DEFAULT_TUN_IPV4_MASK: LazyLock = + LazyLock::new(|| get_env_var_or("DEFAULT_TUN_IPV4_MASK", "255.255.255.0".to_string())); + +pub static DEFAULT_TUN_IPV6_ADDR: LazyLock = + LazyLock::new(|| get_env_var_or("DEFAULT_TUN_IPV6_ADDR", "2001:2::2".to_string())); + +pub static DEFAULT_TUN_IPV6_GW: LazyLock = + LazyLock::new(|| get_env_var_or("DEFAULT_TUN_IPV6_GW", "2001:2::1".to_string())); + +pub static DEFAULT_TUN_IPV6_PREFIXLEN: LazyLock = + LazyLock::new(|| get_env_var_or("DEFAULT_TUN_IPV6_PREFIXLEN", 64)); diff --git a/leaf/src/proxy/amux/mod.rs b/leaf/src/proxy/amux/mod.rs index 7858a089a..1217d489f 100644 --- a/leaf/src/proxy/amux/mod.rs +++ b/leaf/src/proxy/amux/mod.rs @@ -1,29 +1,28 @@ use std::cmp::min; use std::collections::HashMap; use std::convert::TryInto; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::time::Duration; use std::{io, pin::Pin}; use bytes::{Buf, BufMut, Bytes, BytesMut}; -use futures::future::{abortable, AbortHandle}; +use futures::SinkExt; +use futures::StreamExt; +use futures::future::{AbortHandle, abortable}; use futures::sink::Sink; use futures::stream::SplitSink; use futures::stream::SplitStream; use futures::stream::Stream; -use futures::SinkExt; -use futures::StreamExt; use futures::{ - ready, + Future, TryFutureExt, ready, task::{Context, Poll}, - Future, TryFutureExt, }; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio::sync::Mutex; -use tokio::time::{sleep, Instant}; -use tracing::{debug, trace, Instrument}; +use tokio::sync::mpsc::{self, Receiver, Sender}; +use tokio::time::{Instant, sleep}; +use tracing::{Instrument, debug, trace}; #[cfg(feature = "inbound-amux")] pub mod inbound; @@ -35,7 +34,7 @@ pub const FRAME_STREAM_FIN: u8 = 0x02; pub const MAX_STREAM_FRAME_DATA_LEN: u16 = u16::MAX; pub fn random_u16() -> u16 { - use rand::{rngs::StdRng, RngCore, SeedableRng}; + use rand::{RngCore, SeedableRng, rngs::StdRng}; let mut buf = [0u8; std::mem::size_of::()]; let mut rng = StdRng::from_entropy(); rng.fill_bytes(&mut buf); @@ -139,8 +138,7 @@ impl Drop for MuxStream { self.stream_end.store(true, Ordering::Relaxed); trace!( "drop mux stream {} (session {})", - self.stream_id, - self.session_id + self.stream_id, self.session_id ); } } @@ -665,9 +663,7 @@ impl MuxConnector { ) -> Self { trace!( "new mux connector {} (max_accepts: {}, concurrency: {})", - session_id, - max_accepts, - concurrency + session_id, max_accepts, concurrency ); MuxConnector { max_accepts, diff --git a/leaf/src/proxy/amux/outbound/stream.rs b/leaf/src/proxy/amux/outbound/stream.rs index f4f3819bd..6ce57c422 100644 --- a/leaf/src/proxy/amux/outbound/stream.rs +++ b/leaf/src/proxy/amux/outbound/stream.rs @@ -3,14 +3,14 @@ use std::io; use std::sync::Arc; use async_trait::async_trait; -use futures::future::BoxFuture; -use futures::future::{abortable, AbortHandle}; use futures::FutureExt; +use futures::future::BoxFuture; +use futures::future::{AbortHandle, abortable}; +use rand::SeedableRng; use rand::prelude::SliceRandom; use rand::rngs::StdRng; -use rand::SeedableRng; use tokio::sync::Mutex; -use tracing::{debug, Instrument}; +use tracing::{Instrument, debug}; use crate::{ app::SyncDnsClient, @@ -82,10 +82,10 @@ impl MuxManager { pub async fn new_stream(&self, sess: &Session) -> io::Result { // Run the cleanup task, if it's not already running. - if self.monitor_task.lock().await.is_some() { - if let Some(task) = self.monitor_task.lock().await.take() { - tokio::spawn(task); - } + if self.monitor_task.lock().await.is_some() + && let Some(task) = self.monitor_task.lock().await.take() + { + tokio::spawn(task); } if !sess.new_conn_once { diff --git a/leaf/src/proxy/chain/outbound/datagram.rs b/leaf/src/proxy/chain/outbound/datagram.rs index b276b41ba..32f24468f 100644 --- a/leaf/src/proxy/chain/outbound/datagram.rs +++ b/leaf/src/proxy/chain/outbound/datagram.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use std::io; use async_trait::async_trait; -use tracing::{trace, Instrument}; +use tracing::{Instrument, trace}; use crate::{proxy::*, session::*}; @@ -44,13 +44,13 @@ impl Handler { } fn next_session(&self, mut sess: Session, start: usize) -> Session { - if let OutboundConnect::Proxy(_, address, port) = self.next_connect_addr(start) { - if let Ok(addr) = SocksAddr::try_from((address, port)) { - sess.destination = addr; - sess.dns_sniffed_domain = None; - sess.http_sniffed_domain = None; - sess.tls_sniffed_domain = None; - } + if let OutboundConnect::Proxy(_, address, port) = self.next_connect_addr(start) + && let Ok(addr) = SocksAddr::try_from((address, port)) + { + sess.destination = addr; + sess.dns_sniffed_domain = None; + sess.http_sniffed_domain = None; + sess.tls_sniffed_domain = None; } sess } diff --git a/leaf/src/proxy/chain/outbound/stream.rs b/leaf/src/proxy/chain/outbound/stream.rs index 6dd10d90e..e0bc0aeae 100644 --- a/leaf/src/proxy/chain/outbound/stream.rs +++ b/leaf/src/proxy/chain/outbound/stream.rs @@ -39,14 +39,15 @@ impl Handler { } fn next_session(&self, mut sess: Session, start: usize) -> Session { - if let OutboundConnect::Proxy(_, address, port) = self.next_connect_addr(start) { - if let Ok(addr) = SocksAddr::try_from((address, port)) { - sess.destination = addr; - sess.dns_sniffed_domain = None; - sess.http_sniffed_domain = None; - sess.tls_sniffed_domain = None; - } + if let OutboundConnect::Proxy(_, address, port) = self.next_connect_addr(start) + && let Ok(addr) = SocksAddr::try_from((address, port)) + { + sess.destination = addr; + sess.dns_sniffed_domain = None; + sess.http_sniffed_domain = None; + sess.tls_sniffed_domain = None; } + sess } } diff --git a/leaf/src/proxy/datagram.rs b/leaf/src/proxy/datagram.rs index 1810052a7..24fb308ef 100644 --- a/leaf/src/proxy/datagram.rs +++ b/leaf/src/proxy/datagram.rs @@ -186,10 +186,10 @@ impl OutboundDatagram for DomainAssociatedOutboundDatagram { } fn unmapped_ipv4(addr: SocketAddr) -> SocketAddr { - if let SocketAddr::V6(ref a) = addr { - if let Some(a_v4) = a.ip().to_ipv4() { - return SocketAddr::new(IpAddr::V4(a_v4), a.port()); - } + if let SocketAddr::V6(ref a) = addr + && let Some(a_v4) = a.ip().to_ipv4() + { + return SocketAddr::new(IpAddr::V4(a_v4), a.port()); } addr } diff --git a/leaf/src/proxy/failover/datagram.rs b/leaf/src/proxy/failover/datagram.rs index c3b7da23b..1b40ca37c 100644 --- a/leaf/src/proxy/failover/datagram.rs +++ b/leaf/src/proxy/failover/datagram.rs @@ -2,9 +2,9 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::{io, sync::Arc, time::Duration}; use async_trait::async_trait; -use futures::future::BoxFuture; -use futures::future::{abortable, AbortHandle}; use futures::FutureExt; +use futures::future::BoxFuture; +use futures::future::{AbortHandle, abortable}; use tokio::sync::{Mutex, Notify}; use tokio::time::Instant; use tracing::{debug, trace}; @@ -124,20 +124,22 @@ impl OutboundDatagramHandler for Handler { tokio::spawn(task); } - if self.health_check && !self.is_first_health_check_done.load(Ordering::Relaxed) { - if let Some(w) = self.wait_for_health_check.as_ref() { - debug!("holding {}", &sess.destination); - w.notified().await; - debug!("{} resumed", &sess.destination); - } + if self.health_check + && !self.is_first_health_check_done.load(Ordering::Relaxed) + && let Some(w) = self.wait_for_health_check.as_ref() + { + debug!("holding {}", &sess.destination); + w.notified().await; + debug!("{} resumed", &sess.destination); } let schedule = self.schedule.lock().await.clone(); // Use the last resort outbound if all outbounds have failed in // the last health check. - if schedule.is_empty() && self.last_resort.is_some() { - let a = &self.last_resort.as_ref().unwrap(); + if schedule.is_empty() + && let Some(a) = self.last_resort.as_ref() + { debug!( "failover handles udp [{}] to last resort [{}]", sess.destination, diff --git a/leaf/src/proxy/failover/mod.rs b/leaf/src/proxy/failover/mod.rs index c96f04bde..1c971ac0e 100644 --- a/leaf/src/proxy/failover/mod.rs +++ b/leaf/src/proxy/failover/mod.rs @@ -4,13 +4,13 @@ use std::{sync::Arc, time::Duration}; use bytes::BytesMut; use hickory_proto::{ - op::{header::MessageType, op_code::OpCode, query::Query, Message}, - rr::{record_type::RecordType, Name}, + op::{Message, header::MessageType, op_code::OpCode, query::Query}, + rr::{Name, record_type::RecordType}, }; -use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand::{Rng, SeedableRng, rngs::StdRng}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::{Mutex, Notify}; -use tokio::time::{timeout, Instant}; +use tokio::time::{Instant, timeout}; use tracing::{debug, trace, warn}; use crate::{app::SyncDnsClient, proxy::*, session::*}; @@ -148,7 +148,7 @@ async fn single_health_check( let query = Query::query(name, RecordType::A); msg.add_query(query); let mut rng = StdRng::from_entropy(); - let id: u16 = rng.gen(); + let id: u16 = rng.r#gen(); msg.set_id(id); msg.set_op_code(OpCode::Query); msg.set_message_type(MessageType::Query); @@ -299,10 +299,10 @@ async fn health_check_task( let mut min_prefer_actor_rtt = Duration::from_secs(health_check_timeout as u64).as_millis(); for t in health_check_prefers.iter() { - if let Some(m) = measures.iter().find(|x| &x.tag == t) { - if m.rtt < min_prefer_actor_rtt { - min_prefer_actor_rtt = m.rtt; - } + if let Some(m) = measures.iter().find(|x| &x.tag == t) + && m.rtt < min_prefer_actor_rtt + { + min_prefer_actor_rtt = m.rtt; } } diff --git a/leaf/src/proxy/failover/stream.rs b/leaf/src/proxy/failover/stream.rs index 265ddcfb7..48d75ca8b 100644 --- a/leaf/src/proxy/failover/stream.rs +++ b/leaf/src/proxy/failover/stream.rs @@ -2,9 +2,9 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::{io, sync::Arc, time::Duration}; use async_trait::async_trait; -use futures::future::BoxFuture; -use futures::future::{abortable, AbortHandle}; use futures::FutureExt; +use futures::future::BoxFuture; +use futures::future::{AbortHandle, abortable}; use lru_time_cache::LruCache; use tokio::sync::{Mutex, Notify}; use tokio::time::Instant; @@ -138,12 +138,13 @@ impl OutboundStreamHandler for Handler { tokio::spawn(task); } - if self.health_check && !self.is_first_health_check_done.load(Ordering::Relaxed) { - if let Some(w) = self.wait_for_health_check.as_ref() { - debug!("holding {}", &sess.destination); - w.notified().await; - debug!("{} resumed", &sess.destination); - } + if self.health_check + && !self.is_first_health_check_done.load(Ordering::Relaxed) + && let Some(w) = self.wait_for_health_check.as_ref() + { + debug!("holding {}", &sess.destination); + w.notified().await; + debug!("{} resumed", &sess.destination); } if let Some(cache) = &self.cache { @@ -176,8 +177,10 @@ impl OutboundStreamHandler for Handler { // Use the last resort outbound if all outbounds have failed in // the last health check. - if schedule.is_empty() && self.last_resort.is_some() { - let a = &self.last_resort.as_ref().unwrap(); + if schedule.is_empty() + && let Some(a) = self.last_resort.as_ref() + { + // let a = &self.last_resort.as_ref().unwrap(); debug!( "failover handles tcp [{}] to last resort [{}]", sess.destination, @@ -222,13 +225,14 @@ impl OutboundStreamHandler for Handler { Ok(t) => match t { Ok(v) => { // Only cache for fallback actors. - if let Some(cache) = &self.cache { - if sche_idx > 0 { - let cache_key = sess.destination.to_string(); - trace!("failover inserts {} -> {} to cache", cache_key, a.tag()); - cache.lock().await.insert(cache_key, actor_idx); - } + if let Some(cache) = &self.cache + && sche_idx > 0 + { + let cache_key = sess.destination.to_string(); + trace!("failover inserts {} -> {} to cache", cache_key, a.tag()); + cache.lock().await.insert(cache_key, actor_idx); } + return Ok(v); } Err(e) => { diff --git a/leaf/src/proxy/hc/inbound/stream.rs b/leaf/src/proxy/hc/inbound/stream.rs index a52d443cd..edeecf31f 100644 --- a/leaf/src/proxy/hc/inbound/stream.rs +++ b/leaf/src/proxy/hc/inbound/stream.rs @@ -77,30 +77,29 @@ impl Handler { // For POST requests, read and check the body if !is_get_request { let mut request_body = String::new(); - if let Some(content_length) = self.extract_content_length(headers_str) { - if content_length > 0 && content_length <= BUFFER_SIZE { - let mut body = Vec::new(); - body.extend_from_slice(&body_remaining); - - // Read the remaining body if needed - let mut remaining_to_read = content_length.saturating_sub(body.len()); - while remaining_to_read > 0 { - let mut buf = vec![0u8; remaining_to_read.min(BUFFER_SIZE)]; - let n = stream.read(&mut buf).await?; - if n == 0 { - break; - } - body.extend_from_slice(&buf[..n]); - remaining_to_read = remaining_to_read.saturating_sub(n); - } - - // Ensure we don't read more than content_length - if body.len() > content_length { - body.truncate(content_length); + if let Some(content_length @ 1..=BUFFER_SIZE) = self.extract_content_length(headers_str) + { + let mut body = Vec::new(); + body.extend_from_slice(&body_remaining); + + // Read the remaining body if needed + let mut remaining_to_read = content_length.saturating_sub(body.len()); + while remaining_to_read > 0 { + let mut buf = vec![0u8; remaining_to_read.min(BUFFER_SIZE)]; + let n = stream.read(&mut buf).await?; + if n == 0 { + break; } + body.extend_from_slice(&buf[..n]); + remaining_to_read = remaining_to_read.saturating_sub(n); + } - request_body = String::from_utf8_lossy(&body).to_string(); + // Ensure we don't read more than content_length + if body.len() > content_length { + body.truncate(content_length); } + + request_body = String::from_utf8_lossy(&body).to_string(); } // Check if the request body matches @@ -110,8 +109,11 @@ impl Handler { } // Send the configured response - let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n{}", - self.response.len(), self.response); + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n{}", + self.response.len(), + self.response + ); stream.write_all(response.as_bytes()).await?; stream.flush().await?; @@ -155,12 +157,11 @@ impl Handler { fn extract_content_length(&self, headers: &str) -> Option { for line in headers.lines() { - if line.to_lowercase().starts_with("content-length:") { - if let Some(len_str) = line.split(':').nth(1) { - if let Ok(len) = len_str.trim().parse::() { - return Some(len); - } - } + if line.to_lowercase().starts_with("content-length:") + && let Some(len_str) = line.split(':').nth(1) + && let Ok(len) = len_str.trim().parse::() + { + return Some(len); } } None diff --git a/leaf/src/proxy/mod.rs b/leaf/src/proxy/mod.rs index 5d2827a28..20f6ab80a 100644 --- a/leaf/src/proxy/mod.rs +++ b/leaf/src/proxy/mod.rs @@ -6,9 +6,9 @@ use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; +use futures::TryFutureExt; use futures::future::select_ok; use futures::stream::Stream; -use futures::TryFutureExt; use socket2::{Domain, SockRef, Socket, Type}; use thiserror::Error; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite}; @@ -427,9 +427,12 @@ pub async fn connect_datagram_outbound( Ok(ip) if ip.is_loopback() => new_udp_socket(&SocketAddr::new(ip, 0)).await?, _ => new_udp_socket(&crate::option::UNSPECIFIED_BIND_ADDR).await?, }; - Ok(Some(OutboundTransport::Datagram(Box::new( - DomainResolveOutboundDatagram::new(socket, dns_client.clone()), - )))) + Ok(Some( + OutboundTransport::Datagram(Box::new(DomainResolveOutboundDatagram::new( + socket, + dns_client.clone(), + )) as Box) as AnyOutboundTransport, + )) } Network::Tcp => { let stream = new_tcp_stream(dns_client.clone(), &addr, &port).await?; @@ -439,20 +442,20 @@ pub async fn connect_datagram_outbound( OutboundConnect::Direct => match &sess.destination { SocksAddr::Domain(domain, port) => { let socket = new_udp_socket(&crate::option::UNSPECIFIED_BIND_ADDR).await?; - Ok(Some(OutboundTransport::Datagram(Box::new( - DomainAssociatedOutboundDatagram::new( + Ok(Some(OutboundTransport::Datagram( + Box::new(DomainAssociatedOutboundDatagram::new( socket, sess.source, SocksAddr::Domain(domain.to_owned(), *port), dns_client.clone(), - ), - )))) + )) as Box, + ))) } SocksAddr::Ip(addr) => { let socket = new_udp_socket(addr).await?; - Ok(Some(OutboundTransport::Datagram(Box::new( - StdOutboundDatagram::new(socket), - )))) + Ok(Some(OutboundTransport::Datagram( + Box::new(StdOutboundDatagram::new(socket)) as Box, + ))) } }, _ => Ok(None), diff --git a/leaf/src/proxy/mptp/inbound/stream.rs b/leaf/src/proxy/mptp/inbound/stream.rs index 8929764bd..1debe3694 100644 --- a/leaf/src/proxy/mptp/inbound/stream.rs +++ b/leaf/src/proxy/mptp/inbound/stream.rs @@ -5,8 +5,8 @@ use std::sync::{Arc, RwLock}; use std::task::{Context, Poll}; use crate::proxy::mptp::mptp_conn::{ - protocol::{Address, HandshakeRequest, CMD_UDP}, MptpDatagram, MptpStream, + protocol::{Address, CMD_UDP, HandshakeRequest}, }; use async_trait::async_trait; use bytes::{Buf, BytesMut}; @@ -159,12 +159,12 @@ impl InboundStreamHandler for Handler { let mut sessions = self .sessions .write() - .map_err(|_| io::Error::new(io::ErrorKind::Other, "Lock poisoned"))?; + .map_err(|_| io::Error::other("Lock poisoned"))?; if let Some(tx) = sessions.get(&req.cid).cloned() { drop(sessions); tracing::debug!("Joining existing MPTP session: {}", req.cid); - if let Err(_) = tx.send((prefixed_stream, Some(req.cid))) { + if tx.send((prefixed_stream, Some(req.cid))).is_err() { tracing::warn!("MPTP session {} channel closed", req.cid); return Err(io::Error::new( io::ErrorKind::ConnectionAborted, diff --git a/leaf/src/proxy/mptp/mptp_conn/datagram.rs b/leaf/src/proxy/mptp/mptp_conn/datagram.rs index abbd3faeb..0f3d71d2c 100644 --- a/leaf/src/proxy/mptp/mptp_conn/datagram.rs +++ b/leaf/src/proxy/mptp/mptp_conn/datagram.rs @@ -3,7 +3,7 @@ use std::net::SocketAddr; use async_trait::async_trait; use bytes::{BufMut, BytesMut}; -use tokio::io::{split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadHalf, WriteHalf}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadHalf, WriteHalf, split}; use crate::proxy::*; use crate::session::{DatagramSource, SocksAddr}; @@ -49,7 +49,7 @@ where } fn into_std(self: Box) -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "not supported")) + Err(io::Error::other("not supported")) } } diff --git a/leaf/src/proxy/mptp/mptp_conn/protocol.rs b/leaf/src/proxy/mptp/mptp_conn/protocol.rs index 41f8fb3f5..91c369a98 100644 --- a/leaf/src/proxy/mptp/mptp_conn/protocol.rs +++ b/leaf/src/proxy/mptp/mptp_conn/protocol.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use bytes::{Buf, BufMut, Bytes, BytesMut}; use std::net::{Ipv4Addr, Ipv6Addr}; diff --git a/leaf/src/proxy/mptp/mptp_conn/stream.rs b/leaf/src/proxy/mptp/mptp_conn/stream.rs index 54f2ed18c..77c4c154a 100644 --- a/leaf/src/proxy/mptp/mptp_conn/stream.rs +++ b/leaf/src/proxy/mptp/mptp_conn/stream.rs @@ -1,7 +1,7 @@ use tracing::{debug, error}; use super::protocol::{ - Frame, DATA_HEADER_LEN, MTYP_DATA, MTYP_FIN, MTYP_PING, MTYP_PONG, MTYP_RST, + DATA_HEADER_LEN, Frame, MTYP_DATA, MTYP_FIN, MTYP_PING, MTYP_PONG, MTYP_RST, }; use bytes::{Buf, Bytes, BytesMut}; use std::collections::BTreeMap; @@ -388,11 +388,9 @@ impl AsyncWrite for MptpStream { // Broadcast to all non-full subs let mut sent_count = 0; for sub in &mut this.subs { - if !sub.closed { - if sub.write_buf.len() <= 64 * 1024 { - sub.write_buf.extend_from_slice(&encoded_bytes); - sent_count += 1; - } + if !sub.closed && sub.write_buf.len() <= 64 * 1024 { + sub.write_buf.extend_from_slice(&encoded_bytes); + sent_count += 1; } } diff --git a/leaf/src/proxy/mptp/outbound/stream.rs b/leaf/src/proxy/mptp/outbound/stream.rs index 4f3a781d1..b122484fb 100644 --- a/leaf/src/proxy/mptp/outbound/stream.rs +++ b/leaf/src/proxy/mptp/outbound/stream.rs @@ -1,22 +1,22 @@ use std::io; use crate::proxy::mptp::mptp_conn::protocol::{ - Address, HandshakeRequest, CMD_CONNECT, CMD_UDP, VER, + Address, CMD_CONNECT, CMD_UDP, HandshakeRequest, VER, }; use crate::proxy::mptp::mptp_conn::{MptpDatagram, MptpStream}; use async_trait::async_trait; use bytes::BytesMut; use tokio::io::AsyncWriteExt; use tokio::sync::mpsc; -use tracing::{debug, Instrument}; +use tracing::{Instrument, debug}; use uuid::Uuid; use crate::{ app::SyncDnsClient, proxy::{ - connect_stream_outbound, AnyOutboundDatagram, AnyOutboundHandler, AnyOutboundTransport, - AnyStream, DatagramTransportType, OutboundConnect, OutboundDatagramHandler, - OutboundStreamHandler, + AnyOutboundDatagram, AnyOutboundHandler, AnyOutboundTransport, AnyStream, + DatagramTransportType, OutboundConnect, OutboundDatagramHandler, OutboundStreamHandler, + connect_stream_outbound, }, session::{Session, SocksAddr}, }; @@ -60,9 +60,9 @@ impl Handler { let dns_client = self.dns_client.clone(); let tx = tx.clone(); let target_addr = target_addr.clone(); - let cid = cid; - let cmd = cmd; - let target_port = target_port; + // let cid = cid; + // let cmd = cmd; + // let target_port = target_port; debug!("new sub-conn idx={} actor={}", i, &actor.tag()); @@ -136,10 +136,7 @@ impl Handler { rx, )) } else { - Err(io::Error::new( - io::ErrorKind::Other, - "No available sub-connections", - )) + Err(io::Error::other("No available sub-connections")) } } } diff --git a/leaf/src/proxy/nf/inbound/datagram.rs b/leaf/src/proxy/nf/inbound/datagram.rs index fdea2948a..923864ea2 100644 --- a/leaf/src/proxy/nf/inbound/datagram.rs +++ b/leaf/src/proxy/nf/inbound/datagram.rs @@ -12,8 +12,8 @@ use crate::{ session::{SocksAddr, SocksAddrWireType}, }; -use super::packed::{SOCKADDR_IN, SOCKADDR_IN6}; use super::NfManager; +use super::packed::{SOCKADDR_IN, SOCKADDR_IN6}; pub struct Handler { pub manager: Arc, diff --git a/leaf/src/proxy/nf/inbound/mod.rs b/leaf/src/proxy/nf/inbound/mod.rs index 0cc745b32..f0268852b 100644 --- a/leaf/src/proxy/nf/inbound/mod.rs +++ b/leaf/src/proxy/nf/inbound/mod.rs @@ -9,10 +9,10 @@ use std::mem::transmute; use std::net::{IpAddr, SocketAddr}; use std::os::windows::ffi::OsStringExt; use std::ptr::{addr_of, addr_of_mut}; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::sync::LazyLock; use std::sync::Mutex; +use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; mod datagram; @@ -21,7 +21,7 @@ mod stream; pub use datagram::Handler as DatagramHandler; pub use stream::Handler as StreamHandler; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use bytes::{BufMut, BytesMut}; use parking_lot::RwLock; use tracing::{debug, trace, warn}; @@ -185,15 +185,17 @@ struct NfTcpConnInfo { impl NfTcpConnInfo { unsafe fn get_local_address(info: *const NfTcpConnInfo) -> Result { - sockaddr_to_socketaddr( - &addr_of!((*info).localAddress).read_unaligned() as *const [u8; 28] as *const SOCKADDR, - ) + unsafe { + sockaddr_to_socketaddr(&addr_of!((*info).localAddress).read_unaligned() + as *const [u8; 28] as *const SOCKADDR) + } } unsafe fn get_remote_address(info: *const NfTcpConnInfo) -> Result { - sockaddr_to_socketaddr( - &addr_of!((*info).remoteAddress).read_unaligned() as *const [u8; 28] as *const SOCKADDR, - ) + unsafe { + sockaddr_to_socketaddr(&addr_of!((*info).remoteAddress).read_unaligned() + as *const [u8; 28] as *const SOCKADDR) + } } } @@ -206,9 +208,10 @@ struct NfUdpConnInfo { impl NfUdpConnInfo { unsafe fn get_local_address(info: *const NfUdpConnInfo) -> Result { - sockaddr_to_socketaddr( - &addr_of!((*info).localAddress).read_unaligned() as *const [u8; 28] as *const SOCKADDR, - ) + unsafe { + sockaddr_to_socketaddr(&addr_of!((*info).localAddress).read_unaligned() + as *const [u8; 28] as *const SOCKADDR) + } } } @@ -264,25 +267,26 @@ unsafe extern "C" fn threadEnd() { } unsafe extern "C" fn tcpConnectRequest(id: EndpointId, conn_info: *mut NfTcpConnInfo) { - let Ok(local_addr) = NfTcpConnInfo::get_local_address(conn_info) else { + let Ok(local_addr) = (unsafe { NfTcpConnInfo::get_local_address(conn_info) }) else { debug!("unable to get local address"); return; }; - let Ok(remote_addr) = NfTcpConnInfo::get_remote_address(conn_info) else { + let Ok(remote_addr) = (unsafe { NfTcpConnInfo::get_remote_address(conn_info) }) else { debug!("unable to get remote address"); return; }; trace!( "tcpConnectRequest id={} local={} remote={}", - id, - &local_addr, - &remote_addr + id, &local_addr, &remote_addr ); if remote_addr.is_ipv6() { // Block IPv6. - addr_of_mut!((*conn_info).filteringFlag).write_unaligned(NfFilteringFlag::NfBlock.value()); + unsafe { + addr_of_mut!((*conn_info).filteringFlag) + .write_unaligned(NfFilteringFlag::NfBlock.value()) + }; return; } @@ -290,12 +294,8 @@ unsafe extern "C" fn tcpConnectRequest(id: EndpointId, conn_info: *mut NfTcpConn return; } - let process_id = addr_of!((*conn_info).processId).read_unaligned(); - let process_name = if let Ok(name) = get_process_name(process_id) { - Some(name) - } else { - None - }; + let process_id = unsafe { addr_of!((*conn_info).processId).read_unaligned() }; + let process_name = unsafe { get_process_name(process_id).ok() }; debug!( "tcpConnectRequest id={} local={} remote={} process_id={} process_name={}", @@ -334,23 +334,25 @@ unsafe extern "C" fn tcpConnectRequest(id: EndpointId, conn_info: *mut NfTcpConn let new_remote_addr: SOCKADDR_IN = addr.into(); let addr_ptr = &new_remote_addr as *const SOCKADDR_IN as *const u8; let addr_len = std::mem::size_of::(); - let new_remote_addr_data = std::slice::from_raw_parts(addr_ptr, addr_len); + let new_remote_addr_data = unsafe { std::slice::from_raw_parts(addr_ptr, addr_len) }; let mut write_buf = [0u8; 28]; write_buf[..addr_len].copy_from_slice(&new_remote_addr_data[..addr_len]); - addr_of_mut!((*conn_info).remoteAddress).write_unaligned(write_buf); + unsafe { addr_of_mut!((*conn_info).remoteAddress).write_unaligned(write_buf) }; } SocketAddr::V6(addr) => { let new_remote_addr: SOCKADDR_IN6 = addr.into(); let addr_ptr = &new_remote_addr as *const SOCKADDR_IN6 as *const u8; let addr_len = std::mem::size_of::(); - let new_remote_addr_data = std::slice::from_raw_parts(addr_ptr, addr_len); + let new_remote_addr_data = unsafe { std::slice::from_raw_parts(addr_ptr, addr_len) }; let mut write_buf = [0u8; 28]; write_buf[..addr_len].copy_from_slice(&new_remote_addr_data[..addr_len]); - addr_of_mut!((*conn_info).remoteAddress).write_unaligned(write_buf); + unsafe { addr_of_mut!((*conn_info).remoteAddress).write_unaligned(write_buf) }; } } - NF_TCP_DISABLE_FILTERING.unwrap()(id); + unsafe { + NF_TCP_DISABLE_FILTERING.unwrap()(id); + } } unsafe extern "C" fn tcpConnected(id: EndpointId, _conn_info: *mut NfTcpConnInfo) { @@ -368,7 +370,9 @@ unsafe extern "C" fn tcpReceive(id: EndpointId, buf: *const u8, len: i32) { id, len ); - NF_TCP_POST_RECEIVE.unwrap()(id, buf, len); + unsafe { + NF_TCP_POST_RECEIVE.unwrap()(id, buf, len); + } } unsafe extern "C" fn tcpSend(id: EndpointId, _buf: *const u8, len: i32) { @@ -389,13 +393,13 @@ unsafe extern "C" fn tcpCanSend(id: EndpointId) { } unsafe extern "C" fn udpCreated(id: EndpointId, conn_info: *mut NfUdpConnInfo) { - let Ok(local_address) = NfUdpConnInfo::get_local_address(conn_info) else { + let Ok(local_address) = (unsafe { NfUdpConnInfo::get_local_address(conn_info) }) else { debug!("unable to get local address"); return; }; - let process_id = addr_of!((*conn_info).processId).read_unaligned(); - let process_name = if let Ok(name) = get_process_name(process_id) { + let process_id = unsafe { addr_of!((*conn_info).processId).read_unaligned() }; + let process_name = if let Ok(name) = unsafe { get_process_name(process_id) } { Some(name) } else { None @@ -439,7 +443,7 @@ unsafe extern "C" fn udpReceive( options: *mut NfUdpOptions, ) { trace!("udpReceive id={}", id); - NF_UDP_POST_RECEIVE.unwrap()(id, remote_address, buf, len, options); + unsafe { NF_UDP_POST_RECEIVE.unwrap()(id, remote_address, buf, len, options) }; } unsafe extern "C" fn udpSend( @@ -449,9 +453,9 @@ unsafe extern "C" fn udpSend( len: i32, options: *mut NfUdpOptions, ) { - let Ok(remote_addr) = + let Ok(remote_addr) = (unsafe { sockaddr_to_socketaddr(transmute::<*const u8, *const SOCKADDR>(remote_address)) - else { + }) else { debug!("unable to get remote address"); return; }; @@ -461,7 +465,7 @@ unsafe extern "C" fn udpSend( // Drop IPv6 if remote_addr.is_ipv6() { trace!("Pass IPv6"); - let status = NF_UDP_POST_SEND.unwrap()(id, remote_address, buf, len, options); + let status = unsafe { NF_UDP_POST_SEND.unwrap()(id, remote_address, buf, len, options) }; if status != NF_STATUS_SUCCESS { debug!("send to local failed, status={}", status); } @@ -469,17 +473,18 @@ unsafe extern "C" fn udpSend( } if remote_addr.ip().is_loopback() { - NF_UDP_DISABLE_FILTERING.unwrap()(id); + unsafe { NF_UDP_DISABLE_FILTERING.unwrap()(id) }; return; } let mut conn_info = NfUdpConnInfo::default(); - let status = NF_GET_UDP_CONN_INFO.unwrap()(id, &mut conn_info as *mut _); + let status = unsafe { NF_GET_UDP_CONN_INFO.unwrap()(id, &mut conn_info as *mut _) }; if status != NF_STATUS_SUCCESS { debug!("get udp conn info failed id={} status={}", id, status); return; } - let Ok(local_address) = NfUdpConnInfo::get_local_address(&conn_info as *const NfUdpConnInfo) + let Ok(local_address) = + (unsafe { NfUdpConnInfo::get_local_address(&conn_info as *const NfUdpConnInfo) }) else { debug!("unable to get local address"); return; @@ -493,19 +498,19 @@ unsafe extern "C" fn udpSend( }); UDP_OPTIONS.lock().unwrap().entry(id).or_insert_with(|| { - let opts_len = (*options).optionsLength; + let opts_len = unsafe { (*options).optionsLength }; let opts_data_len = std::mem::size_of::() - 1 + opts_len as usize; let mut opts_buf = vec![0u8; opts_data_len]; - let options_data = std::slice::from_raw_parts(options as *mut u8, opts_data_len); + let options_data = unsafe { std::slice::from_raw_parts(options as *mut u8, opts_data_len) }; opts_buf[..opts_data_len] .as_mut() .copy_from_slice(&options_data[..opts_data_len]); opts_buf }); - let Ok(original_remote_addr) = + let Ok(original_remote_addr) = (unsafe { sockaddr_to_socketaddr(transmute::<*const u8, *const SOCKADDR>(remote_address)) - else { + }) else { debug!("unable to get original remote address"); return; }; @@ -514,7 +519,7 @@ unsafe extern "C" fn udpSend( let dst_addr = crate::session::SocksAddr::from(original_remote_addr); dst_addr.write_buf(&mut new_buf, crate::session::SocksAddrWireType::PortLast); new_buf.put_u64(id); - let buf = std::slice::from_raw_parts(buf, len as _); + let buf = unsafe { std::slice::from_raw_parts(buf, len as _) }; new_buf.put_slice(buf); // FIXME retrieve from inbound settings @@ -726,89 +731,55 @@ pub mod packed { } unsafe fn sockaddr_to_socketaddr(addr: *const packed::SOCKADDR) -> Result { - match addr_of!((*addr).sa_family).read_unaligned() { - packed::AF_INET => { - let addr: *const packed::SOCKADDR_IN = transmute(addr); - Ok(SocketAddr::new( - IpAddr::V4(addr_of!((*addr).sin_addr).read_unaligned().into()), - u16::from_be(addr_of!((*addr).sin_port).read_unaligned()), - )) - } - packed::AF_INET6 => { - let addr: *const packed::SOCKADDR_IN6 = transmute(addr); - Ok(SocketAddr::new( - IpAddr::V6(addr_of!((*addr).sin6_addr).read_unaligned().into()), - u16::from_be(addr_of!((*addr).sin6_port).read_unaligned()), - )) + unsafe { + match addr_of!((*addr).sa_family).read_unaligned() { + packed::AF_INET => { + let addr: *const packed::SOCKADDR_IN = transmute(addr); + Ok(SocketAddr::new( + IpAddr::V4(addr_of!((*addr).sin_addr).read_unaligned().into()), + u16::from_be(addr_of!((*addr).sin_port).read_unaligned()), + )) + } + packed::AF_INET6 => { + let addr: *const packed::SOCKADDR_IN6 = transmute(addr); + Ok(SocketAddr::new( + IpAddr::V6(addr_of!((*addr).sin6_addr).read_unaligned().into()), + u16::from_be(addr_of!((*addr).sin6_port).read_unaligned()), + )) + } + _ => Err(anyhow!("unknown address family")), } - _ => Err(anyhow!("unknown address family")), } } #[allow(clippy::missing_transmute_annotations)] unsafe fn init_nf_fns>(nfapi: P) -> Result<()> { - let nfapi = libloading::Library::new(nfapi)?; - - NF_INIT = Some(transmute( - nfapi - .get::>(b"nf_init\0")? - .into_raw(), - )); - - let nf_free: libloading::Symbol = nfapi.get(b"nf_free\0")?; - NF_FREE = Some(transmute(nf_free.into_raw())); - - let nf_add_rule: libloading::Symbol = nfapi.get(b"nf_addRule\0")?; - NF_ADD_RULE = Some(transmute(nf_add_rule.into_raw())); - - let nf_tcp_post_receive: libloading::Symbol = - nfapi.get(b"nf_tcpPostReceive\0")?; - NF_TCP_POST_RECEIVE = Some(transmute(nf_tcp_post_receive.into_raw())); - - let nf_tcp_post_send: libloading::Symbol = nfapi.get(b"nf_tcpPostSend\0")?; - NF_TCP_POST_SEND = Some(transmute(nf_tcp_post_send.into_raw())); - - let nf_udp_post_receive: libloading::Symbol = - nfapi.get(b"nf_udpPostReceive\0")?; - NF_UDP_POST_RECEIVE = Some(transmute(nf_udp_post_receive.into_raw())); - - let nf_udp_post_send: libloading::Symbol = nfapi.get(b"nf_udpPostSend\0")?; - NF_UDP_POST_SEND = Some(transmute(nf_udp_post_send.into_raw())); - - NF_TCP_DISABLE_FILTERING = Some(std::mem::transmute( - nfapi - .get::>(b"nf_tcpDisableFiltering\0")? - .into_raw(), - )); - - NF_UDP_DISABLE_FILTERING = Some(std::mem::transmute( - nfapi - .get::>(b"nf_udpDisableFiltering\0")? - .into_raw(), - )); - - let nf_adjust_process_priviledges: libloading::Symbol = - nfapi.get(b"nf_adjustProcessPriviledges\0")?; - NF_ADJUST_PROCESS_PRIVILEDGES = Some(transmute(nf_adjust_process_priviledges.into_raw())); - - NF_GET_UDP_CONN_INFO = Some(transmute( - nfapi - .get::>(b"nf_getUDPConnInfo\0")? - .into_raw(), - )); - - let nf_get_process_name: libloading::Symbol = - nfapi.get(b"nf_getProcessNameW\0")?; - NF_GET_PROCESS_NAME = Some(std::mem::transmute(nf_get_process_name.into_raw())); - - NF_GET_PROCESS_NAME_FROM_KERNEL = Some(transmute( - nfapi - .get::>( - b"nf_getProcessNameFromKernel\0", - )? - .into_raw(), - )); - + let nfapi = unsafe { libloading::Library::new(nfapi)? }; + + macro_rules! init_statics { + ($s: ident => $t: ty => $n: literal$(,$s_o: ident => $t_o: ty => $n_o: literal)*$(,)? + ) => {{ + $s = Some(transmute(nfapi.get::>(const { $n.to_bytes_with_nul() })?.into_raw())); + $($s_o = Some(transmute(nfapi.get::>(const { $n_o.to_bytes_with_nul() })?.into_raw()));)* + }}; + } + unsafe { + init_statics!( + NF_INIT => NfInitFn => c"nf_init", + NF_FREE => NfFreeFn => c"nf_free", + NF_ADD_RULE => NfAddRuleFn => c"nf_addRule", + NF_TCP_POST_RECEIVE => NfTcpPostReceiveFn => c"nf_tcpPostReceive", + NF_TCP_POST_SEND => NfTcpPostSendFn => c"nf_tcpPostSend", + NF_UDP_POST_RECEIVE => NfUdpPostReceiveFn => c"nf_udpPostReceive", + NF_UDP_POST_SEND => NfUdpPostSendFn => c"nf_udpPostSend", + NF_TCP_DISABLE_FILTERING => NfTcpDisableFilteringFn => c"nf_tcpDisableFiltering", + NF_UDP_DISABLE_FILTERING => NfUdpDisableFilteringFn => c"nf_udpDisableFiltering", + NF_ADJUST_PROCESS_PRIVILEDGES => NfAdjustProcessPriviledgesFn => c"nf_adjustProcessPriviledges", + NF_GET_UDP_CONN_INFO => NfGetUdpConnInfoFn => c"nf_getUdpConnInfo", + NF_GET_PROCESS_NAME => NfGetProcessNameFn => c"nf_getProcessName", + NF_GET_PROCESS_NAME_FROM_KERNEL => NfGetProcessNameFromKernelFn => c"nf_getProcessNameFromKernel", + ); + } *NFAPI.write() = Some(nfapi); Ok(()) @@ -819,9 +790,9 @@ unsafe fn init_nf>( nfapi: P, res_tx: std::sync::mpsc::Sender, ) -> Result<()> { - init_nf_fns(nfapi)?; + unsafe { init_nf_fns(nfapi)? }; - NF_ADJUST_PROCESS_PRIVILEDGES.unwrap()(); + unsafe { NF_ADJUST_PROCESS_PRIVILEDGES.unwrap()() }; let eh = NfEventHandler { threadStart, @@ -842,13 +813,15 @@ unsafe fn init_nf>( udpCanSend, }; - let status = NF_INIT.unwrap()( - CString::new(driver_name) - .unwrap() - .as_bytes_with_nul() - .as_ptr(), - &eh as *const _, - ); + let status = unsafe { + NF_INIT.unwrap()( + CString::new(driver_name) + .unwrap() + .as_bytes_with_nul() + .as_ptr(), + &eh as *const _, + ) + }; if status != NF_STATUS_SUCCESS { return Err(anyhow!("nf_init failed, status={}", status)); } @@ -860,7 +833,7 @@ unsafe fn init_nf>( filteringFlag: NfFilteringFlag::NfIndicateConnectRequests.value(), ..Default::default() }; - let status = NF_ADD_RULE.unwrap()(&rule as *const _, 0); + let status = unsafe { NF_ADD_RULE.unwrap()(&rule as *const _, 0) }; if status != NF_STATUS_SUCCESS { return Err(anyhow!("adding rule failed: {}", status)); } @@ -869,7 +842,7 @@ unsafe fn init_nf>( filteringFlag: NfFilteringFlag::NfFilter.value(), ..Default::default() }; - let status = NF_ADD_RULE.unwrap()(&rule as *const _, 0); + let status = unsafe { NF_ADD_RULE.unwrap()(&rule as *const _, 0) }; if status != NF_STATUS_SUCCESS { return Err(anyhow!("adding rule failed: {}", status)); } @@ -877,7 +850,9 @@ unsafe fn init_nf>( *UDP_SEND_SOCKET.write() = Some(std::net::UdpSocket::bind("0.0.0.0:0")?); let (tx, rx) = std::sync::mpsc::channel(); - TX = Some(tx); + unsafe { + TX = Some(tx); + } if let Err(e) = res_tx.send(true) { debug!("unable to send nf init result: {}", e); @@ -918,7 +893,9 @@ fn init>(driver_name: String, nfapi: P) -> Result<()> { unsafe fn uninit_nf() { if IS_NF_INITIALIZED.swap(false, Ordering::Relaxed) { - NF_FREE.unwrap()(); + unsafe { + NF_FREE.unwrap()(); + } if let Some(nfapi) = NFAPI.write().take() { if let Err(e) = nfapi.close() { debug!("close nf failed: {}", e); @@ -935,12 +912,17 @@ pub fn uninit() { pub unsafe fn get_process_name(pid: u32) -> Result { let mut process_name_buf = vec![0u16; MAX_PATH]; let process_name_len = process_name_buf.len() as u32; - if !NF_GET_PROCESS_NAME_FROM_KERNEL.unwrap()( - pid, - process_name_buf.as_mut_ptr() as _, - process_name_len, - ) && !NF_GET_PROCESS_NAME.unwrap()(pid, process_name_buf.as_mut_ptr() as _, process_name_len) - { + if unsafe { + !NF_GET_PROCESS_NAME_FROM_KERNEL.unwrap()( + pid, + process_name_buf.as_mut_ptr() as _, + process_name_len, + ) && !NF_GET_PROCESS_NAME.unwrap()( + pid, + process_name_buf.as_mut_ptr() as _, + process_name_len, + ) + } { return Err(anyhow!("Unable to get process name pid={}", pid)); } let process_name: OsString = OsString::from_wide( diff --git a/leaf/src/proxy/obfs/http.rs b/leaf/src/proxy/obfs/http.rs index 31e18e5c8..216f008da 100644 --- a/leaf/src/proxy/obfs/http.rs +++ b/leaf/src/proxy/obfs/http.rs @@ -1,11 +1,11 @@ use std::io::Cursor; use std::pin::Pin; -use std::task::{ready, Context, Poll}; +use std::task::{Context, Poll, ready}; use async_trait::async_trait; use base64::prelude::*; use memchr::memmem; -use rand::{thread_rng, RngCore}; +use rand::{RngCore, thread_rng}; use tokio::io::ReadBuf; use tokio_util::io::poll_write_buf; diff --git a/leaf/src/proxy/obfs/tls.rs b/leaf/src/proxy/obfs/tls.rs index 109b50888..68c21d56a 100644 --- a/leaf/src/proxy/obfs/tls.rs +++ b/leaf/src/proxy/obfs/tls.rs @@ -1,10 +1,10 @@ use std::io::{Cursor, IoSlice}; use std::mem::MaybeUninit; use std::pin::Pin; -use std::task::{ready, Context, Poll}; +use std::task::{Context, Poll, ready}; use async_trait::async_trait; -use rand::{thread_rng, RngCore}; +use rand::{RngCore, thread_rng}; use tokio::io::ReadBuf; use tokio_util::io::poll_write_buf; diff --git a/leaf/src/proxy/quic/inbound/datagram.rs b/leaf/src/proxy/quic/inbound/datagram.rs index 7dbbc493a..bfedd19d2 100644 --- a/leaf/src/proxy/quic/inbound/datagram.rs +++ b/leaf/src/proxy/quic/inbound/datagram.rs @@ -3,14 +3,14 @@ use std::path::Path; use std::sync::Arc; use std::{io, pin::Pin}; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use async_trait::async_trait; use futures::stream::Stream; use futures::task::{Context, Poll}; use quinn::{RecvStream, SendStream}; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use rustls_pemfile::{certs, pkcs8_private_keys, rsa_private_keys}; -use tokio::sync::mpsc::{channel, Receiver, Sender}; +use tokio::sync::mpsc::{Receiver, Sender, channel}; use tracing::{debug, trace, warn}; use crate::{proxy::*, session::Session, session::StreamId}; diff --git a/leaf/src/proxy/quic/outbound/stream.rs b/leaf/src/proxy/quic/outbound/stream.rs index 998d130c5..e906a82ea 100644 --- a/leaf/src/proxy/quic/outbound/stream.rs +++ b/leaf/src/proxy/quic/outbound/stream.rs @@ -4,14 +4,14 @@ use std::net::SocketAddr; use std::path::Path; use std::sync::Arc; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use async_trait::async_trait; use futures::TryFutureExt; use rustls::pki_types::CertificateDer; use rustls_pemfile::certs; use tokio::sync::RwLock; -use tokio::time::{timeout, Duration}; -use tracing::{debug, trace, Instrument}; +use tokio::time::{Duration, timeout}; +use tracing::{Instrument, debug, trace}; use crate::{app::SyncDnsClient, proxy::*, session::Session}; diff --git a/leaf/src/proxy/reality/outbound/stream.rs b/leaf/src/proxy/reality/outbound/stream.rs index 442165b81..07cff2f33 100644 --- a/leaf/src/proxy/reality/outbound/stream.rs +++ b/leaf/src/proxy/reality/outbound/stream.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use async_trait::async_trait; use reality_rustls::pki_types::ServerName; -use super::super::stream::{build_rustls_config, create_reality_provider, RealityStream}; +use super::super::stream::{RealityStream, build_rustls_config, create_reality_provider}; use crate::proxy::*; pub struct Handler { @@ -13,8 +13,8 @@ pub struct Handler { pub short_id: String, } -use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; +use base64::engine::general_purpose::URL_SAFE_NO_PAD; #[async_trait] impl OutboundStreamHandler for Handler { diff --git a/leaf/src/proxy/reality/stream.rs b/leaf/src/proxy/reality/stream.rs index 4c2830953..7664041fe 100644 --- a/leaf/src/proxy/reality/stream.rs +++ b/leaf/src/proxy/reality/stream.rs @@ -88,7 +88,7 @@ pub fn build_rustls_config( .with_no_client_auth(); config.reality_callback = Some(reality_state); - config.alpn_protocols = vec![b"h2".to_vec().into(), b"http/1.1".to_vec().into()]; + config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; Ok(Arc::new(config)) } @@ -274,12 +274,10 @@ impl AsyncRead for RealityStream { let this = self.get_mut(); let mut read_raw = this.read_raw; - if !read_raw { - if let Some(shared) = &this.shared_read_raw { - read_raw = shared.load(std::sync::atomic::Ordering::Relaxed); - if read_raw { - this.read_raw = true; // Cache it - } + if !read_raw && let Some(shared) = &this.shared_read_raw { + read_raw = shared.load(std::sync::atomic::Ordering::Relaxed); + if read_raw { + this.read_raw = true; // Cache it } } diff --git a/leaf/src/proxy/select/datagram.rs b/leaf/src/proxy/select/datagram.rs index 54757cb66..81b2bf6e9 100644 --- a/leaf/src/proxy/select/datagram.rs +++ b/leaf/src/proxy/select/datagram.rs @@ -1,6 +1,6 @@ use std::io; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; use async_trait::async_trait; @@ -17,10 +17,11 @@ impl OutboundDatagramHandler for Handler { let a = &self.actors[self.selected.load(Ordering::Relaxed)]; match a.datagram() { Ok(h) => return h.connect_addr(), - _ => match a.stream() { - Ok(h) => return h.connect_addr(), - _ => (), - }, + _ => { + if let Ok(h) = a.stream() { + return h.connect_addr(); + } + } } OutboundConnect::Unknown } diff --git a/leaf/src/proxy/select/stream.rs b/leaf/src/proxy/select/stream.rs index 0b8f5273f..ba22d52b2 100644 --- a/leaf/src/proxy/select/stream.rs +++ b/leaf/src/proxy/select/stream.rs @@ -16,10 +16,11 @@ impl OutboundStreamHandler for Handler { let a = &self.actors[self.selected.load(Ordering::Relaxed)]; match a.stream() { Ok(h) => return h.connect_addr(), - _ => match a.datagram() { - Ok(h) => return h.connect_addr(), - _ => (), - }, + _ => { + if let Ok(h) = a.datagram() { + return h.connect_addr(); + } + } } OutboundConnect::Unknown } diff --git a/leaf/src/proxy/shadowsocks/crypto.rs b/leaf/src/proxy/shadowsocks/crypto.rs index f60db0ba4..161bdab34 100644 --- a/leaf/src/proxy/shadowsocks/crypto.rs +++ b/leaf/src/proxy/shadowsocks/crypto.rs @@ -1,5 +1,5 @@ -use anyhow::anyhow; use anyhow::Result; +use anyhow::anyhow; use hkdf::Hkdf; use md5::{Digest, Md5}; use sha1::Sha1; diff --git a/leaf/src/proxy/shadowsocks/shadow.rs b/leaf/src/proxy/shadowsocks/shadow.rs index 3c03171ea..7940f7e0a 100644 --- a/leaf/src/proxy/shadowsocks/shadow.rs +++ b/leaf/src/proxy/shadowsocks/shadow.rs @@ -6,16 +6,16 @@ use futures::{ ready, task::{Context, Poll}, }; -use rand::{rngs::StdRng, Rng, RngCore, SeedableRng}; +use rand::{Rng, RngCore, SeedableRng, rngs::StdRng}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tracing::debug; use crate::common::crypto::{ - aead::{AeadCipher, AeadDecryptor, AeadEncryptor}, Cipher, Decryptor, Encryptor, SizedCipher, + aead::{AeadCipher, AeadDecryptor, AeadEncryptor}, }; -use super::crypto::{hkdf_sha1, kdf, ShadowsocksNonceSequence}; +use super::crypto::{ShadowsocksNonceSequence, hkdf_sha1, kdf}; enum ReadState { WaitingSalt, @@ -50,15 +50,16 @@ impl ShadowedStream { .map_err(|e| io::Error::other(format!("create AEAD cipher failed: {}", e)))?; let psk = kdf(password, cipher.key_len()) .map_err(|e| io::Error::other(format!("derive key failed: {}", e)))?; - if let Some(prefix) = prefix.as_ref() { - if prefix.len() > cipher.key_len() { - return Err(io::Error::other(format!( - "prefix length exceeding cipher key length: {} > {}", - prefix.len(), - cipher.key_len() - ))); - } + if let Some(prefix) = prefix.as_ref() + && prefix.len() > cipher.key_len() + { + return Err(io::Error::other(format!( + "prefix length exceeding cipher key length: {} > {}", + prefix.len(), + cipher.key_len() + ))); } + Ok(ShadowedStream { inner: s, cipher, @@ -384,7 +385,7 @@ impl ShadowedDatagram { // generate random salt let mut rng = StdRng::from_entropy(); for i in 0..salt_size { - buffer[i] = rng.gen(); + buffer[i] = rng.r#gen(); } let key = hkdf_sha1( diff --git a/leaf/src/proxy/socks/inbound/stream.rs b/leaf/src/proxy/socks/inbound/stream.rs index ee3db84c6..b334427b3 100644 --- a/leaf/src/proxy/socks/inbound/stream.rs +++ b/leaf/src/proxy/socks/inbound/stream.rs @@ -3,7 +3,7 @@ use std::io; use async_trait::async_trait; use bytes::{BufMut, BytesMut}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tracing::{debug, Instrument}; +use tracing::{Instrument, debug}; use crate::{ proxy::*, diff --git a/leaf/src/proxy/socks/outbound/datagram.rs b/leaf/src/proxy/socks/outbound/datagram.rs index 911d0c2ae..de37a7481 100644 --- a/leaf/src/proxy/socks/outbound/datagram.rs +++ b/leaf/src/proxy/socks/outbound/datagram.rs @@ -42,10 +42,10 @@ impl OutboundDatagramHandler for Handler { .new_tcp_stream(self.dns_client.clone(), &self.address, &self.port) .await?; let mut indicator = sess.source; - if let Ok(ip) = self.address.parse::() { - if ip.is_loopback() { - indicator = SocketAddr::new(ip, 0); - } + if let Ok(ip) = self.address.parse::() + && ip.is_loopback() + { + indicator = SocketAddr::new(ip, 0); } let socket = self.new_udp_socket(&indicator).await?; diff --git a/leaf/src/proxy/static/datagram.rs b/leaf/src/proxy/static/datagram.rs index 831278de3..f276a8b3c 100644 --- a/leaf/src/proxy/static/datagram.rs +++ b/leaf/src/proxy/static/datagram.rs @@ -1,9 +1,9 @@ use std::io; use std::sync::atomic::{AtomicUsize, Ordering}; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use async_trait::async_trait; -use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand::{Rng, SeedableRng, rngs::StdRng}; use crate::{proxy::*, session::Session}; diff --git a/leaf/src/proxy/static/stream.rs b/leaf/src/proxy/static/stream.rs index 0058bf1c6..be0d59090 100644 --- a/leaf/src/proxy/static/stream.rs +++ b/leaf/src/proxy/static/stream.rs @@ -1,9 +1,9 @@ use std::io; use std::sync::atomic::{AtomicUsize, Ordering}; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use async_trait::async_trait; -use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand::{Rng, SeedableRng, rngs::StdRng}; use crate::{proxy::*, session::Session}; diff --git a/leaf/src/proxy/tls/inbound/stream.rs b/leaf/src/proxy/tls/inbound/stream.rs index 30e7038c5..2d6a20c87 100644 --- a/leaf/src/proxy/tls/inbound/stream.rs +++ b/leaf/src/proxy/tls/inbound/stream.rs @@ -6,11 +6,11 @@ use anyhow::Result; #[cfg(feature = "rustls-tls")] use { rustls_pemfile::{certs, ec_private_keys, pkcs8_private_keys, rsa_private_keys}, + tokio_rustls::TlsAcceptor, tokio_rustls::rustls::{ - pki_types::{CertificateDer, PrivateKeyDer}, ServerConfig, + pki_types::{CertificateDer, PrivateKeyDer}, }, - tokio_rustls::TlsAcceptor, }; use crate::{proxy::*, session::Session}; diff --git a/leaf/src/proxy/tls/outbound/stream.rs b/leaf/src/proxy/tls/outbound/stream.rs index 409236397..337568c8b 100644 --- a/leaf/src/proxy/tls/outbound/stream.rs +++ b/leaf/src/proxy/tls/outbound/stream.rs @@ -10,15 +10,15 @@ use { std::sync::Arc, std::{fs::File, io::BufReader, io::Cursor}, tokio_rustls::{ - rustls::{pki_types::ServerName, ClientConfig, RootCertStore}, TlsConnector, + rustls::{ClientConfig, RootCertStore, pki_types::ServerName}, }, }; #[cfg(all(feature = "rustls-tls", feature = "rustls-tls-aws-lc"))] use tokio_rustls::rustls::client::{EchConfig, EchMode}; #[cfg(all(feature = "rustls-tls", feature = "rustls-tls-aws-lc"))] -use tokio_rustls::rustls::pki_types::{pem::PemObject, EchConfigListBytes}; +use tokio_rustls::rustls::pki_types::{EchConfigListBytes, pem::PemObject}; #[cfg(feature = "openssl-tls")] use { @@ -33,9 +33,9 @@ use crate::{app::SyncDnsClient, proxy::*, session::Session}; #[cfg(feature = "rustls-tls")] mod dangerous { use tokio_rustls::rustls::{ + DigitallySignedStruct, Error, SignatureScheme, client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, pki_types::{CertificateDer, ServerName, UnixTime}, - DigitallySignedStruct, Error, SignatureScheme, }; #[derive(Debug)] @@ -122,7 +122,7 @@ impl Handler { fn build_rustls_config( alpns: &[String], certificate: Option<&String>, - certificate_key: Option<&String>, + _certificate_key: Option<&String>, insecure: bool, ech_config_list: Option<&str>, ) -> Result> { @@ -182,18 +182,19 @@ impl Handler { }; let mut config = if insecure { - let builder = builder + builder .dangerous() - .with_custom_certificate_verifier(Arc::new(dangerous::NotVerified)); - if certificate.is_some() { - if certificate_key.is_some() { - builder.with_no_client_auth() - } else { - builder.with_no_client_auth() - } - } else { - builder.with_no_client_auth() - } + .with_custom_certificate_verifier(Arc::new(dangerous::NotVerified)) + .with_no_client_auth() + // if certificate.is_some() { + // if certificate_key.is_some() { + // builder.with_no_client_auth() + // } else { + // builder.with_no_client_auth() + // } + // } else { + // builder.with_no_client_auth() + // } } else { builder.with_root_certificates(roots).with_no_client_auth() }; @@ -218,15 +219,13 @@ impl Handler { if let Some(fixed) = fixed_ech_config_list { trace!( "auto ech fetch failed for {}, fallback to fixed ech config: {}", - name, - err + name, err ); Ok(Some(fixed.to_string())) } else { trace!( "auto ech fetch failed for {}, no fixed ech config available: {}", - name, - err + name, err ); Err(io::Error::other(format!( "auto ech fetch failed for {}: {}", @@ -573,10 +572,7 @@ impl OutboundStreamHandler for Handler { }; trace!( "handling TLS {} with rustls, ech_enabled={}, ech_config_selected={}, ech_dns_lookup_skipped={}", - &name, - self.ech_enabled, - ech_config_selected, - ech_dns_lookup_skipped + &name, self.ech_enabled, ech_config_selected, ech_dns_lookup_skipped ); let connector = TlsConnector::from(tls_config); let domain = ServerName::try_from(name.as_str()).map_err(|e| { @@ -618,8 +614,7 @@ impl OutboundStreamHandler for Handler { })?; trace!( "handling TLS {} with openssl, ech_enabled={}", - &name, - self.ech_enabled + &name, self.ech_enabled ); let mut stream = SslStream::new(ssl, stream).map_err(|e| { io::Error::new( @@ -653,11 +648,11 @@ mod tests { use protobuf::MessageField; use tokio::sync::RwLock; - use crate::app::{dns::DnsClient, SyncDnsClient}; + use crate::app::{SyncDnsClient, dns::DnsClient}; #[cfg(feature = "rustls-tls-aws-lc")] use crate::session::Session; - use super::{decode_base64, ensure_ech_config_list_bytes, Handler}; + use super::{Handler, decode_base64, ensure_ech_config_list_bytes}; fn new_test_dns_client() -> SyncDnsClient { let mut dns = crate::config::Dns::new(); @@ -729,16 +724,19 @@ mod tests { Some(Err(anyhow!("dns failed"))), ) .unwrap_err(); - assert!(err - .to_string() - .contains("auto ech fetch failed for example.com: dns failed")); + assert!( + err.to_string() + .contains("auto ech fetch failed for example.com: dns failed") + ); } #[cfg(any(feature = "openssl-tls", feature = "rustls-tls-aws-lc"))] #[test] fn test_should_skip_ech_dns_lookup_for_dnsclient_session() { - let mut sess = Session::default(); - sess.inbound_tag = "dnsclient".to_string(); + let mut sess = Session { + inbound_tag: "dnsclient".into(), + ..Default::default() + }; assert!(Handler::should_skip_ech_dns_lookup_for_session(&sess)); sess.inbound_tag = "socks".to_string(); assert!(!Handler::should_skip_ech_dns_lookup_for_session(&sess)); diff --git a/leaf/src/proxy/trojan/inbound/stream.rs b/leaf/src/proxy/trojan/inbound/stream.rs index 6f766c8d3..2d5fa4ea5 100644 --- a/leaf/src/proxy/trojan/inbound/stream.rs +++ b/leaf/src/proxy/trojan/inbound/stream.rs @@ -75,8 +75,7 @@ where .await?; trace!( "trojan inbound received UDP {} bytes for {}", - payload_len, - &dst_addr + payload_len, &dst_addr ); Ok((payload_len, self.1.clone(), dst_addr)) } diff --git a/leaf/src/proxy/trojan/outbound/datagram.rs b/leaf/src/proxy/trojan/outbound/datagram.rs index 46e7f69fc..1e3c60fae 100644 --- a/leaf/src/proxy/trojan/outbound/datagram.rs +++ b/leaf/src/proxy/trojan/outbound/datagram.rs @@ -104,18 +104,16 @@ where // domain address instead of the real source address. That also // means we assume all received packets are comming from a same // address. - if self.1.is_some() { + if let Some(addr) = self.1.as_ref() { trace!( "trojan outbound received UDP {} bytes from {}", - payload_len, - self.1.as_ref().unwrap() + payload_len, addr ); - Ok((payload_len, self.1.as_ref().unwrap().clone())) + Ok((payload_len, addr.to_owned())) } else { trace!( "trojan outbound received UDP {} bytes from {}", - payload_len, - &addr + payload_len, &addr ); Ok((payload_len, addr)) } @@ -138,11 +136,11 @@ where data.put_slice(buf); // Writes the header along with the first payload. - if self.1.is_some() { - if let Some(mut head) = self.1.take() { - head.extend_from_slice(&data); - return self.0.write_all(&head).map_ok(|_| buf.len()).await; - } + if self.1.is_some() + && let Some(mut head) = self.1.take() + { + head.extend_from_slice(&data); + return self.0.write_all(&head).map_ok(|_| buf.len()).await; } self.0.write_all(&data).map_ok(|_| buf.len()).await diff --git a/leaf/src/proxy/tun/inbound.rs b/leaf/src/proxy/tun/inbound.rs index 591672514..6992e85af 100644 --- a/leaf/src/proxy/tun/inbound.rs +++ b/leaf/src/proxy/tun/inbound.rs @@ -2,15 +2,16 @@ use std::net::SocketAddr; use std::pin::Pin; use std::sync::Arc; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use futures::{sink::SinkExt, stream::StreamExt}; use protobuf::Message; +use tokio::sync::Mutex; use tokio::sync::mpsc::channel as tokio_channel; use tokio::sync::mpsc::{Receiver as TokioReceiver, Sender as TokioSender}; -use tokio::sync::Mutex; use tracing::{debug, error, info, warn}; use crate::{ + Runner, app::dispatcher::Dispatcher, app::fake_dns::{FakeDns, FakeDnsMode}, app::nat_manager::NatManager, @@ -18,7 +19,6 @@ use crate::{ config::{Inbound, TunInboundSettings}, option, session::{DatagramSource, Network, Session, SocksAddr}, - Runner, }; #[cfg(feature = "netstack-lwip")] @@ -44,23 +44,23 @@ async fn handle_inbound_stream_lwip( ..Default::default() }; // Whether to override the destination according to Fake DNS. - if let Some(fakedns) = fakedns { - if fakedns.is_fake_ip(&remote_addr.ip()).await { - if let Some(domain) = fakedns.query_domain(&remote_addr.ip()).await { - sess.destination = SocksAddr::Domain(domain, remote_addr.port()); - } else { - // Although requests targeting fake IPs are assumed - // never happen in real network traffic, which are - // likely caused by poisoned DNS cache records, we - // still have a chance to sniff the request domain - // for TLS traffic in dispatcher. - if remote_addr.port() != 443 && remote_addr.port() != 80 { - debug!( - "No paired domain found for this fake IP: {}, connection is rejected.", - &remote_addr.ip() - ); - return; - } + if let Some(fakedns) = fakedns + && fakedns.is_fake_ip(&remote_addr.ip()).await + { + if let Some(domain) = fakedns.query_domain(&remote_addr.ip()).await { + sess.destination = SocksAddr::Domain(domain, remote_addr.port()); + } else { + // Although requests targeting fake IPs are assumed + // never happen in real network traffic, which are + // likely caused by poisoned DNS cache records, we + // still have a chance to sniff the request domain + // for TLS traffic in dispatcher. + if remote_addr.port() != 443 && remote_addr.port() != 80 { + debug!( + "No paired domain found for this fake IP: {}, connection is rejected.", + &remote_addr.ip() + ); + return; } } } @@ -85,23 +85,23 @@ async fn handle_inbound_stream_smoltcp( ..Default::default() }; // Whether to override the destination according to Fake DNS. - if let Some(fakedns) = fakedns { - if fakedns.is_fake_ip(&remote_addr.ip()).await { - if let Some(domain) = fakedns.query_domain(&remote_addr.ip()).await { - sess.destination = SocksAddr::Domain(domain, remote_addr.port()); - } else { - // Although requests targeting fake IPs are assumed - // never happen in real network traffic, which are - // likely caused by poisoned DNS cache records, we - // still have a chance to sniff the request domain - // for TLS traffic in dispatcher. - if remote_addr.port() != 443 && remote_addr.port() != 80 { - debug!( - "No paired domain found for this fake IP: {}, connection is rejected.", - &remote_addr.ip() - ); - return; - } + if let Some(fakedns) = fakedns + && fakedns.is_fake_ip(&remote_addr.ip()).await + { + if let Some(domain) = fakedns.query_domain(&remote_addr.ip()).await { + sess.destination = SocksAddr::Domain(domain, remote_addr.port()); + } else { + // Although requests targeting fake IPs are assumed + // never happen in real network traffic, which are + // likely caused by poisoned DNS cache records, we + // still have a chance to sniff the request domain + // for TLS traffic in dispatcher. + if remote_addr.port() != 443 && remote_addr.port() != 80 { + debug!( + "No paired domain found for this fake IP: {}, connection is rejected.", + &remote_addr.ip() + ); + return; } } } @@ -164,18 +164,18 @@ async fn handle_inbound_datagram_lwip( } Ok((data, src_addr, dst_addr)) => { // Fake DNS logic. - if dst_addr.port() == 53 { - if let Some(fakedns) = &fakedns { - match fakedns.generate_fake_response(&data).await { - Ok(resp) => { - if let Err(e) = ls.send_to(resp.as_ref(), &dst_addr, &src_addr) { - warn!("A packet failed to send to the netstack: {}", e); - } - continue; - } - Err(err) => { - debug!("generate fake ip failed: {}", err); + if dst_addr.port() == 53 + && let Some(fakedns) = fakedns.as_deref() + { + match fakedns.generate_fake_response(&data).await { + Ok(resp) => { + if let Err(e) = ls.send_to(resp.as_ref(), &dst_addr, &src_addr) { + warn!("A packet failed to send to the netstack: {}", e); } + continue; + } + Err(err) => { + debug!("generate fake ip failed: {}", err); } } } @@ -277,18 +277,18 @@ async fn handle_inbound_datagram_smoltcp( while let Some(item) = lr.next().await { let (data, src_addr, dst_addr) = item; // Fake DNS logic. - if dst_addr.port() == 53 { - if let Some(fakedns) = &fakedns { - match fakedns.generate_fake_response(&data).await { - Ok(resp) => { - if let Err(e) = ls.lock().await.send((resp, dst_addr, src_addr)).await { - warn!("A packet failed to send to the netstack: {}", e); - } - continue; - } - Err(err) => { - debug!("generate fake ip failed: {}", err); + if dst_addr.port() == 53 + && let Some(fakedns) = fakedns.as_deref() + { + match fakedns.generate_fake_response(&data).await { + Ok(resp) => { + if let Err(e) = ls.lock().await.send((resp, dst_addr, src_addr)).await { + warn!("A packet failed to send to the netstack: {}", e); } + continue; + } + Err(err) => { + debug!("generate fake ip failed: {}", err); } } } @@ -529,7 +529,7 @@ pub fn new( let s = UdpSocket::bind("0.0.0.0:0")?; s.connect("1.1.1.1:53")?; let bind_addr = s.local_addr()?.ip().to_string(); - std::env::set_var("OUTBOUND_INTERFACE", &bind_addr); + unsafe { std::env::set_var("OUTBOUND_INTERFACE", &bind_addr) }; tracing::info!("set OUTBOUND_INTERFACE={}", bind_addr); } @@ -598,7 +598,7 @@ pub fn new( .collect(); cfg.metric(0); cfg.platform_config(|x| { - x.device_guid(rng.gen()); + x.device_guid(rng.r#gen()); if !dns_servers.is_empty() { x.dns_servers(&dns_servers); } diff --git a/leaf/src/proxy/vless/mod.rs b/leaf/src/proxy/vless/mod.rs index 3f765ea56..9f8359071 100644 --- a/leaf/src/proxy/vless/mod.rs +++ b/leaf/src/proxy/vless/mod.rs @@ -1,8 +1,8 @@ pub mod datagram; pub mod stream; -pub use datagram::{build_vless_udp_header, VlessDatagram}; -pub use stream::{build_vless_tcp_header, VlessStream}; +pub use datagram::{VlessDatagram, build_vless_udp_header}; +pub use stream::{VlessStream, build_vless_tcp_header}; #[cfg(feature = "outbound-vless")] pub mod outbound; diff --git a/leaf/src/proxy/vless/outbound/datagram.rs b/leaf/src/proxy/vless/outbound/datagram.rs index 019ff16b5..f39417de7 100644 --- a/leaf/src/proxy/vless/outbound/datagram.rs +++ b/leaf/src/proxy/vless/outbound/datagram.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadHalf, WriteHalf}; use uuid::Uuid; -use super::super::datagram::{build_vless_udp_header, VlessUdpParser}; +use super::super::datagram::{VlessUdpParser, build_vless_udp_header}; use crate::{proxy::*, session::*}; pub struct Handler { diff --git a/leaf/src/proxy/vless/outbound/stream.rs b/leaf/src/proxy/vless/outbound/stream.rs index 47e6a5ffd..ad4b4070c 100644 --- a/leaf/src/proxy/vless/outbound/stream.rs +++ b/leaf/src/proxy/vless/outbound/stream.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use tokio::io::AsyncWriteExt; use uuid::Uuid; -use super::super::stream::{build_vless_tcp_header, VlessStream}; +use super::super::stream::{VlessStream, build_vless_tcp_header}; use crate::{proxy::*, session::*}; pub struct Handler { diff --git a/leaf/src/proxy/vless/stream.rs b/leaf/src/proxy/vless/stream.rs index 997a22250..0afb2bdd5 100644 --- a/leaf/src/proxy/vless/stream.rs +++ b/leaf/src/proxy/vless/stream.rs @@ -189,8 +189,8 @@ impl VisionParser { } use std::pin::Pin; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; diff --git a/leaf/src/proxy/vmess/crypto.rs b/leaf/src/proxy/vmess/crypto.rs index fdb51874d..22625682d 100644 --- a/leaf/src/proxy/vmess/crypto.rs +++ b/leaf/src/proxy/vmess/crypto.rs @@ -1,12 +1,12 @@ -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use byteorder::{BigEndian, ByteOrder}; use digest::{ExtendableOutput, Update, XofReader}; use md5::{Digest, Md5}; use sha3::Shake128; use crate::common::crypto::{ - aead::{AeadCipher, AeadDecryptor, AeadEncryptor}, Cipher, NonceSequence, SizedCipher, + aead::{AeadCipher, AeadDecryptor, AeadEncryptor}, }; pub fn generate_chacha20poly1305_key(key: &[u8]) -> Vec { diff --git a/leaf/src/proxy/vmess/outbound/datagram.rs b/leaf/src/proxy/vmess/outbound/datagram.rs index 53008ad92..cf44e1a96 100644 --- a/leaf/src/proxy/vmess/outbound/datagram.rs +++ b/leaf/src/proxy/vmess/outbound/datagram.rs @@ -59,7 +59,7 @@ impl OutboundDatagramHandler for Handler { return Err(io::Error::other(format!( "unsupported cipher: {}", &self.security - ))) + ))); } } diff --git a/leaf/src/proxy/vmess/outbound/stream.rs b/leaf/src/proxy/vmess/outbound/stream.rs index 85260bc15..451edff69 100644 --- a/leaf/src/proxy/vmess/outbound/stream.rs +++ b/leaf/src/proxy/vmess/outbound/stream.rs @@ -55,7 +55,7 @@ impl OutboundStreamHandler for Handler { return Err(io::Error::other(format!( "unsupported cipher: {}", &self.security - ))) + ))); } } diff --git a/leaf/src/proxy/vmess/protocol.rs b/leaf/src/proxy/vmess/protocol.rs index 5ef2905a4..31e5d8cda 100644 --- a/leaf/src/proxy/vmess/protocol.rs +++ b/leaf/src/proxy/vmess/protocol.rs @@ -1,16 +1,16 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use aes::cipher::{AsyncStreamCipher, BlockEncrypt, KeyInit, KeyIvInit}; use aes::Aes128; +use aes::cipher::{AsyncStreamCipher, BlockEncrypt, KeyInit, KeyIvInit}; use aes_gcm::{AeadInPlace, Aes128Gcm}; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use byteorder::{BigEndian, ByteOrder}; use bytes::{BufMut, BytesMut}; use hmac::{Hmac, Mac}; use lz_fnv::{Fnv1a, FnvHasher}; use md5::{Digest, Md5}; use rand::RngCore; -use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand::{Rng, SeedableRng, rngs::StdRng}; use uuid::Uuid; use crate::session::{SocksAddr, SocksAddrWireType}; @@ -51,7 +51,7 @@ impl RequestHeader { let mut buf = BytesMut::new(); buf.put_i64(ts); let mut rng = StdRng::from_entropy(); - let rand_bytes: [u8; 4] = rng.gen(); + let rand_bytes: [u8; 4] = rng.r#gen(); buf.put_slice(&rand_bytes); buf.put_u32(crc32fast::hash(&buf)); let cipher = Aes128::new_from_slice( @@ -172,7 +172,7 @@ impl RequestHeader { unsafe { padding_bytes.set_len(padding_len as usize) }; let mut rng = StdRng::from_entropy(); for i in 0..padding_bytes.len() { - padding_bytes[i] = rng.gen(); + padding_bytes[i] = rng.r#gen(); } buf.put_slice(&padding_bytes); } @@ -195,7 +195,7 @@ impl RequestHeader { let key = hasher.finalize(); if sess.aead { - let out = Self::seal_vmess_aead_header(&key[..16], &buf)?; + let out = Self::seal_vmess_aead_header(&key[..16], buf)?; buf.clear(); buf.extend_from_slice(&out); } else { @@ -233,7 +233,7 @@ impl ClientSession { unsafe { rand_bytes.set_len(16 + 16 + 1) }; let mut rng = StdRng::from_entropy(); for i in 0..rand_bytes.len() { - rand_bytes[i] = rng.gen(); + rand_bytes[i] = rng.r#gen(); } request_body_key[..].copy_from_slice(&rand_bytes[..16]); request_body_iv[..].copy_from_slice(&rand_bytes[16..32]); diff --git a/leaf/src/proxy/vmess/stream.rs b/leaf/src/proxy/vmess/stream.rs index d9aca2a35..2b16bce13 100644 --- a/leaf/src/proxy/vmess/stream.rs +++ b/leaf/src/proxy/vmess/stream.rs @@ -8,12 +8,12 @@ use futures::{ ready, task::{Context, Poll}, }; -use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand::{Rng, SeedableRng, rngs::StdRng}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use crate::common::crypto::{ - aead::{AeadDecryptor, AeadEncryptor}, Decryptor, Encryptor, + aead::{AeadDecryptor, AeadEncryptor}, }; use super::crypto::{PaddingLengthGenerator, ShakeSizeParser, VMessAEADSequence}; @@ -265,7 +265,7 @@ impl AsyncWrite for VMessAuthStream { piece3.resize(padding_size, 0); let mut rng = StdRng::from_entropy(); for i in 0..piece3.len() { - piece3[i] = rng.gen(); + piece3[i] = rng.r#gen(); } } diff --git a/leaf/src/proxy/ws/inbound/stream.rs b/leaf/src/proxy/ws/inbound/stream.rs index e32b8cac6..2bd56aff0 100644 --- a/leaf/src/proxy/ws/inbound/stream.rs +++ b/leaf/src/proxy/ws/inbound/stream.rs @@ -31,17 +31,15 @@ impl<'a> Callback for SimpleCallback<'a> { .headers() .get(&*crate::option::HTTP_FORWARDED_HEADER) .map(|x| x.to_str()) - { - if let Some(f) = forwarded + && let Some(f) = forwarded .split(',') .map(str::trim) .map(|x| x.parse::()) .take_while(|x| x.is_ok()) .map(|x| x.unwrap()) .last() - { - self.sess.forwarded_source.replace(f); - } + { + self.sess.forwarded_source.replace(f); } Ok(response) } diff --git a/leaf/src/proxy/ws/stream.rs b/leaf/src/proxy/ws/stream.rs index 4fc5ae6d0..b0df90537 100644 --- a/leaf/src/proxy/ws/stream.rs +++ b/leaf/src/proxy/ws/stream.rs @@ -11,8 +11,8 @@ use futures::{ }; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tracing::trace; -use tungstenite::error::Error as WsError; use tungstenite::Message; +use tungstenite::error::Error as WsError; pub struct WebSocketToStream { buf: BytesMut, @@ -78,9 +78,11 @@ impl + Unpin> AsyncWrite for WebSocketToStream { buf: &[u8], ) -> Poll> { trace!("poll_write {} bytes", buf.len()); - ready!(Pin::new(&mut self.inner) - .poll_ready(cx) - .map_err(|_| broken_pipe()))?; + ready!( + Pin::new(&mut self.inner) + .poll_ready(cx) + .map_err(|_| broken_pipe()) + )?; let msg = Message::Binary(buf.to_vec()); Pin::new(&mut self.inner) diff --git a/leaf/src/session.rs b/leaf/src/session.rs index cfcd8b7ef..7f3bf27b4 100644 --- a/leaf/src/session.rs +++ b/leaf/src/session.rs @@ -187,24 +187,22 @@ impl Session { pub fn destination_for_routing(&self) -> io::Result> { let mut target_domain = None; - if crate::option::TLS_DOMAIN_SNIFFING.load(std::sync::atomic::Ordering::Relaxed) { - if let Some(domain) = &self.tls_sniffed_domain { - target_domain = Some(domain); - } + if crate::option::TLS_DOMAIN_SNIFFING.load(std::sync::atomic::Ordering::Relaxed) + && let Some(domain) = &self.tls_sniffed_domain + { + target_domain = Some(domain); } if target_domain.is_none() && crate::option::HTTP_DOMAIN_SNIFFING.load(std::sync::atomic::Ordering::Relaxed) + && let Some(domain) = &self.http_sniffed_domain { - if let Some(domain) = &self.http_sniffed_domain { - target_domain = Some(domain); - } + target_domain = Some(domain); } if target_domain.is_none() && crate::option::DNS_DOMAIN_SNIFFING.load(std::sync::atomic::Ordering::Relaxed) + && let Some(domain) = &self.dns_sniffed_domain { - if let Some(domain) = &self.dns_sniffed_domain { - target_domain = Some(domain); - } + target_domain = Some(domain); } if let Some(domain) = target_domain { @@ -272,7 +270,7 @@ impl SocksAddr { pub fn must_ip(&self) -> &SocketAddr { match self { - SocksAddr::Ip(ref a) => a, + SocksAddr::Ip(a) => a, _ => { panic!("assert SocksAddr as SocketAddr failed"); } @@ -304,7 +302,7 @@ impl SocksAddr { } pub fn domain(&self) -> Option<&String> { - if let SocksAddr::Domain(ref domain, _) = self { + if let SocksAddr::Domain(domain, _) = self { Some(domain) } else { None diff --git a/leaf/src/util.rs b/leaf/src/util.rs index 83bd60081..b5c3bb326 100644 --- a/leaf/src/util.rs +++ b/leaf/src/util.rs @@ -4,13 +4,13 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::RwLock; use tokio::time::timeout; use crate::{ - app::{dns::DnsClient, outbound::manager::OutboundManager, SyncDnsClient}, + app::{SyncDnsClient, dns::DnsClient, outbound::manager::OutboundManager}, config::Config, proxy::*, session::*, @@ -96,10 +96,10 @@ async fn test_udp_outbound( handler: AnyOutboundHandler, ) -> Result { use hickory_proto::{ - op::{header::MessageType, op_code::OpCode, query::Query, Message}, - rr::{record_type::RecordType, Name}, + op::{Message, header::MessageType, op_code::OpCode, query::Query}, + rr::{Name, record_type::RecordType}, }; - use rand::{rngs::StdRng, Rng, SeedableRng}; + use rand::{Rng, SeedableRng, rngs::StdRng}; let addr = SocksAddr::Ip(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 53)); let sess = Session { destination: addr.clone(), @@ -114,7 +114,7 @@ async fn test_udp_outbound( let query = Query::query(name, RecordType::A); msg.add_query(query); let mut rng = StdRng::from_entropy(); - let id: u16 = rng.gen(); + let id: u16 = rng.r#gen(); msg.set_id(id); msg.set_op_code(OpCode::Query); msg.set_message_type(MessageType::Query); diff --git a/leaf/tests/common.rs b/leaf/tests/common.rs index bc3badd56..824bf5c9f 100644 --- a/leaf/tests/common.rs +++ b/leaf/tests/common.rs @@ -2,14 +2,14 @@ use std::io::Write; use std::path::Path; -use std::sync::atomic::{AtomicU16, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicU16, Ordering}; use std::time::Duration; use futures::future::abortable; use rand::RngCore; -use rand::{rngs::StdRng, SeedableRng}; +use rand::{SeedableRng, rngs::StdRng}; use sha2::{Digest, Sha256}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, UdpSocket}; @@ -208,8 +208,10 @@ pub fn test_tcp_half_close_on_configs( let local_addr = listener .local_addr() .map_err(|e| anyhow::anyhow!("get local addr failed: {}", e))?; - let mut sess = leaf::session::Session::default(); - sess.destination = leaf::session::SocksAddr::Ip(local_addr); + let sess = leaf::session::Session { + destination: leaf::session::SocksAddr::Ip(local_addr), + ..Default::default() + }; let mut client_stream = new_socks_stream(&socks_addr, socks_port, &sess, None, None).await?; let (mut server_stream, _) = listener @@ -393,13 +395,14 @@ pub fn test_tcp_half_close_on_configs( })); for id in leaf_rt_ids.into_iter() { leaf::shutdown(id); - assert!(rt - .block_on(rt.spawn(async move { + assert!( + rt.block_on(rt.spawn(async move { timeout(Duration::from_millis(50), wait_for_shutdown(id)) .await .map_err(|e| anyhow::anyhow!("wait shutdown timeout: {}", e)) })) - .is_ok()); + .is_ok() + ); } match res { Ok(Ok(())) => Ok(()), @@ -516,8 +519,10 @@ pub fn test_data_transfering_reliability_on_configs( let send_task = async move { tokio::time::sleep(std::time::Duration::from_millis(200)).await; let source = path.join(src_file); - let mut sess = leaf::session::Session::default(); - sess.destination = leaf::session::SocksAddr::Ip(local_addr); + let sess = leaf::session::Session { + destination: leaf::session::SocksAddr::Ip(local_addr), + ..Default::default() + }; let mut stream = new_socks_stream(&socks_addr_cloned, socks_port, &sess, None, None).await?; let mut src = tokio::fs::File::open(source) @@ -533,21 +538,20 @@ pub fn test_data_transfering_reliability_on_configs( Ok::<(), anyhow::Error>(()) }; let leaf_rt_ids = run_leaf_instances(&rt, configs.clone())?; - let mut futs: Vec< - std::pin::Pin> + Send>>, - > = Vec::new(); - futs.push(Box::pin(recv_task)); - futs.push(Box::pin(send_task)); + let futs: Vec> + Send>>> = + vec![Box::pin(recv_task), Box::pin(send_task)]; + let res = rt.block_on(rt.spawn(futures::future::try_join_all(futs))); for id in leaf_rt_ids.into_iter() { leaf::shutdown(id); - assert!(rt - .block_on(rt.spawn(async move { + assert!( + rt.block_on(rt.spawn(async move { timeout(Duration::from_millis(50), wait_for_shutdown(id)) .await .map_err(|e| anyhow::anyhow!("wait shutdown timeout: {}", e)) })) - .is_ok()); + .is_ok() + ); } match res { Ok(Ok(_)) => (), @@ -570,8 +574,10 @@ pub fn test_data_transfering_reliability_on_configs( let source = path.join(src_file); let dst = path.join(dst_file); tokio::time::sleep(std::time::Duration::from_millis(200)).await; - let mut sess = leaf::session::Session::default(); - sess.destination = leaf::session::SocksAddr::Ip(local_addr); + let sess = leaf::session::Session { + destination: leaf::session::SocksAddr::Ip(local_addr), + ..Default::default() + }; let mut stream = new_socks_stream(&socks_addr_cloned, socks_port, &sess, None, None).await?; if dst.exists() { @@ -632,21 +638,20 @@ pub fn test_data_transfering_reliability_on_configs( Ok::<(), anyhow::Error>(()) }; let leaf_rt_ids = run_leaf_instances(&rt, configs.clone())?; - let mut futs: Vec< - std::pin::Pin> + Send>>, - > = Vec::new(); - futs.push(Box::pin(recv_task)); - futs.push(Box::pin(send_task)); + let futs: Vec> + Send>>> = + vec![Box::pin(recv_task), Box::pin(send_task)]; + let res = rt.block_on(rt.spawn(futures::future::try_join_all(futs))); for id in leaf_rt_ids.into_iter() { leaf::shutdown(id); - assert!(rt - .block_on(rt.spawn(async move { + assert!( + rt.block_on(rt.spawn(async move { timeout(Duration::from_millis(50), wait_for_shutdown(id)) .await .map_err(|e| anyhow::anyhow!("wait shutdown timeout: {}", e)) })) - .is_ok()); + .is_ok() + ); } match res { Ok(Ok(_)) => (), @@ -731,8 +736,10 @@ pub fn test_data_transfering_reliability_on_configs( let send_task = async move { tokio::time::sleep(std::time::Duration::from_millis(200)).await; let source = path.join(src_file); - let mut sess = leaf::session::Session::default(); - sess.destination = leaf::session::SocksAddr::Ip(local_addr); + let sess = leaf::session::Session { + destination: leaf::session::SocksAddr::Ip(local_addr), + ..Default::default() + }; let dgram = new_socks_datagram(&socks_addr_cloned, socks_port, &sess, None, None).await?; let (_, mut s) = dgram.split(); let mut src = tokio::fs::File::open(source) @@ -762,21 +769,20 @@ pub fn test_data_transfering_reliability_on_configs( Ok::<(), anyhow::Error>(()) }; let leaf_rt_ids = run_leaf_instances(&rt, configs.clone())?; - let mut futs: Vec< - std::pin::Pin> + Send>>, - > = Vec::new(); - futs.push(Box::pin(recv_task)); - futs.push(Box::pin(send_task)); + let futs: Vec> + Send>>> = + vec![Box::pin(recv_task), Box::pin(send_task)]; + let res = rt.block_on(rt.spawn(futures::future::try_join_all(futs))); for id in leaf_rt_ids.into_iter() { leaf::shutdown(id); - assert!(rt - .block_on(rt.spawn(async move { + assert!( + rt.block_on(rt.spawn(async move { timeout(Duration::from_millis(50), wait_for_shutdown(id)) .await .map_err(|e| anyhow::anyhow!("wait shutdown timeout: {}", e)) })) - .is_ok()); + .is_ok() + ); } match res { Ok(Ok(_)) => (), @@ -798,8 +804,10 @@ pub fn test_data_transfering_reliability_on_configs( path.pop(); let recv_task = async move { tokio::time::sleep(std::time::Duration::from_millis(200)).await; - let mut sess = leaf::session::Session::default(); - sess.destination = leaf::session::SocksAddr::Ip(local_addr); + let sess = leaf::session::Session { + destination: leaf::session::SocksAddr::Ip(local_addr), + ..Default::default() + }; let dgram = new_socks_datagram(&socks_addr_cloned, socks_port, &sess, None, None).await?; let (mut r, mut s) = dgram.split(); let source = path.join(src_file); @@ -899,21 +907,20 @@ pub fn test_data_transfering_reliability_on_configs( Ok::<(), anyhow::Error>(()) }; let leaf_rt_ids = run_leaf_instances(&rt, configs.clone())?; - let mut futs: Vec< - std::pin::Pin> + Send>>, - > = Vec::new(); - futs.push(Box::pin(recv_task)); - futs.push(Box::pin(send_task)); + let futs: Vec> + Send>>> = + vec![Box::pin(recv_task), Box::pin(send_task)]; + let res = rt.block_on(rt.spawn(futures::future::try_join_all(futs))); for id in leaf_rt_ids.into_iter() { leaf::shutdown(id); - assert!(rt - .block_on(rt.spawn(async move { + assert!( + rt.block_on(rt.spawn(async move { timeout(Duration::from_millis(50), wait_for_shutdown(id)) .await .map_err(|e| anyhow::anyhow!("wait shutdown timeout: {}", e)) })) - .is_ok()); + .is_ok() + ); } match res { Ok(Ok(_)) => Ok(()), @@ -959,8 +966,10 @@ pub fn test_configs_with_auth( let app_task = async move { tokio::time::sleep(Duration::from_millis(200)).await; - let mut sess = leaf::session::Session::default(); - sess.destination = leaf::session::SocksAddr::Ip(tcp_addr); + let mut sess = leaf::session::Session { + destination: leaf::session::SocksAddr::Ip(tcp_addr), + ..Default::default() + }; let mut s = timeout( Duration::from_secs(1), new_socks_stream( @@ -1040,7 +1049,7 @@ pub fn test_configs_with_auth( &buf[..n] )); } - if &raddr != &sess.destination { + if raddr != sess.destination { return Err(anyhow::anyhow!( "datagram source mismatch: expected {:?}, got {:?}", sess.destination, @@ -1095,7 +1104,7 @@ pub fn test_configs_with_auth( &buf[..n] )); } - if &raddr != &sess.destination { + if raddr != sess.destination { return Err(anyhow::anyhow!( "second datagram source mismatch: expected {:?}, got {:?}", sess.destination, @@ -1113,9 +1122,8 @@ pub fn test_configs_with_auth( Err(_) => Ok(()), // Aborted } }; - let mut futs = Vec::new(); - futs.push(rt.spawn(bg_task)); - futs.push(rt.spawn(app_task)); + let futs = vec![rt.spawn(bg_task), rt.spawn(app_task)]; + let res = rt.block_on(async { timeout(Duration::from_secs(30), futures::future::select_all(futs)) .await @@ -1124,14 +1132,15 @@ pub fn test_configs_with_auth( for id in leaf_rt_ids.into_iter() { assert!(leaf::shutdown(id)); - assert!(rt - .block_on(rt.spawn(async move { + assert!( + rt.block_on(rt.spawn(async move { timeout(Duration::from_millis(50), wait_for_shutdown(id)) .await .map_err(|e| anyhow::anyhow!("wait shutdown timeout: {}", e))?; Ok::<(), anyhow::Error>(()) })) - .is_ok()); + .is_ok() + ); } match res { diff --git a/leaf/tests/test_amux_trojan.rs b/leaf/tests/test_amux_trojan.rs index 369db5061..109a0c6f1 100644 --- a/leaf/tests/test_amux_trojan.rs +++ b/leaf/tests/test_amux_trojan.rs @@ -87,9 +87,10 @@ fn test_amux_trojan() -> anyhow::Result<()> { ] } "#; - - std::env::set_var("TCP_DOWNLINK_TIMEOUT", "3"); - std::env::set_var("TCP_UPLINK_TIMEOUT", "3"); + unsafe { + std::env::set_var("TCP_DOWNLINK_TIMEOUT", "3"); + std::env::set_var("TCP_UPLINK_TIMEOUT", "3"); + } let configs = vec![config1.to_string(), config2.to_string()]; common::test_configs(configs.clone(), "127.0.0.1", 1086)?; diff --git a/leaf/tests/test_quic_trojan.rs b/leaf/tests/test_quic_trojan.rs index 9b4440d1c..dd89c1c14 100644 --- a/leaf/tests/test_quic_trojan.rs +++ b/leaf/tests/test_quic_trojan.rs @@ -183,23 +183,23 @@ fn test_quic_trojan() -> anyhow::Result<()> { ] } "#; - - std::env::set_var("TCP_DOWNLINK_TIMEOUT", "3"); - std::env::set_var("TCP_UPLINK_TIMEOUT", "3"); - + unsafe { + std::env::set_var("TCP_DOWNLINK_TIMEOUT", "3"); + std::env::set_var("TCP_UPLINK_TIMEOUT", "3"); + } let mut path = std::env::current_exe().map_err(|e| anyhow::anyhow!("current exe failed: {}", e))?; path.pop(); let rcgen::CertifiedKey { cert, key_pair } = rcgen::generate_simple_self_signed(vec!["localhost".into()]) .map_err(|e| anyhow::anyhow!("generate cert failed: {}", e))?; - std::fs::write(&path.join("key.der"), &key_pair.serialize_der()) + std::fs::write(path.join("key.der"), key_pair.serialize_der()) .map_err(|e| anyhow::anyhow!("write key.der failed: {}", e))?; - std::fs::write(&path.join("cert.der"), &cert.der().to_vec()) + std::fs::write(path.join("cert.der"), cert.der()) .map_err(|e| anyhow::anyhow!("write cert.der failed: {}", e))?; - std::fs::write(&path.join("key.pem"), &key_pair.serialize_pem()) + std::fs::write(path.join("key.pem"), key_pair.serialize_pem()) .map_err(|e| anyhow::anyhow!("write key.pem failed: {}", e))?; - std::fs::write(&path.join("cert.pem"), &cert.pem()) + std::fs::write(path.join("cert.pem"), cert.pem()) .map_err(|e| anyhow::anyhow!("write cert.pem failed: {}", e))?; let cert_pem = cert.pem(); diff --git a/leaf/tests/test_shadowsocks.rs b/leaf/tests/test_shadowsocks.rs index 922d0aeac..beeac26a0 100644 --- a/leaf/tests/test_shadowsocks.rs +++ b/leaf/tests/test_shadowsocks.rs @@ -53,10 +53,10 @@ fn test_shadowsocks() -> anyhow::Result<()> { ] } "#; - - std::env::set_var("TCP_DOWNLINK_TIMEOUT", "3"); - std::env::set_var("TCP_UPLINK_TIMEOUT", "3"); - + unsafe { + std::env::set_var("TCP_DOWNLINK_TIMEOUT", "3"); + std::env::set_var("TCP_UPLINK_TIMEOUT", "3"); + } let configs = vec![config1.to_string(), config2.to_string()]; common::test_configs(configs.clone(), "127.0.0.1", 1086)?; common::test_tcp_half_close_on_configs(configs.clone(), "127.0.0.1", 1086)?; diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 000000000..712de81d5 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,20 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "clippy"] + +targets = [ + # Linux + "x86_64-unknown-linux-musl", + "x86_64-unknown-linux-gnu", + "i686-unknown-linux-musl", + "aarch64-unknown-linux-musl", + "arm-unknown-linux-musleabi", + "armv7-unknown-linux-musleabihf", + # Windows + "x86_64-pc-windows-gnu", + # MacOS + "aarch64-apple-darwin", + "x86_64-apple-darwin", +] + +profile = "minimal"