diff --git a/Cargo.lock b/Cargo.lock index 400bb2f6f..691ae4846 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1304,6 +1304,9 @@ name = "clash-ffi" version = "0.10.4" dependencies = [ "clash-lib", + "tracing", + "tracing-oslog", + "tracing-subscriber", ] [[package]] diff --git a/clash-ffi/Cargo.toml b/clash-ffi/Cargo.toml index 39974ce9c..653ec8b4e 100644 --- a/clash-ffi/Cargo.toml +++ b/clash-ffi/Cargo.toml @@ -5,7 +5,11 @@ version = { workspace = true } edition = { workspace = true } [dependencies] -clash-lib = { path = "../clash-lib", default-features = false, features = ["shadowsocks", "tuic", "ssh", "zero_copy"] } +clash-lib = { path = "../clash-lib", default-features = false, features = ["shadowsocks", "tuic", "ssh", "zero_copy", "aws-lc-rs"] } +tracing = "0.1.41" +tracing-oslog = "0.3.0" +tracing-subscriber = "0.3.20" + [lib] name = "clashrs" diff --git a/clash-lib/src/app/dispatcher/statistics_manager.rs b/clash-lib/src/app/dispatcher/statistics_manager.rs index 22099aa8b..1faa2ff21 100644 --- a/clash-lib/src/app/dispatcher/statistics_manager.rs +++ b/clash-lib/src/app/dispatcher/statistics_manager.rs @@ -74,7 +74,7 @@ pub struct Snapshot { download_total: u64, upload_total: u64, connections: Vec, - memory: usize, + // memory: usize, } type ConnectionMap = HashMap)>; @@ -276,7 +276,7 @@ impl Manager { .upload_total .load(std::sync::atomic::Ordering::Relaxed), connections, - memory: self.memory_usage(), + // memory: self.memory_usage(), } } diff --git a/clash-lib/src/app/logging.rs b/clash-lib/src/app/logging.rs index ffc375294..7ea011667 100644 --- a/clash-lib/src/app/logging.rs +++ b/clash-lib/src/app/logging.rs @@ -139,7 +139,7 @@ fn setup_logging_inner( } else { format!("{cwd}/{log_file}") }; - let writer = std::fs::File::options().append(true).open(log_path)?; + let writer = std::fs::File::options().append(true).create(true).open(log_path)?; let (non_blocking, guard) = tracing_appender::non_blocking::NonBlockingBuilder::default() .buffered_lines_limit(16_000) diff --git a/clash-lib/src/proxy/tun/runner.rs b/clash-lib/src/proxy/tun/runner.rs index 30f11f8b4..07e703be5 100644 --- a/clash-lib/src/proxy/tun/runner.rs +++ b/clash-lib/src/proxy/tun/runner.rs @@ -15,9 +15,11 @@ use crate::{ runner::Runner, }; +#[cfg(not(any(target_os = "ios", target_os = "android")))] /// Maximum number of attempts to wait for a newly created TUN interface to /// become visible via NetworkInterface::show(). const TUN_VISIBILITY_MAX_ATTEMPTS: u32 = 40; +#[cfg(not(any(target_os = "ios", target_os = "android")))] /// Interval in milliseconds between each visibility poll attempt. const TUN_VISIBILITY_POLL_INTERVAL_MS: u64 = 50; diff --git a/clash-lib/src/proxy/utils/platform/apple.rs b/clash-lib/src/proxy/utils/platform/apple.rs index 6d2f10d73..4ec71b1fd 100644 --- a/clash-lib/src/proxy/utils/platform/apple.rs +++ b/clash-lib/src/proxy/utils/platform/apple.rs @@ -19,10 +19,36 @@ pub(crate) fn must_bind_socket_on_interface( } match family { socket2::Domain::IPV4 => { - socket.bind_device_by_index_v4(std::num::NonZeroU32::new(index)) + socket + .bind_device_by_index_v4(std::num::NonZeroU32::new(index)) + .or_else(|e| { + if e.kind() == io::ErrorKind::AddrNotAvailable { + warn!( + "stale interface index {index} for '{}', \ + falling back to default route: {e}", + iface.name + ); + Ok(()) + } else { + Err(e) + } + }) } socket2::Domain::IPV6 => { - socket.bind_device_by_index_v6(std::num::NonZeroU32::new(index)) + socket + .bind_device_by_index_v6(std::num::NonZeroU32::new(index)) + .or_else(|e| { + if e.kind() == io::ErrorKind::AddrNotAvailable { + warn!( + "stale interface index {index} for '{}', \ + falling back to default route: {e}", + iface.name + ); + Ok(()) + } else { + Err(e) + } + }) } _ => Err(io::Error::new( io::ErrorKind::InvalidInput, diff --git a/clash-lib/src/proxy/utils/platform/unix.rs b/clash-lib/src/proxy/utils/platform/unix.rs index 81c4b7375..2e1007083 100644 --- a/clash-lib/src/proxy/utils/platform/unix.rs +++ b/clash-lib/src/proxy/utils/platform/unix.rs @@ -9,11 +9,23 @@ pub(crate) fn must_bind_socket_on_interface( ) -> io::Result<()> { #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux",))] { - use tracing::error; socket .bind_device(Some(iface.name.as_bytes())) - .inspect_err(|e| { - error!("failed to bind socket to interface {}: {e}", iface.name); + .or_else(|e| { + if matches!( + e.kind(), + io::ErrorKind::AddrNotAvailable | io::ErrorKind::NotFound + ) { + tracing::warn!( + "stale interface '{}' for bind_device, \ + falling back to default route: {e}", + iface.name + ); + Ok(()) + } else { + tracing::error!("failed to bind socket to interface {}: {e}", iface.name); + Err(e) + } }) } #[cfg(not(any( diff --git a/clash-lib/src/proxy/utils/socket_helpers.rs b/clash-lib/src/proxy/utils/socket_helpers.rs index 60b4c1567..6f754259b 100644 --- a/clash-lib/src/proxy/utils/socket_helpers.rs +++ b/clash-lib/src/proxy/utils/socket_helpers.rs @@ -131,7 +131,7 @@ pub async fn new_udp_socket( if !cfg!(target_os = "android") { match (src, iface) { (_, Some(iface)) => { - must_bind_socket_on_interface(&socket, iface, family).inspect_err( +must_bind_socket_on_interface(&socket, iface, family).inspect_err( |x| { error!("failed to bind socket to interface: {}", x); }, diff --git a/scripts/build_apple.sh b/scripts/build_apple.sh index 1da966741..253245c16 100755 --- a/scripts/build_apple.sh +++ b/scripts/build_apple.sh @@ -7,10 +7,22 @@ set -o pipefail # Fail pipeline if any command fails IOS_ARCHS=("aarch64-apple-ios" "x86_64-apple-ios" "aarch64-apple-ios-sim") MACOS_ARCHS=("aarch64-apple-darwin" "x86_64-apple-darwin") +# Min deploy version +MIN_IOS_VERSION="17.0" +MIN_MACOS_VERSION="14.0" + # Variables CRATE_NAME="clash-ffi" LIB_NAME="clashrs" OUTPUT_DIR="build" +FULL_APPLE_BUILD="false" + +if [ "${1:-}" = "--full" ]; then + FULL_APPLE_BUILD="true" +elif [ -n "${1:-}" ]; then + echo "Usage: $0 [--full]" + exit 1 +fi HEADERS_DIR="${OUTPUT_DIR}/Headers" HEADER_FILE="${HEADERS_DIR}/${LIB_NAME}/${LIB_NAME}.h" @@ -39,7 +51,12 @@ fi # Install necessary Rust targets echo "Installing necessary Rust targets..." -for target in "${IOS_ARCHS[@]}" "${MACOS_ARCHS[@]}"; do +TARGETS=("${IOS_ARCHS[@]}") +if [ "$FULL_APPLE_BUILD" = "true" ]; then + TARGETS+=("${MACOS_ARCHS[@]}") +fi + +for target in "${TARGETS[@]}"; do rustup target add "$target" --toolchain $TOOLCHAIN || echo "Target $target is Tier 3 and may need local stdlib build." done @@ -58,41 +75,58 @@ EOF mkdir -p "$OUTPUT_DIR" mkdir -p "$HEADERS_DIR" -# Build for all targets -echo "Building library for iOS and macOS targets..." -for target in "${IOS_ARCHS[@]}" "${MACOS_ARCHS[@]}"; do - cargo +$TOOLCHAIN build --target "$target" --release +# Build for selected targets +if [ "$FULL_APPLE_BUILD" = "true" ]; then + echo "Building library for iOS and macOS targets..." +else + echo "Building library for iOS targets only..." +fi + +for target in "${TARGETS[@]}"; do + MACOSX_DEPLOYMENT_TARGET=$MIN_MACOS_VERSION IPHONEOS_DEPLOYMENT_TARGET=$MIN_IOS_VERSION cargo +$TOOLCHAIN build --target "$target" --release mkdir -p "$OUTPUT_DIR/$target" cp "target/$target/release/lib${LIB_NAME}.a" "$OUTPUT_DIR/$target/" done # Ensure directories for universal binaries mkdir -p "$OUTPUT_DIR/ios-simulator-universal" -mkdir -p "$OUTPUT_DIR/macos-universal" +if [ "$FULL_APPLE_BUILD" = "true" ]; then + mkdir -p "$OUTPUT_DIR/macos-universal" +fi # Lipo operations for universal binaries echo "Creating universal binaries using lipo..." # iOS lipo: x86_64-apple-ios + aarch64-apple-ios-sim -lipo -create \ - "$OUTPUT_DIR/x86_64-apple-ios/lib${LIB_NAME}.a" \ - "$OUTPUT_DIR/aarch64-apple-ios-sim/lib${LIB_NAME}.a" \ - -output "$OUTPUT_DIR/ios-simulator-universal/lib${LIB_NAME}.a" - -# macOS lipo: aarch64-apple-darwin + x86_64-apple-darwin -lipo -create \ - "$OUTPUT_DIR/aarch64-apple-darwin/lib${LIB_NAME}.a" \ - "$OUTPUT_DIR/x86_64-apple-darwin/lib${LIB_NAME}.a" \ - -output "$OUTPUT_DIR/macos-universal/lib${LIB_NAME}.a" +# lipo -create \ +# "$OUTPUT_DIR/x86_64-apple-ios/lib${LIB_NAME}.a" \ +# "$OUTPUT_DIR/aarch64-apple-ios-sim/lib${LIB_NAME}.a" \ +# -output "$OUTPUT_DIR/ios-simulator-universal/lib${LIB_NAME}.a" + +if [ "$FULL_APPLE_BUILD" = "true" ]; then + # macOS lipo: aarch64-apple-darwin + x86_64-apple-darwin + lipo -create \ + "$OUTPUT_DIR/aarch64-apple-darwin/lib${LIB_NAME}.a" \ + "$OUTPUT_DIR/x86_64-apple-darwin/lib${LIB_NAME}.a" \ + -output "$OUTPUT_DIR/macos-universal/lib${LIB_NAME}.a" +fi # Create XCFramework echo "Creating XCFramework..." rm -rf "$XCFRAMEWORK_DIR" -xcodebuild -create-xcframework \ - -library "$OUTPUT_DIR/aarch64-apple-ios/lib${LIB_NAME}.a" -headers "$HEADERS_DIR" \ - -library "$OUTPUT_DIR/ios-simulator-universal/lib${LIB_NAME}.a" -headers "$HEADERS_DIR" \ - -library "$OUTPUT_DIR/macos-universal/lib${LIB_NAME}.a" -headers "$HEADERS_DIR" \ - -output "$XCFRAMEWORK_DIR" + +if [ "$FULL_APPLE_BUILD" = "true" ]; then + xcodebuild -create-xcframework \ + -library "$OUTPUT_DIR/aarch64-apple-ios/lib${LIB_NAME}.a" -headers "$HEADERS_DIR" \ + -library "$OUTPUT_DIR/aarch64-apple-ios-sim/lib${LIB_NAME}.a" -headers "$HEADERS_DIR" \ + -library "$OUTPUT_DIR/macos-universal/lib${LIB_NAME}.a" -headers "$HEADERS_DIR" \ + -output "$XCFRAMEWORK_DIR" +else + xcodebuild -create-xcframework \ + -library "$OUTPUT_DIR/aarch64-apple-ios/lib${LIB_NAME}.a" -headers "$HEADERS_DIR" \ + -library "$OUTPUT_DIR/aarch64-apple-ios-sim/lib${LIB_NAME}.a" -headers "$HEADERS_DIR" \ + -output "$XCFRAMEWORK_DIR" +fi echo "XCFramework created at $XCFRAMEWORK_DIR"