Compare commits

...

11 Commits

Author SHA1 Message Date
Owen 21861a6dcd Merge branch 'local-connection' of github.com:fosrl/pangolin into local-connection 2026-07-16 17:25:58 -04:00
Owen 6135f2b727 Local endpoints from the get config 2026-07-16 17:25:51 -04:00
Owen 3a616cc804 Add handlers 2026-07-16 17:25:51 -04:00
Owen d5eb67a8fc Send localEndpoints on sites to olm 2026-07-16 17:25:51 -04:00
Owen 3ac7ef23ae Save the local endpoints 2026-07-16 17:25:51 -04:00
Owen 08c5ec2be7 Add new column 2026-07-16 17:25:51 -04:00
Owen dbfb8e83e8 Local endpoints from the get config 2026-07-16 14:43:45 -04:00
Owen 515afc14a5 Add handlers 2026-07-16 11:23:53 -04:00
Owen 903e8c0fa1 Send localEndpoints on sites to olm 2026-07-16 10:47:24 -04:00
Owen 7f9c760380 Save the local endpoints 2026-07-16 09:59:11 -04:00
Owen 530a1a5350 Add new column 2026-07-16 09:59:07 -04:00
12 changed files with 203 additions and 29 deletions
+1
View File
@@ -107,6 +107,7 @@ export const sites = pgTable(
lastPing: integer("lastPing"), lastPing: integer("lastPing"),
address: varchar("address"), address: varchar("address"),
endpoint: varchar("endpoint"), endpoint: varchar("endpoint"),
localEndpoints: varchar("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to
publicKey: varchar("publicKey"), publicKey: varchar("publicKey"),
lastHolePunch: bigint("lastHolePunch", { mode: "number" }), lastHolePunch: bigint("lastHolePunch", { mode: "number" }),
listenPort: integer("listenPort"), listenPort: integer("listenPort"),
+1
View File
@@ -118,6 +118,7 @@ export const sites = sqliteTable("sites", {
// exit node stuff that is how to connect to the site when it has a wg server // exit node stuff that is how to connect to the site when it has a wg server
address: text("address"), // this is the address of the wireguard interface in newt address: text("address"), // this is the address of the wireguard interface in newt
endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config
localEndpoints: text("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to
publicKey: text("publicKey"), // TODO: Fix typo in publicKey publicKey: text("publicKey"), // TODO: Fix typo in publicKey
lastHolePunch: integer("lastHolePunch"), lastHolePunch: integer("lastHolePunch"),
listenPort: integer("listenPort"), listenPort: integer("listenPort"),
@@ -29,7 +29,7 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => {
return; return;
} }
const { publicKey, port, chainId } = message.data; const { publicKey, port, localEndpoints, chainId } = message.data;
const siteId = newt.siteId; const siteId = newt.siteId;
// Get the current site data // Get the current site data
@@ -69,7 +69,10 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => {
.update(sites) .update(sites)
.set({ .set({
publicKey, publicKey,
listenPort: port listenPort: port,
localEndpoints: localEndpoints
? JSON.stringify(localEndpoints)
: null
}) })
.where(eq(sites.siteId, siteId)) .where(eq(sites.siteId, siteId))
.returning(); .returning();
+4
View File
@@ -30,6 +30,7 @@ export async function buildSiteConfigurationForOlmClient(
siteId: number; siteId: number;
name?: string; name?: string;
endpoint?: string; endpoint?: string;
localEndpoints?: string[];
publicKey?: string; publicKey?: string;
serverIP?: string | null; serverIP?: string | null;
serverPort?: number | null; serverPort?: number | null;
@@ -206,6 +207,9 @@ export async function buildSiteConfigurationForOlmClient(
name: site.name, name: site.name,
// relayEndpoint: relayEndpoint, // this can be undefined now if not relayed // lets not do this for now because it would conflict with the hole punch testing // relayEndpoint: relayEndpoint, // this can be undefined now if not relayed // lets not do this for now because it would conflict with the hole punch testing
endpoint: site.endpoint, endpoint: site.endpoint,
localEndpoints: site.localEndpoints
? JSON.parse(site.localEndpoints)
: undefined,
publicKey: site.publicKey, publicKey: site.publicKey,
serverIP: site.address, serverIP: site.address,
serverPort: site.listenPort, serverPort: site.listenPort,
@@ -0,0 +1,74 @@
import { db, sites } from "@server/db";
import { MessageHandler } from "@server/routers/ws";
import { clients, Olm } from "@server/db";
import { and, eq } from "drizzle-orm";
import { updatePeer as newtUpdatePeer } from "../newt/peers";
import logger from "@server/logger";
export const handleOlmLocalMessage: MessageHandler = async (context) => {
const { message, client: c, sendToClient } = context;
const olm = c as Olm;
logger.info("Handling local olm message!");
if (!olm) {
logger.warn("Olm not found");
return;
}
if (!olm.clientId) {
logger.warn("Olm has no client!");
return;
}
const clientId = olm.clientId;
const [client] = await db
.select()
.from(clients)
.where(eq(clients.clientId, clientId))
.limit(1);
if (!client) {
logger.warn("Client not found");
return;
}
// make sure we hand endpoints for both the site and the client and the lastHolePunch is not too old
if (!client.pubKey) {
logger.warn("Client has no endpoint or listen port");
return;
}
const { siteId, chainId } = message.data;
// Get the site
const [site] = await db
.select()
.from(sites)
.where(eq(sites.siteId, siteId))
.limit(1);
if (!site || !site.exitNodeId) {
logger.warn("Site not found or has no exit node");
return;
}
// update the peer on the newt
await newtUpdatePeer(siteId, client.pubKey, {
endpoint: "" // this removes the endpoint so the newt knows to accept local
});
// Just ack the message, we don't keep sending it
return {
message: {
type: "olm/wg/peer/local",
data: {
siteId: siteId,
chainId
}
},
broadcast: false,
excludeSender: false
};
};
+2 -2
View File
@@ -79,9 +79,9 @@ export const handleOlmRelayMessage: MessageHandler = async (context) => {
) )
); );
// update the peer on the exit node // update the peer on the newt
await newtUpdatePeer(siteId, client.pubKey, { await newtUpdatePeer(siteId, client.pubKey, {
endpoint: "" // this removes the endpoint so the exit node knows to relay endpoint: "" // this removes the endpoint so the newt knows to relay
}); });
return { return {
@@ -3,24 +3,15 @@ import {
db, db,
networks, networks,
siteNetworks, siteNetworks,
siteResources, siteResources
} from "@server/db"; } from "@server/db";
import { MessageHandler } from "@server/routers/ws"; import { MessageHandler } from "@server/routers/ws";
import { import { clients, clientSitesAssociationsCache, Olm, sites } from "@server/db";
clients,
clientSitesAssociationsCache,
Olm,
sites
} from "@server/db";
import { and, eq, inArray, isNotNull, isNull } from "drizzle-orm"; import { and, eq, inArray, isNotNull, isNull } from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import { import { generateAliasConfig } from "@server/lib/ip";
generateAliasConfig,
} from "@server/lib/ip";
import { generateRemoteSubnets } from "@server/lib/ip"; import { generateRemoteSubnets } from "@server/lib/ip";
import { import { addPeer as newtAddPeer } from "@server/routers/newt/peers";
addPeer as newtAddPeer,
} from "@server/routers/newt/peers";
export const handleOlmServerPeerAddMessage: MessageHandler = async ( export const handleOlmServerPeerAddMessage: MessageHandler = async (
context context
@@ -135,10 +126,7 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async (
clientSiteResourcesAssociationsCache.siteResourceId clientSiteResourcesAssociationsCache.siteResourceId
) )
) )
.innerJoin( .innerJoin(networks, eq(siteResources.networkId, networks.networkId))
networks,
eq(siteResources.networkId, networks.networkId)
)
.innerJoin( .innerJoin(
siteNetworks, siteNetworks,
and( and(
@@ -147,10 +135,7 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async (
) )
) )
.where( .where(
eq( eq(clientSiteResourcesAssociationsCache.clientId, client.clientId)
clientSiteResourcesAssociationsCache.clientId,
client.clientId
)
); );
// Return connect message with all site configurations // Return connect message with all site configurations
@@ -161,6 +146,9 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async (
siteId: site.siteId, siteId: site.siteId,
name: site.name, name: site.name,
endpoint: site.endpoint, endpoint: site.endpoint,
localEndpoints: site.localEndpoints
? JSON.parse(site.localEndpoints)
: undefined,
publicKey: site.publicKey, publicKey: site.publicKey,
serverIP: site.address, serverIP: site.address,
serverPort: site.listenPort, serverPort: site.listenPort,
@@ -170,7 +158,7 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async (
aliases: generateAliasConfig( aliases: generateAliasConfig(
allSiteResources.map(({ siteResources }) => siteResources) allSiteResources.map(({ siteResources }) => siteResources)
), ),
chainId: chainId, chainId: chainId
} }
}, },
broadcast: false, broadcast: false,
@@ -0,0 +1,95 @@
import { db, exitNodes, sites } from "@server/db";
import { MessageHandler } from "@server/routers/ws";
import { clients, clientSitesAssociationsCache, Olm } from "@server/db";
import { and, eq } from "drizzle-orm";
import { updatePeer as newtUpdatePeer } from "../newt/peers";
import logger from "@server/logger";
export const handleOlmUnLocalMessage: MessageHandler = async (context) => {
const { message, client: c, sendToClient } = context;
const olm = c as Olm;
logger.info("Handling unlocal olm message!");
if (!olm) {
logger.warn("Olm not found");
return;
}
if (!olm.clientId) {
logger.warn("Olm has no client!");
return;
}
const clientId = olm.clientId;
const [client] = await db
.select()
.from(clients)
.where(eq(clients.clientId, clientId))
.limit(1);
if (!client) {
logger.warn("Client not found");
return;
}
// make sure we hand endpoints for both the site and the client and the lastHolePunch is not too old
if (!client.pubKey) {
logger.warn("Client has no endpoint or listen port");
return;
}
const { siteId, chainId } = message.data;
// Get the site
const [site] = await db
.select()
.from(sites)
.where(eq(sites.siteId, siteId))
.limit(1);
if (!site) {
logger.warn("Site not found or has no exit node");
return;
}
const [clientSiteAssociation] = await db
.select()
.from(clientSitesAssociationsCache)
.where(
and(
eq(clientSitesAssociationsCache.clientId, olm.clientId),
eq(clientSitesAssociationsCache.siteId, siteId)
)
);
if (!clientSiteAssociation) {
logger.warn("Client-Site association not found");
return;
}
if (!clientSiteAssociation.endpoint) {
logger.warn("Client-Site association has no endpoint, cannot unrelay");
return;
}
// update the peer on the newt
await newtUpdatePeer(siteId, client.pubKey, {
endpoint: clientSiteAssociation.isRelayed
? ""
: clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the newt
});
return {
message: {
type: "olm/wg/peer/unlocal",
data: {
siteId: siteId,
chainId
}
},
broadcast: false,
excludeSender: false
};
};
@@ -77,9 +77,9 @@ export const handleOlmUnRelayMessage: MessageHandler = async (context) => {
return; return;
} }
// update the peer on the exit node // update the peer on the newt
await newtUpdatePeer(siteId, client.pubKey, { await newtUpdatePeer(siteId, client.pubKey, {
endpoint: clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the exit node endpoint: clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the newt
}); });
return { return {
+2
View File
@@ -13,3 +13,5 @@ export * from "./recoverOlmWithFingerprint";
export * from "./handleOlmDisconnectingMessage"; export * from "./handleOlmDisconnectingMessage";
export * from "./handleOlmServerInitAddPeerHandshake"; export * from "./handleOlmServerInitAddPeerHandshake";
export * from "./offlineChecker"; export * from "./offlineChecker";
export * from "./handleOlmUnLocalMessage";
export * from "./handleOlmLocalMessage";
+2
View File
@@ -18,6 +18,7 @@ export async function addPeer(
serverPort: number | null; serverPort: number | null;
remoteSubnets: string[] | null; // optional, comma-separated list of subnets that this site can access remoteSubnets: string[] | null; // optional, comma-separated list of subnets that this site can access
aliases: Alias[]; aliases: Alias[];
localEndpoints?: string[]; // optional, list of local endpoints for the peer
}, },
olmId?: string, olmId?: string,
version?: string | null version?: string | null
@@ -44,6 +45,7 @@ export async function addPeer(
name: peer.name, name: peer.name,
publicKey: peer.publicKey, publicKey: peer.publicKey,
endpoint: peer.endpoint, endpoint: peer.endpoint,
localEndpoints: peer.localEndpoints,
relayEndpoint: peer.relayEndpoint, relayEndpoint: peer.relayEndpoint,
serverIP: peer.serverIP, serverIP: peer.serverIP,
serverPort: peer.serverPort, serverPort: peer.serverPort,
+5 -1
View File
@@ -20,7 +20,9 @@ import {
handleOlmServerPeerAddMessage, handleOlmServerPeerAddMessage,
handleOlmUnRelayMessage, handleOlmUnRelayMessage,
handleOlmDisconnectingMessage, handleOlmDisconnectingMessage,
handleOlmServerInitAddPeerHandshake handleOlmServerInitAddPeerHandshake,
handleOlmLocalMessage,
handleOlmUnLocalMessage
} from "../olm"; } from "../olm";
import { handleHealthcheckStatusMessage } from "../target"; import { handleHealthcheckStatusMessage } from "../target";
import { handleRoundTripMessage } from "./handleRoundTripMessage"; import { handleRoundTripMessage } from "./handleRoundTripMessage";
@@ -32,6 +34,8 @@ export const messageHandlers: Record<string, MessageHandler> = {
"olm/wg/register": handleOlmRegisterMessage, "olm/wg/register": handleOlmRegisterMessage,
"olm/wg/relay": handleOlmRelayMessage, "olm/wg/relay": handleOlmRelayMessage,
"olm/wg/unrelay": handleOlmUnRelayMessage, "olm/wg/unrelay": handleOlmUnRelayMessage,
"olm/wg/local": handleOlmLocalMessage,
"olm/wg/unlocal": handleOlmUnLocalMessage,
"olm/ping": handleOlmPingMessage, "olm/ping": handleOlmPingMessage,
"olm/disconnecting": handleOlmDisconnectingMessage, "olm/disconnecting": handleOlmDisconnectingMessage,
"newt/disconnecting": handleNewtDisconnectingMessage, "newt/disconnecting": handleNewtDisconnectingMessage,