From f2dc0e7bf99b3844e78d5e4e6bb5361dcf0ef3f8 Mon Sep 17 00:00:00 2001 From: noderaven <130021665+noderaven@users.noreply.github.com> Date: Wed, 24 Jun 2026 17:46:22 +0000 Subject: [PATCH] feat(routes): collect desired routes from enabled clients --- src/server/utils/routes.ts | 33 +++++++++++++++++++ src/test/unit/routes.spec.ts | 62 +++++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/server/utils/routes.ts b/src/server/utils/routes.ts index e906f114..f268bcb6 100644 --- a/src/server/utils/routes.ts +++ b/src/server/utils/routes.ts @@ -67,7 +67,40 @@ function isManageable(cidr: string, version: IpVersion): boolean { return true; } +/** + * Build the desired managed routes (per IP family) from enabled clients' + * Server Allowed IPs. IPv6 is dropped when IPv6 is disabled. + */ +function collectDesiredRoutes( + clients: RoutesClient[], + options: { enableIpv6: boolean } +): DesiredRoutes { + const desired: DesiredRoutes = { 4: new Set(), 6: new Set() }; + + for (const client of clients) { + if (!client.enabled) { + continue; + } + for (const entry of client.serverAllowedIps ?? []) { + const normalized = normalizeRoute(entry); + if (!normalized) { + continue; + } + if (!isManageable(normalized.cidr, normalized.version)) { + continue; + } + if (normalized.version === 6 && !options.enableIpv6) { + continue; + } + desired[normalized.version].add(normalized.cidr); + } + } + + return desired; +} + export const routesTestExports = { normalizeRoute, isManageable, + collectDesiredRoutes, }; diff --git a/src/test/unit/routes.spec.ts b/src/test/unit/routes.spec.ts index d805666b..353e5002 100644 --- a/src/test/unit/routes.spec.ts +++ b/src/test/unit/routes.spec.ts @@ -2,7 +2,8 @@ import { describe, expect, test } from 'vitest'; import { routesTestExports } from '#server/utils/routes'; -const { normalizeRoute, isManageable } = routesTestExports; +const { normalizeRoute, isManageable, collectDesiredRoutes } = + routesTestExports; describe('routes', () => { describe('normalizeRoute', () => { @@ -50,4 +51,63 @@ describe('routes', () => { expect(isManageable('2001:db8::/64', 6)).toBe(true); }); }); + + describe('collectDesiredRoutes', () => { + test('unions enabled clients and splits by family', () => { + const result = collectDesiredRoutes( + [ + { + enabled: true, + serverAllowedIps: ['192.168.120.0/22', '2001:db8::/64'], + }, + { enabled: true, serverAllowedIps: ['10.10.0.0/24'] }, + ], + { enableIpv6: true } + ); + expect([...result[4]].sort()).toEqual([ + '10.10.0.0/24', + '192.168.120.0/22', + ]); + expect([...result[6]]).toEqual(['2001:db8::/64']); + }); + test('ignores disabled clients', () => { + const result = collectDesiredRoutes( + [{ enabled: false, serverAllowedIps: ['192.168.120.0/22'] }], + { enableIpv6: true } + ); + expect(result[4].size).toBe(0); + }); + test('dedupes and normalizes host bits across clients', () => { + const result = collectDesiredRoutes( + [ + { enabled: true, serverAllowedIps: ['192.168.120.5/22'] }, + { enabled: true, serverAllowedIps: ['192.168.120.99/22'] }, + ], + { enableIpv6: true } + ); + expect([...result[4]]).toEqual(['192.168.120.0/22']); + }); + test('drops default routes', () => { + const result = collectDesiredRoutes( + [{ enabled: true, serverAllowedIps: ['0.0.0.0/0', '::/0'] }], + { enableIpv6: true } + ); + expect(result[4].size).toBe(0); + expect(result[6].size).toBe(0); + }); + test('drops IPv6 when disabled', () => { + const result = collectDesiredRoutes( + [{ enabled: true, serverAllowedIps: ['2001:db8::/64'] }], + { enableIpv6: false } + ); + expect(result[6].size).toBe(0); + }); + test('skips unparseable entries', () => { + const result = collectDesiredRoutes( + [{ enabled: true, serverAllowedIps: ['garbage', '10.0.0.0/24'] }], + { enableIpv6: true } + ); + expect([...result[4]]).toEqual(['10.0.0.0/24']); + }); + }); });