mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-27 01:32:08 +03:00
fix(xray): hot-apply Hysteria client changes without replacing the inbound (#6606)
* fix(xray): hot-apply Hysteria client changes without replacing the inbound diffInboundUsers only allowed per-user AlterInbound ops for vless, vmess and trojan. For a hysteria inbound every client add/remove/update became DelInbound + AddInbound: the UDP listener was recreated and all QUIC sessions of that inbound were lost. quic-go sends no stateless reset, so every connected client stalled until its idle timeout (30s by default) after each unrelated client mutation. XrayAPI.AddUser already builds a hysteria account and Xray-core's hysteria server implements AddUser/RemoveUser, so adding the protocol to userDiffableProtocols is sufficient. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(xray): say which branch each drop-guard protocol takes With hysteria in userDiffableProtocols its dropped client reaches the guard through the per-user diff, so the test named for protocols the diff cannot handle no longer described its hysteria case. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
@@ -221,7 +221,7 @@ func droppedClients(oldIb, newIb *InboundConfig) []UserOp {
|
||||
return dropped
|
||||
}
|
||||
|
||||
var userDiffableProtocols = map[string]struct{}{"vless": {}, "vmess": {}, "trojan": {}}
|
||||
var userDiffableProtocols = map[string]struct{}{"vless": {}, "vmess": {}, "trojan": {}, "hysteria": {}}
|
||||
|
||||
// diffInboundUsers emits per-user AlterInbound ops when two same-tag inbounds
|
||||
// differ only in settings.clients, so the handler (and its listener) survives.
|
||||
|
||||
@@ -16,9 +16,9 @@ func hotConfigWithClients(clients string) *Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// diffInboundUsers refuses shadowsocks and hysteria, so their dropped clients
|
||||
// reach the guard through the inbound instead of through a per-user op.
|
||||
func TestHotDiffDropsUsersOnProtocolsItCannotDiff(t *testing.T) {
|
||||
// Shadowsocks reaches the drop guard through the inbound's settings.clients compare,
|
||||
// hysteria through its per-user diff; either way a dropped client must be reported.
|
||||
func TestHotDiffDropsUsersOnShadowsocksAndHysteria(t *testing.T) {
|
||||
for _, protocol := range []string{"shadowsocks", "hysteria"} {
|
||||
t.Run(protocol, func(t *testing.T) {
|
||||
withClients := func(clients string) *Config {
|
||||
|
||||
@@ -511,3 +511,28 @@ func TestComputeHotDiff_NoauthSocksBridgeStaysHot(t *testing.T) {
|
||||
t.Fatalf("expected a plain remove+add for the changed bridge, got %+v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
// Replacing a hysteria handler closes the UDP listener its QUIC sessions share,
|
||||
// and clients get no reset: they stall until their own idle timeout expires.
|
||||
func TestComputeHotDiff_HysteriaClientOnlyChangeKeepsListener(t *testing.T) {
|
||||
stream := json_util.RawMessage(`{"network":"hysteria","security":"tls","hysteriaSettings":{"version":2}}`)
|
||||
oldCfg := makeHotConfig()
|
||||
oldCfg.InboundConfigs[1].Protocol = "hysteria"
|
||||
oldCfg.InboundConfigs[1].StreamSettings = stream
|
||||
oldCfg.InboundConfigs[1].Settings = json_util.RawMessage(`{"version":2,"clients":[{"email":"a","auth":"auth-a"}]}`)
|
||||
newCfg := makeHotConfig()
|
||||
newCfg.InboundConfigs[1].Protocol = "hysteria"
|
||||
newCfg.InboundConfigs[1].StreamSettings = stream
|
||||
newCfg.InboundConfigs[1].Settings = json_util.RawMessage(`{"version":2,"clients":[{"email":"a","auth":"auth-a"},{"email":"b","auth":"auth-b"}]}`)
|
||||
|
||||
diff, ok := ComputeHotDiff(oldCfg, newCfg)
|
||||
if !ok {
|
||||
t.Fatal("client-only change must be hot-appliable")
|
||||
}
|
||||
if len(diff.RemovedInboundTags) != 0 || len(diff.AddedInbounds) != 0 {
|
||||
t.Fatalf("hysteria client-only change must not replace the handler, got removed=%v added=%d", diff.RemovedInboundTags, len(diff.AddedInbounds))
|
||||
}
|
||||
if len(diff.RemovedUsers) != 0 || len(diff.AddedUsers) != 1 || diff.AddedUsers[0].Email != "b" || diff.AddedUsers[0].Protocol != "hysteria" {
|
||||
t.Fatalf("expected a single AddUser op for b, got %+v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user