mirror of https://github.com/wg-easy/wg-easy
2 changed files with 126 additions and 0 deletions
@ -0,0 +1,73 @@ |
|||
import { parseCidr, containsCidr } from 'cidr-tools'; |
|||
import { stringifyIp } from 'ip-bigint'; |
|||
import { createDebug } from 'obug'; |
|||
|
|||
import { exec } from '#server/utils/cmd'; |
|||
import type { ClientType } from '#db/repositories/client/types'; |
|||
import type { InterfaceType } from '#db/repositories/interface/types'; |
|||
|
|||
const ROUTES_DEBUG = createDebug('Routes'); |
|||
|
|||
// Mutex to prevent concurrent route reconciles
|
|||
let reconcileInProgress = false; |
|||
let reconcileQueued = false; |
|||
|
|||
type IpVersion = 4 | 6; |
|||
|
|||
type NormalizedRoute = { |
|||
cidr: string; |
|||
version: IpVersion; |
|||
}; |
|||
|
|||
type DeviceRoute = { |
|||
cidr: string; |
|||
managed: boolean; |
|||
}; |
|||
|
|||
type DesiredRoutes = { |
|||
4: Set<string>; |
|||
6: Set<string>; |
|||
}; |
|||
|
|||
type RoutesClient = Pick<ClientType, 'enabled' | 'serverAllowedIps'>; |
|||
|
|||
/** |
|||
* Canonicalize a Server Allowed IPs entry to `network/prefix` form. |
|||
* `192.168.120.5/22` -> `192.168.120.0/22`; a bare IP gains `/32` or `/128`. |
|||
* Returns null for input cidr-tools cannot parse (serverAllowedIps is validated |
|||
* only as a permissive string). |
|||
*/ |
|||
function normalizeRoute(entry: string): NormalizedRoute | null { |
|||
try { |
|||
const parsed = parseCidr(entry); |
|||
const network = stringifyIp({ |
|||
number: parsed.start, |
|||
version: parsed.version, |
|||
}); |
|||
return { cidr: `${network}/${parsed.prefix}`, version: parsed.version }; |
|||
} catch { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Whether wg-easy should add/remove this route. Excludes default routes (they |
|||
* need wg-quick's policy routing) and IPv6 link-local / multicast. |
|||
*/ |
|||
function isManageable(cidr: string, version: IpVersion): boolean { |
|||
if (cidr.endsWith('/0')) { |
|||
return false; |
|||
} |
|||
if ( |
|||
version === 6 && |
|||
(containsCidr('fe80::/10', cidr) || containsCidr('ff00::/8', cidr)) |
|||
) { |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
export const routesTestExports = { |
|||
normalizeRoute, |
|||
isManageable, |
|||
}; |
|||
@ -0,0 +1,53 @@ |
|||
import { describe, expect, test } from 'vitest'; |
|||
|
|||
import { routesTestExports } from '#server/utils/routes'; |
|||
|
|||
const { normalizeRoute, isManageable } = routesTestExports; |
|||
|
|||
describe('routes', () => { |
|||
describe('normalizeRoute', () => { |
|||
test('canonicalizes IPv4 host bits to the network address', () => { |
|||
expect(normalizeRoute('192.168.120.5/22')).toEqual({ |
|||
cidr: '192.168.120.0/22', |
|||
version: 4, |
|||
}); |
|||
}); |
|||
test('adds /32 to a bare IPv4 address', () => { |
|||
expect(normalizeRoute('10.0.0.5')).toEqual({ |
|||
cidr: '10.0.0.5/32', |
|||
version: 4, |
|||
}); |
|||
}); |
|||
test('canonicalizes IPv6 host bits to the network address', () => { |
|||
expect(normalizeRoute('2001:db8::5/64')).toEqual({ |
|||
cidr: '2001:db8::/64', |
|||
version: 6, |
|||
}); |
|||
}); |
|||
test('adds /128 to a bare IPv6 address', () => { |
|||
expect(normalizeRoute('2001:db8::1')).toEqual({ |
|||
cidr: '2001:db8::1/128', |
|||
version: 6, |
|||
}); |
|||
}); |
|||
test('returns null for unparseable input', () => { |
|||
expect(normalizeRoute('garbage')).toBeNull(); |
|||
expect(normalizeRoute('')).toBeNull(); |
|||
}); |
|||
}); |
|||
|
|||
describe('isManageable', () => { |
|||
test('rejects default routes', () => { |
|||
expect(isManageable('0.0.0.0/0', 4)).toBe(false); |
|||
expect(isManageable('::/0', 6)).toBe(false); |
|||
}); |
|||
test('rejects IPv6 link-local and multicast', () => { |
|||
expect(isManageable('fe80::/64', 6)).toBe(false); |
|||
expect(isManageable('ff00::/8', 6)).toBe(false); |
|||
}); |
|||
test('accepts ordinary subnets', () => { |
|||
expect(isManageable('192.168.120.0/22', 4)).toBe(true); |
|||
expect(isManageable('2001:db8::/64', 6)).toBe(true); |
|||
}); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue