mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
fix(clients): bound the auto-renew interval at a century
An interval renewal turned the client's reset days into milliseconds with no bound, so a value that wraps int64 (a node reports it unchecked) sent the catch-up loop stepping through time for seconds per client while the traffic writer held the database. Intervals above 36500 days no longer renew, and every client write refuses them.
This commit is contained in:
@@ -93,6 +93,10 @@ func localCalendarDateStart(date time.Time, loc *time.Location) (time.Time, bool
|
||||
return start, localDate(start).Equal(date)
|
||||
}
|
||||
|
||||
// maxClientResetDays bounds an interval renewal at a century: a larger one, which a node can
|
||||
// report, wraps int64 once turned into milliseconds.
|
||||
const maxClientResetDays = 36500
|
||||
|
||||
func nextClientRenewal(expiry int64, reset, day, weekday int, loc *time.Location) int64 {
|
||||
if day > 0 {
|
||||
return nextCalendarRenewal(time.UnixMilli(expiry), day, loc).UnixMilli()
|
||||
@@ -100,6 +104,9 @@ func nextClientRenewal(expiry int64, reset, day, weekday int, loc *time.Location
|
||||
if weekday > 0 {
|
||||
return nextWeeklyRenewal(time.UnixMilli(expiry), weekday, loc).UnixMilli()
|
||||
}
|
||||
if reset > maxClientResetDays {
|
||||
return expiry
|
||||
}
|
||||
return expiry + int64(reset)*86400000
|
||||
}
|
||||
|
||||
|
||||
@@ -76,3 +76,21 @@ func TestWeeklyRenewalSearchFailsClosed(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A node reports the renewal interval unchecked; one that wraps int64 when turned into
|
||||
// milliseconds made the catch-up loop step through time for seconds per client.
|
||||
func TestCatchUpClientRenewalRefusesAnOversizedInterval(t *testing.T) {
|
||||
now := time.Now().UnixMilli()
|
||||
for _, reset := range []int{106751991167, maxClientResetDays + 1} {
|
||||
traffic := &xray.ClientTraffic{Email: "c", Reset: reset, ExpiryTime: now - 86400000}
|
||||
start := time.Now()
|
||||
expiry, renewals := catchUpClientRenewal(traffic, now, time.UTC)
|
||||
if expiry != traffic.ExpiryTime || renewals != 0 || time.Since(start) > 100*time.Millisecond {
|
||||
t.Fatalf("reset %d: expiry %d, renewals %d after %v; want no renewal", reset, expiry, renewals, time.Since(start))
|
||||
}
|
||||
}
|
||||
traffic := &xray.ClientTraffic{Email: "c", Reset: maxClientResetDays, ExpiryTime: now - 86400000}
|
||||
if _, renewals := catchUpClientRenewal(traffic, now, time.UTC); renewals != 1 {
|
||||
t.Fatalf("a %d-day interval renewed %d times, want once", maxClientResetDays, renewals)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,9 @@ func validateClientRenewal(client model.Client) error {
|
||||
if err := validateClientResetDay(client.ResetDay); err != nil {
|
||||
return err
|
||||
}
|
||||
if client.Reset > maxClientResetDays {
|
||||
return common.NewError("client reset must not exceed", maxClientResetDays, "days")
|
||||
}
|
||||
if client.ResetWeekday < 0 || client.ResetWeekday > 7 {
|
||||
return common.NewError("client resetWeekday must be between 0 and 7, got:", client.ResetWeekday)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ func TestClientRenewalWriteValidation(t *testing.T) {
|
||||
{"weekly and interval", 7, 0, 3, "client weekly renewal cannot be combined with reset or resetDay"},
|
||||
{"weekly and monthly", 0, 1, 3, "client weekly renewal cannot be combined with reset or resetDay"},
|
||||
{"invalid weekday", 0, 0, 8, "client resetWeekday must be between 0 and 7, got: 8"},
|
||||
{"oversized interval", maxClientResetDays + 1, 0, 0, "client reset must not exceed 36500 days"},
|
||||
} {
|
||||
t.Run(operation+"/"+schedule.name, func(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
|
||||
Reference in New Issue
Block a user