mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-22 02:08:31 +02:00
Use new exit node functions
This commit is contained in:
parent
69a9bcb3da
commit
5c94887949
6 changed files with 87 additions and 53 deletions
|
@ -2,7 +2,7 @@ import { db, exitNodes } from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { eq, and, or } from "drizzle-orm";
|
import { eq, and, or } from "drizzle-orm";
|
||||||
|
|
||||||
export async function privateVerifyExitNodeOrgAccess(
|
export async function verifyExitNodeOrgAccess(
|
||||||
exitNodeId: number,
|
exitNodeId: number,
|
||||||
orgId: string
|
orgId: string
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { hashPassword } from "@server/auth/password";
|
||||||
import { isValidCIDR, isValidIP } from "@server/lib/validators";
|
import { isValidCIDR, isValidIP } from "@server/lib/validators";
|
||||||
import { isIpInCidr } from "@server/lib/ip";
|
import { isIpInCidr } from "@server/lib/ip";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import { listExitNodes } from "@server/lib/exitNodes";
|
||||||
|
|
||||||
const createClientParamsSchema = z
|
const createClientParamsSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -177,20 +178,9 @@ export async function createClient(
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
// TODO: more intelligent way to pick the exit node
|
// TODO: more intelligent way to pick the exit node
|
||||||
|
const exitNodesList = await listExitNodes(orgId);
|
||||||
// make sure there is an exit node by counting the exit nodes table
|
const randomExitNode =
|
||||||
const nodes = await db.select().from(exitNodes);
|
exitNodesList[Math.floor(Math.random() * exitNodesList.length)];
|
||||||
if (nodes.length === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
"No exit nodes available"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the first exit node
|
|
||||||
const exitNode = nodes[0];
|
|
||||||
|
|
||||||
const adminRole = await trx
|
const adminRole = await trx
|
||||||
.select()
|
.select()
|
||||||
|
@ -208,7 +198,7 @@ export async function createClient(
|
||||||
const [newClient] = await trx
|
const [newClient] = await trx
|
||||||
.insert(clients)
|
.insert(clients)
|
||||||
.values({
|
.values({
|
||||||
exitNodeId: exitNode.exitNodeId,
|
exitNodeId: randomExitNode.exitNodeId,
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
subnet: updatedSubnet,
|
subnet: updatedSubnet,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { exitNodes, Newt } from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import { ne, eq, or, and, count } from "drizzle-orm";
|
import { ne, eq, or, and, count } from "drizzle-orm";
|
||||||
|
import { listExitNodes } from "@server/lib/exitNodes";
|
||||||
|
|
||||||
export const handleNewtPingRequestMessage: MessageHandler = async (context) => {
|
export const handleNewtPingRequestMessage: MessageHandler = async (context) => {
|
||||||
const { message, client, sendToClient } = context;
|
const { message, client, sendToClient } = context;
|
||||||
|
@ -16,12 +17,19 @@ export const handleNewtPingRequestMessage: MessageHandler = async (context) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: pick which nodes to send and ping better than just all of them
|
// Get the newt's orgId through the site relationship
|
||||||
let exitNodesList = await db
|
if (!newt.siteId) {
|
||||||
.select()
|
logger.warn("Newt siteId not found");
|
||||||
.from(exitNodes);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
exitNodesList = exitNodesList.filter((node) => node.maxConnections !== 0);
|
const [site] = await db
|
||||||
|
.select({ orgId: sites.orgId })
|
||||||
|
.from(sites)
|
||||||
|
.where(eq(sites.siteId, newt.siteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
const exitNodesList = await listExitNodes(site.orgId, true); // filter for only the online ones
|
||||||
|
|
||||||
let lastExitNodeId = null;
|
let lastExitNodeId = null;
|
||||||
if (newt.siteId) {
|
if (newt.siteId) {
|
||||||
|
@ -54,9 +62,9 @@ export const handleNewtPingRequestMessage: MessageHandler = async (context) => {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (currentConnections.count >= maxConnections) {
|
if (currentConnections.count >= maxConnections) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
weight =
|
weight =
|
||||||
(maxConnections - currentConnections.count) /
|
(maxConnections - currentConnections.count) /
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
findNextAvailableCidr,
|
findNextAvailableCidr,
|
||||||
getNextAvailableClientSubnet
|
getNextAvailableClientSubnet
|
||||||
} from "@server/lib/ip";
|
} from "@server/lib/ip";
|
||||||
|
import { verifyExitNodeOrgAccess } from "@server/lib/exitNodes";
|
||||||
|
|
||||||
export type ExitNodePingResult = {
|
export type ExitNodePingResult = {
|
||||||
exitNodeId: number;
|
exitNodeId: number;
|
||||||
|
@ -24,7 +25,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => {
|
||||||
const { message, client, sendToClient } = context;
|
const { message, client, sendToClient } = context;
|
||||||
const newt = client as Newt;
|
const newt = client as Newt;
|
||||||
|
|
||||||
logger.info("Handling register newt message!");
|
logger.debug("Handling register newt message!");
|
||||||
|
|
||||||
if (!newt) {
|
if (!newt) {
|
||||||
logger.warn("Newt not found");
|
logger.warn("Newt not found");
|
||||||
|
@ -81,6 +82,18 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => {
|
||||||
// This effectively moves the exit node to the new one
|
// This effectively moves the exit node to the new one
|
||||||
exitNodeIdToQuery = exitNodeId; // Use the provided exitNodeId if it differs from the site's exitNodeId
|
exitNodeIdToQuery = exitNodeId; // Use the provided exitNodeId if it differs from the site's exitNodeId
|
||||||
|
|
||||||
|
const { exitNode, hasAccess } = await verifyExitNodeOrgAccess(exitNodeIdToQuery, oldSite.orgId);
|
||||||
|
|
||||||
|
if (!exitNode) {
|
||||||
|
logger.warn("Exit node not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAccess) {
|
||||||
|
logger.warn("Not authorized to use this exit node");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const sitesQuery = await db
|
const sitesQuery = await db
|
||||||
.select({
|
.select({
|
||||||
subnet: sites.subnet
|
subnet: sites.subnet
|
||||||
|
@ -88,14 +101,10 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => {
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(eq(sites.exitNodeId, exitNodeId));
|
.where(eq(sites.exitNodeId, exitNodeId));
|
||||||
|
|
||||||
const [exitNode] = await db
|
|
||||||
.select()
|
|
||||||
.from(exitNodes)
|
|
||||||
.where(eq(exitNodes.exitNodeId, exitNodeIdToQuery))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
const blockSize = config.getRawConfig().gerbil.site_block_size;
|
const blockSize = config.getRawConfig().gerbil.site_block_size;
|
||||||
const subnets = sitesQuery.map((site) => site.subnet).filter((subnet) => subnet !== null);
|
const subnets = sitesQuery
|
||||||
|
.map((site) => site.subnet)
|
||||||
|
.filter((subnet) => subnet !== null);
|
||||||
subnets.push(exitNode.address.replace(/\/\d+$/, `/${blockSize}`));
|
subnets.push(exitNode.address.replace(/\/\d+$/, `/${blockSize}`));
|
||||||
const newSubnet = findNextAvailableCidr(
|
const newSubnet = findNextAvailableCidr(
|
||||||
subnets,
|
subnets,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { clients, db } from "@server/db";
|
import { clients, db, exitNodes } from "@server/db";
|
||||||
import { roles, userSites, sites, roleSites, Site, orgs } from "@server/db";
|
import { roles, userSites, sites, roleSites, Site, orgs } from "@server/db";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
@ -17,6 +17,7 @@ import { hashPassword } from "@server/auth/password";
|
||||||
import { isValidIP } from "@server/lib/validators";
|
import { isValidIP } from "@server/lib/validators";
|
||||||
import { isIpInCidr } from "@server/lib/ip";
|
import { isIpInCidr } from "@server/lib/ip";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
|
import { verifyExitNodeOrgAccess } from "@server/lib/exitNodes";
|
||||||
|
|
||||||
const createSiteParamsSchema = z
|
const createSiteParamsSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -217,6 +218,32 @@ export async function createSite(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { exitNode, hasAccess } =
|
||||||
|
await verifyExitNodeOrgAccess(
|
||||||
|
exitNodeId,
|
||||||
|
orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!exitNode) {
|
||||||
|
logger.warn("Exit node not found");
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
"Exit node not found"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAccess) {
|
||||||
|
logger.warn("Not authorized to use this exit node");
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.FORBIDDEN,
|
||||||
|
"Not authorized to use this exit node"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
[newSite] = await trx
|
[newSite] = await trx
|
||||||
.insert(sites)
|
.insert(sites)
|
||||||
.values({
|
.values({
|
||||||
|
|
|
@ -6,12 +6,16 @@ import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { findNextAvailableCidr, getNextAvailableClientSubnet } from "@server/lib/ip";
|
import {
|
||||||
|
findNextAvailableCidr,
|
||||||
|
getNextAvailableClientSubnet
|
||||||
|
} from "@server/lib/ip";
|
||||||
import { generateId } from "@server/auth/sessions/app";
|
import { generateId } from "@server/auth/sessions/app";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { listExitNodes } from "@server/lib/exitNodes";
|
||||||
|
|
||||||
export type PickSiteDefaultsResponse = {
|
export type PickSiteDefaultsResponse = {
|
||||||
exitNodeId: number;
|
exitNodeId: number;
|
||||||
|
@ -65,16 +69,10 @@ export async function pickSiteDefaults(
|
||||||
const { orgId } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
// TODO: more intelligent way to pick the exit node
|
// TODO: more intelligent way to pick the exit node
|
||||||
|
|
||||||
// make sure there is an exit node by counting the exit nodes table
|
const exitNodesList = await listExitNodes(orgId);
|
||||||
const nodes = await db.select().from(exitNodes);
|
|
||||||
if (nodes.length === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.NOT_FOUND, "No exit nodes available")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the first exit node
|
const randomExitNode =
|
||||||
const exitNode = nodes[0];
|
exitNodesList[Math.floor(Math.random() * exitNodesList.length)];
|
||||||
|
|
||||||
// TODO: this probably can be optimized...
|
// TODO: this probably can be optimized...
|
||||||
// list all of the sites on that exit node
|
// list all of the sites on that exit node
|
||||||
|
@ -83,13 +81,15 @@ export async function pickSiteDefaults(
|
||||||
subnet: sites.subnet
|
subnet: sites.subnet
|
||||||
})
|
})
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(eq(sites.exitNodeId, exitNode.exitNodeId));
|
.where(eq(sites.exitNodeId, randomExitNode.exitNodeId));
|
||||||
|
|
||||||
// TODO: we need to lock this subnet for some time so someone else does not take it
|
// TODO: we need to lock this subnet for some time so someone else does not take it
|
||||||
const subnets = sitesQuery.map((site) => site.subnet).filter((subnet) => subnet !== null);
|
const subnets = sitesQuery
|
||||||
|
.map((site) => site.subnet)
|
||||||
|
.filter((subnet) => subnet !== null);
|
||||||
// exclude the exit node address by replacing after the / with a site block size
|
// exclude the exit node address by replacing after the / with a site block size
|
||||||
subnets.push(
|
subnets.push(
|
||||||
exitNode.address.replace(
|
randomExitNode.address.replace(
|
||||||
/\/\d+$/,
|
/\/\d+$/,
|
||||||
`/${config.getRawConfig().gerbil.site_block_size}`
|
`/${config.getRawConfig().gerbil.site_block_size}`
|
||||||
)
|
)
|
||||||
|
@ -97,7 +97,7 @@ export async function pickSiteDefaults(
|
||||||
const newSubnet = findNextAvailableCidr(
|
const newSubnet = findNextAvailableCidr(
|
||||||
subnets,
|
subnets,
|
||||||
config.getRawConfig().gerbil.site_block_size,
|
config.getRawConfig().gerbil.site_block_size,
|
||||||
exitNode.address
|
randomExitNode.address
|
||||||
);
|
);
|
||||||
if (!newSubnet) {
|
if (!newSubnet) {
|
||||||
return next(
|
return next(
|
||||||
|
@ -125,12 +125,12 @@ export async function pickSiteDefaults(
|
||||||
|
|
||||||
return response<PickSiteDefaultsResponse>(res, {
|
return response<PickSiteDefaultsResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
exitNodeId: exitNode.exitNodeId,
|
exitNodeId: randomExitNode.exitNodeId,
|
||||||
address: exitNode.address,
|
address: randomExitNode.address,
|
||||||
publicKey: exitNode.publicKey,
|
publicKey: randomExitNode.publicKey,
|
||||||
name: exitNode.name,
|
name: randomExitNode.name,
|
||||||
listenPort: exitNode.listenPort,
|
listenPort: randomExitNode.listenPort,
|
||||||
endpoint: exitNode.endpoint,
|
endpoint: randomExitNode.endpoint,
|
||||||
// subnet: `${newSubnet.split("/")[0]}/${config.getRawConfig().gerbil.block_size}`, // we want the block size of the whole subnet
|
// subnet: `${newSubnet.split("/")[0]}/${config.getRawConfig().gerbil.block_size}`, // we want the block size of the whole subnet
|
||||||
subnet: newSubnet,
|
subnet: newSubnet,
|
||||||
clientAddress: clientAddress,
|
clientAddress: clientAddress,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue