Start changes for multi site clients

- Org subnet and assign sites and clients out of the same subnet group
  on each org
- Add join table for client on multiple sites
- Start to handle websocket endpoints for these multiple connections
This commit is contained in:
Owen
2025-03-25 22:01:08 -04:00
parent fbe7e0a427
commit 87012c47ea
8 changed files with 210 additions and 196 deletions

View File

@@ -3,13 +3,12 @@ import { MessageHandler } from "../ws";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import db from "@server/db";
import { clients, Newt, Site, sites } from "@server/db/schema";
import { eq, isNotNull } from "drizzle-orm";
import { findNextAvailableCidr } from "@server/lib/ip";
import config from "@server/lib/config";
import { clients, clientSites, Newt, Site, sites } from "@server/db/schema";
import { eq } from "drizzle-orm";
import { getNextAvailableClientSubnet } from "@server/lib/ip";
const inputSchema = z.object({
publicKey: z.string(),
publicKey: z.string()
});
type Input = z.infer<typeof inputSchema>;
@@ -57,16 +56,15 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
let site: Site | undefined;
if (!siteRes.address) {
const address = await getNextAvailableSubnet();
const listenPort = await getNextAvailablePort();
let address = await getNextAvailableClientSubnet(siteRes.orgId);
address = address.split("/")[0]; // get the first part of the CIDR
// create a new exit node
const [updateRes] = await db
.update(sites)
.set({
publicKey,
address,
listenPort
address
})
.where(eq(sites.siteId, siteId))
.returning();
@@ -95,28 +93,33 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
const clientsRes = await db
.select()
.from(clients)
.where(eq(clients.siteId, siteId));
.innerJoin(clientSites, eq(clients.clientId, clientSites.clientId))
.where(eq(clientSites.siteId, siteId));
const now = new Date().getTime() / 1000;
const peers = await Promise.all(
clientsRes
.filter((client) => {
if (client.lastHolePunch && now - client.lastHolePunch > 6) {
// This filter wasn't returning anything - fixed to properly filter clients
if (
!client.clients.lastHolePunch ||
now - client.clients.lastHolePunch > 6
) {
logger.warn("Client last hole punch is too old");
return;
return false;
}
return true;
})
.map(async (client) => {
return {
publicKey: client.pubKey,
allowedIps: [client.subnet],
endpoint: client.endpoint
publicKey: client.clients.pubKey,
allowedIps: [client.clients.subnet],
endpoint: client.clients.endpoint
};
})
);
const configResponse = {
listenPort: site.listenPort,
ipAddress: site.address,
peers
};
@@ -133,57 +136,4 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
broadcast: false, // Send to all clients
excludeSender: false // Include sender in broadcast
};
};
async function getNextAvailableSubnet(): Promise<string> {
const existingAddresses = await db
.select({
address: sites.address
})
.from(sites)
.where(isNotNull(sites.address));
const addresses = existingAddresses
.map((a) => a.address)
.filter((a) => a) as string[];
let subnet = findNextAvailableCidr(
addresses,
config.getRawConfig().newt.block_size,
config.getRawConfig().newt.subnet_group
);
if (!subnet) {
throw new Error("No available subnets remaining in space");
}
// replace the last octet with 1
subnet =
subnet.split(".").slice(0, 3).join(".") +
".1" +
"/" +
subnet.split("/")[1];
return subnet;
}
async function getNextAvailablePort(): Promise<number> {
// Get all existing ports from exitNodes table
const existingPorts = await db
.select({
listenPort: sites.listenPort
})
.from(sites);
// Find the first available port between 1024 and 65535
let nextPort = config.getRawConfig().newt.start_port;
for (const port of existingPorts) {
if (port.listenPort && port.listenPort > nextPort) {
break;
}
nextPort++;
if (nextPort > 65535) {
throw new Error("No available ports remaining in space");
}
}
return nextPort;
}
};