Skip to content

Commit 972db68

Browse files
authored
Merge pull request #26 from hawkff/feature/hy2-gecko-native
feat: native Hysteria2 Gecko obfs (drop the sidecar)
2 parents 3f82b8e + 80a573f commit 972db68

6 files changed

Lines changed: 46 additions & 60 deletions

File tree

app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt

Lines changed: 10 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import io.nekohasekai.sagernet.fmt.LOCALHOST
1111
import io.nekohasekai.sagernet.fmt.buildConfig
1212
import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean
1313
import io.nekohasekai.sagernet.fmt.hysteria.buildHysteria1Config
14-
import io.nekohasekai.sagernet.fmt.hysteria.buildHysteria2SidecarConfig
1514
import io.nekohasekai.sagernet.fmt.masterdnsvpn.MasterDnsVpnBean
1615
import io.nekohasekai.sagernet.fmt.masterdnsvpn.buildMasterDnsVpnConfig
1716
import io.nekohasekai.sagernet.fmt.masterdnsvpn.resolverLines
@@ -82,26 +81,16 @@ abstract class BoxInstance(
8281
}
8382

8483
is HysteriaBean -> {
85-
if (bean.protocolVersion == 2 &&
86-
bean.hysteria2ObfsType == HysteriaBean.OBFS_GECKO
87-
) {
88-
// Gecko obfs: use the bundled official hysteria binary sidecar.
89-
// Loopback-only SOCKS5 (no auth), matching the other sidecars
90-
// (Mieru/Naive); the sing-box socks outbound dials 127.0.0.1:port.
91-
initPlugin("hysteria2-plugin")
92-
pluginConfigs[port] = profile.type to bean.buildHysteria2SidecarConfig(
93-
port,
94-
File(app.noBackupFilesDir, "protect_path").absolutePath
95-
)
96-
} else {
97-
initPlugin("hysteria-plugin")
98-
pluginConfigs[port] = profile.type to bean.buildHysteria1Config(port) {
99-
File(
100-
app.cacheDir, "hysteria_" + SystemClock.elapsedRealtime() + ".ca"
101-
).apply {
102-
parentFile?.mkdirs()
103-
cacheFiles.add(this)
104-
}
84+
// Only reached via the external path (needExternal == !canUseSingBox).
85+
// Hysteria2 (incl. Gecko obfs) runs natively in sing-box now, so the
86+
// external path is Hysteria v1 only.
87+
initPlugin("hysteria-plugin")
88+
pluginConfigs[port] = profile.type to bean.buildHysteria1Config(port) {
89+
File(
90+
app.cacheDir, "hysteria_" + SystemClock.elapsedRealtime() + ".ca"
91+
).apply {
92+
parentFile?.mkdirs()
93+
cacheFiles.add(this)
10594
}
10695
}
10796
}
@@ -199,30 +188,6 @@ abstract class BoxInstance(
199188
processes.start(commands, envMap)
200189
}
201190

202-
bean is HysteriaBean && bean.protocolVersion == 2 &&
203-
bean.hysteria2ObfsType == HysteriaBean.OBFS_GECKO -> {
204-
// Official apernet/hysteria client binary (Gecko obfs sidecar).
205-
// viper detects the config format from the .json extension.
206-
val configFile = File(
207-
cacheDir, "hysteria2_" + SystemClock.elapsedRealtime() + ".json"
208-
)
209-
configFile.parentFile?.mkdirs()
210-
configFile.writeText(config)
211-
cacheFiles.add(configFile)
212-
213-
val commands = mutableListOf(
214-
initPlugin("hysteria2-plugin").path,
215-
"--disable-update-check",
216-
"--config",
217-
configFile.absolutePath,
218-
"--log-level",
219-
if (DataStore.logLevel > 0) "debug" else "warn",
220-
"client"
221-
)
222-
223-
processes.start(commands)
224-
}
225-
226191
bean is HysteriaBean -> {
227192
val configFile = File(
228193
cacheDir, "hysteria_" + SystemClock.elapsedRealtime() + ".json"

app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaFmt.kt

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,8 @@ fun getFirstPort(portStr: String): Int {
294294

295295
fun HysteriaBean.canUseSingBox(): Boolean {
296296
if (protocol != HysteriaBean.PROTOCOL_UDP) return false
297-
// The starifly sing-box core only supports Salamander obfs. Gecko obfs is provided by
298-
// the bundled official hysteria binary sidecar, so force the external path for Gecko.
299-
if (protocolVersion == 2 && hysteria2ObfsType == HysteriaBean.OBFS_GECKO) return false
297+
// Gecko obfs is now supported natively by this fork's sing-box core (via the
298+
// hawkff/sing-quic gecko backport), so it no longer needs the sidecar.
300299
return true
301300
}
302301

@@ -354,11 +353,25 @@ fun buildSingBoxOutboundHysteriaBean(bean: HysteriaBean): SingBoxOptions.SingBox
354353
up_mbps = bean.uploadMbps
355354
down_mbps = bean.downloadMbps
356355
if (bean.obfuscation.isNotBlank() && bean.hysteria2ObfsType != HysteriaBean.OBFS_NONE) {
357-
// Native sing-box core only supports Salamander; Gecko goes through the
358-
// bundled hysteria binary sidecar (canUseSingBox() == false for Gecko).
359356
obfs = SingBoxOptions.Hysteria2Obfs().apply {
360-
type = "salamander"
361-
password = bean.obfuscation
357+
when (bean.hysteria2ObfsType) {
358+
HysteriaBean.OBFS_GECKO -> {
359+
type = "gecko"
360+
password = bean.obfuscation
361+
// Clamp + order to match the sidecar builder's bounds
362+
// (1..2048, min <= max); an inverted/out-of-range pair
363+
// would otherwise be rejected by the core at connect time.
364+
val min = bean.geckoMinPacketSize?.takeIf { it > 0 }?.coerceIn(1, 2048)
365+
val max = bean.geckoMaxPacketSize?.takeIf { it > 0 }?.coerceIn(min ?: 1, 2048)
366+
if (min != null) min_packet_size = min
367+
if (max != null) max_packet_size = max
368+
}
369+
370+
else -> {
371+
type = "salamander"
372+
password = bean.obfuscation
373+
}
374+
}
362375
}
363376
}
364377
// disable_mtu_discovery = bean.disableMtuDiscovery
@@ -398,8 +411,11 @@ fun hopPortsToSingboxList(s: String): List<String> {
398411
}
399412

400413
/**
401-
* Builds a config for the bundled official apernet/hysteria client binary (sidecar),
402-
* used for Hysteria2 profiles with Gecko obfs (which the native sing-box core can't do).
414+
* Builds a config for the bundled official apernet/hysteria client binary (sidecar).
415+
*
416+
* NOTE: Hysteria2 (including Gecko obfs) now runs natively in the sing-box core, so this
417+
* sidecar path is no longer used for Gecko. This builder is retained for the bundled
418+
* hysteria2 binary infrastructure and potential fallback use.
403419
*
404420
* Emitted as JSON (hysteria uses viper, which detects format by the .json extension).
405421
* The client's upstream QUIC sockets are protected from the VPN via

app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,11 @@ public static class Hysteria2Obfs extends SingBoxOption {
656656

657657
public String password;
658658

659+
// Gecko obfs only (flattened into the obfs object by the core's badjson serializer).
660+
public Integer min_packet_size;
661+
662+
public Integer max_packet_size;
663+
659664
}
660665

661666
public static class Hysteria2User extends SingBoxOption {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export COMMIT_SING_BOX="ed45ea96fa27d27bd346539403303c8384b7d210"
1+
export COMMIT_SING_BOX="d003dccf165816f51b669ded6d2d4e66df5c3b3b"
22
export COMMIT_LIBNEKO="1c47a3af71990a7b2192e03292b4d246c308ef0b"

libcore/go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
module libcore
22

3-
go 1.24.7
4-
5-
toolchain go1.26.4
3+
go 1.26.4
64

75
require (
86
github.com/dyhkwong/sing-juicity v0.0.3
@@ -107,3 +105,5 @@ replace github.com/sagernet/sing-vmess => github.com/starifly/sing-vmess v0.2.8-
107105
// replace github.com/sagernet/sing-dns => ../../sing-dns
108106

109107
// replace berty.tech/go-libtor => github.com/berty/go-libtor v0.0.0-20220627102132-9189eb6e3982
108+
109+
replace github.com/sagernet/sing-quic => github.com/hawkff/sing-quic v0.5.3-0.20260618035506-f2f558c5aaba

libcore/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23
9595
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
9696
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
9797
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
98+
github.com/hawkff/sing-quic v0.5.3-0.20260618035506-f2f558c5aaba h1:r2nlyrd4MBY8OPS4wEOw8SojAdnyzzl81OEKD4CES60=
99+
github.com/hawkff/sing-quic v0.5.3-0.20260618035506-f2f558c5aaba/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
98100
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
99101
github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE=
100102
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
@@ -200,8 +202,6 @@ github.com/sagernet/sing v0.7.18 h1:iZHkaru1/MoHugx3G+9S3WG4owMewKO/KvieE2Pzk4E=
200202
github.com/sagernet/sing v0.7.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
201203
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
202204
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
203-
github.com/sagernet/sing-quic v0.5.2 h1:I3vlfRImhr0uLwRS3b3ib70RMG9FcXtOKKUDz3eKRWc=
204-
github.com/sagernet/sing-quic v0.5.2/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
205205
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
206206
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
207207
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=

0 commit comments

Comments
 (0)