fix: probe alternate WARP endpoints

This commit is contained in:
RickeyRen 2026-08-18 10:09:12 +08:00
parent c48679f94b
commit ad6e71ffef
5 changed files with 184 additions and 9 deletions

View file

@ -19,7 +19,9 @@
},
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"format": "prettier --write package.json \"src/**/*.ts\" \"test/**/*.{ts,mjs,cjs}\"",
"format:check": "prettier --check package.json \"src/**/*.ts\" \"test/**/*.{ts,mjs,cjs}\"",
"test": "node --require ts-node/register --test test/*.test.*",
"start": "nest start",
"start:dev": "NODE_ENV=development nest start --watch",
"start:debug": "NODE_ENV=development nest start --debug --watch",
@ -102,4 +104,4 @@
"tsconfig-paths": "^4.2.0",
"typescript": "^5.9.3"
}
}
}

View file

@ -0,0 +1,26 @@
export const WARP_ENDPOINT_CANDIDATES = [
'162.159.192.1:2408',
'162.159.192.1:500',
'162.159.192.1:1701',
'162.159.192.1:4500',
] as const;
type TWarpTraceState = null | { warp: string };
export function getWarpEndpointCandidates(configuredEndpoint: string | null): string[] {
if (
!configuredEndpoint ||
!WARP_ENDPOINT_CANDIDATES.some((endpoint) => endpoint === configuredEndpoint)
) {
return [...WARP_ENDPOINT_CANDIDATES];
}
return [
configuredEndpoint,
...WARP_ENDPOINT_CANDIDATES.filter((endpoint) => endpoint !== configuredEndpoint),
];
}
export function hasDualStackWarpTrace(ipv4: TWarpTraceState, ipv6: TWarpTraceState): boolean {
return ipv4?.warp === 'on' && ipv6?.warp === 'on';
}

View file

