Skip to content

Commit 83c876d

Browse files
Merge pull request #104 from loss-and-quick/fix/naive-cronet-ss-routing
fix(singbox): make shadowsocks ciphers, naive uTLS, and naive libcronet work
2 parents 1b8a139 + 2f1c5aa commit 83c876d

9 files changed

Lines changed: 139 additions & 19 deletions

File tree

.github/workflows/ci.yml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ jobs:
122122
- name: Nix store cache
123123
uses: DeterminateSystems/magic-nix-cache-action@87b14cf437d03d37989d87f0fa5ce4f5dc1a330b # v8
124124

125+
- name: Stub the libcronet bundle resource (clippy/codegen build the app)
126+
run: |
127+
# clippy/codegen compile kasumi-desktop, whose tauri.linux.conf.json
128+
# declares the libcronet.so resource; tauri-build checks the path exists.
129+
# The real lib is fetched only for the bundle — a placeholder suffices.
130+
mkdir -p src-tauri/binaries
131+
: > src-tauri/binaries/libcronet.so
132+
125133
- name: fmt + clippy + test + codegen drift
126134
run: |
127135
nix develop --command bash -euo pipefail -c '
@@ -171,6 +179,13 @@ jobs:
171179
bun install --frozen-lockfile
172180
( cd frontend && bun run build )
173181
182+
- name: Stub the libcronet bundle resource (compile-smoke doesn't bundle)
183+
run: |
184+
# tauri-build validates tauri.linux.conf.json's libcronet.so resource at
185+
# compile time; the real lib is fetched only for the bundle. Placeholder.
186+
mkdir -p src-tauri/binaries
187+
: > src-tauri/binaries/libcronet.so
188+
174189
- name: Compile the Tauri app (no bundle)
175190
run: cargo build -p kasumi-desktop
176191

@@ -206,14 +221,15 @@ jobs:
206221
bun install --frozen-lockfile
207222
( cd frontend && bun run build )
208223
209-
- name: Stub the wintun bundle resource (compile-smoke doesn't bundle)
224+
- name: Stub the bundle resources (compile-smoke doesn't bundle)
210225
shell: bash
211226
run: |
212227
# tauri-build validates declared bundle.resources at compile time. The
213-
# real wintun.dll is fetched only for the nightly/release bundle; a
214-
# placeholder satisfies the path check for this compile-only job.
228+
# real wintun.dll / libcronet.dll are fetched only for the nightly/release
229+
# bundle; placeholders satisfy the path check for this compile-only job.
215230
mkdir -p src-tauri/binaries
216231
: > src-tauri/binaries/wintun.dll
232+
: > src-tauri/binaries/libcronet.dll
217233
218234
- name: Compile the Tauri app (no bundle)
219235
run: cargo build -p kasumi-desktop

.github/workflows/nightly.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,9 @@ jobs:
232232
if: runner.os == 'Windows'
233233
shell: bash
234234
run: |
235-
# Portable build: app exe + cores (suffix stripped) + wintun.dll + a
236-
# `portable.dat` marker that pins all state next to the exe at runtime.
235+
# Portable build: app exe + cores (suffix stripped) + wintun.dll +
236+
# libcronet.dll + a `portable.dat` marker that pins all state next to the
237+
# exe at runtime.
237238
t="${{ matrix.target }}"
238239
out="dist/portable/Kasumi-Proxy"
239240
mkdir -p "$out"
@@ -242,6 +243,8 @@ jobs:
242243
cp "src-tauri/binaries/$c-$t.exe" "$out/$c.exe"
243244
done
244245
cp src-tauri/binaries/wintun.dll "$out/"
246+
# libcronet.dll must sit next to sing-box for its naive outbound to load.
247+
cp src-tauri/binaries/libcronet.dll "$out/"
245248
: > "$out/portable.dat"
246249
name=$(scripts/artifact-name.sh windows-portable)
247250
( cd dist/portable && 7z a -tzip "../$name" Kasumi-Proxy >/dev/null )
@@ -261,6 +264,8 @@ jobs:
261264
for c in xray sing-box tun2socks; do
262265
cp "src-tauri/binaries/$c-$t" "$out/$c"
263266
done
267+
# libcronet.so must sit next to sing-box for its naive outbound to load.
268+
cp src-tauri/binaries/libcronet.so "$out/"
264269
chmod +x "$out"/*
265270
: > "$out/portable.dat"
266271
name=$(scripts/artifact-name.sh linux-portable)

.github/workflows/release.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,8 +414,9 @@ jobs:
414414
if: runner.os == 'Windows'
415415
shell: bash
416416
run: |
417-
# Portable build: app exe + cores (suffix stripped) + wintun.dll + a
418-
# `portable.dat` marker that pins all state next to the exe at runtime.
417+
# Portable build: app exe + cores (suffix stripped) + wintun.dll +
418+
# libcronet.dll + a `portable.dat` marker that pins all state next to the
419+
# exe at runtime.
419420
t="${{ matrix.target }}"
420421
out="dist/portable/Kasumi-Proxy"
421422
mkdir -p "$out"
@@ -424,6 +425,8 @@ jobs:
424425
cp "src-tauri/binaries/$c-$t.exe" "$out/$c.exe"
425426
done
426427
cp src-tauri/binaries/wintun.dll "$out/"
428+
# libcronet.dll must sit next to sing-box for its naive outbound to load.
429+
cp src-tauri/binaries/libcronet.dll "$out/"
427430
: > "$out/portable.dat"
428431
name=$(scripts/artifact-name.sh windows-portable)
429432
( cd dist/portable && 7z a -tzip "../$name" Kasumi-Proxy >/dev/null )
@@ -443,6 +446,8 @@ jobs:
443446
for c in xray sing-box tun2socks; do
444447
cp "src-tauri/binaries/$c-$t" "$out/$c"
445448
done
449+
# libcronet.so must sit next to sing-box for its naive outbound to load.
450+
cp src-tauri/binaries/libcronet.so "$out/"
446451
chmod +x "$out"/*
447452
: > "$out/portable.dat"
448453
name=$(scripts/artifact-name.sh linux-portable)

crates/kasumi-core/src/core.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,19 @@ pub fn forced_core(p: &Profile) -> Option<CoreEngine> {
5959
return Some(Xray);
6060
}
6161
Profile::Shadowsocks(ss) => {
62+
// Ciphers only Xray implements — sing-box has the `-ietf-` chacha variant
63+
// only and no `plain`, so these must run on Xray even though the default
64+
// TLS would otherwise route shadowsocks to sing-box below.
6265
if matches!(
6366
ss.method,
64-
SsMethod::Plain
65-
| SsMethod::Chacha20IetfPoly1305
67+
SsMethod::Plain | SsMethod::Chacha20Poly1305 | SsMethod::Xchacha20Poly1305
68+
) {
69+
return Some(Xray);
70+
}
71+
// The 2022 AEAD ciphers and the IETF chacha variant route to sing-box.
72+
if matches!(
73+
ss.method,
74+
SsMethod::Chacha20IetfPoly1305
6675
| SsMethod::Blake3Aes128Gcm
6776
| SsMethod::Blake3Aes256Gcm
6877
| SsMethod::Blake3Chacha20Poly1305
@@ -200,6 +209,33 @@ mod tests {
200209
);
201210
}
202211

212+
#[test]
213+
fn shadowsocks_method_routing_splits_by_core() {
214+
use CoreEngine::{SingBox, Xray};
215+
// Ciphers only Xray implements (sing-box has no `plain` and only the IETF
216+
// chacha variant) — must route to Xray despite shadowsocks' default TLS.
217+
for m in ["plain", "chacha20-poly1305", "xchacha20-poly1305"] {
218+
assert_eq!(
219+
forced_core(&p(&format!("ss://{m}:pw@h.ex:443#x"))),
220+
Some(Xray),
221+
"{m} must route to xray"
222+
);
223+
}
224+
// The 2022 AEAD ciphers and the IETF chacha variant route to sing-box.
225+
for m in [
226+
"chacha20-ietf-poly1305",
227+
"2022-blake3-aes-128-gcm",
228+
"2022-blake3-aes-256-gcm",
229+
"2022-blake3-chacha20-poly1305",
230+
] {
231+
assert_eq!(
232+
forced_core(&p(&format!("ss://{m}:pw@h.ex:443#x"))),
233+
Some(SingBox),
234+
"{m} must route to sing-box"
235+
);
236+
}
237+
}
238+
203239
#[test]
204240
fn selectable_falls_through_to_override_and_table() {
205241
// plain vless tcp tls: not forced.

crates/kasumi-core/src/singbox_config.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,15 @@ fn build_singbox_tls(p: &Profile, force: bool, s: &AdvancedSettings) -> Option<V
143143
if !tls.ech.is_empty() {
144144
t["ech"] = json!({ "enabled": true, "config": [tls.ech] });
145145
}
146-
// QUIC outbounds (hysteria2/tuic) drive their own TLS stack and reject a uTLS
147-
// config — sing-box errors `unsupported usage for uTLS` on every dial.
148-
let quic = matches!(p, Profile::Hysteria2(_) | Profile::Tuic(_));
149-
if tls.fingerprint != Fingerprint::Empty && !quic {
146+
// Some outbounds reject a uTLS config: hysteria2/tuic drive their own QUIC TLS
147+
// stack (sing-box errors `unsupported usage for uTLS` on every dial), and the
148+
// naive outbound uses a chromium-style TLS (`uTLS is not supported on naive
149+
// outbound` at init). Skip uTLS for those.
150+
let no_utls = matches!(
151+
p,
152+
Profile::Hysteria2(_) | Profile::Tuic(_) | Profile::Naive(_)
153+
);
154+
if tls.fingerprint != Fingerprint::Empty && !no_utls {
150155
t["utls"] = json!({ "enabled": true, "fingerprint": wire(&tls.fingerprint) });
151156
}
152157
if tls.security == Security::Reality {
@@ -1188,4 +1193,42 @@ mod tests {
11881193
let socks = &cfg["inbounds"].as_array().unwrap()[0];
11891194
assert!(socks.get("users").is_none());
11901195
}
1196+
1197+
#[test]
1198+
fn naive_outbound_omits_utls() {
1199+
let build = |uri: &str| {
1200+
let p = crate::share::parse_share_link(uri, None).unwrap();
1201+
build_singbox_config(
1202+
&p,
1203+
&AdvancedSettings::default(),
1204+
&[],
1205+
std::slice::from_ref(&p),
1206+
SingboxBuildOpts::default(),
1207+
)
1208+
.unwrap()
1209+
};
1210+
let proxy = |cfg: &Value| {
1211+
cfg["outbounds"]
1212+
.as_array()
1213+
.unwrap()
1214+
.iter()
1215+
.find(|o| o["tag"] == "proxy")
1216+
.cloned()
1217+
.unwrap()
1218+
};
1219+
1220+
// sing-box rejects uTLS on the naive outbound (`uTLS is not supported on
1221+
// naive outbound`), so the builder must omit it even though the profile
1222+
// carries a default fingerprint.
1223+
let naive = proxy(&build("naive+https://user:pw@n.ex:443?sni=s.ex&fp=chrome"));
1224+
assert_eq!(naive["type"], "naive");
1225+
assert!(
1226+
naive["tls"]["utls"].is_null(),
1227+
"naive tls must not carry utls"
1228+
);
1229+
1230+
// A protocol that does accept uTLS still gets it (guard against an over-broad skip).
1231+
let anytls = proxy(&build("anytls://pw@a.ex:443?sni=s.ex&fp=chrome"));
1232+
assert_eq!(anytls["tls"]["utls"]["enabled"], true);
1233+
}
11911234
}

crates/kasumi-core/tests/fixtures/singbox_config.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -511,11 +511,7 @@
511511
"tls": {
512512
"enabled": true,
513513
"server_name": "n.ex",
514-
"insecure": false,
515-
"utls": {
516-
"enabled": true,
517-
"fingerprint": "chrome"
518-
}
514+
"insecure": false
519515
}
520516
},
521517
{

scripts/fetch-cores-desktop.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ case "$TARGET" in
4242
XRAY_ASSET="Xray-linux-64.zip"
4343
T2S_ASSET="tun2socks-linux-amd64.zip"
4444
SB_ASSET="sing-box-%s-linux-amd64.tar.gz"; SB_KIND="tgz"
45+
CRONET_LIB="libcronet.so"
4546
EXT="" ;;
4647
x86_64-pc-windows-msvc)
4748
XRAY_ASSET="Xray-windows-64.zip"
4849
T2S_ASSET="tun2socks-windows-amd64.zip"
4950
SB_ASSET="sing-box-%s-windows-amd64.zip"; SB_KIND="zip"
51+
CRONET_LIB="libcronet.dll"
5052
EXT=".exe" ;;
5153
*)
5254
echo "❌ unsupported desktop target: $TARGET" >&2
@@ -91,6 +93,13 @@ else
9193
fi
9294
copy_one "$TMP/sb" "sing-box$EXT" "$OUT/sing-box-$TARGET$EXT"
9395

96+
# ---- libcronet (sing-box naive outbound) ----
97+
# sing-box is a purego build that dlopen()s libcronet from the sing-box binary's
98+
# own directory at runtime; without it every naive profile fails to initialise
99+
# (`cronet: library not found`). It ships inside the sing-box release archive, so
100+
# stage it next to sing-box — no target suffix (it's loaded by name, like wintun).
101+
copy_one "$TMP/sb" "$CRONET_LIB" "$OUT/$CRONET_LIB"
102+
94103
# ---- wintun (Windows only) ----
95104
# tun2socks loads wintun.dll from its own directory (the xray data-path needs it).
96105
# sing-box embeds its own copy, so this is only for the tun2socks path. Staged
@@ -112,4 +121,5 @@ for c in xray sing-box tun2socks; do
112121
f="$OUT/$c-$TARGET$EXT"
113122
[ -f "$f" ] && printf ' %-12s %s\n' "$c" "$(du -h "$f" | cut -f1)" || echo " ⚠️ missing: $c"
114123
done
124+
{ f="$OUT/$CRONET_LIB"; [ -f "$f" ] && printf ' %-12s %s\n' "$CRONET_LIB" "$(du -h "$f" | cut -f1)" || echo " ⚠️ missing: $CRONET_LIB"; }
115125
[ -z "$EXT" ] || { f="$OUT/wintun.dll"; [ -f "$f" ] && printf ' %-12s %s\n' "wintun.dll" "$(du -h "$f" | cut -f1)" || echo " ⚠️ missing: wintun.dll"; }

src-tauri/tauri.linux.conf.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$schema": "https://schema.tauri.app/config/2",
3+
"bundle": {
4+
"resources": {
5+
"binaries/libcronet.so": "libcronet.so"
6+
}
7+
}
8+
}

src-tauri/tauri.windows.conf.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"bundle": {
44
"targets": ["nsis", "msi"],
55
"resources": {
6-
"binaries/wintun.dll": "wintun.dll"
6+
"binaries/wintun.dll": "wintun.dll",
7+
"binaries/libcronet.dll": "libcronet.dll"
78
},
89
"windows": {
910
"webviewInstallMode": { "type": "downloadBootstrapper" }

0 commit comments

Comments
 (0)