mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-14 01:45:00 +03:00
fix(node): apply a pushed inbound's client changes in place instead of rebuilding it
A master pushes the whole inbound to its node whenever a client on it is auto-disabled, renewed or reset, and the node-sync reconcile re-sends every inbound whose payload changed (all of them after a master restart). The node's UpdateInbound answered every push with DelInbound + AddInbound, so Xray closed the listener and every open connection on that port dropped. On a node inbound with ~2,700 clients that was dozens of times a day. UpdateInbound now diffs the config Xray is running against the new one. When only settings.clients differ (vless, vmess, trojan, hysteria, masque) it applies per-user RemoveUser/AddUser through the runtime, and when nothing the core sees changed it does nothing. Any other change, or any per-user failure (a handler the core lost included), still rebuilds as before. AmneziaWG joins MTProto and TUIC on rt.UpdateInbound, whose in-place Ensure keeps the device and its peers; Del+Add tore it down. A pushed removal does not consult the node's own restartXrayOnClientDisable: it defaults to on, and the master already restarts its nodes from its own setting (restartRemoteNodesOnDisable). Nodes on an older release keep rebuilding until they update; the master side needs no change. Verified: the new service tests, TestXrayAPI_E2E_Users against a real core (pins the missing-user vs missing-handler error texts) and make node-e2e. make race not run: no goroutine, lock or shared state changed. Closes #6795
This commit is contained in:
@@ -145,7 +145,7 @@ func panelUser(email string, fields map[string]any) map[string]any {
|
||||
|
||||
// TestXrayAPI_E2E_Users runs the panel's add/remove user surface against a live
|
||||
// core for every protocol it builds accounts for, pinning the error texts
|
||||
// IsUserExistsErr and IsMissingHandlerErr match on.
|
||||
// IsUserExistsErr, IsMissingHandlerErr and IsMissingUserErr match on.
|
||||
func TestXrayAPI_E2E_Users(t *testing.T) {
|
||||
c := startE2ECore(t, []any{
|
||||
map[string]any{
|
||||
@@ -248,6 +248,9 @@ func TestXrayAPI_E2E_Users(t *testing.T) {
|
||||
if !IsMissingHandlerErr(err) {
|
||||
t.Fatalf("missing-user error not matched by IsMissingHandlerErr: %q", err)
|
||||
}
|
||||
if !IsMissingUserErr(err) {
|
||||
t.Fatalf("missing-user error not matched by IsMissingUserErr: %q", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -259,6 +262,13 @@ func TestXrayAPI_E2E_Users(t *testing.T) {
|
||||
if !IsMissingHandlerErr(err) {
|
||||
t.Fatalf("unknown-tag error not matched by IsMissingHandlerErr: %q", err)
|
||||
}
|
||||
err = c.api.RemoveUser("no-such-tag", "e2e-ghost")
|
||||
if err == nil {
|
||||
t.Fatal("RemoveUser on an unknown tag must fail")
|
||||
}
|
||||
if IsMissingUserErr(err) {
|
||||
t.Fatalf("a missing handler must not read as a missing user: %q", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user