mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-18 20:05:03 +03:00
fix(xray): apply shadowsocks client changes in place instead of rebuilding
The per-user hot path only covered vless, vmess, trojan, hysteria and masque, so every client change on a multi-user shadowsocks inbound - a node push for an auto-disable, renew, reset or add, a local Edit Inbound save, or a config-diff hot apply - fell through to DelInbound + AddInbound, closing the listener and dropping every live connection on the port. Shadowsocks joins userDiffableProtocols. The emitted user ops also carry the inbound's method as "cipher": 2022 clients have no method of their own, and without it AddUser cannot build the account, which would leave 2022 inbounds rebuilding anyway. Changes outside settings.clients (method, 2022 server key, network) still differ in the rest of the settings and still rebuild. Verified against the pinned core: TestXrayAPI_E2E_Users and the shadowsocks e2e tests pass with XRAY_E2E_BINARY set. Closes #6820
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/json_util"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
"github.com/xtls/xray-core/proxy/shadowsocks"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -242,7 +243,7 @@ func TestComputeHotDiff_ClientChangeFallsBackToReplace(t *testing.T) {
|
||||
{
|
||||
name: "unsupported protocol",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.InboundConfigs[1].Protocol = "shadowsocks"
|
||||
cfg.InboundConfigs[1].Protocol = "http"
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -559,3 +560,43 @@ func TestComputeHotDiff_HysteriaClientOnlyChangeKeepsListener(t *testing.T) {
|
||||
t.Fatalf("expected a single AddUser op for b, got %+v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
// 2022 clients carry no method of their own, so a user op built from the client
|
||||
// object alone has no cipher and its account cannot be built for the core.
|
||||
func TestInboundUserOps_ShadowsocksAddedUserBuildsTheInboundAccount(t *testing.T) {
|
||||
cases := []struct {
|
||||
name, settingsHead, clientMethod string
|
||||
wantLegacy bool
|
||||
}{
|
||||
{"legacy aead", `"method":"aes-256-gcm"`, `"method":"aes-256-gcm",`, true},
|
||||
{"2022", `"method":"2022-blake3-aes-128-gcm","password":"AAECAwQFBgcICQoLDA0ODw=="`, "", false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
inbound := func(clients ...string) *InboundConfig {
|
||||
return &InboundConfig{
|
||||
Port: 8388, Protocol: "shadowsocks", Tag: "inbound-8388",
|
||||
Listen: json_util.RawMessage(`"0.0.0.0"`),
|
||||
Settings: json_util.RawMessage(`{` + tc.settingsHead + `,"network":"tcp,udp","clients":[` + strings.Join(clients, ",") + `]}`),
|
||||
}
|
||||
}
|
||||
clientA := `{` + tc.clientMethod + `"email":"a","password":"EBESExQVFhcYGRobHB0eHw=="}`
|
||||
clientB := `{` + tc.clientMethod + `"email":"b","password":"ICEiIyQlJicoKSorLC0uLw=="}`
|
||||
|
||||
diff, ok := InboundUserOps(inbound(clientA), inbound(clientA, clientB))
|
||||
if !ok {
|
||||
t.Fatal("a client-only shadowsocks change must apply through per-user ops")
|
||||
}
|
||||
if len(diff.RemovedUsers) != 0 || len(diff.AddedUsers) != 1 || diff.AddedUsers[0].Email != "b" {
|
||||
t.Fatalf("expected only user b added, got added %+v removed %+v", diff.AddedUsers, diff.RemovedUsers)
|
||||
}
|
||||
_, legacy, modern := decodeSSAccount(t, diff.AddedUsers[0].User)
|
||||
if tc.wantLegacy && (legacy == nil || legacy.CipherType != shadowsocks.CipherType_AES_256_GCM) {
|
||||
t.Fatalf("want a legacy aes-256-gcm account, got legacy %v modern %v", legacy, modern)
|
||||
}
|
||||
if !tc.wantLegacy && (modern == nil || modern.Key != "ICEiIyQlJicoKSorLC0uLw==") {
|
||||
t.Fatalf("want a 2022 account keyed by b's password, got legacy %v modern %v", legacy, modern)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user