perf: avoid repeated client scans during CIDR changes (#2707)
* perf: avoid repeated client scans during CIDR changes (#2706) * refactor: retain CIDR change guards * perf: avoid repeated client reads during migration --------- Co-authored-by: potatosips <potatosips@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import Database from '#server/utils/Database';
|
import Database from '#server/utils/Database';
|
||||||
import { defineSetupEventHandler } from '#server/utils/handler';
|
import { defineSetupEventHandler } from '#server/utils/handler';
|
||||||
import { nextIP } from '#server/utils/ip';
|
import { nextIPFromUsedAddresses } from '#server/utils/ip';
|
||||||
import { FileSchema, validateZod } from '#server/utils/types';
|
import { FileSchema, validateZod } from '#server/utils/types';
|
||||||
|
|
||||||
export default defineSetupEventHandler('migrate', async ({ event }) => {
|
export default defineSetupEventHandler('migrate', async ({ event }) => {
|
||||||
@@ -58,6 +58,9 @@ export default defineSetupEventHandler('migrate', async ({ event }) => {
|
|||||||
`/${ipv4Cidr.prefix}`,
|
`/${ipv4Cidr.prefix}`,
|
||||||
ipv6Cidr: ipv6Cidr.cidr,
|
ipv6Cidr: ipv6Cidr.cidr,
|
||||||
});
|
});
|
||||||
|
const ipv6Addresses = new Set(
|
||||||
|
(await Database.clients.getAll()).map((client) => client.ipv6Address)
|
||||||
|
);
|
||||||
|
|
||||||
for (const clientId in oldConfig.clients) {
|
for (const clientId in oldConfig.clients) {
|
||||||
const clientConfig = oldConfig.clients[clientId];
|
const clientConfig = oldConfig.clients[clientId];
|
||||||
@@ -66,15 +69,14 @@ export default defineSetupEventHandler('migrate', async ({ event }) => {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clients = await Database.clients.getAll();
|
const ipv6Address = nextIPFromUsedAddresses(6, ipv6Cidr, ipv6Addresses);
|
||||||
|
|
||||||
const ipv6Address = nextIP(6, ipv6Cidr, clients);
|
|
||||||
|
|
||||||
await Database.clients.createFromExisting({
|
await Database.clients.createFromExisting({
|
||||||
...clientConfig,
|
...clientConfig,
|
||||||
ipv4Address: clientConfig.address,
|
ipv4Address: clientConfig.address,
|
||||||
ipv6Address,
|
ipv6Address,
|
||||||
});
|
});
|
||||||
|
ipv6Addresses.add(ipv6Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Database.general.setSetupStep(0);
|
await Database.general.setSetupStep(0);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { parseCidr } from 'cidr-tools';
|
|||||||
import { wgInterface } from './schema';
|
import { wgInterface } from './schema';
|
||||||
import type { InterfaceCidrUpdateType, InterfaceUpdateType } from './types';
|
import type { InterfaceCidrUpdateType, InterfaceUpdateType } from './types';
|
||||||
|
|
||||||
import { nextIP } from '#server/utils/ip';
|
import { nextIPFromUsedAddresses } from '#server/utils/ip';
|
||||||
import { client as clientSchema } from '#db/schema';
|
import { client as clientSchema } from '#db/schema';
|
||||||
import type { DBType } from '#db/sqlite';
|
import type { DBType } from '#db/sqlite';
|
||||||
|
|
||||||
@@ -93,21 +93,36 @@ export class InterfaceService {
|
|||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const clients = await tx.query.client.findMany().execute();
|
const clients = await tx.query.client.findMany().execute();
|
||||||
|
const ipv4Addresses = new Set(
|
||||||
|
clients.map((client) => client.ipv4Address)
|
||||||
|
);
|
||||||
|
const ipv6Addresses = new Set(
|
||||||
|
clients.map((client) => client.ipv6Address)
|
||||||
|
);
|
||||||
|
|
||||||
for (const client of clients) {
|
for (const client of clients) {
|
||||||
// TODO: optimize
|
|
||||||
const clients = await tx.query.client.findMany().execute();
|
|
||||||
|
|
||||||
// only calculate ip if cidr has changed
|
// only calculate ip if cidr has changed
|
||||||
|
|
||||||
let nextIpv4 = client.ipv4Address;
|
let nextIpv4 = client.ipv4Address;
|
||||||
if (data.ipv4Cidr !== oldCidr.ipv4Cidr) {
|
if (data.ipv4Cidr !== oldCidr.ipv4Cidr) {
|
||||||
nextIpv4 = nextIP(4, parseCidr(data.ipv4Cidr), clients);
|
nextIpv4 = nextIPFromUsedAddresses(
|
||||||
|
4,
|
||||||
|
parseCidr(data.ipv4Cidr),
|
||||||
|
ipv4Addresses
|
||||||
|
);
|
||||||
|
ipv4Addresses.add(nextIpv4);
|
||||||
|
ipv4Addresses.delete(client.ipv4Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextIpv6 = client.ipv6Address;
|
let nextIpv6 = client.ipv6Address;
|
||||||
if (data.ipv6Cidr !== oldCidr.ipv6Cidr) {
|
if (data.ipv6Cidr !== oldCidr.ipv6Cidr) {
|
||||||
nextIpv6 = nextIP(6, parseCidr(data.ipv6Cidr), clients);
|
nextIpv6 = nextIPFromUsedAddresses(
|
||||||
|
6,
|
||||||
|
parseCidr(data.ipv6Cidr),
|
||||||
|
ipv6Addresses
|
||||||
|
);
|
||||||
|
ipv6Addresses.add(nextIpv6);
|
||||||
|
ipv6Addresses.delete(client.ipv6Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
await tx
|
await tx
|
||||||
|
|||||||
+13
-4
@@ -13,15 +13,24 @@ export function nextIP(
|
|||||||
version: 4 | 6,
|
version: 4 | 6,
|
||||||
cidr: ParsedCidr,
|
cidr: ParsedCidr,
|
||||||
clients: ClientNextIpType[]
|
clients: ClientNextIpType[]
|
||||||
|
) {
|
||||||
|
const usedAddresses = new Set(
|
||||||
|
clients.map((client) => client[`ipv${version}Address`])
|
||||||
|
);
|
||||||
|
|
||||||
|
return nextIPFromUsedAddresses(version, cidr, usedAddresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nextIPFromUsedAddresses(
|
||||||
|
version: 4 | 6,
|
||||||
|
cidr: ParsedCidr,
|
||||||
|
usedAddresses: Set<string>
|
||||||
) {
|
) {
|
||||||
let address;
|
let address;
|
||||||
for (let i = cidr.start + 2n; i <= cidr.end - 1n; i++) {
|
for (let i = cidr.start + 2n; i <= cidr.end - 1n; i++) {
|
||||||
const currentIp = stringifyIp({ number: i, version: version });
|
const currentIp = stringifyIp({ number: i, version: version });
|
||||||
const client = clients.find((client) => {
|
|
||||||
return client[`ipv${version}Address`] === currentIp;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!client) {
|
if (!usedAddresses.has(currentIp)) {
|
||||||
address = currentIp;
|
address = currentIp;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { parseCidr } from 'cidr-tools';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import { nextIPFromUsedAddresses } from '#server/utils/ip';
|
||||||
|
|
||||||
|
describe('nextIPFromUsedAddresses', () => {
|
||||||
|
test('returns the first available address without scanning client records', () => {
|
||||||
|
const usedAddresses = new Set(['10.0.0.2', '10.0.0.3']);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
nextIPFromUsedAddresses(4, parseCidr('10.0.0.0/29'), usedAddresses)
|
||||||
|
).toBe('10.0.0.4');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws when every usable address is allocated', () => {
|
||||||
|
const usedAddresses = new Set([
|
||||||
|
'10.0.0.2',
|
||||||
|
'10.0.0.3',
|
||||||
|
'10.0.0.4',
|
||||||
|
'10.0.0.5',
|
||||||
|
'10.0.0.6',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
nextIPFromUsedAddresses(4, parseCidr('10.0.0.0/29'), usedAddresses)
|
||||||
|
).toThrow('Maximum number of clients reached');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports replacing addresses while retaining the current allocation state', () => {
|
||||||
|
const cidr = parseCidr('10.0.0.0/29');
|
||||||
|
const usedAddresses = new Set(['10.0.0.2', '10.0.0.3']);
|
||||||
|
|
||||||
|
const firstReplacement = nextIPFromUsedAddresses(4, cidr, usedAddresses);
|
||||||
|
usedAddresses.add(firstReplacement);
|
||||||
|
usedAddresses.delete('10.0.0.2');
|
||||||
|
|
||||||
|
expect(firstReplacement).toBe('10.0.0.4');
|
||||||
|
expect(nextIPFromUsedAddresses(4, cidr, usedAddresses)).toBe('10.0.0.2');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user