mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 19:25:05 +03:00
fix(sub): restore TUIC links in subscriptions after v3.11.0 (#6963)
The subscription settings cache strips each inbound's clients array, but the TUIC link generator still reads credentials from it, so raw subscriptions (v2rayN, Shadowrocket) lost every TUIC link while the browser page kept it. TUIC now reads its full settings, as WireGuard and AmneziaWG already do.
This commit is contained in:
@@ -863,15 +863,15 @@ func (s *SubService) loadInboundsBySubId(subId string) ([]*model.Inbound, error)
|
||||
return inbounds, nil
|
||||
}
|
||||
|
||||
// subscriptionSettings is an inbound's settings for a subscription body: WireGuard and AmneziaWG
|
||||
// keep their peers, every other protocol gets the shared copy without the clients array.
|
||||
// WireGuard/AmneziaWG need their peers, and TUIC link generation reads embedded clients.
|
||||
// Other protocols can share cached settings without the clients array.
|
||||
func (s *SubService) subscriptionSettings(inbound *model.Inbound) (string, error) {
|
||||
load := func() (string, error) {
|
||||
var settings string
|
||||
err := database.GetDB().Model(&model.Inbound{}).Select("settings").Where("id = ?", inbound.Id).Row().Scan(&settings)
|
||||
return settings, err
|
||||
}
|
||||
if inbound.Protocol == model.WireGuard || inbound.Protocol == model.AmneziaWG {
|
||||
if inbound.Protocol == model.WireGuard || inbound.Protocol == model.AmneziaWG || inbound.Protocol == model.TUIC {
|
||||
return load()
|
||||
}
|
||||
return s.settingsCache.settingsFor(inbound.Id, load)
|
||||
|
||||
@@ -166,6 +166,49 @@ func TestSettingsCacheRendersTheSameSubscription(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingsCachePreservesTuicSubscriptions(t *testing.T) {
|
||||
seedSubDB(t)
|
||||
ib := seedSubProtocolInbound(t, "tuic-sub", "tuic", 4704, 1, `{}`, model.TUIC)
|
||||
settings := `{"server":{"sni":"tuic.example.com"},"clients":[{"uuid":"11111111-2222-4333-8444-000000004704","password":"tuic-password","email":"tuic@e","subId":"tuic-sub","enable":true}]}`
|
||||
if err := database.GetDB().Model(ib).Update("settings", settings).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := database.GetDB().Model(&model.ClientRecord{}).Where("email = ?", "tuic@e").Update("password", "tuic-password").Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
render := func(svc *SubService) []string {
|
||||
t.Helper()
|
||||
req := svc.ForRequest("req.example.com")
|
||||
req.subscriptionBody = true
|
||||
links, _, _, _, err := req.getSubs("tuic-sub")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(links) != 1 || !strings.HasPrefix(links[0], "tuic://") {
|
||||
t.Fatalf("TUIC link missing from subscription: %v", links)
|
||||
}
|
||||
clash, _, err := NewSubClashService(false, "", false, svc).GetClash("tuic-sub", "req.example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(clash, "tuic-password") {
|
||||
t.Fatalf("TUIC credentials missing from Clash subscription: %s", clash)
|
||||
}
|
||||
return []string{links[0], clash}
|
||||
}
|
||||
want := render(NewSubService(""))
|
||||
cached := NewSubService("")
|
||||
t.Cleanup(cached.enableSettingsCache())
|
||||
for round := range 2 {
|
||||
got := render(cached)
|
||||
for i, format := range []string{"raw", "clash"} {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("round %d %s differs with settings cache:\n got: %s\nwant: %s", round, format, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only the settings blob is cached: the inbound row is read on every fetch, so a port change or a
|
||||
// disabled inbound reaches the next fetch, while settings wait for a panel edit's purge.
|
||||
func TestSettingsCacheReadsTheInboundRowFresh(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user