mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-04 03:44:22 +00:00
fix(job): gate ip-limit scan on clients.limit_ip instead of parsing all settings
hasLimitIp ran settings LIKE '%limitIp%' and JSON-parsed every matching inbound's settings blob — and since clients marshal limitIp without omitempty, every inbound matched, so each 10s scan loaded and parsed every settings blob in the database (~75MB of JSON at 500k clients) just to decide whether any limit exists. It now probes the normalized clients table (limit_ip > 0, Limit(1) count like depletedCond does), which SyncInbound and the legacy seeder keep in sync with the settings JSON. Semantics note: a limitIp that exists only in settings JSON with no clients row no longer enables enforcement — the enforcement path itself already resolves clients through the same normalized tables.
This commit is contained in:
@@ -113,33 +113,15 @@ func (j *CheckClientIpJob) collectFromOnlineAPI() (map[string]map[string]int64,
|
||||
return observed, true
|
||||
}
|
||||
|
||||
// hasLimitIp reports whether any client carries an IP limit. It probes the
|
||||
// normalized clients table (limit_ip is synced there by SyncInbound and the
|
||||
// legacy seeder), replacing the old `settings LIKE '%limitIp%'` scan that
|
||||
// loaded and JSON-parsed every inbound's settings blob on each 10s run.
|
||||
func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
|
||||
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%limitIp%").Find(&inbounds).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, inbound := range inbounds {
|
||||
if inbound.Settings == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
settings := map[string][]model.Client{}
|
||||
_ = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
clients := settings["clients"]
|
||||
|
||||
for _, client := range clients {
|
||||
limitIp := client.LimitIP
|
||||
if limitIp > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
var probe int64
|
||||
err := db.Model(&model.ClientRecord{}).Where("limit_ip > 0").Limit(1).Count(&probe).Error
|
||||
return err == nil && probe > 0
|
||||
}
|
||||
|
||||
// processObserved runs collection + enforcement for one scan's observations
|
||||
|
||||
@@ -388,3 +388,28 @@ func TestGetInboundByEmailRejectsSubstringFallbackMatch(t *testing.T) {
|
||||
t.Fatalf("substring email matched inbound %d; want no exact match", got.Id)
|
||||
}
|
||||
}
|
||||
|
||||
// hasLimitIp gates every 10s scan on the normalized clients table: a bare
|
||||
// "limitIp":0 in settings JSON (which the old LIKE scan matched and parsed)
|
||||
// must not enable enforcement, while a single clients.limit_ip > 0 row must.
|
||||
func TestHasLimitIp_ProbesClientRecords(t *testing.T) {
|
||||
setupIntegrationDB(t)
|
||||
j := &CheckClientIpJob{}
|
||||
|
||||
if j.hasLimitIp() {
|
||||
t.Fatal("hasLimitIp = true on an empty database")
|
||||
}
|
||||
|
||||
seedLinkedInboundWithClient(t, "no-limit", "nolimit@example.com", 0)
|
||||
if j.hasLimitIp() {
|
||||
t.Fatal("hasLimitIp = true with only limit_ip=0 clients")
|
||||
}
|
||||
|
||||
limited := &model.ClientRecord{Email: "limited@example.com", LimitIP: 2}
|
||||
if err := database.GetDB().Create(limited).Error; err != nil {
|
||||
t.Fatalf("seed limited client: %v", err)
|
||||
}
|
||||
if !j.hasLimitIp() {
|
||||
t.Fatal("hasLimitIp = false with a limit_ip=2 client present")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user