mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-15 17:45:03 +03:00
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:
@@ -126,22 +126,24 @@ func (s *ClientService) BulkResetTraffic(inboundSvc *InboundService, emails []st
|
|||||||
|
|
||||||
func (s *ClientService) ResetAllClientTraffics(inboundSvc *InboundService, id int) error {
|
func (s *ClientService) ResetAllClientTraffics(inboundSvc *InboundService, id int) error {
|
||||||
var resetNodes []int
|
var resetNodes []int
|
||||||
|
var resetEmails []string
|
||||||
err := submitTrafficWrite(func() error {
|
err := submitTrafficWrite(func() error {
|
||||||
var inner error
|
var inner error
|
||||||
resetNodes, inner = s.resetAllClientTrafficsLocked(id)
|
resetEmails, resetNodes, inner = s.resetAllClientTrafficsLocked(id)
|
||||||
return inner
|
return inner
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
inboundSvc.resetAllMtprotoQuotas()
|
inboundSvc.resetMtprotoClientQuotas(resetEmails)
|
||||||
inboundSvc.deliverNodeResetsNow(resetNodes)
|
inboundSvc.deliverNodeResetsNow(resetNodes)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]int, error) {
|
func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]string, []int, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
now := time.Now().Unix() * 1000
|
now := time.Now().Unix() * 1000
|
||||||
var resetNodes []int
|
var resetNodes []int
|
||||||
|
var reset []string
|
||||||
|
|
||||||
if err := db.Transaction(func(tx *gorm.DB) error {
|
if err := db.Transaction(func(tx *gorm.DB) error {
|
||||||
// client_traffics.inbound_id is stale: it reflects the inbound the row was
|
// 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 {
|
if len(resetEmails) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
reset = resetEmails
|
||||||
|
|
||||||
if err := adjustGroupBaselinesForRemovedTraffic(tx, resetEmails); err != nil {
|
if err := adjustGroupBaselinesForRemovedTraffic(tx, resetEmails); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -204,9 +207,9 @@ func (s *ClientService) resetAllClientTrafficsLocked(id int) ([]int, error) {
|
|||||||
|
|
||||||
return result.Error
|
return result.Error
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return resetNodes, nil
|
return reset, resetNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientService) ResetAllTraffics() (bool, error) {
|
func (s *ClientService) ResetAllTraffics() (bool, error) {
|
||||||
|
|||||||
@@ -783,7 +783,6 @@ func (s *InboundService) ResetAllTraffics() error {
|
|||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.propagateResetAllTrafficsToNodes()
|
s.propagateResetAllTrafficsToNodes()
|
||||||
s.resetAllMtprotoQuotas()
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,15 @@ func TestPanelResetsZeroSidecarQuota(t *testing.T) {
|
|||||||
t.Fatalf("sidecar quota resets %v, want [mtga]", got)
|
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) {
|
t.Run("reset all", func(t *testing.T) {
|
||||||
_, logPath := startQuotaSidecar(t, 46202, model.Client{Enable: true})
|
_, logPath := startQuotaSidecar(t, 46202, model.Client{Enable: true})
|
||||||
if _, err := (&ClientService{}).ResetAllTraffics(); err != nil {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user