diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 08cf333d1..77a045ebe 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -319,12 +319,12 @@ jobs: with: use-flakehub: false - - name: Check with mobile + - name: Check with mobile lib working-directory: ./lightway-client - run: nix develop -c cargo check --features=mobile - - name: Clippy with mobile + run: nix develop -c cargo check --lib --features=mobile-test + - name: Clippy with mobile lib working-directory: ./lightway-client - run: nix develop -c cargo clippy --features=mobile --all-targets -- -D warnings + run: nix develop -c cargo clippy --lib --all-targets -- -D warnings - name: Test with mobile features working-directory: ./lightway-client run: nix develop -c cargo test --features=mobile-test @@ -342,7 +342,7 @@ jobs: path: ${{ steps.build-android-aar.outputs.path }} if-no-files-found: error - name: Build JsonSchema - run: nix develop -c cargo run --features=mobile,wolfssl -p lightway-client -- -c client.schema.json -g jsonschema + run: nix develop -c cargo run --features=wolfssl -p lightway-client -- -c client.schema.json -g jsonschema - name: Upload JsonSchema artifact uses: actions/upload-artifact@v4.6.2 with: diff --git a/Makefile.toml b/Makefile.toml index 2c89b6859..7768eeb3e 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -276,7 +276,7 @@ run_task = { name = [ "build-android-uniffi-library", "bindgen-android", "bundle description = "Build native libraries for all Android architectures (x86_64, x86, armeabi-v7a, arm64-v8a)" condition = { env_set = [ "ANDROID_NDK_HOME" ] } command = "cargo" -args = [ "ndk", "--bindgen", "-t", "x86_64", "-t", "x86", "-t", "armeabi-v7a", "-t", "arm64-v8a", "-o", "${ANDROID_UNIFFI_JNI_LIBS_DIR}", "build", "--features", "mobile", "--release", "-p", "lightway-client", "--lib" ] +args = [ "ndk", "--bindgen", "-t", "x86_64", "-t", "x86", "-t", "armeabi-v7a", "-t", "arm64-v8a", "-o", "${ANDROID_UNIFFI_JNI_LIBS_DIR}", "build", "--release", "-p", "lightway-client", "--lib" ] [tasks.bindgen-android] description = "Generate bindgen kotlin classes" diff --git a/lightway-client/Cargo.toml b/lightway-client/Cargo.toml index 195c9a248..35c43ac85 100644 --- a/lightway-client/Cargo.toml +++ b/lightway-client/Cargo.toml @@ -17,8 +17,7 @@ crate-type = ["cdylib", "rlib"] [features] default = ["wolfssl"] -mobile = ["uniffi", "tracing-core", "tracing-panic"] -mobile-test = [ "mobile" ] # Only mock struct, no uniffi structs +mobile-test = ["dep:uniffi", "dep:tracing-core", "dep:tracing-panic"] # Only mock struct, no uniffi exports boringssl = ["lightway-app-utils/boringssl"] wolfssl = ["lightway-app-utils/wolfssl"] debug = ["lightway-core/debug","lightway-app-utils/debug"] @@ -54,10 +53,9 @@ tokio.workspace = true tracing = { workspace = true, features = ["attributes"] } tracing-subscriber = { workspace = true, features = ["env-filter"] } -# mobile feature +tracing-core = { version = "0.1.33", optional = true } +tracing-panic = { version = "0.1.2", optional = true } uniffi = { workspace = true, optional = true } -tracing-core = { version = "0.1.33", optional =true } -tracing-panic = { version = "0.1.2", optional= true } [dev-dependencies] more-asserts.workspace = true @@ -70,6 +68,11 @@ tempfile = "3.24.0" tracing-test = "0.2.6" tun-rs.workspace = true +[target.'cfg(any(target_os = "android", target_os = "ios", target_os = "tvos"))'.dependencies] +tracing-core = "0.1.33" +tracing-panic = "0.1.2" +uniffi = { workspace = true } + [target.'cfg(target_os = "android")'.dependencies] tracing-android = "0.2.0" diff --git a/lightway-client/src/config.rs b/lightway-client/src/config.rs index 1d9607ec1..44c4bfdf1 100644 --- a/lightway-client/src/config.rs +++ b/lightway-client/src/config.rs @@ -345,10 +345,10 @@ pub struct Config { #[schemars(extend("x-cfg" = "windows"))] pub enable_dpapi: bool, - /// SNI header for TLS connections - #[cfg(feature = "mobile")] - #[patch(attribute(clap(skip)))] - #[schemars(extend("x-cfg" = "mobile"))] + #[patch(attribute(clap(long)))] + #[patch(attribute(doc = r#"SNI header for TLS connections + Only used if `servers` is empty"#))] + /// ex: example.com pub sni_header: String, #[patch(attribute(clap(short, long)))] @@ -390,6 +390,8 @@ impl Config { .then(|| std::mem::take(&mut self.server_dn)), cipher: self.cipher, outside_mtu: self.outside_mtu, + sni_header: (!self.sni_header.is_empty()) + .then(|| std::mem::take(&mut self.sni_header)), ..Default::default() }]; } @@ -578,7 +580,6 @@ impl Default for Config { wintun_ring_capacity: ByteSize::mib(8), #[cfg(windows)] enable_dpapi: false, - #[cfg(feature = "mobile")] sni_header: String::new(), accept_unknowns: false, unknowns: HashMap::new(), @@ -625,6 +626,10 @@ pub struct ConnectionConfig { /// The CA Cert content or Path #[serde(default)] pub ca_cert: Option, + + /// SNI header for TLS connections + #[serde(default)] + pub sni_header: Option, } impl ConnectionConfig { @@ -634,7 +639,7 @@ impl ConnectionConfig { } /// Try build CA from ca_crt - #[cfg(feature = "mobile")] + #[cfg(any(mobile, feature = "mobile-test"))] pub fn load_ca(&self) -> Result, Error> { self.ca_cert .as_ref() @@ -651,7 +656,7 @@ impl ConnectionConfig { } /// Try build SocketAddress from server field - #[cfg(feature = "mobile")] + #[cfg(any(mobile, feature = "mobile-test"))] pub fn skt_addr(&self) -> Result { self.server .parse() @@ -660,10 +665,7 @@ impl ConnectionConfig { } #[derive(Debug, thiserror::Error)] -#[cfg_attr( - all(feature = "mobile", not(feature = "mobile-test")), - derive(uniffi::Error) -)] +#[cfg_attr(all(mobile, not(feature = "mobile-test")), derive(uniffi::Error))] pub enum Error { /// Invalid network protocol #[error("Invalid network protocol")] @@ -674,7 +676,7 @@ pub enum Error { #[error("Unable to load certificate")] InvalidCertificate, - #[cfg(feature = "mobile")] + #[cfg(any(mobile, feature = "mobile-test"))] /// Unable to parse the sokcet address #[error("Invalid Socket Address")] InvalidSocketAddress, diff --git a/lightway-client/src/io/outside/tcp.rs b/lightway-client/src/io/outside/tcp.rs index aed57059f..82f99e5d8 100644 --- a/lightway-client/src/io/outside/tcp.rs +++ b/lightway-client/src/io/outside/tcp.rs @@ -12,11 +12,11 @@ impl Tcp { pub async fn new( remote_addr: SocketAddr, maybe_sock: Option, - #[cfg(all(linux, not(feature = "mobile")))] fwmark: u32, + #[cfg(all(linux, not(feature = "mobile-test")))] fwmark: u32, ) -> Result { let sock = match maybe_sock { Some(s) => { - #[cfg(all(linux, not(feature = "mobile")))] + #[cfg(all(linux, not(feature = "mobile-test")))] if fwmark != 0 { let socket = socket2::SockRef::from(&s); match socket.set_mark(fwmark) { @@ -36,7 +36,7 @@ impl Tcp { // SO_MARK must be set before connect() so that the SYN packet is also // marked, ensuring all traffic (including connection setup) is subject // to policy routing rules based on the mark. - #[cfg(all(linux, not(feature = "mobile")))] + #[cfg(all(linux, not(feature = "mobile-test")))] if fwmark != 0 { match socket2::SockRef::from(&socket).set_mark(fwmark) { Ok(_) => tracing::info!("Applied firewall mark to outside TCP socket"), diff --git a/lightway-client/src/io/outside/udp.rs b/lightway-client/src/io/outside/udp.rs index be04e0f40..4090567e9 100644 --- a/lightway-client/src/io/outside/udp.rs +++ b/lightway-client/src/io/outside/udp.rs @@ -52,7 +52,7 @@ impl Udp { pub async fn new( remote_addr: SocketAddr, sock: Option, - #[cfg(all(linux, not(feature = "mobile")))] fwmark: u32, + #[cfg(all(linux, not(feature = "mobile-test")))] fwmark: u32, ) -> Result { let peer_addr = tokio::net::lookup_host(remote_addr) .await? @@ -73,7 +73,7 @@ impl Udp { // Apply the firewall mark *before* the socket is used for anything, so // that no packet can escape unmarked and be captured by the tunnel's // own routes. - #[cfg(all(linux, not(feature = "mobile")))] + #[cfg(all(linux, not(feature = "mobile-test")))] if fwmark != 0 { let socket = socket2::SockRef::from(&sock); match socket.set_mark(fwmark) { diff --git a/lightway-client/src/lib.rs b/lightway-client/src/lib.rs index fccc35494..9a0a97547 100644 --- a/lightway-client/src/lib.rs +++ b/lightway-client/src/lib.rs @@ -8,10 +8,10 @@ pub mod platform; #[cfg(desktop)] pub mod route_manager; -#[cfg(feature = "mobile")] +#[cfg(any(mobile, feature = "mobile-test"))] pub mod mobile; -#[cfg(all(feature = "mobile", not(feature = "mobile-test")))] +#[cfg(all(mobile, not(feature = "mobile-test")))] uniffi::setup_scaffolding!(); use anyhow::{Context, Result, anyhow}; @@ -90,21 +90,18 @@ impl std::fmt::Debug for ClientConnectionMode { } #[derive(Debug)] -#[cfg_attr( - all(feature = "mobile", not(feature = "mobile-test")), - derive(uniffi::Enum) -)] +#[cfg_attr(all(mobile, not(feature = "mobile-test")), derive(uniffi::Enum))] pub enum ClientResult { UserDisconnect, NetworkChange, - #[cfg(feature = "mobile")] + #[cfg(any(mobile, feature = "mobile-test"))] ServerGoodbye, } #[derive(Debug, thiserror::Error)] #[cfg_attr( - all(feature = "mobile", not(feature = "mobile-test")), + all(mobile, not(feature = "mobile-test")), derive(uniffi::Error), uniffi(flat_error) )] @@ -120,7 +117,7 @@ pub enum LightwayError { #[error("Config Format Error: `{0}`")] ConfigFormatError(#[from] serde_saphyr::Error), - #[cfg(feature = "mobile")] + #[cfg(any(mobile, feature = "mobile-test"))] #[error("Logging bridge initialization error: `{0}`")] LoggingBridgeError(#[from] crate::mobile::tracing_utils::LoggingBridgeError), } @@ -410,6 +407,9 @@ pub struct ClientConnectionConfig #[educe(Debug(method(debug_pkt_codec_fac)))] pub inside_pkt_codec: Option, + /// SNI header for TLS connections + pub sni_header: Option, + /// Allow injection of a custom handler for event callback #[educe(Debug(ignore))] pub event_handler: Option, @@ -445,6 +445,7 @@ impl ClientConnectionConfig, enable_keepalive: bool, enable_expresslane: bool, @@ -568,7 +566,6 @@ async fn lightway_client_connect( LightwayClientConnectArgs { instance_id, mut connect_conf, - sni_header, socket, enable_keepalive, enable_expresslane, @@ -590,6 +587,7 @@ async fn lightway_client_connect( let auth = connect_conf.take_auth()?; let server_sockaddr = connect_conf.skt_addr()?; let server_dn = connect_conf.server_dn.take(); + let sni_header = connect_conf.sni_header.take(); let (connection_type, outside_io): (ConnectionType, Arc) = { let builder = OutsideIOBuilder::new(socket, server_sockaddr); @@ -644,7 +642,9 @@ async fn lightway_client_connect( .when(server_dn.is_some(), |b| { b.with_server_domain_name_validation(&server_dn.expect("checked in builder pattern")) }) - .when(!sni_header.is_empty(), |b| b.with_sni_header(&sni_header)) + .when(sni_header.is_some(), |b| { + b.with_sni_header(&sni_header.expect("checked in builder pattern")) + }) .when(connection_type.is_datagram() && ENABLE_PMTUD, |b| { b.with_pmtud_timer(pmtud_timer) })