The running core must only see clients the panel has enabled, and
UpdateInbound and SetInboundEnable already build their payload through
buildInboundForLocalRuntime, which strips disabled ones. AddInbound used
it only for MTProto and TUIC and hot-added the stored row for every
other protocol, so an imported or pushed inbound re-armed its disabled
clients until the next restart.
AddInbound now builds the runtime payload the same way for every
protocol.
UpdateInbound applies a master's push to the node's running core as the
difference between the stored and the pushed user sets, but both sets
dropped clients whose traffic row is off, and the same transaction had
already stored the pushed flags on those rows. A client the push
disabled therefore vanished from both sets, no RemoveUser was sent and
the node's Xray kept serving it.
The running set is now taken from the stored row's own enable flags,
so the client the push switches off is removed from every handler,
including a second inbound sharing the same traffic row.
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
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