@ -5,6 +5,11 @@ import type { THostConnectivity, TWarpOperation, TWarpStatus } from '@libs/contr
import { Injectable, Logger } from '@nestjs/common';
import {
getWarpEndpointCandidates,
hasDualStackWarpTrace,
WARP_ENDPOINT_CANDIDATES,
} from './warp-endpoint-policy';
import { TWarpCommandResult } from './warp.types';
const WARP_INTERFACE = 'warp';
@ -12,7 +17,7 @@ const WARP_CONFIG_PATH = '/etc/wireguard/warp.conf';
const WARP_TOOL_PATH = '/usr/local/bin/wgcf';
const WARP_TRACE_URL = 'https://www.cloudflare.com/cdn-cgi/trace';
const WGCF_RELEASES_API_URL = 'https://api.github.com/repos/ViRb3/wgcf/releases/latest';
const WARP_ENDPOINT = '162.159.192.1:2408';
const WARP_DEFAULT_ENDPOINT = WARP_ENDPOINT_CANDIDATES[0];
const WARP_IPV6_ROUTE_METRIC = 4242;
const WARP_IPV6_ROUTE_POST_UP = `PostUp = ip -6 route replace ::/0 dev %i metric ${WARP_IPV6_ROUTE_METRIC}`;
const WARP_IPV6_ROUTE_PRE_DOWN = `PreDown = ip -6 route del ::/0 dev %i metric ${WARP_IPV6_ROUTE_METRIC} 2>/dev/null || true`;
@ -67,7 +72,7 @@ test -s wgcf-profile.conf
echo "[warp] normalizing WireGuard profile"
sed -i -E '/^DNS =/d' wgcf-profile.conf
sed -i -E 's#^Endpoint = .*$#Endpoint = ${WARP_ENDPOINT}#' wgcf-profile.conf
sed -i -E 's#^Endpoint = .*$#Endpoint = ${WARP_DEFAULT_ENDPOINT}#' wgcf-profile.conf
grep -q '^Table = off$' wgcf-profile.conf || sed -i '/^MTU =/a Table = off' wgcf-profile.conf
grep -q '^PersistentKeepalive = ' wgcf-profile.conf \
|| sed -i '/^Endpoint =/a PersistentKeepalive = 25' wgcf-profile.conf
@ -213,6 +218,7 @@ export class WarpService {
await this.execFixed('/usr/bin/wg-quick', ['up', WARP_INTERFACE], 20_000, {
onOutput: (line) => this.appendOperationLog(line),
});
await this.ensureReachableWarpEndpoint();
this.finishOperation('WARP enabled');
return await this.getStatus();
} catch (error) {
@ -335,7 +341,9 @@ export class WarpService {
if (!this.hasWarpConfig()) return;
const original = readFileSync(WARP_CONFIG_PATH, 'utf8');
let updated = original.replace(/^Endpoint = .*$/m, `Endpoint = ${WARP_ENDPOINT}`);
const configuredEndpoint = this.getConfiguredWarpEndpoint(original);
const preferredEndpoint = getWarpEndpointCandidates(configuredEndpoint)[0];
let updated = original.replace(/^Endpoint = .*$/m, `Endpoint = ${preferredEndpoint}`);
if (!/^Table = off$/m.test(updated)) {
updated = updated.replace(/^MTU = .*$/m, (line) => `${line}\nTable = off`);
@ -384,6 +392,38 @@ export class WarpService {
}
}
private async ensureReachableWarpEndpoint(): Promise<void> {
const configuredEndpoint = this.getConfiguredWarpEndpoint();
const peer = (
await this.execFixed('/usr/bin/wg', ['show', WARP_INTERFACE, 'peers'], 5_000)
).stdout
.split('\n')
.map((line) => line.trim())
.find(Boolean);
if (!peer) {
throw new Error('WARP peer is missing');
}
for (const endpoint of getWarpEndpointCandidates(configuredEndpoint)) {
this.appendOperationLog(`Testing WARP endpoint ${endpoint}`);
await this.execFixed(
'/usr/bin/wg',
['set', WARP_INTERFACE, 'peer', peer, 'endpoint', endpoint],
5_000,
);
const [ipv4, ipv6] = await Promise.all([this.getTrace('4'), this.getTrace('6')]);
if (!hasDualStackWarpTrace(ipv4, ipv6)) continue;
this.persistWarpEndpoint(endpoint);
this.appendOperationLog(`Using WARP endpoint ${endpoint}`);
return;
}
throw new Error('No WARP endpoint provided working IPv4 and IPv6 traces');
}
private async installIfMissingOrSingleStack(): Promise<void> {
if (this.hasWarpConfig() && this.hasDualStackConfig()) {
this.appendOperationLog('Existing WARP profile is already dual-stack');
@ -446,7 +486,24 @@ export class WarpService {
if (config === null && !this.hasWarpConfig()) return false;
const warpConfig = config ?? readFileSync(WARP_CONFIG_PATH, 'utf8');
return warpConfig.includes(`Endpoint = ${WARP_ENDPOINT}`);
const configuredEndpoint = this.getConfiguredWarpEndpoint(warpConfig);
return getWarpEndpointCandidates(configuredEndpoint)[0] === configuredEndpoint;
}
private getConfiguredWarpEndpoint(config: string | null = null): string | null {
if (config === null && !this.hasWarpConfig()) return null;
const warpConfig = config ?? readFileSync(WARP_CONFIG_PATH, 'utf8');
return warpConfig.match(/^Endpoint = (.+)$/m)?.[1].trim() ?? null;
}
private persistWarpEndpoint(endpoint: string): void {
const original = readFileSync(WARP_CONFIG_PATH, 'utf8');
const updated = original.replace(/^Endpoint = .*$/m, `Endpoint = ${endpoint}`);
if (updated !== original) {
writeFileSync(WARP_CONFIG_PATH, updated, { mode: 0o600 });
}
}
private hasDeprecatedIpv6EndpointRouteConfig(config: string | null = null): boolean {

View file

@ -0,0 +1,93 @@
const assert = require('node:assert/strict');
const { describe, it } = require('node:test');
let endpointPolicy;
try {
endpointPolicy = require('../src/modules/warp/warp-endpoint-policy.ts');
} catch (error) {
assert.fail(`WARP endpoint policy is not implemented: ${error.message}`);
}
const { getWarpEndpointCandidates, hasDualStackWarpTrace } = endpointPolicy;
const { WarpService } = require('../src/modules/warp/warp.service.ts');
const createServiceHarness = (tracesByEndpoint) => {
const service = new WarpService();
const attemptedEndpoints = [];
const persistedEndpoints = [];
let activeEndpoint = null;
service.getConfiguredWarpEndpoint = () => '162.159.192.1:2408';
service.appendOperationLog = () => {};
service.execFixed = async (_command, args) => {
if (args[0] === 'show') {
return { stdout: 'public-peer-key\n', stderr: '' };
}
activeEndpoint = args.at(-1);
attemptedEndpoints.push(activeEndpoint);
return { stdout: '', stderr: '' };
};
service.getTrace = async (ipVersion) => tracesByEndpoint[activeEndpoint]?.[ipVersion] ?? null;
service.persistWarpEndpoint = (endpoint) => persistedEndpoints.push(endpoint);
return { attemptedEndpoints, persistedEndpoints, service };
};
describe('WARP endpoint policy', () => {
it('keeps a supported configured endpoint first and falls back to alternate UDP ports', () => {
assert.deepEqual(getWarpEndpointCandidates('162.159.192.1:500'), [
'162.159.192.1:500',
'162.159.192.1:2408',
'162.159.192.1:1701',
'162.159.192.1:4500',
]);
});
it('uses the default candidate order for an unsupported endpoint', () => {
assert.deepEqual(getWarpEndpointCandidates('engage.cloudflareclient.com:2408'), [
'162.159.192.1:2408',
'162.159.192.1:500',
'162.159.192.1:1701',
'162.159.192.1:4500',
]);
});
it('accepts an endpoint only when both WARP traces are on', () => {
assert.equal(hasDualStackWarpTrace({ warp: 'on' }, { warp: 'on' }), true);
assert.equal(hasDualStackWarpTrace({ warp: 'on' }, null), false);
assert.equal(hasDualStackWarpTrace({ warp: 'on' }, { warp: 'off' }), false);
});
it('retries a failed default endpoint and persists the first dual-stack endpoint', async () => {
const harness = createServiceHarness({
'162.159.192.1:2408': { 4: { warp: 'on' }, 6: null },
'162.159.192.1:500': { 4: { warp: 'on' }, 6: { warp: 'on' } },
});
await harness.service.ensureReachableWarpEndpoint();
assert.deepEqual(harness.attemptedEndpoints, ['162.159.192.1:2408', '162.159.192.1:500']);
assert.deepEqual(harness.persistedEndpoints, ['162.159.192.1:500']);
});
it('does not persist an endpoint when every candidate fails dual-stack verification', async () => {
const harness = createServiceHarness(
Object.fromEntries(
getWarpEndpointCandidates(null).map((endpoint) => [
endpoint,
{ 4: { warp: 'on' }, 6: { warp: 'off' } },
]),
),
);
await assert.rejects(
harness.service.ensureReachableWarpEndpoint(),
/No WARP endpoint provided working IPv4 and IPv6 traces/,
);
assert.deepEqual(harness.attemptedEndpoints, getWarpEndpointCandidates(null));
assert.deepEqual(harness.persistedEndpoints, []);
});
});

View file

@ -70,9 +70,6 @@ describe('WARP contract shape', () => {
assert.match(service, /wgcf generate/);
assert.doesNotMatch(service, /s#\^\(Address = \[\^,\]\+\),\.\*#\\1#/);
assert.match(service, /WGCF_RELEASES_API_URL/);
assert.match(service, /WARP_ENDPOINT = '162\.159\.192\.1:2408'/);
assert.match(service, /Endpoint = \$\{WARP_ENDPOINT\}/);
assert.doesNotMatch(service, /WARP_ENDPOINT = 'engage\.cloudflareclient\.com:2408'/);
assert.doesNotMatch(service, /WARP_IPV6_ENDPOINT_ROUTE_POST_UP/);
assert.match(service, /getTrace\('4'\)/);
assert.match(service, /getTrace\('6'\)/);