mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
fix(node): bound the host groups a node's inbounds may adopt
A node-reported host group is stored once per address and per adopted inbound, so the rows and bytes a merge wrote were the product of three values the node chooses, unbounded by the response size cap: a small hosts/list answer could fill the master's disk. A group whose serialized form exceeds 16 KiB is no longer adopted, and a node's inbounds hold at most 2000 host rows, counted over what they already hold, so repeated merges do not add more; the merge logs how many groups it left out.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -182,19 +183,51 @@ func buildHostRows(groupId string, req *entity.HostGroup) []*model.Host {
|
||||
return rows
|
||||
}
|
||||
|
||||
// A node's group is stored once per address and per adopted inbound, so the
|
||||
// response cap does not bound the write: the group's size and a per-node row budget do.
|
||||
const (
|
||||
maxAdoptedHostGroupBytes = 16 << 10
|
||||
maxAdoptedHostRows = 2000
|
||||
)
|
||||
|
||||
// adoptableHostGroups drops the node-reported groups too large to adopt.
|
||||
func adoptableHostGroups(groups []*entity.HostGroup) []*entity.HostGroup {
|
||||
return slices.DeleteFunc(slices.Clone(groups), func(g *entity.HostGroup) bool {
|
||||
if g == nil {
|
||||
return true
|
||||
}
|
||||
wire, err := json.Marshal(g)
|
||||
return err != nil || len(wire) > maxAdoptedHostGroupBytes
|
||||
})
|
||||
}
|
||||
|
||||
// adoptedHostRowBudget is how many more host rows a node's inbounds may adopt.
|
||||
func adoptedHostRowBudget(db *gorm.DB, nodeID int) (int, error) {
|
||||
var held int64
|
||||
err := db.Model(&model.Host{}).
|
||||
Where("inbound_id IN (?)", db.Model(&model.Inbound{}).Select("id").Where("node_id = ?", nodeID)).
|
||||
Count(&held).Error
|
||||
return max(maxAdoptedHostRows-int(held), 0), err
|
||||
}
|
||||
|
||||
// adoptedHostRows projects a node's host groups onto a freshly adopted central
|
||||
// inbound so TLS/SNI/fingerprint overrides survive the node-to-master import.
|
||||
func adoptedHostRows(groups []*entity.HostGroup, nodeInboundId, centralInboundId int) []*model.Host {
|
||||
// inbound, building at most budget rows; it also returns how many groups that left out.
|
||||
func adoptedHostRows(groups []*entity.HostGroup, nodeInboundId, centralInboundId, budget int) ([]*model.Host, int) {
|
||||
var rows []*model.Host
|
||||
skipped := 0
|
||||
for _, g := range groups {
|
||||
if g == nil || !slices.Contains(g.InboundIds, nodeInboundId) {
|
||||
continue
|
||||
}
|
||||
if len(rows)+max(len(g.Hosts), 1) > budget {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
scoped := *g
|
||||
scoped.InboundIds = []int{centralInboundId}
|
||||
rows = append(rows, buildHostRows(g.GroupId, &scoped)...)
|
||||
}
|
||||
return rows
|
||||
return rows, skipped
|
||||
}
|
||||
|
||||
func validateInboundsExist(tx *gorm.DB, inboundIds []int) error {
|
||||
|
||||
Reference in New Issue
Block a user