Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion clash-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions clash-lib/src/app/dispatcher/statistics_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub struct Snapshot {
download_total: u64,
upload_total: u64,
connections: Vec<TrackerInfo>,
memory: usize,
// memory: usize,
}

type ConnectionMap = HashMap<uuid::Uuid, (Tracked, Sender<()>)>;
Expand Down Expand Up @@ -276,7 +276,7 @@ impl Manager {
.upload_total
.load(std::sync::atomic::Ordering::Relaxed),
connections,
memory: self.memory_usage(),
// memory: self.memory_usage(),
}
}

Expand Down
2 changes: 1 addition & 1 deletion clash-lib/src/app/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions clash-lib/src/proxy/tun/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
30 changes: 28 additions & 2 deletions clash-lib/src/proxy/utils/platform/apple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
Comment on lines +22 to +51

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Extract duplicate error handling logic.

The IPv4 and IPv6 branches have identical error handling code for AddrNotAvailable. Consider extracting this into a helper closure to eliminate duplication.

♻️ Proposed refactor to eliminate duplication
 pub(crate) fn bind_socket_on_interface(
     socket: &socket2::Socket,
     iface: &OutboundInterface,
     family: socket2::Domain,
 ) -> io::Result<()> {
     let index = iface.index;
     if index == 0 {
         warn!(
             "OutboundInterface index is 0, skipping binding to interface {}",
             iface.name
         );
         return Ok(());
     }
+    let handle_stale = |e: io::Error| {
+        if e.kind() == io::ErrorKind::AddrNotAvailable {
+            warn!(
+                "stale interface index {index} for '{}', \
+                 falling back to default route: {e}",
+                iface.name
+            );
+            Ok(())
+        } else {
+            Err(e)
+        }
+    };
     match family {
         socket2::Domain::IPV4 => {
             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)
-                    }
-                })
+                .or_else(handle_stale)
         }
         socket2::Domain::IPV6 => {
             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)
-                    }
-                })
+                .or_else(handle_stale)
         }
         _ => Err(io::Error::new(
             io::ErrorKind::InvalidInput,
             "unsupported socket family",
         )),
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@clash-lib/src/proxy/utils/platform/apple.rs` around lines 22 - 51, The IPv4
and IPv6 branches duplicate the same or_else error handling for
AddrNotAvailable; extract that logic into a helper (e.g., a closure or function
like handle_addr_not_available) that accepts the Result from
bind_device_by_index_v4/bind_device_by_index_v6 and maps the io::Error with kind
AddrNotAvailable to Ok(()) while otherwise re-raising the error; then replace
both inline or_else blocks with a call to this helper, keeping the warn! call
and iface.name message intact.

}
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
Expand Down
18 changes: 15 additions & 3 deletions clash-lib/src/proxy/utils/platform/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion clash-lib/src/proxy/utils/socket_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
Expand Down
76 changes: 55 additions & 21 deletions scripts/build_apple.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand All @@ -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"

Expand Down
Loading