Skip to content

fix: remove ring from aws-lc-rs code path (~500KB-1MB binary savings)#1397

Merged
ibigbug merged 3 commits into
masterfrom
fix/remove-ring-from-aws-lc-rs
May 12, 2026
Merged

fix: remove ring from aws-lc-rs code path (~500KB-1MB binary savings)#1397
ibigbug merged 3 commits into
masterfrom
fix/remove-ring-from-aws-lc-rs

Conversation

@ibigbug

@ibigbug ibigbug commented May 11, 2026

Copy link
Copy Markdown
Member

Summary

When aws-lc-rs is the active crypto backend, ring was still being compiled in as a duplicate dependency via three paths. This PR eliminates all of them.

Result: cargo tree -p clash-lib -F aws-lc-rs,shadowquic -i ring returns empty — ring is completely absent from the aws-lc-rs code path. Estimated binary size reduction: ~500KB–1MB stripped release.

Root causes fixed

1. quinn (v0.11) — rustls feature aliases to rustls-ring

# before
quinn = { ..., features = ["futures-io", "runtime-tokio", "rustls"] }
# "rustls" in quinn v0.11 = rustls-ring, always pulls ring regardless of backend

# after
quinn = { ..., features = ["futures-io", "runtime-tokio"] }
# quinn/rustls-aws-lc-rs added to aws-lc-rs feature
# quinn/rustls-ring added to ring feature

2. rcgen — default features include ring backend

# before
rcgen = { version = "0.14", features = ["pem"] }
# default features include "ring"

# after
rcgen = { version = "0.14", default-features = false, features = ["pem"] }
# rcgen/aws_lc_rs added to aws-lc-rs feature
# rcgen/ring added to ring feature

3. shadowquic — hardcoded ring throughout

Shadowquic unconditionally required ring in all sub-crates (quinn-jls, iroh-quinn, rustls-jls, etc.) and in source code.

Switched to a fork that adds a proper aws-lc-rs feature flag: spongebob888/shadowquic#134

# before
shadowquic = { git = "https://github.com/spongebob888/shadowquic.git", rev = "277f47a5..." }

# after
shadowquic = { git = "https://github.com/ibigbug/shadowquic.git", rev = "a6f3fde0...", default-features = false }
# shadowquic?/aws-lc-rs added to aws-lc-rs feature
# shadowquic?/ring added to ring feature
# shadowquic?/shadowquic-quinn + shadowquic?/sunnyquic-iroh-quinn added to shadowquic feature

Once spongebob888/shadowquic#134 merges, the dep can be pointed back to upstream.

Verification

# aws-lc-rs path (default) - ring completely absent
cargo tree -p clash-lib -F aws-lc-rs,shadowquic -i ring
# → (empty)

# ring path - aws-lc-rs not pulled (was already clean)
cargo tree -p clash-lib -F ring,shadowquic -i aws-lc-rs
# → (empty)

# all-features - builds cleanly
cargo check -p clash-lib --all-features

Summary by CodeRabbit

  • Chores
    • Updated crypto and QUIC feature wiring and dependency defaults to improve compatibility with alternative TLS backends and QUIC implementations, and to support alternate transport providers.
  • Tests
    • Simplified and modernized test setup and imports; replaced manual thread-based startup with instance-based lifecycle management for more reliable integration tests.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Updated clash-lib Cargo feature forwarding and dependency options to align AWS‑LC and ring rustls variants across quinn, rcgen, and shadowquic; refined test imports in clash-dns; integration tests now use ClashInstance::start(...) instead of thread-based startup.

Changes

TLS Backend Feature and Test Lifecycle

