feat(xray): add loopback sniffing and per-segment fragment masks

- Loopback outbound: add sniffing support (xray-core #6320)

- FinalMask fragment: support per-segment lengths/delays arrays with legacy length/delay migration (xray-core #6334)

- Consolidate sniffing into a shared SniffingFields component and the canonical SniffingSchema across inbound, VLESS reverse, and loopback
This commit is contained in:
MHSanaei
2026-06-23 13:24:16 +02:00
parent 42cd351e4e
commit 852b53db79
18 changed files with 343 additions and 255 deletions
@@ -101,6 +101,29 @@ exports[`FinalMaskStreamSettingsSchema fixtures > parses salamander-gecko byte-s
}
`;
exports[`FinalMaskStreamSettingsSchema fixtures > parses tcp-fragment-segments byte-stably 1`] = `
{
"tcp": [
{
"settings": {
"delays": [
"5-10",
"0",
],
"lengths": [
"10-20",
"50-100",
],
"maxSplit": "0",
"packets": "tlshello",
},
"type": "fragment",
},
],
"udp": [],
}
`;
exports[`FinalMaskStreamSettingsSchema fixtures > parses tcp-mask byte-stably 1`] = `
{
"tcp": [
@@ -0,0 +1,13 @@
{
"tcp": [
{
"type": "fragment",
"settings": {
"packets": "tlshello",
"lengths": ["10-20", "50-100"],
"delays": ["5-10", "0"],
"maxSplit": "0"
}
}
]
}
@@ -359,6 +359,39 @@ describe('outbound-form-adapter: round-trip', () => {
expect(back.settings).toEqual({ inboundTag: 'tagged-inbound' });
});
it('loopback omits sniffing when disabled', () => {
const form = rawOutboundToFormValues({
protocol: 'loopback',
settings: { inboundTag: 'tagged-inbound' },
});
if (form.protocol === 'loopback') {
expect(form.settings.sniffing.enabled).toBe(false);
}
const back = formValuesToWirePayload(form);
expect(back.settings).not.toHaveProperty('sniffing');
});
it('loopback round-trips sniffing when enabled', () => {
const wire = {
protocol: 'loopback',
settings: {
inboundTag: 'tagged-inbound',
sniffing: { enabled: true, destOverride: ['tls', 'http'], routeOnly: true },
},
};
const form = rawOutboundToFormValues(wire);
if (form.protocol === 'loopback') {
expect(form.settings.sniffing.enabled).toBe(true);
expect(form.settings.sniffing.destOverride).toEqual(['tls', 'http']);
expect(form.settings.sniffing.routeOnly).toBe(true);
}
const back = formValuesToWirePayload(form);
const sniffing = (back.settings as Record<string, unknown>).sniffing as Record<string, unknown>;
expect(sniffing.enabled).toBe(true);
expect(sniffing.destOverride).toEqual(['tls', 'http']);
expect(sniffing.routeOnly).toBe(true);
});
it('unknown protocol falls back to vless without throwing', () => {
const form = rawOutboundToFormValues({ protocol: 'mysterious', settings: {} });
expect(form.protocol).toBe('vless');