Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8287bd8
edition migration (2021->2024): cargo --fix --edition apply
SaelKimberly Mar 24, 2026
3fb0827
edition migration (2021->2024): set edition and rust version on the w…
SaelKimberly Mar 24, 2026
b8a7d7b
edition migration (2021->2024): cargo fmt --all apply
SaelKimberly Mar 24, 2026
c008db2
edition migration (2021->2024): cargo --fix --edition apply (manual e…
SaelKimberly Mar 24, 2026
dd923fe
edition migration (2021->2024): cargo --fix --edition apply (manual e…
SaelKimberly Mar 24, 2026
6a34098
edition migration (2021->2024): set edition and rust version on the w…
SaelKimberly Mar 24, 2026
e148395
edition migration (2021->2024): cargo clippy --fix and manual clippy …
SaelKimberly Mar 24, 2026
4f4397f
edition migration (2021->2024): fix no_mangle compilation errors
SaelKimberly Mar 24, 2026
eab2fc7
move thiserror dependency to workspace level
SaelKimberly Mar 24, 2026
374fcd9
remove lazy_static dependency in prior to use std::sync::LazyLock.
SaelKimberly Mar 24, 2026
00cf052
fix unsafe functions in tests
SaelKimberly Mar 24, 2026
276fc6b
move anyhow dependency to workspace level
SaelKimberly Mar 24, 2026
565a1a1
move tracing related dependencies to workspace level
SaelKimberly Mar 24, 2026
7664996
fix almost all clippy lints/warnings, fix "inbound-nf" feature for wi…
SaelKimberly Apr 6, 2026
ebc2cbb
Simplify destination domain check
SaelKimberly Apr 6, 2026
5b4ff1c
Upgrade resolver to version 3
SaelKimberly Apr 6, 2026
3055319
Unify and upgrade dependencies: async-ffi (0.2 -> 0.5)
SaelKimberly Apr 6, 2026
d83c630
Add rust-toolchain.toml file
SaelKimberly Apr 6, 2026
ecbca5c
Fix missing LazyLock path
SaelKimberly Apr 10, 2026
84ee11a
Fix some clippy lints in "proxy::select" submodules.
SaelKimberly Apr 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
12 changes: 5 additions & 7 deletions leaf-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
name = "leaf-cli"
version = "0.14.2"
authors = ["eycorsican <eric.y.corsican@gmail.com>"]
edition = "2021"

edition.workspace = true
rust-version.workspace = true

[[bin]]
name = "leaf"
Expand All @@ -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"
Expand All @@ -29,5 +27,5 @@ features = [
"ctrlc",
"auto-reload",
"inbound-nf",
"rule-process-name"
"rule-process-name",
]
18 changes: 11 additions & 7 deletions leaf-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
24 changes: 9 additions & 15 deletions leaf-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,29 @@
name = "leaf-ffi"
version = "0.1.0"
authors = ["eycorsican <eric.y.corsican@gmail.com>"]
edition = "2021"

edition.workspace = true
rust-version.workspace = true

[lib]
name = "leaf"
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
20 changes: 10 additions & 10 deletions leaf-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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);
Expand All @@ -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)
}
Expand All @@ -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) {
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 9 additions & 3 deletions leaf-plugins/shadowsocks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
[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

[lib]
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"
32 changes: 18 additions & 14 deletions leaf-plugins/shadowsocks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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<dyn ProxyStream>)
}
.into_ffi()
Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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))
}
Expand All @@ -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<()> {
Expand Down
Loading