Layer / File(s) Summary
TLS Backend Feature Forwarding
clash-lib/Cargo.toml
aws-lc-rs now forwards quinn/rustls-aws-lc-rs, rcgen/aws_lc_rs, and shadowquic?/aws-lc-rs. ring now forwards quinn/rustls-ring, rcgen/ring, and shadowquic?/ring. shadowquic also forwards shadowquic?/shadowquic-quinn and shadowquic?/sunnyquic-iroh-quinn.
Dependency Configuration Alignment
clash-lib/Cargo.toml
rcgen sets default-features = false and keeps features = ["pem"]. quinn removes the rustls feature from its enabled list. shadowquic dependency updated (repo/rev) and sets default-features = false.
clash-dns test import tidy
clash-dns/src/handler.rs
Test imports refined: removed unused HttpsClientStreamBuilder/H3ClientStreamBuilder and dropped explicit rdata::A import.
Integration tests: lifecycle startup
clash-lib/tests/integration_tests.rs
Replaced std::thread::spawn(start_clash(...)) and wait_port_ready with ClashInstance::start(...) for server and client startup in AnyTLS tests.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Watfaq/clash-rs#1354: Related to instance-scoped start/ClashInstance lifecycle APIs used by the updated tests.
  • Watfaq/clash-rs#1395: Related Cargo feature wiring changes for rustls backends and dependency updates.
  • Watfaq/clash-rs#1396: Similar changes to hickory client/stream imports in clash-dns tests.

Poem

🐰 I hop through Cargo tomls bright,
forwarding flags from dark to light,
aws‑lc and ring in tidy rows,
tests start neatly where the instance goes,
a small rabbit cheers the bytes.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main purpose of the PR: removing ring from the aws-lc-rs code path to achieve binary size savings. The changes across Cargo.toml, handler.rs, and integration_tests.rs all support this primary objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

When aws-lc-rs is the active crypto backend, ring was still being
pulled in as a duplicate dependency by three paths:

1. quinn (v0.11) - used feature = "rustls" which aliases to
   "rustls-ring". Removed "rustls" from static features; now routes
   via aws-lc-rs feature (quinn/rustls-aws-lc-rs) or ring feature
   (quinn/rustls-ring).

2. rcgen - used default features which include ring backend. Switched
   to default-features = false; crypto backend now selected via
   aws-lc-rs/ring features (rcgen/aws_lc_rs or rcgen/ring).

3. shadowquic - hardcoded ring throughout. Switched to the fork
   ibigbug/shadowquic@a6f3fde which adds an aws-lc-rs feature flag
   (see spongebob888/shadowquic#134).
   Added default-features = false on the dep and explicit feature
   routing in aws-lc-rs/ring clash-lib features.

Result: cargo tree -p clash-lib -F aws-lc-rs,shadowquic -i ring
returns empty — ring is completely absent from the aws-lc-rs path.
Estimated binary size reduction: ~500KB-1MB stripped release.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ibigbug ibigbug force-pushed the fix/remove-ring-from-aws-lc-rs branch from 9bd7323 to 55f6d79 Compare May 11, 2026 21:46
@codecov

codecov Bot commented May 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

ibigbug and others added 2 commits May 11, 2026 16:23
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The anytls tests were using std::thread::spawn + start_clash without
any cleanup mechanism. Threads kept running after each test completed,
leaving ports 8902/9092 and 8998/9095 bound. The subsequent
integration_test_anytls_udp test would then fail to bind port 8902
with 'Address already in use'.

Replace both anytls tests with ClashInstance, which cancels the
shutdown token on drop and waits for all ports to be released before
returning — matching the pattern used throughout api_tests.rs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with 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.

Inline comments:
In `@clash-lib/tests/integration_tests.rs`:
- Around line 138-158: Tests call ClashInstance::start which triggers TLS setup
and can race under aws-lc-rs; add a module-level initializer that calls
rustls::crypto::aws_lc_rs::default_provider().install_default() once (use
std::sync::Once) and invoke it at the start of affected tests (before calling
ClashInstance::start in integration_tests.rs) so the AWS-LC provider is
installed exactly once across tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4ad77269-edf5-40b5-a45b-dadbdf80396f

📥 Commits

Reviewing files that changed from the base of the PR and between 67b08a9 and b7037e2.

📒 Files selected for processing (1)
  • clash-lib/tests/integration_tests.rs

Comment on lines +138 to +158
let _server = ClashInstance::start(
Options {
config: Config::File(server_config.to_string_lossy().to_string()),
cwd: Some(wd_server.to_string_lossy().to_string()),
rt: None,
log_file: None,
})
.expect("Failed to start AnyTLS server");
});
},
vec![8902, 9092],
)
.expect("Failed to start AnyTLS server");

