mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
Merge branch 'main' into dev
This commit is contained in:
@@ -116,6 +116,68 @@ function bigIntToIp(num: bigint, version: IPVersion): string {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an endpoint string (ip:port) handling both IPv4 and IPv6 addresses.
|
||||
* IPv6 addresses may be bracketed like [::1]:8080 or unbracketed like ::1:8080.
|
||||
* For unbracketed IPv6, the last colon-separated segment is treated as the port.
|
||||
*
|
||||
* @param endpoint The endpoint string to parse (e.g., "192.168.1.1:8080" or "[::1]:8080" or "2607:fea8::1:8080")
|
||||
* @returns An object with ip and port, or null if parsing fails
|
||||
*/
|
||||
export function parseEndpoint(endpoint: string): { ip: string; port: number } | null {
|
||||
if (!endpoint) return null;
|
||||
|
||||
// Check for bracketed IPv6 format: [ip]:port
|
||||
const bracketedMatch = endpoint.match(/^\[([^\]]+)\]:(\d+)$/);
|
||||
if (bracketedMatch) {
|
||||
const ip = bracketedMatch[1];
|
||||
const port = parseInt(bracketedMatch[2], 10);
|
||||
if (isNaN(port)) return null;
|
||||
return { ip, port };
|
||||
}
|
||||
|
||||
// Check if this looks like IPv6 (contains multiple colons)
|
||||
const colonCount = (endpoint.match(/:/g) || []).length;
|
||||
|
||||
if (colonCount > 1) {
|
||||
// This is IPv6 - the port is after the last colon
|
||||
const lastColonIndex = endpoint.lastIndexOf(":");
|
||||
const ip = endpoint.substring(0, lastColonIndex);
|
||||
const portStr = endpoint.substring(lastColonIndex + 1);
|
||||
const port = parseInt(portStr, 10);
|
||||
if (isNaN(port)) return null;
|
||||
return { ip, port };
|
||||
}
|
||||
|
||||
// IPv4 format: ip:port
|
||||
if (colonCount === 1) {
|
||||
const [ip, portStr] = endpoint.split(":");
|
||||
const port = parseInt(portStr, 10);
|
||||
if (isNaN(port)) return null;
|
||||
return { ip, port };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an IP and port into a consistent endpoint string.
|
||||
* IPv6 addresses are wrapped in brackets for proper parsing.
|
||||
*
|
||||
* @param ip The IP address (IPv4 or IPv6)
|
||||
* @param port The port number
|
||||
* @returns Formatted endpoint string
|
||||
*/
|
||||
export function formatEndpoint(ip: string, port: number): string {
|
||||
// Check if this is IPv6 (contains colons)
|
||||
if (ip.includes(":")) {
|
||||
// Remove brackets if already present
|
||||
const cleanIp = ip.replace(/^\[|\]$/g, "");
|
||||
return `[${cleanIp}]:${port}`;
|
||||
}
|
||||
return `${ip}:${port}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts CIDR to IP range
|
||||
*/
|
||||
|
||||
@@ -32,7 +32,9 @@ import logger from "@server/logger";
|
||||
import {
|
||||
generateAliasConfig,
|
||||
generateRemoteSubnets,
|
||||
generateSubnetProxyTargets
|
||||
generateSubnetProxyTargets,
|
||||
parseEndpoint,
|
||||
formatEndpoint
|
||||
} from "@server/lib/ip";
|
||||
import {
|
||||
addPeerData,
|
||||
@@ -542,6 +544,13 @@ export async function updateClientSiteDestinations(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse the endpoint properly for both IPv4 and IPv6
|
||||
const parsedEndpoint = parseEndpoint(site.clientSitesAssociationsCache.endpoint);
|
||||
if (!parsedEndpoint) {
|
||||
logger.warn(`Failed to parse endpoint ${site.clientSitesAssociationsCache.endpoint}, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// find the destinations in the array
|
||||
let destinations = exitNodeDestinations.find(
|
||||
(d) => d.reachableAt === site.exitNodes?.reachableAt
|
||||
@@ -553,13 +562,8 @@ export async function updateClientSiteDestinations(
|
||||
exitNodeId: site.exitNodes?.exitNodeId || 0,
|
||||
type: site.exitNodes?.type || "",
|
||||
name: site.exitNodes?.name || "",
|
||||
sourceIp:
|
||||
site.clientSitesAssociationsCache.endpoint.split(":")[0] ||
|
||||
"",
|
||||
sourcePort:
|
||||
parseInt(
|
||||
site.clientSitesAssociationsCache.endpoint.split(":")[1]
|
||||
) || 0,
|
||||
sourceIp: parsedEndpoint.ip,
|
||||
sourcePort: parsedEndpoint.port,
|
||||
destinations: [
|
||||
{
|
||||
destinationIP: site.sites.subnet.split("/")[0],
|
||||
|
||||
@@ -21,6 +21,7 @@ import { validateOlmSessionToken } from "@server/auth/sessions/olm";
|
||||
import { checkExitNodeOrg } from "#dynamic/lib/exitNodes";
|
||||
import { updatePeer as updateOlmPeer } from "../olm/peers";
|
||||
import { updatePeer as updateNewtPeer } from "../newt/peers";
|
||||
import { formatEndpoint } from "@server/lib/ip";
|
||||
|
||||
// Define Zod schema for request validation
|
||||
const updateHolePunchSchema = z.object({
|
||||
@@ -207,9 +208,12 @@ export async function updateAndGenerateEndpointDestinations(
|
||||
// `Updating site ${site.siteId} on exit node ${exitNode.exitNodeId}`
|
||||
// );
|
||||
|
||||
// Format the endpoint properly for both IPv4 and IPv6
|
||||
const formattedEndpoint = formatEndpoint(ip, port);
|
||||
|
||||
// if the public key or endpoint has changed, update it otherwise continue
|
||||
if (
|
||||
site.endpoint === `${ip}:${port}` &&
|
||||
site.endpoint === formattedEndpoint &&
|
||||
site.publicKey === publicKey
|
||||
) {
|
||||
continue;
|
||||
@@ -218,7 +222,7 @@ export async function updateAndGenerateEndpointDestinations(
|
||||
const [updatedClientSitesAssociationsCache] = await db
|
||||
.update(clientSitesAssociationsCache)
|
||||
.set({
|
||||
endpoint: `${ip}:${port}`,
|
||||
endpoint: formattedEndpoint,
|
||||
publicKey: publicKey
|
||||
})
|
||||
.where(
|
||||
@@ -310,11 +314,14 @@ export async function updateAndGenerateEndpointDestinations(
|
||||
|
||||
currentSiteId = newt.siteId;
|
||||
|
||||
// Format the endpoint properly for both IPv4 and IPv6
|
||||
const formattedSiteEndpoint = formatEndpoint(ip, port);
|
||||
|
||||
// Update the current site with the new endpoint
|
||||
const [updatedSite] = await db
|
||||
.update(sites)
|
||||
.set({
|
||||
endpoint: `${ip}:${port}`,
|
||||
endpoint: formattedSiteEndpoint,
|
||||
lastHolePunch: timestamp
|
||||
})
|
||||
.where(eq(sites.siteId, newt.siteId))
|
||||
|
||||
Reference in New Issue
Block a user