mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-13 16:16:06 +00:00
feat(xhttp): support sessionID* rename + sessionIDTable/Length (xray v26.6.22) (#5506)
* feat(xhttp): support sessionID* rename + sessionIDTable/Length (xray v26.6.22) xray-core v26.6.22 (PR #6258) renamed the XHTTP session config keys sessionPlacement/sessionKey to sessionIDPlacement/sessionIDKey (no fallback kept in core) and added sessionIDTable (predefined charset name or literal ASCII) and sessionIDLength (range, e.g. 16-32, lower bound > 0). Panel changes: - Schema (xhttp.ts): rename the two keys, add sessionIDTable/sessionIDLength, and a z.preprocess that lifts legacy keys off stored configs so an upgraded panel never silently drops a saved session setting. - Wire normalize + share-link build/parse: rename keys, emit the two new fields, and accept legacy sessionPlacement/sessionKey from old share links. - Inbound + outbound XHTTP forms: rename field paths, add a sessionIDTable autocomplete (9 predefined tables + free ASCII) and a sessionIDLength range input shown only when a table is set, with light client validation (ASCII table, length min > 0; xray enforces the room-size minimum server-side). - Subscription (service.go) and Clash (clash_service.go) builders: emit the renamed + new keys, with a legacy fallback for not-yet-resaved inbounds. - Locales: add sessionIDTable/sessionIDLength labels + hints in all 13 files. Two sibling v26.6.22 XHTTP commits need no panel change and are covered by the core bump alone: #6332 (XHTTP/3 closes QUIC/UDP) and #6320 (udpHop honors the existing dialerProxy). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(xhttp): add Session ID Table to inbound form-blocks snapshot The new sessionIDTable input renders by default in the inbound XHTTP form, so its label joins the field-structure snapshot. sessionIDLength stays conditional (only shown when a table is set), so it does not appear here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(xhttp): migrate legacy session keys in the running xray config The Zod preprocess plus the subscription/Clash fallbacks only covered the panel UI and share-link output. The config handed to the running xray-core process is built from the raw stored streamSettings in GetXrayConfig, which did not rewrite the renamed XHTTP session keys — so a pre-upgrade inbound (or template outbound) stored with a non-default sessionPlacement was emitted unchanged and dropped by xray-core v26.6.22, until the admin re-saved it. Lift sessionPlacement/sessionKey onto sessionIDPlacement/sessionIDKey at config-generation time, in the existing inbound stream-rewrite block (next to the tls/reality/externalProxy handling) and across template outbounds. The lift is idempotent and leaves unchanged configs byte-identical so the hot-reload diff never sees a spurious change. Also tighten validateSessionIDLength to reject an inverted range (e.g. 32-16) in addition to the existing lower-bound > 0 check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(xray): avoid summed-capacity allocation in mergeSubscriptionOutbounds CodeQL go/allocation-size-overflow flagged the pre-sized make() whose capacity was a sum of three slice lengths. Grow the slice via append on a nil slice instead; same result, no overflow-prone capacity expression.
This commit is contained in:
@@ -64,8 +64,10 @@ function buildXhttpExtra(xhttp: XHttpStreamSettings | undefined): Record<string,
|
||||
|
||||
const stringFields = [
|
||||
'uplinkHTTPMethod',
|
||||
'sessionPlacement',
|
||||
'sessionKey',
|
||||
'sessionIDPlacement',
|
||||
'sessionIDKey',
|
||||
'sessionIDTable',
|
||||
'sessionIDLength',
|
||||
'seqPlacement',
|
||||
'seqKey',
|
||||
'uplinkDataPlacement',
|
||||
|
||||
@@ -24,10 +24,18 @@ type Raw = Record<string, unknown>;
|
||||
// match the schema's authoring order so diffs read naturally.
|
||||
const XHTTP_STRING_KEYS = [
|
||||
'xPaddingBytes', 'xPaddingKey', 'xPaddingHeader', 'xPaddingPlacement',
|
||||
'xPaddingMethod', 'sessionPlacement', 'sessionKey', 'seqPlacement',
|
||||
'seqKey', 'uplinkDataPlacement', 'uplinkDataKey', 'scMaxEachPostBytes',
|
||||
'scMinPostsIntervalMs', 'scStreamUpServerSecs', 'uplinkHTTPMethod',
|
||||
'xPaddingMethod', 'sessionIDPlacement', 'sessionIDKey', 'sessionIDTable',
|
||||
'sessionIDLength', 'seqPlacement', 'seqKey', 'uplinkDataPlacement',
|
||||
'uplinkDataKey', 'scMaxEachPostBytes', 'scMinPostsIntervalMs',
|
||||
'scStreamUpServerSecs', 'uplinkHTTPMethod',
|
||||
] as const;
|
||||
// Legacy share links (pre xray-core #6258) carry sessionPlacement/sessionKey.
|
||||
// Map them onto the renamed keys so old links still import. Mirrors the
|
||||
// schema-level migrateLegacyXhttp.
|
||||
const XHTTP_LEGACY_ALIASES: Record<string, string> = {
|
||||
sessionPlacement: 'sessionIDPlacement',
|
||||
sessionKey: 'sessionIDKey',
|
||||
};
|
||||
const XHTTP_NUMBER_KEYS = [
|
||||
'scMaxBufferedPosts', 'serverMaxHeaderBytes', 'uplinkChunkSize',
|
||||
] as const;
|
||||
@@ -81,12 +89,24 @@ function applyXhttpStringFromParams(xhttp: Raw, params: URLSearchParams): void {
|
||||
const v = params.get(k);
|
||||
if (v !== null && v !== '') xhttp[k] = asBool(v);
|
||||
}
|
||||
// Fill renamed keys from legacy params only when the new key is absent.
|
||||
for (const [legacy, renamed] of Object.entries(XHTTP_LEGACY_ALIASES)) {
|
||||
if (xhttp[renamed] === undefined) {
|
||||
const v = params.get(legacy);
|
||||
if (v !== null && v !== '') xhttp[renamed] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyXhttpStringFromJson(xhttp: Raw, json: Record<string, unknown>): void {
|
||||
for (const k of XHTTP_STRING_KEYS) {
|
||||
if (typeof json[k] === 'string') xhttp[k] = json[k];
|
||||
}
|
||||
for (const [legacy, renamed] of Object.entries(XHTTP_LEGACY_ALIASES)) {
|
||||
if (xhttp[renamed] === undefined && typeof json[legacy] === 'string') {
|
||||
xhttp[renamed] = json[legacy];
|
||||
}
|
||||
}
|
||||
for (const k of XHTTP_NUMBER_KEYS) {
|
||||
if (typeof json[k] === 'number') xhttp[k] = json[k];
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ const PACKET_UP_FIELDS = [
|
||||
const STREAM_UP_SERVER_FIELDS = ['scStreamUpServerSecs'] as const;
|
||||
|
||||
const PLACEMENT_STRING_FIELDS = [
|
||||
'sessionPlacement',
|
||||
'sessionKey',
|
||||
'sessionIDPlacement',
|
||||
'sessionIDKey',
|
||||
'sessionIDTable',
|
||||
'sessionIDLength',
|
||||
'seqPlacement',
|
||||
'seqKey',
|
||||
'uplinkDataPlacement',
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Client-side validation for xray-core #6258 sessionIDTable / sessionIDLength.
|
||||
// xray-core also enforces a room-size minimum (sum(table^k for k in
|
||||
// from..to) >= 2<<30) server-side; we deliberately skip replicating that
|
||||
// big-int check and only catch the cheap, obvious mistakes here.
|
||||
|
||||
// xray-core requires the charset table to be ASCII-only.
|
||||
export function validateSessionIDTable(_rule: unknown, value: unknown): Promise<void> {
|
||||
const str = typeof value === 'string' ? value : '';
|
||||
if (str === '') return Promise.resolve();
|
||||
// eslint-disable-next-line no-control-regex
|
||||
if (/[^\x00-\x7f]/.test(str)) {
|
||||
return Promise.reject(new Error('sessionIDTable must contain only ASCII characters'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// A dash-range like "8-16" or a single "8". The lower bound must be > 0
|
||||
// (xray rejects sessionIDLength.from <= 0 when a table is set).
|
||||
export function validateSessionIDLength(_rule: unknown, value: unknown): Promise<void> {
|
||||
const str = typeof value === 'string' ? value.trim() : '';
|
||||
if (str === '') return Promise.resolve();
|
||||
if (!/^\d+(?:-\d+)?$/.test(str)) {
|
||||
return Promise.reject(new Error('Use a length or range, e.g. 8 or 8-16'));
|
||||
}
|
||||
const parts = str.split('-');
|
||||
const from = Number(parts[0]);
|
||||
if (!Number.isFinite(from) || from <= 0) {
|
||||
return Promise.reject(new Error('sessionIDLength minimum must be greater than 0'));
|
||||
}
|
||||
if (parts.length === 2 && Number(parts[1]) < from) {
|
||||
return Promise.reject(new Error('sessionIDLength range upper bound must be ≥ lower bound'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
Reference in New Issue
Block a user