mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-27 08:15:01 +03:00
feat(clients): bind host rules to individual clients in subscriptions (#6620)
Host rules applied per inbound only, so every client of an inbound got the same subscription endpoints (#6618). A client can now be bound to one or more host rules, and its raw, JSON and Clash subscriptions render only those hosts, in either subscription order; an unbound client is unchanged. A binding whose hosts are deleted, disabled or excluded from the format fails closed: the client gets no links there but stays in traffic accounting. Host edits keep row ids, so bindings survive them; nil leaves a stored binding alone and an empty list clears it.
This commit is contained in:
@@ -208,6 +208,9 @@ func renderedHostAddresses(t *testing.T, format string, base *SubService) []stri
|
||||
} `json:"settings"`
|
||||
} `json:"outbounds"`
|
||||
}
|
||||
if body == "" {
|
||||
break
|
||||
}
|
||||
if err := json.Unmarshal([]byte(body), &docs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -277,3 +280,32 @@ func TestSub_HostOrderExpandsCIDRHosts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A client bound to host rules must get only those hosts in the hosts-first order too, on every
|
||||
// format, and a binding whose hosts are all gone must render nothing rather than every host.
|
||||
func TestSub_HostOrderRespectsClientHostBindings(t *testing.T) {
|
||||
seedSubDB(t)
|
||||
setHostSortMode(t, "hosts-first")
|
||||
ib := seedSubInbound(t, "order-sub", "bound", 46920, 0, wsTLSStream)
|
||||
seedHost(t, &model.Host{GroupId: "a", InboundId: ib.Id, SortOrder: 0, Remark: "A", Address: "a.example.com"})
|
||||
b := seedHost(t, &model.Host{GroupId: "b", InboundId: ib.Id, SortOrder: 1, Remark: "B", Address: "b.example.com"})
|
||||
bind := func(ids ...int) {
|
||||
t.Helper()
|
||||
if err := database.GetDB().Model(&model.ClientRecord{}).Where("sub_id = ?", "order-sub").
|
||||
Update("client_host_rule_ids", model.EncodeClientHostRuleIds(ids)).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
bind(b.Id)
|
||||
for _, format := range []string{"raw", "clash", "json"} {
|
||||
if got := renderedHostAddresses(t, format, NewSubService("")); !reflect.DeepEqual(got, []string{"b.example.com"}) {
|
||||
t.Fatalf("%s addresses = %v, want only the bound host", format, got)
|
||||
}
|
||||
}
|
||||
bind(999999)
|
||||
for _, format := range []string{"raw", "clash", "json"} {
|
||||
if got := renderedHostAddresses(t, format, NewSubService("")); len(got) != 0 {
|
||||
t.Fatalf("%s addresses = %v, want none for a binding whose hosts are gone", format, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user