Files
3x-ui/frontend/src/test/amneziawg-schema-cleared.test.ts
T
MHSanaei 425a2bbf08 fix(amneziawg): bound what a handshake allocates and the tunnel MTU
Every handshake initiation builds Jc junk packets of Jmin..Jmax bytes
and the I1-I5 signature packets, and the limits allowed the full uint32
range and lengths of any size, so one connection made the embedded
device allocate without bound. The MTU reaches the device as an int32
and a wider value made it panic. Both ended the whole panel at the next
connection, after every restart.

Jc is now at most 128, Jmin/Jmax and each signature packet at most one
65535-byte datagram, and the MTU at most 65535. Saves refuse more, the
device setup refuses rows stored before, and the stored MTU is capped
where it is read. The form schemas carry the same limits.
2026-10-31 14:50:18 +01:00

88 lines
3.0 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { AmneziawgServerSchema } from '@/schemas/protocols/inbound/amneziawg';
import { AmneziaWGOutboundSettingsSchema } from '@/schemas/protocols/outbound/amneziawg';
// AntD InputNumber emits null when cleared; a cleared numeric field must
// refill its schema default instead of failing validation and blocking the save.
describe('AmneziawgServerSchema cleared numeric fields', () => {
it('accepts null for every InputNumber-backed field and refills the default', () => {
const parsed = AmneziawgServerSchema.parse({
subnetCidr: null,
jc: null,
jmin: null,
jmax: null,
s1: null,
s2: null,
s3: null,
s4: null,
});
expect(parsed.subnetCidr).toBe(24);
expect(parsed.jc).toBe(5);
expect(parsed.jmin).toBe(10);
expect(parsed.jmax).toBe(50);
expect(parsed.s1).toBe(30);
expect(parsed.s2).toBe(45);
expect(parsed.s3).toBe(10);
expect(parsed.s4).toBe(5);
});
it('keeps absent-key defaults unchanged', () => {
const parsed = AmneziawgServerSchema.parse({});
expect(parsed.subnetCidr).toBe(24);
expect(parsed.jc).toBe(5);
});
});
// The form must reject what cannot apply or be received: jc/jmin/jmax past uint32 (device/uapi.go),
// S1-S3 past amneziawg-go's 1700-byte iOS receive buffer, S4 past 32 (MTU headroom).
describe('AmneziawgServerSchema obfuscation bounds', () => {
const overWidth: Array<[string, number]> = [
['s1', 1553],
['s2', 1609],
['s3', 1637],
['s4', 33],
['jc', 129],
['jmin', 65536],
['jmax', 65536],
['mtu', 65536],
];
it.each(overWidth)('rejects %s past what amneziawg-go can apply or receive', (field, value) => {
expect(AmneziawgServerSchema.safeParse({ [field]: value }).success).toBe(false);
});
const atLimit: Array<[string, number]> = [
['s1', 1552],
['s2', 1608],
['s3', 1636],
['s4', 32],
['jc', 128],
['jmax', 65535],
['mtu', 65535],
];
it.each(atLimit)('accepts %s exactly at its limit', (field, value) => {
const parsed = AmneziawgServerSchema.safeParse({ [field]: value });
expect(parsed.success).toBe(true);
});
it('still rejects negatives on every junk and padding field', () => {
for (const field of ['jc', 'jmin', 'jmax', 's1', 's2', 's3', 's4']) {
expect(AmneziawgServerSchema.safeParse({ [field]: -1 }).success).toBe(false);
}
});
});
// An outbound's S values come from the remote server and are received on Linux,
// so only amneziawg-go's uint16 UAPI width bounds them, not the iOS buffer.
describe('AmneziaWGOutboundSettingsSchema padding bounds', () => {
it.each(['s1', 's2', 's3'])('accepts %s past the inbound iOS cap', (field) => {
expect(AmneziaWGOutboundSettingsSchema.safeParse({ [field]: 2000 }).success).toBe(true);
});
it.each(['s1', 's2', 's3'])('rejects %s past uint16', (field) => {
expect(AmneziaWGOutboundSettingsSchema.safeParse({ [field]: 65536 }).success).toBe(false);
});
});