fix(mtproto): zero sidecar quotas only for clients whose usage was reset

Invariant: the sidecar's quota counter for a client is zeroed exactly when
the panel zeroes that client's usage. InboundService.ResetAllTraffics
resets only inbound counters, yet it cleared every MTProto client's
sidecar quota - on the master and, through its node propagation, on every
node - handing out a fresh quota while the panel still counted the old
usage. ResetAllClientTraffics for one inbound likewise cleared the quotas
of MTProto clients on every other inbound.

The inbound-level reset no longer touches sidecar quotas, and the
per-inbound client reset zeroes only the clients it reset.
This commit is contained in:
MHSanaei
2026-09-28 13:02:50 +02:00
parent 4210a50cb4
commit fb7418f7bd
3 changed files with 45 additions and 6 deletions
+8 -5
View File
@@ -126,22 +126,24 @@ func (s *ClientService) BulkResetTraffic(inboundSvc *InboundService, emails []st
func (s *ClientService) ResetAllClientTraffics(inboundSvc *InboundService, id int) error {
var resetNodes []int
var resetEmails []string
err := submitTrafficWrite(func() error {
var inner error
resetNodes, inner = s.resetAllClientTrafficsLocked(id)
resetEmails, resetNodes, inner = s.resetAllClientTrafficsLocked(id)
return inner
})
if err == nil {
inboundSvc.resetAllMtprotoQuotas()
inboundSvc.resetMtprotoClientQuotas(resetEmails)
inboundSvc.deliverNodeResetsNow(resetNodes)
}
return err
}
func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]int, error) {
func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]string, []int, error) {
db := database.GetDB()
now := time.Now().Unix() * 1000
var resetNodes []int
var reset []string
if err := db.Transaction(func(tx *gorm.DB) error {
// client_traffics.inbound_id is stale: it reflects the inbound the row was
@@ -164,6 +166,7 @@ func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]int, error) {
if len(resetEmails) == 0 {
return nil
}
reset = resetEmails
if err := adjustGroupBaselinesForRemovedTraffic(tx, resetEmails); err != nil {
return err
@@ -204,9 +207,9 @@ func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]int, error) {
return result.Error
}); err != nil {
return nil, err
return nil, nil, err
}
return resetNodes, nil
return reset, resetNodes, nil
}
func (s *ClientService) ResetAllTraffics() (bool, error) {
-1
View File
@@ -783,7 +783,6 @@ func (s *InboundService) ResetAllTraffics() error {
})
if err == nil {
s.propagateResetAllTrafficsToNodes()
s.resetAllMtprotoQuotas()
}
return err
}
@@ -78,6 +78,15 @@ func TestPanelResetsZeroSidecarQuota(t *testing.T) {
t.Fatalf("sidecar quota resets %v, want [mtga]", got)
}
})
t.Run("inbound clients", func(t *testing.T) {
ib, logPath := startQuotaSidecar(t, 46207, model.Client{Enable: true})
if err := (&ClientService{}).ResetAllClientTraffics(&InboundService{}, ib.Id); err != nil {
t.Fatalf("ResetAllClientTraffics: %v", err)
}
if got := quotaResets(t, logPath); !slices.Equal(got, []string{"mtga", "mtgb"}) {
t.Fatalf("sidecar quota resets %v, want [mtga mtgb]", got)
}
})
t.Run("reset all", func(t *testing.T) {
_, logPath := startQuotaSidecar(t, 46202, model.Client{Enable: true})
if _, err := (&ClientService{}).ResetAllTraffics(); err != nil {
@@ -98,3 +107,31 @@ func TestPanelResetsZeroSidecarQuota(t *testing.T) {
}
})
}
// Resetting inbound counters leaves every client's usage in place, so the
// sidecar's quota counters must stay too or clients get their quota again free.
func TestInboundResetAllKeepsSidecarQuota(t *testing.T) {
_, logPath := startQuotaSidecar(t, 46204, model.Client{Enable: true})
if err := (&InboundService{}).ResetAllTraffics(); err != nil {
t.Fatalf("ResetAllTraffics: %v", err)
}
if got := quotaResets(t, logPath); len(got) != 0 {
t.Fatalf("inbound reset zeroed sidecar quotas %v, want none", got)
}
}
// Resetting one inbound's clients zeroes only their sidecar quotas, not those
// of MTProto clients whose usage the reset left in place.
func TestInboundClientResetKeepsOtherSidecarQuotas(t *testing.T) {
_, logPath := startQuotaSidecar(t, 46205, model.Client{Enable: true})
other := mkInbound(t, 46206, model.VLESS, clientsSettings(t, []model.Client{{Email: "vless-only", ID: "11111111-1111-1111-1111-1111111111ab", Enable: true}}))
if err := (&ClientService{}).SyncInbound(nil, other.Id, []model.Client{{Email: "vless-only", ID: "11111111-1111-1111-1111-1111111111ab", Enable: true}}); err != nil {
t.Fatalf("SyncInbound: %v", err)
}
if err := (&ClientService{}).ResetAllClientTraffics(&InboundService{}, other.Id); err != nil {
t.Fatalf("ResetAllClientTraffics: %v", err)
}
if got := quotaResets(t, logPath); len(got) != 0 {
t.Fatalf("resetting another inbound zeroed sidecar quotas %v, want none", got)
}
}