fix: treat tailnet host as local for pairing
This commit is contained in:
41
src/gateway/net.test.ts
Normal file
41
src/gateway/net.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
const testTailnetIPv4 = { value: undefined as string | undefined };
|
||||
const testTailnetIPv6 = { value: undefined as string | undefined };
|
||||
|
||||
vi.mock("../infra/tailnet.js", () => ({
|
||||
pickPrimaryTailnetIPv4: () => testTailnetIPv4.value,
|
||||
pickPrimaryTailnetIPv6: () => testTailnetIPv6.value,
|
||||
}));
|
||||
|
||||
import { isLocalGatewayAddress } from "./net.js";
|
||||
|
||||
describe("gateway net", () => {
|
||||
beforeEach(() => {
|
||||
testTailnetIPv4.value = undefined;
|
||||
testTailnetIPv6.value = undefined;
|
||||
});
|
||||
|
||||
test("treats loopback as local", () => {
|
||||
expect(isLocalGatewayAddress("127.0.0.1")).toBe(true);
|
||||
expect(isLocalGatewayAddress("127.0.1.1")).toBe(true);
|
||||
expect(isLocalGatewayAddress("::1")).toBe(true);
|
||||
expect(isLocalGatewayAddress("::ffff:127.0.0.1")).toBe(true);
|
||||
});
|
||||
|
||||
test("treats local tailnet IPv4 as local", () => {
|
||||
testTailnetIPv4.value = "100.64.0.1";
|
||||
expect(isLocalGatewayAddress("100.64.0.1")).toBe(true);
|
||||
expect(isLocalGatewayAddress("::ffff:100.64.0.1")).toBe(true);
|
||||
});
|
||||
|
||||
test("ignores non-matching tailnet IPv4", () => {
|
||||
testTailnetIPv4.value = "100.64.0.1";
|
||||
expect(isLocalGatewayAddress("100.64.0.2")).toBe(false);
|
||||
});
|
||||
|
||||
test("treats local tailnet IPv6 as local", () => {
|
||||
testTailnetIPv6.value = "fd7a:115c:a1e0::123";
|
||||
expect(isLocalGatewayAddress("fd7a:115c:a1e0::123")).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import net from "node:net";
|
||||
|
||||
import { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js";
|
||||
import { pickPrimaryTailnetIPv4, pickPrimaryTailnetIPv6 } from "../infra/tailnet.js";
|
||||
|
||||
export function isLoopbackAddress(ip: string | undefined): boolean {
|
||||
if (!ip) return false;
|
||||
@@ -11,6 +11,22 @@ export function isLoopbackAddress(ip: string | undefined): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function normalizeIPv4MappedAddress(ip: string): string {
|
||||
if (ip.startsWith("::ffff:")) return ip.slice("::ffff:".length);
|
||||
return ip;
|
||||
}
|
||||
|
||||
export function isLocalGatewayAddress(ip: string | undefined): boolean {
|
||||
if (isLoopbackAddress(ip)) return true;
|
||||
if (!ip) return false;
|
||||
const normalized = normalizeIPv4MappedAddress(ip.trim().toLowerCase());
|
||||
const tailnetIPv4 = pickPrimaryTailnetIPv4();
|
||||
if (tailnetIPv4 && normalized === tailnetIPv4.toLowerCase()) return true;
|
||||
const tailnetIPv6 = pickPrimaryTailnetIPv6();
|
||||
if (tailnetIPv6 && ip.trim().toLowerCase() === tailnetIPv6.toLowerCase()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves gateway bind host with fallback strategy.
|
||||
*
|
||||
|
||||
@@ -25,7 +25,7 @@ import type { ResolvedGatewayAuth } from "../../auth.js";
|
||||
import { authorizeGatewayConnect } from "../../auth.js";
|
||||
import { loadConfig } from "../../../config/config.js";
|
||||
import { buildDeviceAuthPayload } from "../../device-auth.js";
|
||||
import { isLoopbackAddress } from "../../net.js";
|
||||
import { isLocalGatewayAddress } from "../../net.js";
|
||||
import { resolveNodeCommandAllowlist } from "../../node-command-policy.js";
|
||||
import {
|
||||
type ConnectParams,
|
||||
@@ -347,7 +347,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
close(1008, "device signature expired");
|
||||
return;
|
||||
}
|
||||
const nonceRequired = !isLoopbackAddress(remoteAddr);
|
||||
const nonceRequired = !isLocalGatewayAddress(remoteAddr);
|
||||
const providedNonce = typeof device.nonce === "string" ? device.nonce.trim() : "";
|
||||
if (nonceRequired && !providedNonce) {
|
||||
setHandshakeState("failed");
|
||||
@@ -524,7 +524,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
role,
|
||||
scopes,
|
||||
remoteIp: remoteAddr,
|
||||
silent: isLoopbackAddress(remoteAddr),
|
||||
silent: isLocalGatewayAddress(remoteAddr),
|
||||
});
|
||||
const context = buildRequestContext();
|
||||
if (pairing.request.silent === true) {
|
||||
@@ -656,7 +656,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
if (presenceKey) {
|
||||
upsertPresence(presenceKey, {
|
||||
host: connectParams.client.displayName ?? connectParams.client.id ?? os.hostname(),
|
||||
ip: isLoopbackAddress(remoteAddr) ? undefined : remoteAddr,
|
||||
ip: isLocalGatewayAddress(remoteAddr) ? undefined : remoteAddr,
|
||||
version: connectParams.client.version,
|
||||
platform: connectParams.client.platform,
|
||||
deviceFamily: connectParams.client.deviceFamily,
|
||||
|
||||
Reference in New Issue
Block a user