Update Harbor client and gateway functionality
This commit is contained in:
@@ -6,6 +6,9 @@ import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import { buildGatewayPresence } from '../../dist/server/gatewayPresence.js';
|
||||
import { normalizeSubscriptionConfig } from '../../dist/server/subscription.js';
|
||||
|
||||
const root = path.resolve(import.meta.dirname, '../..');
|
||||
|
||||
function listen(server, ...args) {
|
||||
@@ -72,7 +75,7 @@ if (process.argv[2] === 'run') {
|
||||
|
||||
function profileState(server) {
|
||||
return {
|
||||
schemaVersion: 5,
|
||||
schemaVersion: 6,
|
||||
revision: 4,
|
||||
profiles: [{
|
||||
id: 'profile-a',
|
||||
@@ -116,6 +119,8 @@ async function startClientFixture(t, {
|
||||
state,
|
||||
cacheContents,
|
||||
config,
|
||||
hostNetwork,
|
||||
gatewayPresencePort,
|
||||
}) {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-startup-recovery-'));
|
||||
const { binDirectory, markerPath } = fakeSingbox(directory);
|
||||
@@ -126,6 +131,8 @@ async function startClientFixture(t, {
|
||||
if (config !== undefined) {
|
||||
fs.writeFileSync(path.join(directory, 'sing-box-config.json'), JSON.stringify(config));
|
||||
}
|
||||
const hostNetworkPath = path.join(directory, 'host-network.json');
|
||||
if (hostNetwork !== undefined) fs.writeFileSync(hostNetworkPath, JSON.stringify(hostNetwork));
|
||||
const port = await freePort();
|
||||
const child = spawn(process.execPath, ['dist/server/main.js'], {
|
||||
cwd: root,
|
||||
@@ -136,7 +143,10 @@ async function startClientFixture(t, {
|
||||
DATA_DIR: directory,
|
||||
PORT: String(port),
|
||||
PATH: `${binDirectory}:${process.env.PATH || ''}`,
|
||||
HARBOR_HOST_NETWORK_STATE: path.join(directory, 'missing-network.json'),
|
||||
HARBOR_HOST_NETWORK_STATE: hostNetwork === undefined
|
||||
? path.join(directory, 'missing-network.json')
|
||||
: hostNetworkPath,
|
||||
...(gatewayPresencePort ? { HARBOR_GATEWAY_CONTROL_PORT: String(gatewayPresencePort) } : {}),
|
||||
HARBOR_TEST_RUN_MARKER: markerPath,
|
||||
},
|
||||
stdio: ['ignore', 'ignore', 'pipe'],
|
||||
@@ -154,6 +164,82 @@ async function startClientFixture(t, {
|
||||
};
|
||||
}
|
||||
|
||||
function bootStateWithRules() {
|
||||
const normalized = normalizeSubscriptionConfig({
|
||||
outbounds: [{
|
||||
type: 'vless',
|
||||
tag: 'Boot VPN',
|
||||
server: 'boot.example',
|
||||
server_port: 443,
|
||||
uuid: '00000000-0000-4000-8000-000000000000',
|
||||
}],
|
||||
});
|
||||
const server = normalized.servers[0];
|
||||
const state = profileState(server);
|
||||
state.profiles[0].subscriptionUrl = 'https://provider.example/0123456789abcdef0123456789abcdef';
|
||||
state.profiles[0].subscriptionConfig = normalized.config;
|
||||
state.routeRules = [
|
||||
{ type: 'domain', value: 'api.example.com', enabled: true, outbound: 'vpn' },
|
||||
{ type: 'domain_suffix', value: 'example.com', enabled: true, outbound: 'direct' },
|
||||
];
|
||||
state.appliedRouteRules = [];
|
||||
return state;
|
||||
}
|
||||
|
||||
test('local-vpn boot promotes the exact desired ordered rules to applied truth', async (t) => {
|
||||
const state = bootStateWithRules();
|
||||
const fixture = await startClientFixture(t, { state });
|
||||
const config = JSON.parse(fs.readFileSync(path.join(fixture.directory, 'sing-box-config.json'), 'utf8'));
|
||||
|
||||
assert.equal(fixture.state.route.mode, 'local-vpn');
|
||||
assert.deepEqual(fixture.state.route.activeLocalRules, state.routeRules);
|
||||
assert.equal(fixture.state.route.localRulesPendingRestart, false);
|
||||
assert.deepEqual(config.route.rules.slice(2, 4), [
|
||||
{ domain: ['api.example.com'], outbound: state.profiles[0].servers[0].id },
|
||||
{ domain_suffix: ['example.com'], outbound: 'direct' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('gateway-direct boot keeps the local proxy and diagnostics but omits every user rule', async (t) => {
|
||||
const state = bootStateWithRules();
|
||||
const gateway = http.createServer((req, res) => {
|
||||
const nonce = new URL(req.url, 'http://127.0.0.1').searchParams.get('nonce');
|
||||
const payload = buildGatewayPresence({
|
||||
appMode: 'gateway',
|
||||
subscriptionUrl: state.profiles[0].subscriptionUrl,
|
||||
gatewayId: 'gateway-test',
|
||||
nonce,
|
||||
});
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(payload));
|
||||
});
|
||||
const address = await listen(gateway, 0, '127.0.0.1');
|
||||
t.after(() => close(gateway));
|
||||
const fixture = await startClientFixture(t, {
|
||||
state,
|
||||
gatewayPresencePort: address.port,
|
||||
hostNetwork: {
|
||||
gateway: '127.0.0.1',
|
||||
interface: 'lo0',
|
||||
mac: 'aa:bb:cc:dd:ee:ff',
|
||||
observedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
const config = JSON.parse(fs.readFileSync(path.join(fixture.directory, 'sing-box-config.json'), 'utf8'));
|
||||
const stored = JSON.parse(fs.readFileSync(path.join(fixture.directory, 'state.json'), 'utf8'));
|
||||
|
||||
assert.equal(fixture.state.route.mode, 'gateway-direct');
|
||||
assert.deepEqual(fixture.state.route.activeLocalRules, []);
|
||||
assert.equal(fixture.state.route.localRulesPendingRestart, false);
|
||||
assert.deepEqual(stored.appliedRouteRules, []);
|
||||
assert.deepEqual(config.inbounds.map(({ tag }) => tag), ['mixed-in', 'diagnostics-vpn-in']);
|
||||
assert.equal(config.route.rules.some((rule) => Object.keys(rule).some((key) => key.startsWith('domain'))), false);
|
||||
assert.deepEqual(config.route.rules[1], {
|
||||
inbound: ['diagnostics-vpn-in'],
|
||||
outbound: state.profiles[0].servers[0].id,
|
||||
});
|
||||
});
|
||||
|
||||
test('corrupt legacy cache with no canonical subscription fails closed instead of starting stale config', async (t) => {
|
||||
const staleServer = {
|
||||
id: 'stale-server',
|
||||
@@ -298,7 +384,7 @@ test('stopped Gateway boot explicitly stops an already running remote dataplane'
|
||||
});
|
||||
await listen(dataplane, socketPath);
|
||||
fs.writeFileSync(path.join(directory, 'state.json'), JSON.stringify({
|
||||
schemaVersion: 5,
|
||||
schemaVersion: 6,
|
||||
revision: 3,
|
||||
profiles: [],
|
||||
desiredProfileId: '',
|
||||
|
||||
Reference in New Issue
Block a user