Skip to content
Merged
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
55 changes: 10 additions & 45 deletions app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import io.nekohasekai.sagernet.fmt.LOCALHOST
import io.nekohasekai.sagernet.fmt.buildConfig
import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean
import io.nekohasekai.sagernet.fmt.hysteria.buildHysteria1Config
import io.nekohasekai.sagernet.fmt.hysteria.buildHysteria2SidecarConfig
import io.nekohasekai.sagernet.fmt.masterdnsvpn.MasterDnsVpnBean
import io.nekohasekai.sagernet.fmt.masterdnsvpn.buildMasterDnsVpnConfig
import io.nekohasekai.sagernet.fmt.masterdnsvpn.resolverLines
Expand Down Expand Up @@ -82,26 +81,16 @@ abstract class BoxInstance(
}

is HysteriaBean -> {
if (bean.protocolVersion == 2 &&
bean.hysteria2ObfsType == HysteriaBean.OBFS_GECKO
) {
// Gecko obfs: use the bundled official hysteria binary sidecar.
// Loopback-only SOCKS5 (no auth), matching the other sidecars
// (Mieru/Naive); the sing-box socks outbound dials 127.0.0.1:port.
initPlugin("hysteria2-plugin")
pluginConfigs[port] = profile.type to bean.buildHysteria2SidecarConfig(
port,
File(app.noBackupFilesDir, "protect_path").absolutePath
)
} else {
initPlugin("hysteria-plugin")
pluginConfigs[port] = profile.type to bean.buildHysteria1Config(port) {
File(
app.cacheDir, "hysteria_" + SystemClock.elapsedRealtime() + ".ca"
).apply {
parentFile?.mkdirs()
cacheFiles.add(this)
}
// Only reached via the external path (needExternal == !canUseSingBox).
// Hysteria2 (incl. Gecko obfs) runs natively in sing-box now, so the
// external path is Hysteria v1 only.
initPlugin("hysteria-plugin")
pluginConfigs[port] = profile.type to bean.buildHysteria1Config(port) {
File(
app.cacheDir, "hysteria_" + SystemClock.elapsedRealtime() + ".ca"
).apply {
parentFile?.mkdirs()
cacheFiles.add(this)
}
}
}
Expand Down Expand Up @@ -199,30 +188,6 @@ abstract class BoxInstance(
processes.start(commands, envMap)
}

bean is HysteriaBean && bean.protocolVersion == 2 &&
bean.hysteria2ObfsType == HysteriaBean.OBFS_GECKO -> {
// Official apernet/hysteria client binary (Gecko obfs sidecar).
// viper detects the config format from the .json extension.
val configFile = File(
cacheDir, "hysteria2_" + SystemClock.elapsedRealtime() + ".json"
)
configFile.parentFile?.mkdirs()
configFile.writeText(config)
cacheFiles.add(configFile)

val commands = mutableListOf(
initPlugin("hysteria2-plugin").path,
"--disable-update-check",
"--config",
configFile.absolutePath,
"--log-level",
if (DataStore.logLevel > 0) "debug" else "warn",
"client"
)

processes.start(commands)
}

bean is HysteriaBean -> {
val configFile = File(
cacheDir, "hysteria_" + SystemClock.elapsedRealtime() + ".json"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,8 @@ fun getFirstPort(portStr: String): Int {

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

Expand Down Expand Up @@ -354,11 +353,25 @@ fun buildSingBoxOutboundHysteriaBean(bean: HysteriaBean): SingBoxOptions.SingBox
up_mbps = bean.uploadMbps
down_mbps = bean.downloadMbps
if (bean.obfuscation.isNotBlank() && bean.hysteria2ObfsType != HysteriaBean.OBFS_NONE) {
// Native sing-box core only supports Salamander; Gecko goes through the
// bundled hysteria binary sidecar (canUseSingBox() == false for Gecko).
obfs = SingBoxOptions.Hysteria2Obfs().apply {
type = "salamander"
password = bean.obfuscation
when (bean.hysteria2ObfsType) {
HysteriaBean.OBFS_GECKO -> {
type = "gecko"
password = bean.obfuscation
// Clamp + order to match the sidecar builder's bounds
// (1..2048, min <= max); an inverted/out-of-range pair
// would otherwise be rejected by the core at connect time.
val min = bean.geckoMinPacketSize?.takeIf { it > 0 }?.coerceIn(1, 2048)
val max = bean.geckoMaxPacketSize?.takeIf { it > 0 }?.coerceIn(min ?: 1, 2048)
if (min != null) min_packet_size = min
if (max != null) max_packet_size = max
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

else -> {
type = "salamander"
password = bean.obfuscation
}
}
}
}
// disable_mtu_discovery = bean.disableMtuDiscovery
Expand Down Expand Up @@ -398,8 +411,11 @@ fun hopPortsToSingboxList(s: String): List<String> {
}

/**
* Builds a config for the bundled official apernet/hysteria client binary (sidecar),
* used for Hysteria2 profiles with Gecko obfs (which the native sing-box core can't do).
* Builds a config for the bundled official apernet/hysteria client binary (sidecar).
*
* NOTE: Hysteria2 (including Gecko obfs) now runs natively in the sing-box core, so this
* sidecar path is no longer used for Gecko. This builder is retained for the bundled
* hysteria2 binary infrastructure and potential fallback use.
*
* Emitted as JSON (hysteria uses viper, which detects format by the .json extension).
* The client's upstream QUIC sockets are protected from the VPN via
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,11 @@ public static class Hysteria2Obfs extends SingBoxOption {

public String password;

// Gecko obfs only (flattened into the obfs object by the core's badjson serializer).
public Integer min_packet_size;

public Integer max_packet_size;

}

public static class Hysteria2User extends SingBoxOption {
Expand Down
2 changes: 1 addition & 1 deletion buildScript/lib/core/get_source_env.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export COMMIT_SING_BOX="ed45ea96fa27d27bd346539403303c8384b7d210"
export COMMIT_SING_BOX="d003dccf165816f51b669ded6d2d4e66df5c3b3b"
export COMMIT_LIBNEKO="1c47a3af71990a7b2192e03292b4d246c308ef0b"
6 changes: 3 additions & 3 deletions libcore/go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module libcore

go 1.24.7

toolchain go1.26.4
go 1.26.4

require (
github.com/dyhkwong/sing-juicity v0.0.3
Expand Down Expand Up @@ -107,3 +105,5 @@ replace github.com/sagernet/sing-vmess => github.com/starifly/sing-vmess v0.2.8-
// replace github.com/sagernet/sing-dns => ../../sing-dns

// replace berty.tech/go-libtor => github.com/berty/go-libtor v0.0.0-20220627102132-9189eb6e3982

replace github.com/sagernet/sing-quic => github.com/hawkff/sing-quic v0.5.3-0.20260618035506-f2f558c5aaba
4 changes: 2 additions & 2 deletions libcore/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/hawkff/sing-quic v0.5.3-0.20260618035506-f2f558c5aaba h1:r2nlyrd4MBY8OPS4wEOw8SojAdnyzzl81OEKD4CES60=
github.com/hawkff/sing-quic v0.5.3-0.20260618035506-f2f558c5aaba/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
Expand Down Expand Up @@ -200,8 +202,6 @@ github.com/sagernet/sing v0.7.18 h1:iZHkaru1/MoHugx3G+9S3WG4owMewKO/KvieE2Pzk4E=
github.com/sagernet/sing v0.7.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
github.com/sagernet/sing-quic v0.5.2 h1:I3vlfRImhr0uLwRS3b3ib70RMG9FcXtOKKUDz3eKRWc=
github.com/sagernet/sing-quic v0.5.2/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
Expand Down
Loading