test(#828): regression for serial-accept DoS under queued silent peers
This commit is contained in:
@@ -333,6 +333,77 @@ describe('authenticated external lease broker', () => {
|
|||||||
silent.destroy();
|
silent.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('queued silent peers cannot serialize the next valid registration', async () => {
|
||||||
|
const { socket } = await startBroker();
|
||||||
|
const silentConnections = await Promise.all(
|
||||||
|
Array.from(
|
||||||
|
{ length: 4 },
|
||||||
|
() =>
|
||||||
|
new Promise<Socket>((resolve, reject) => {
|
||||||
|
const connection = createConnection(socket);
|
||||||
|
connection.once('connect', () => resolve(connection));
|
||||||
|
connection.once('error', reject);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
const started = performance.now();
|
||||||
|
const registered = await withTimeout(
|
||||||
|
request(socket, { action: 'register_anchor', runtime_generation: 1 }),
|
||||||
|
'registration behind queued silent connections',
|
||||||
|
6_000,
|
||||||
|
);
|
||||||
|
const elapsed = performance.now() - started;
|
||||||
|
|
||||||
|
expect(registered.ok).toBe(true);
|
||||||
|
expect(elapsed).toBeLessThan(1_500);
|
||||||
|
} finally {
|
||||||
|
for (const connection of silentConnections) connection.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('silent peers are reaped at the concurrency bound and their slots are reclaimed', async () => {
|
||||||
|
const { socket } = await startBroker();
|
||||||
|
const concurrencyCap = 16;
|
||||||
|
const peers = Array.from({ length: concurrencyCap }, () => {
|
||||||
|
const connection = createConnection(socket);
|
||||||
|
return {
|
||||||
|
connection,
|
||||||
|
connected: new Promise<void>((resolve, reject) => {
|
||||||
|
connection.once('connect', resolve);
|
||||||
|
connection.once('error', reject);
|
||||||
|
}),
|
||||||
|
closed: new Promise<void>((resolve) => connection.once('close', () => resolve())),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(peers.map(({ connected }) => connected));
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
const started = performance.now();
|
||||||
|
const registration = withTimeout(
|
||||||
|
request(socket, { action: 'register_anchor', runtime_generation: 1 }),
|
||||||
|
'registration while silent peers hold the concurrency bound',
|
||||||
|
2_500,
|
||||||
|
);
|
||||||
|
const reaping = withTimeout(
|
||||||
|
Promise.all(peers.map(({ closed }) => closed)),
|
||||||
|
'silent peer deadline reaping',
|
||||||
|
2_500,
|
||||||
|
);
|
||||||
|
const [registered] = await Promise.all([registration, reaping]);
|
||||||
|
const elapsed = performance.now() - started;
|
||||||
|
|
||||||
|
expect(registered.ok).toBe(true);
|
||||||
|
expect(elapsed).toBeGreaterThan(500);
|
||||||
|
expect(elapsed).toBeLessThan(2_500);
|
||||||
|
} finally {
|
||||||
|
for (const { connection } of peers) connection.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('newline-only client without half-close gets no success and cannot block next request', async () => {
|
test('newline-only client without half-close gets no success and cannot block next request', async () => {
|
||||||
const { socket } = await startBroker();
|
const { socket } = await startBroker();
|
||||||
const incomplete = createConnection(socket);
|
const incomplete = createConnection(socket);
|
||||||
|
|||||||
Reference in New Issue
Block a user