feat(xray): keep the XHTTP server's h2Params

xray-core 26.10.31 (#7295) added xhttpSettings.h2Params, a Go net/http
HTTP2Config the XHTTP server applies to its HTTP/2 listener. The
inbound form parsed xhttpSettings through a Zod object that strips
unknown keys, so a value set through the JSON editor vanished on the
next save; it is now kept as an optional record.

h2Params tunes the server only. Share links already carry an allowlist
of XHTTP keys, but the JSON subscription copies the inbound's
xhttpSettings minus a denylist, which now drops h2Params too.
This commit is contained in:
MHSanaei
2026-11-01 14:55:57 +01:00
parent 7982794291
commit ba27b74be8
4 changed files with 31 additions and 0 deletions
@@ -108,6 +108,8 @@ export const XHttpStreamSettingsSchema = z.preprocess(
serverMaxHeaderBytes: z.number().int().min(0).default(0),
uplinkHTTPMethod: z.string().default(''),
headers: WsHeaderMapSchema.default({}),
// Server-only Go net/http HTTP2Config (xray-core 26.10.31); never put into share links.
h2Params: z.record(z.string(), z.unknown()).optional(),
// Client-side fields stored on inbound for subscription propagation.
// The server listener ignores them at runtime, but the panel embeds
// them in share-link `extra` blobs so the same xhttp config can
@@ -228,6 +228,21 @@ describe('formValuesToWirePayload', () => {
expect(payload.sniffing).toBe('');
});
// xray-core 26.10.31 (#7295): the XHTTP server reads h2Params; the form must not strip it.
it('keeps an XHTTP inbound h2Params set through JSON', () => {
const h2Params = { MaxConcurrentStreams: 512, MaxReadFrameSize: 1048576 };
const values = rawInboundToFormValues({
...vlessRow,
streamSettings: {
network: 'xhttp',
security: 'none',
xhttpSettings: { path: '/x', mode: 'auto', h2Params },
},
});
const payload = formValuesToWirePayload(values);
expect(JSON.parse(payload.streamSettings).xhttpSettings.h2Params).toEqual(h2Params);
});
it('omits nodeId when null', () => {
const values = rawInboundToFormValues({ ...vlessRow, nodeId: null });
const payload = formValuesToWirePayload(values);
+1
View File
@@ -809,6 +809,7 @@ func (s *SubJsonService) streamData(stream string, clientKey string) map[string]
delete(xhttp, "scMaxBufferedPosts")
delete(xhttp, "scStreamUpServerSecs")
delete(xhttp, "serverMaxHeaderBytes")
delete(xhttp, "h2Params")
// Values matching xray-core's own defaults stay off the wire:
// old panels seeded them into every stored config and the
// literal scMinPostsIntervalMs=30 is a DPI fingerprint (#5141).
+13
View File
@@ -155,6 +155,19 @@ func TestSubJsonServiceInjectsGlobalFinalMask(t *testing.T) {
}
}
// h2Params (xray-core 26.10.31, #7295) tunes the XHTTP server's HTTP/2; clients must not get it.
func TestSubJsonServiceDropsXhttpServerOnlyH2Params(t *testing.T) {
svc := NewSubJsonService("", "", "", "", nil)
stream := svc.streamData(`{"network":"xhttp","security":"none","xhttpSettings":{"path":"/x","mode":"auto","h2Params":{"MaxConcurrentStreams":512}}}`, "")
xhttp, _ := stream["xhttpSettings"].(map[string]any)
if xhttp == nil || xhttp["path"] != "/x" {
t.Fatalf("xhttpSettings = %#v, want the inbound's path kept", stream["xhttpSettings"])
}
if h2, ok := xhttp["h2Params"]; ok {
t.Fatalf("xhttpSettings.h2Params = %#v, want the server-only key dropped", h2)
}
}
func TestSubJsonServiceMergesWithExistingFinalMask(t *testing.T) {
finalMask := `{"tcp":[{"type":"fragment","settings":{"packets":"tlshello"}}]}`
svc := NewSubJsonService("", "", finalMask, "", nil)