mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-30 23:17:14 +00:00
fix(node-sync): don't delete a node's central inbounds when its snapshot is empty
The central-inbound sweep deletes any central inbound whose tag is absent from the node's snapshot, with no guard for an empty snapshot. A node mid-restart or with a transient DB error (e.g. Postgres 57P01) can return an empty inbound list with success=true, which wiped all of that node's central inbounds and their clients (and reset traffic history on re-create) — observed on the Germany node: 0 clients but still 44 online (online survives because it comes from the snapshot's online tree, not the central inbound). Skip the sweep entirely when the snapshot reports zero inbounds; a real per-inbound deletion still sweeps via a non-empty snapshot that omits one tag.
This commit is contained in:
@@ -499,6 +499,15 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
|||||||
if dirty {
|
if dirty {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if len(snapTags) == 0 {
|
||||||
|
// A node mid-restart or with a transient DB error can return an empty
|
||||||
|
// inbound list with success=true. Treat "zero inbounds reported" as
|
||||||
|
// "nothing to say", not "delete all my inbounds" — otherwise a blip
|
||||||
|
// wipes the node's central inbounds and every client on them (and
|
||||||
|
// resets traffic history on re-create). A real per-inbound deletion
|
||||||
|
// still sweeps, because the node keeps reporting its other inbounds.
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, kept := snapTags[c.Tag]; kept {
|
if _, kept := snapTags[c.Tag]; kept {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,43 @@ func TestSetRemoteTraffic_RemapsClonedNodeOwnGuidOrigin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A node mid-restart can return an empty inbound list with success=true. The
|
||||||
|
// sync must NOT treat that as "delete all my inbounds" — otherwise a blip wipes
|
||||||
|
// the node's central inbounds and every client on them (what happened to the
|
||||||
|
// Germany node: 0 clients but still online).
|
||||||
|
func TestSetRemoteTraffic_EmptySnapshotKeepsCentralInbounds(t *testing.T) {
|
||||||
|
setupConflictDB(t)
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
const nodeID = 1
|
||||||
|
if err := db.Create(&model.Node{
|
||||||
|
Id: nodeID, Name: "n", Address: "10.0.0.1", Port: 2053, ApiToken: "t", Guid: "g",
|
||||||
|
}).Error; err != nil {
|
||||||
|
t.Fatalf("create node: %v", err)
|
||||||
|
}
|
||||||
|
nidPtr := nodeID
|
||||||
|
if err := db.Create(&model.Inbound{
|
||||||
|
UserId: 1, NodeID: &nidPtr, Tag: "remote-in", Enable: true,
|
||||||
|
Port: 443, Protocol: model.VLESS, Settings: `{"clients":[]}`,
|
||||||
|
}).Error; err != nil {
|
||||||
|
t.Fatalf("create central inbound: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty snapshot — the node reported no inbounds this cycle.
|
||||||
|
svc := InboundService{}
|
||||||
|
if _, err := svc.setRemoteTrafficLocked(nodeID, &runtime.TrafficSnapshot{}, false); err != nil {
|
||||||
|
t.Fatalf("setRemoteTrafficLocked: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
if err := db.Model(&model.Inbound{}).Where("tag = ?", "remote-in").Count(&count).Error; err != nil {
|
||||||
|
t.Fatalf("count inbounds: %v", err)
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
t.Fatalf("empty snapshot must not delete the central inbound; got count = %d", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSetRemoteTraffic_PreservesLocalShareAddressStrategy(t *testing.T) {
|
func TestSetRemoteTraffic_PreservesLocalShareAddressStrategy(t *testing.T) {
|
||||||
setupConflictDB(t)
|
setupConflictDB(t)
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|||||||
Reference in New Issue
Block a user