Add native traffic inspection to Harbor Connect and Gateway
This commit is contained in:
@@ -0,0 +1,692 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { ConnectionEventType } from '../../dist/server/generated/daemon/started_service_pb.js';
|
||||
import { createLiveTrafficLedger } from '../../dist/server/services/liveTrafficService.js';
|
||||
|
||||
function connection(id, overrides = {}) {
|
||||
return {
|
||||
id,
|
||||
inbound: 'mixed-in',
|
||||
inboundType: 'mixed',
|
||||
network: 'tcp',
|
||||
source: '127.0.0.1:54000',
|
||||
destination: '203.0.113.10:443',
|
||||
domain: 'example.test',
|
||||
protocol: 'tls',
|
||||
createdAt: 1_700_000_000_000n,
|
||||
closedAt: 0n,
|
||||
uplinkTotal: 0n,
|
||||
downlinkTotal: 0n,
|
||||
outbound: 'test-vpn',
|
||||
outboundType: 'vless',
|
||||
rule: 'final',
|
||||
chainList: ['test-vpn'],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function event(type, id, overrides = {}) {
|
||||
return {
|
||||
type,
|
||||
id,
|
||||
uplinkDelta: 0n,
|
||||
downlinkDelta: 0n,
|
||||
closedAt: 0n,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function status(connectionsIn, uplinkTotal, downlinkTotal) {
|
||||
return { connectionsIn, uplinkTotal, downlinkTotal };
|
||||
}
|
||||
|
||||
test('ledger applies NEW, UPDATE and only the final CLOSED tail once', () => {
|
||||
let now = new Date('2026-08-31T10:00:00.000Z');
|
||||
const ledger = createLiveTrafficLedger({ now: () => now });
|
||||
ledger.beginEpoch(1_700_000_000_000n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({ reset: true, events: [] });
|
||||
ledger.applyStatus(status(0, 0n, 0n));
|
||||
|
||||
const opened = connection('a', { uplinkTotal: 10n, downlinkTotal: 20n });
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'a', { connection: opened })],
|
||||
});
|
||||
let snapshot = ledger.snapshot();
|
||||
assert.deepEqual(snapshot.connections[0].traffic, {
|
||||
uploadBytes: '10',
|
||||
downloadBytes: '20',
|
||||
uploadBytesPerSecond: '0',
|
||||
downloadBytesPerSecond: '0',
|
||||
});
|
||||
assert.equal(snapshot.connections[0].origin.label, 'Этот Mac');
|
||||
assert.equal(snapshot.connections[0].closedAt, null);
|
||||
assert.equal(snapshot.connections[0].route.kind, 'vpn');
|
||||
|
||||
now = new Date('2026-08-31T10:00:01.000Z');
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_UPDATE, 'a', {
|
||||
uplinkDelta: 5n,
|
||||
downlinkDelta: 7n,
|
||||
})],
|
||||
});
|
||||
snapshot = ledger.snapshot();
|
||||
assert.deepEqual(snapshot.connections[0].traffic, {
|
||||
uploadBytes: '15',
|
||||
downloadBytes: '27',
|
||||
uploadBytesPerSecond: '5',
|
||||
downloadBytesPerSecond: '7',
|
||||
});
|
||||
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'a', {
|
||||
connection: connection('a', {
|
||||
closedAt: BigInt(now.getTime()),
|
||||
uplinkTotal: 18n,
|
||||
downlinkTotal: 30n,
|
||||
}),
|
||||
})],
|
||||
});
|
||||
const firstClosed = ledger.snapshot().connections[0];
|
||||
now = new Date('2026-08-31T10:00:10.000Z');
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'a', {
|
||||
connection: connection('a', { uplinkTotal: 999n, downlinkTotal: 999n }),
|
||||
})],
|
||||
});
|
||||
ledger.applyStatus(status(0, 18n, 30n));
|
||||
snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.source.state, 'live');
|
||||
assert.equal(snapshot.summary.active, 0);
|
||||
assert.equal(snapshot.summary.recent, 1);
|
||||
assert.equal(snapshot.connections[0].closedAt, firstClosed.closedAt);
|
||||
assert.deepEqual(snapshot.connections[0].traffic, {
|
||||
uploadBytes: '18',
|
||||
downloadBytes: '30',
|
||||
uploadBytesPerSecond: '0',
|
||||
downloadBytesPerSecond: '0',
|
||||
});
|
||||
assert.equal(snapshot.source.unattributedUploadBytes, '0');
|
||||
assert.equal(snapshot.source.unattributedDownloadBytes, '0');
|
||||
ledger.markStopped();
|
||||
snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.source.state, 'stopped');
|
||||
assert.equal(snapshot.summary.recent, 0);
|
||||
});
|
||||
|
||||
test('connection churn does not clear rates before the next update tick', () => {
|
||||
const ledger = createLiveTrafficLedger();
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({
|
||||
reset: true,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'a', {
|
||||
connection: connection('a'),
|
||||
})],
|
||||
});
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_UPDATE, 'a', {
|
||||
uplinkDelta: 5n,
|
||||
downlinkDelta: 7n,
|
||||
})],
|
||||
});
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'b', {
|
||||
connection: connection('b'),
|
||||
})],
|
||||
});
|
||||
|
||||
const active = new Map(ledger.snapshot().connections.map((item) => [item.id, item]));
|
||||
assert.equal(active.get('a').traffic.uploadBytesPerSecond, '5');
|
||||
assert.equal(active.get('a').traffic.downloadBytesPerSecond, '7');
|
||||
assert.equal(active.get('b').traffic.uploadBytesPerSecond, '0');
|
||||
});
|
||||
|
||||
test('a connection completed between polls remains visible until the exact 30 second boundary', () => {
|
||||
let now = new Date('2026-08-31T10:00:00.000Z');
|
||||
const ledger = createLiveTrafficLedger({ now: () => now });
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [
|
||||
event(ConnectionEventType.CONNECTION_EVENT_NEW, 'quick', {
|
||||
connection: connection('quick', {
|
||||
createdAt: BigInt(now.getTime()),
|
||||
uplinkTotal: 2n,
|
||||
downlinkTotal: 3n,
|
||||
}),
|
||||
}),
|
||||
event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'quick', {
|
||||
uplinkDelta: 5n,
|
||||
downlinkDelta: 7n,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
let snapshot = ledger.snapshot();
|
||||
assert.deepEqual(snapshot.summary, {
|
||||
active: 0,
|
||||
recent: 1,
|
||||
visible: 1,
|
||||
recognized: 0,
|
||||
unresolved: 0,
|
||||
unresolvedOrigin: 0,
|
||||
truncated: false,
|
||||
});
|
||||
assert.equal(snapshot.connections[0].closedAt, '2026-08-31T10:00:00.000Z');
|
||||
assert.deepEqual(snapshot.connections[0].traffic, {
|
||||
uploadBytes: '7',
|
||||
downloadBytes: '10',
|
||||
uploadBytesPerSecond: '0',
|
||||
downloadBytesPerSecond: '0',
|
||||
});
|
||||
|
||||
now = new Date('2026-08-31T10:00:29.999Z');
|
||||
assert.equal(ledger.snapshot().summary.recent, 1);
|
||||
now = new Date('2026-08-31T10:00:30.000Z');
|
||||
snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.summary.recent, 0);
|
||||
assert.equal(snapshot.connections.length, 0);
|
||||
});
|
||||
|
||||
test('a connection already closed in an RC5 reset remains visible as recent', () => {
|
||||
const ledger = createLiveTrafficLedger({ now: () => new Date('2026-08-31T10:00:02.000Z') });
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({
|
||||
reset: true,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'reset-closed', {
|
||||
connection: connection('reset-closed', {
|
||||
createdAt: BigInt(Date.parse('2026-08-31T10:00:00.000Z')),
|
||||
closedAt: BigInt(Date.parse('2026-08-31T10:00:01.000Z')),
|
||||
uplinkTotal: 3n,
|
||||
downlinkTotal: 7n,
|
||||
}),
|
||||
})],
|
||||
});
|
||||
|
||||
const snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.summary.active, 0);
|
||||
assert.equal(snapshot.summary.recent, 1);
|
||||
assert.equal(snapshot.connections[0].id, 'reset-closed');
|
||||
assert.equal(snapshot.connections[0].closedAt, '2026-08-31T10:00:01.000Z');
|
||||
});
|
||||
|
||||
test('a genuinely newer lifecycle with the same UUID supersedes its recent tombstone', () => {
|
||||
let now = new Date('2026-08-31T10:00:00.000Z');
|
||||
const ledger = createLiveTrafficLedger({ now: () => now });
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({ reset: true, events: [] });
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'revived', {
|
||||
connection: connection('revived', { createdAt: BigInt(now.getTime()), uplinkTotal: 3n }),
|
||||
})],
|
||||
});
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'revived')],
|
||||
});
|
||||
|
||||
const firstClosedAt = ledger.snapshot().connections[0].closedAt;
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'revived', {
|
||||
connection: connection('revived', { createdAt: BigInt(now.getTime()), uplinkTotal: 999n }),
|
||||
})],
|
||||
});
|
||||
assert.equal(ledger.snapshot().summary.active, 0);
|
||||
assert.equal(ledger.snapshot().connections[0].closedAt, firstClosedAt);
|
||||
|
||||
now = new Date('2026-08-31T10:00:01.000Z');
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'revived', {
|
||||
connection: connection('revived', { createdAt: BigInt(now.getTime()), uplinkTotal: 4n }),
|
||||
})],
|
||||
});
|
||||
const snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.summary.active, 1);
|
||||
assert.equal(snapshot.summary.recent, 0);
|
||||
assert.equal(snapshot.connections[0].closedAt, null);
|
||||
assert.equal(snapshot.connections[0].traffic.uploadBytes, '4');
|
||||
});
|
||||
|
||||
test('reset reconciles active deltas without treating earlier closed traffic as a gap', () => {
|
||||
const ledger = createLiveTrafficLedger();
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({
|
||||
reset: true,
|
||||
events: [
|
||||
event(ConnectionEventType.CONNECTION_EVENT_NEW, 'closed-a', {
|
||||
connection: connection('closed-a', { uplinkTotal: 100n }),
|
||||
}),
|
||||
event(ConnectionEventType.CONNECTION_EVENT_NEW, 'active-b', {
|
||||
connection: connection('active-b', { uplinkTotal: 10n }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
ledger.applyStatus(status(2, 110n, 0n));
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'closed-a')],
|
||||
});
|
||||
ledger.applyStatus(status(1, 110n, 0n));
|
||||
ledger.applyConnections({
|
||||
reset: true,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'active-b', {
|
||||
connection: connection('active-b', { uplinkTotal: 20n }),
|
||||
})],
|
||||
});
|
||||
ledger.applyStatus(status(1, 120n, 0n));
|
||||
|
||||
const snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.source.state, 'live');
|
||||
assert.equal(snapshot.source.unattributedUploadBytes, '0');
|
||||
assert.equal(snapshot.summary.active, 1);
|
||||
assert.equal(snapshot.summary.recent, 1);
|
||||
assert.equal(snapshot.connections.find(({ id }) => id === 'active-b').traffic.uploadBytes, '20');
|
||||
});
|
||||
|
||||
test('reset can revive the same lifecycle tombstone without counting its totals twice', () => {
|
||||
const ledger = createLiveTrafficLedger();
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'same', {
|
||||
connection: connection('same', { uplinkTotal: 10n }),
|
||||
})],
|
||||
});
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'same')],
|
||||
});
|
||||
ledger.applyConnections({
|
||||
reset: true,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'same', {
|
||||
connection: connection('same', { uplinkTotal: 10n }),
|
||||
})],
|
||||
});
|
||||
ledger.applyStatus(status(1, 10n, 0n));
|
||||
ledger.applyStatus(status(1, 10n, 0n));
|
||||
ledger.applyStatus(status(1, 10n, 0n));
|
||||
|
||||
const snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.source.state, 'live');
|
||||
assert.equal(snapshot.source.unattributedUploadBytes, '0');
|
||||
assert.equal(snapshot.summary.active, 1);
|
||||
assert.equal(snapshot.summary.recent, 0);
|
||||
});
|
||||
|
||||
test('destination hostnames remain recognized without protocol sniffing', () => {
|
||||
const ledger = createLiveTrafficLedger();
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({
|
||||
reset: true,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'hostname', {
|
||||
connection: connection('hostname', {
|
||||
destination: 'Example.COM:443',
|
||||
domain: '',
|
||||
protocol: '',
|
||||
}),
|
||||
})],
|
||||
});
|
||||
|
||||
const snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.summary.recognized, 1);
|
||||
assert.deepEqual(snapshot.connections[0].destination, {
|
||||
domain: 'example.com',
|
||||
ip: null,
|
||||
port: 443,
|
||||
provenance: 'sing-box',
|
||||
});
|
||||
});
|
||||
|
||||
test('CLOSED keeps newly available native domain, protocol and route metadata', () => {
|
||||
const ledger = createLiveTrafficLedger();
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'late-metadata', {
|
||||
connection: connection('late-metadata', {
|
||||
domain: '',
|
||||
protocol: '',
|
||||
uplinkTotal: 1n,
|
||||
downlinkTotal: 2n,
|
||||
}),
|
||||
})],
|
||||
});
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'late-metadata', {
|
||||
connection: connection('late-metadata', {
|
||||
domain: 'recognized.example',
|
||||
protocol: 'http2',
|
||||
outbound: 'direct',
|
||||
outboundType: 'direct',
|
||||
chainList: ['direct'],
|
||||
rule: 'domain-final',
|
||||
uplinkTotal: 3n,
|
||||
downlinkTotal: 5n,
|
||||
}),
|
||||
})],
|
||||
});
|
||||
|
||||
const closed = ledger.snapshot().connections[0];
|
||||
assert.equal(closed.destination.domain, 'recognized.example');
|
||||
assert.equal(closed.protocol, 'http2');
|
||||
assert.deepEqual(closed.route, {
|
||||
kind: 'direct',
|
||||
scope: 'local-sing-box',
|
||||
outbound: 'direct',
|
||||
outboundType: 'direct',
|
||||
chain: ['direct'],
|
||||
rule: 'domain-final',
|
||||
});
|
||||
assert.equal(closed.traffic.uploadBytes, '3');
|
||||
assert.equal(closed.traffic.downloadBytes, '5');
|
||||
});
|
||||
|
||||
test('CLOSED partial route metadata keeps route kind consistent with its outbound', () => {
|
||||
const ledger = createLiveTrafficLedger();
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'partial-route', {
|
||||
connection: connection('partial-route'),
|
||||
})],
|
||||
});
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'partial-route', {
|
||||
connection: connection('partial-route', {
|
||||
outbound: '',
|
||||
outboundType: '',
|
||||
chainList: ['late-hop'],
|
||||
rule: 'late-rule',
|
||||
}),
|
||||
})],
|
||||
});
|
||||
|
||||
const route = ledger.snapshot().connections[0].route;
|
||||
assert.equal(route.kind, 'vpn');
|
||||
assert.equal(route.outbound, 'test-vpn');
|
||||
assert.equal(route.outboundType, 'vless');
|
||||
assert.deepEqual(route.chain, ['late-hop']);
|
||||
assert.equal(route.rule, 'late-rule');
|
||||
});
|
||||
|
||||
test('transport errors preserve the last observed data timestamp', () => {
|
||||
let now = new Date('2026-08-31T10:00:00.000Z');
|
||||
const ledger = createLiveTrafficLedger({ now: () => now });
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({ reset: true, events: [] });
|
||||
ledger.applyStatus(status(0, 0n, 0n));
|
||||
const lastGood = ledger.snapshot();
|
||||
|
||||
now = new Date('2026-08-31T10:01:00.000Z');
|
||||
ledger.markTransportError(new Error('stream ended'));
|
||||
const firstStale = ledger.snapshot();
|
||||
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.markTransportError(new Error('stream ended again'));
|
||||
const stale = ledger.snapshot();
|
||||
|
||||
assert.equal(stale.source.state, 'stale');
|
||||
assert.equal(stale.observedAt, lastGood.observedAt);
|
||||
assert.equal(firstStale.observedAt, lastGood.observedAt);
|
||||
assert.equal(stale.sequence, lastGood.sequence + 3);
|
||||
});
|
||||
|
||||
test('reset is idempotent, a restart creates a clean epoch, and settled UUIDs are bounded', () => {
|
||||
const ledger = createLiveTrafficLedger();
|
||||
const active = connection('active', { uplinkTotal: 10n, downlinkTotal: 20n });
|
||||
const closed = connection('closed', {
|
||||
closedAt: 1_700_000_001_000n,
|
||||
uplinkTotal: 5n,
|
||||
downlinkTotal: 7n,
|
||||
});
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
const reset = {
|
||||
reset: true,
|
||||
events: [
|
||||
event(ConnectionEventType.CONNECTION_EVENT_NEW, 'active', { connection: active }),
|
||||
event(ConnectionEventType.CONNECTION_EVENT_NEW, 'closed', { connection: closed }),
|
||||
],
|
||||
};
|
||||
const firstReset = ledger.applyConnections(reset);
|
||||
ledger.applyStatus(status(1, 15n, 27n));
|
||||
const repeatedReset = ledger.applyConnections(reset);
|
||||
ledger.applyStatus(status(1, 15n, 27n));
|
||||
assert.equal(ledger.snapshot().source.state, 'live');
|
||||
assert.equal(ledger.snapshot().source.unattributedUploadBytes, '0');
|
||||
assert.deepEqual(ledger.snapshot().connections.map(({ id }) => id), ['active']);
|
||||
assert.equal(ledger.snapshot().summary.recent, 0);
|
||||
assert.deepEqual(firstReset.connections.map(({ id }) => id), ['active', 'closed']);
|
||||
assert.deepEqual(repeatedReset.connections.map(({ id }) => id), ['active']);
|
||||
|
||||
const settledEvents = Array.from({ length: 2_049 }, (_, index) => {
|
||||
const id = `settled-${String(index).padStart(4, '0')}`;
|
||||
return event(ConnectionEventType.CONNECTION_EVENT_CLOSED, id);
|
||||
});
|
||||
ledger.applyConnections({ reset: false, events: settledEvents });
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [
|
||||
event(ConnectionEventType.CONNECTION_EVENT_NEW, 'settled-0000', {
|
||||
connection: connection('settled-0000'),
|
||||
}),
|
||||
event(ConnectionEventType.CONNECTION_EVENT_NEW, 'settled-2048', {
|
||||
connection: connection('settled-2048'),
|
||||
}),
|
||||
],
|
||||
});
|
||||
assert.deepEqual(
|
||||
ledger.snapshot().connections.map(({ id }) => id).sort(),
|
||||
['active', 'settled-0000'],
|
||||
);
|
||||
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [
|
||||
event(ConnectionEventType.CONNECTION_EVENT_NEW, 'epoch-recent', {
|
||||
connection: connection('epoch-recent'),
|
||||
}),
|
||||
event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'epoch-recent'),
|
||||
],
|
||||
});
|
||||
assert.equal(ledger.snapshot().summary.recent, 1);
|
||||
|
||||
ledger.beginEpoch(101n, '1.14.0-rc.5', 4);
|
||||
const restarted = ledger.snapshot();
|
||||
assert.equal(restarted.epoch, 'sing-box-101');
|
||||
assert.equal(restarted.source.state, 'connecting');
|
||||
assert.equal(restarted.summary.active, 0);
|
||||
assert.equal(restarted.summary.recent, 0);
|
||||
assert.equal(restarted.source.unattributedUploadBytes, '0');
|
||||
});
|
||||
|
||||
test('three consecutive status mismatches degrade without assigning the byte gap', () => {
|
||||
const ledger = createLiveTrafficLedger();
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({
|
||||
reset: true,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'a', {
|
||||
connection: connection('a', { uplinkTotal: 10n, downlinkTotal: 20n }),
|
||||
})],
|
||||
});
|
||||
|
||||
assert.ok(ledger.applyStatus(status(1, 15n, 27n)));
|
||||
assert.ok(ledger.applyStatus(status(1, 15n, 27n)));
|
||||
assert.equal(ledger.snapshot().source.state, 'live');
|
||||
assert.equal(ledger.applyStatus(status(1, 15n, 27n)), null);
|
||||
let snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.source.state, 'degraded');
|
||||
assert.equal(snapshot.source.unattributedUploadBytes, '5');
|
||||
assert.equal(snapshot.source.unattributedDownloadBytes, '7');
|
||||
assert.deepEqual(snapshot.connections[0].traffic, {
|
||||
uploadBytes: '10',
|
||||
downloadBytes: '20',
|
||||
uploadBytesPerSecond: '0',
|
||||
downloadBytesPerSecond: '0',
|
||||
});
|
||||
|
||||
assert.ok(ledger.applyStatus(status(1, 10n, 20n)));
|
||||
snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.source.state, 'live');
|
||||
assert.equal(snapshot.source.unattributedUploadBytes, '0');
|
||||
assert.equal(snapshot.source.unattributedDownloadBytes, '0');
|
||||
});
|
||||
|
||||
test('a missing NEW does not double-count UPDATE before an absolute CLOSED total', () => {
|
||||
const ledger = createLiveTrafficLedger({ now: () => new Date('2023-11-14T22:13:21.000Z') });
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({ reset: true, events: [] });
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_UPDATE, 'missed', {
|
||||
uplinkDelta: 10n,
|
||||
downlinkDelta: 20n,
|
||||
})],
|
||||
});
|
||||
ledger.applyStatus(status(0, 10n, 20n));
|
||||
assert.equal(ledger.snapshot().source.unattributedUploadBytes, '10');
|
||||
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'missed', {
|
||||
connection: connection('missed', {
|
||||
closedAt: 1_700_000_001_000n,
|
||||
uplinkTotal: 15n,
|
||||
downlinkTotal: 27n,
|
||||
}),
|
||||
})],
|
||||
});
|
||||
ledger.applyStatus(status(0, 15n, 27n));
|
||||
let snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.source.unattributedUploadBytes, '15');
|
||||
assert.equal(snapshot.source.unattributedDownloadBytes, '27');
|
||||
assert.equal(snapshot.summary.recent, 1);
|
||||
assert.equal(snapshot.connections[0].closedAt, '2023-11-14T22:13:21.000Z');
|
||||
assert.deepEqual(snapshot.connections[0].traffic, {
|
||||
uploadBytes: '15',
|
||||
downloadBytes: '27',
|
||||
uploadBytesPerSecond: '0',
|
||||
downloadBytesPerSecond: '0',
|
||||
});
|
||||
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'metadata-less', {
|
||||
uplinkDelta: 3n,
|
||||
downlinkDelta: 4n,
|
||||
})],
|
||||
});
|
||||
ledger.applyStatus(status(0, 18n, 31n));
|
||||
snapshot = ledger.snapshot();
|
||||
assert.equal(snapshot.source.unattributedUploadBytes, '18');
|
||||
assert.equal(snapshot.source.unattributedDownloadBytes, '31');
|
||||
});
|
||||
|
||||
test('snapshot caps visibility at 256 while summary covers every active connection', () => {
|
||||
const ledger = createLiveTrafficLedger();
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [
|
||||
event(ConnectionEventType.CONNECTION_EVENT_NEW, 'recent', { connection: connection('recent') }),
|
||||
event(ConnectionEventType.CONNECTION_EVENT_CLOSED, 'recent'),
|
||||
],
|
||||
});
|
||||
const events = Array.from({ length: 257 }, (_, index) => {
|
||||
const id = index === 255 ? 'z-tie' : index === 256 ? 'a-tie' : `id-${String(index).padStart(3, '0')}`;
|
||||
const createdAt = index >= 255 ? 1_700_000_000_255n : 1_700_000_000_000n + BigInt(index);
|
||||
return event(ConnectionEventType.CONNECTION_EVENT_NEW, id, {
|
||||
connection: connection(id, {
|
||||
createdAt,
|
||||
domain: index % 2 === 0 ? `service-${index}.example` : '',
|
||||
destination: `203.0.113.${index % 255}:443`,
|
||||
outbound: index % 3 === 0 ? 'direct' : 'test-vpn',
|
||||
outboundType: index % 3 === 0 ? 'direct' : 'vless',
|
||||
}),
|
||||
});
|
||||
});
|
||||
ledger.applyConnections({ reset: true, events });
|
||||
const snapshot = ledger.snapshot();
|
||||
|
||||
assert.deepEqual(snapshot.summary, {
|
||||
active: 257,
|
||||
recent: 1,
|
||||
visible: 256,
|
||||
recognized: 129,
|
||||
unresolved: 128,
|
||||
unresolvedOrigin: 0,
|
||||
truncated: true,
|
||||
});
|
||||
assert.equal(snapshot.connections.length, 256);
|
||||
assert.deepEqual(snapshot.connections.slice(0, 2).map(({ id }) => id), ['a-tie', 'z-tie']);
|
||||
assert.equal(snapshot.connections.some(({ id }) => id === 'id-000'), false);
|
||||
assert.equal(snapshot.connections.some(({ id }) => id === 'recent'), false);
|
||||
});
|
||||
|
||||
test('gateway projection uses selector chains and resolves every active origin from the current device map', () => {
|
||||
let deviceVisible = false;
|
||||
const ledger = createLiveTrafficLedger({
|
||||
gateway: true,
|
||||
resolveOrigin: (sourceIp) => deviceVisible
|
||||
? { kind: 'device', id: 'device-a', label: 'MacBook', provenance: 'source-ip' }
|
||||
: { kind: 'unknown', id: null, label: sourceIp, provenance: 'unknown' },
|
||||
});
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
const first = ledger.applyConnections({
|
||||
reset: true,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_NEW, 'gateway', {
|
||||
connection: connection('gateway', {
|
||||
inbound: 'tproxy-in',
|
||||
inboundType: 'tproxy',
|
||||
source: '192.168.50.7:54000',
|
||||
outbound: 'channel-selector',
|
||||
outboundType: 'selector',
|
||||
chainList: ['channel-primary', 'channel-selector'],
|
||||
}),
|
||||
})],
|
||||
});
|
||||
|
||||
assert.equal(first.connections[0].route.kind, 'vpn');
|
||||
assert.equal(ledger.snapshot().summary.unresolvedOrigin, 1);
|
||||
deviceVisible = true;
|
||||
assert.equal(ledger.snapshot().summary.unresolvedOrigin, 0);
|
||||
assert.equal(ledger.snapshot().connections[0].origin.id, 'device-a');
|
||||
|
||||
const second = ledger.applyConnections({
|
||||
reset: false,
|
||||
events: [event(ConnectionEventType.CONNECTION_EVENT_UPDATE, 'gateway', { uplinkDelta: 1n })],
|
||||
});
|
||||
assert.equal(second.connections[0].origin.id, 'device-a');
|
||||
assert.equal(second.connections[0].traffic.uploadBytes, '1');
|
||||
assert.equal(ledger.snapshot().capabilities.deviceAttribution, true);
|
||||
});
|
||||
|
||||
test('recent closed storage is bounded independently from the 256-row response cap', () => {
|
||||
const ledger = createLiveTrafficLedger();
|
||||
ledger.beginEpoch(100n, '1.14.0-rc.5', 4);
|
||||
const events = Array.from({ length: 2_049 }, (_, index) => {
|
||||
const id = `recent-${String(index).padStart(4, '0')}`;
|
||||
return event(ConnectionEventType.CONNECTION_EVENT_CLOSED, id, { connection: connection(id) });
|
||||
});
|
||||
const projection = ledger.applyConnections({ reset: false, events });
|
||||
|
||||
const snapshot = ledger.snapshot();
|
||||
assert.equal(projection.connections.length, 2_049);
|
||||
assert.equal(projection.closedIds.length, 2_049);
|
||||
assert.equal(snapshot.summary.active, 0);
|
||||
assert.equal(snapshot.summary.recent, 2_048);
|
||||
assert.equal(snapshot.summary.visible, 256);
|
||||
assert.equal(snapshot.summary.truncated, true);
|
||||
assert.equal(snapshot.connections[0].id, 'recent-0001');
|
||||
assert.equal(snapshot.connections.some(({ id }) => id === 'recent-0000'), false);
|
||||
});
|
||||
Reference in New Issue
Block a user