fix(xray): refuse xhttp downloadSettings the core's dialer cannot use

ValidateOutboundConfig, the save-time gate shared by template saves,
API saves and outbound subscription refreshes, relied on conf.Build,
which accepts a downloadSettings block with no address or on another
transport. The core's XHTTP dialer dereferences that address and
asserts that transport on every dial, so an imported link or a
subscription carrying such a block panicked Xray, and every user's
connections with it, whenever traffic was routed to that outbound.

The built sender settings are now checked for both, and the outbound
is refused with a message that names the missing part.
This commit is contained in:
MHSanaei
2026-11-01 00:41:37 +01:00
parent 68e06a174b
commit c92c7718bf
3 changed files with 89 additions and 0 deletions
@@ -717,3 +717,19 @@ func TestOutboundSubscriptionRefreshCapsXMCMasks(t *testing.T) {
t.Fatalf("kept %d outbounds, tags by address = %v", len(tags), byAddress)
}
}
// The core's XHTTP dialer panics on a downloadSettings block without an address or on another transport.
func TestOutboundSubscriptionRefreshDropsXhttpDownloadSettingsTheCorePanicsOn(t *testing.T) {
setupSettingTestDB(t)
const server = "vless://00000000-0000-4000-8000-000000000000@%s:443?security=tls&type=xhttp&extra=%s#%s\n"
subID := serveOutboundSubscription(t, "p-", func(int) string {
return fmt.Sprintf(server, "1.1.1.1", url.QueryEscape(`{"downloadSettings":{}}`), "Empty") +
fmt.Sprintf(server, "8.8.8.8", url.QueryEscape(`{"downloadSettings":{"address":"8.8.4.4","port":443,"network":"tcp"}}`), "Tcp") +
fmt.Sprintf(server, "9.9.9.9", url.QueryEscape(`{"downloadSettings":{"address":"9.9.9.10","port":443,"network":"xhttp"}}`), "Complete")
})
want := map[string]string{"9.9.9.9": "p-complete"}
if _, got := refreshOutboundTags(t, subID); !maps.Equal(got, want) {
t.Fatalf("tags by address = %v, want %v", got, want)
}
}