std::thread::spawn(move || {
start_clash(Options {
let _client = ClashInstance::start(
Options {
config: Config::File(client_config.to_string_lossy().to_string()),
cwd: Some(wd_client.to_string_lossy().to_string()),
rt: None,
log_file: None,
})
.expect("Failed to start AnyTLS client");
});
},
vec![8998, 9095],
)
.expect("Failed to start AnyTLS client");

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether aws-lc provider install is present before AnyTLS starts in this test file.
rg -n --type rust -C2 'install_default|integration_test_anytls|integration_test_anytls_udp|ClashInstance::start' clash-lib/tests/integration_tests.rs

Repository: Watfaq/clash-rs

Length of output: 1635


🏁 Script executed:

# Check entire integration_tests.rs file for any TLS provider installations
rg -n "install_default|rustls::crypto" clash-lib/tests/integration_tests.rs

Repository: Watfaq/clash-rs

Length of output: 41


🏁 Script executed:

# Count total lines and show the overall structure of test functions in the file
wc -l clash-lib/tests/integration_tests.rs && echo "---" && rg -n "^async fn integration_test" clash-lib/tests/integration_tests.rs

Repository: Watfaq/clash-rs

Length of output: 335


🏁 Script executed:

# Show lines around 117 (start of integration_test_anytls) through 200 to see the full first test
sed -n '117,200p' clash-lib/tests/integration_tests.rs | head -50

Repository: Watfaq/clash-rs

Length of output: 1846


🏁 Script executed:

# Search for module-level setup functions, once patterns, or feature gates
rg -n "static|Once|#\[cfg" clash-lib/tests/integration_tests.rs | head -20

Repository: Watfaq/clash-rs

Length of output: 94


🏁 Script executed:

# Get lines 1-50 to see file header, imports, and any setup
head -50 clash-lib/tests/integration_tests.rs

Repository: Watfaq/clash-rs

Length of output: 1700


🏁 Script executed:

# Check if any other test functions also use ClashInstance with AnyTLS configs
rg -n "anytls|ClashInstance::start" clash-lib/tests/integration_tests.rs

Repository: Watfaq/clash-rs

Length of output: 626


Install the AWS-LC rustls provider before AnyTLS startup in these tests.

ClashInstance::start(...) on line 138 and line 223 enter TLS code paths; without installing the aws-lc provider first, these tests can fail/flap under aws-lc-rs feature runs.

Per coding guidelines (**/*test*.rs), rustls::crypto::aws_lc_rs::default_provider().install_default() must be called before constructing TLS configs in tests. Add a module-level helper function with std::sync::Once to ensure single initialization across both affected test functions:

Suggested patch
+fn install_tls_provider_for_tests() {
+    #[cfg(feature = "aws-lc-rs")]
+    {
+        static INIT: std::sync::Once = std::sync::Once::new();
+        INIT.call_once(|| {
+            let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
+        });
+    }
+}
+
 #[tokio::test(flavor = "current_thread")]
 #[serial_test::serial]
 /// Test AnyTLS inbound and outbound functionality (ephemeral self-signed cert)
 async fn integration_test_anytls() {
+    install_tls_provider_for_tests();
     let wd_server =
         PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/config/server");
@@
 #[tokio::test(flavor = "current_thread")]
 #[serial_test::serial]
 /// Test AnyTLS UDP-over-TCP v2: send a UDP datagram through SOCKS5 UDP
 /// ASSOCIATE → AnyTLS inbound → echo server, verify round-trip payload.
 async fn integration_test_anytls_udp() {
+    install_tls_provider_for_tests();
     use std::net::SocketAddr;
     use tokio::net::UdpSocket;
🤖 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/tests/integration_tests.rs` around lines 138 - 158, Tests call
ClashInstance::start which triggers TLS setup and can race under aws-lc-rs; add
a module-level initializer that calls
rustls::crypto::aws_lc_rs::default_provider().install_default() once (use
std::sync::Once) and invoke it at the start of affected tests (before calling
ClashInstance::start in integration_tests.rs) so the AWS-LC provider is
installed exactly once across tests.

@ibigbug ibigbug merged commit 735e7ae into master May 12, 2026
35 checks passed
@ibigbug ibigbug deleted the fix/remove-ring-from-aws-lc-rs branch May 12, 2026 01:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant