fix(db): clamp traffic counters below int64 max and repair overflowed rows

A counter pushed past int64 (multi-node setups hit this via historic
delta-compounding bugs) makes SQLite silently promote the INTEGER cell
to REAL. From then on the column no longer scans into the Go int64
field and every reader of client_traffics fails at once: the inbounds
page, xray restarts, and node traffic sync all return "converting
driver.Value type float64 to int64".

Two-part fix: every unbounded "up = up + ?" add (local traffic, node
delta merge, inbound counters, plus the Go-side outbound accumulation)
now saturates at TrafficMax, a cap safely below math.MaxInt64 so one
more delta cannot overflow; and a startup repair casts REAL-promoted
cells back to INTEGER and clamps all traffic counters into
[0, TrafficMax] across client_traffics, inbounds, outbound_traffics
and node_client_traffics, restoring access to already-corrupted panels
without manual sqlite surgery.

Closes #5762
This commit is contained in:
MHSanaei
2026-07-05 20:33:09 +02:00
parent b1fa76f9b6
commit 837cf5f24e
6 changed files with 185 additions and 7 deletions
+13 -3
View File
@@ -42,6 +42,16 @@ func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []
return nil, false
}
// saturatingAdd caps counters at database.TrafficMax: unlike the SQL paths,
// this read-modify-write add happens in Go, where an int64 overflow silently
// wraps negative instead of erroring (#5762).
func saturatingAdd(a, b int64) int64 {
if b > database.TrafficMax-a {
return database.TrafficMax
}
return a + b
}
func (s *OutboundService) addOutboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
if len(traffics) == 0 {
return nil
@@ -61,9 +71,9 @@ func (s *OutboundService) addOutboundTraffic(tx *gorm.DB, traffics []*xray.Traff
}
outbound.Tag = traffic.Tag
outbound.Up = outbound.Up + traffic.Up
outbound.Down = outbound.Down + traffic.Down
outbound.Total = outbound.Up + outbound.Down
outbound.Up = saturatingAdd(outbound.Up, traffic.Up)
outbound.Down = saturatingAdd(outbound.Down, traffic.Down)
outbound.Total = saturatingAdd(outbound.Up, outbound.Down)
err = tx.Save(&outbound).Error
if err != nil {