Add native traffic inspection to Harbor Connect and Gateway

This commit is contained in:
2026-08-31 05:19:15 +03:00
parent 116686a138
commit 4d066cb879
62 changed files with 10975 additions and 220 deletions
+104
View File
@@ -16,6 +16,40 @@ const connection = (connectionId, type, host, upload, download, sourceIP = devic
download,
chains,
});
const nativeConnection = (connectionId, uploadBytes, downloadBytes, overrides = {}) => ({
id: connectionId,
startedAt: '2026-08-31T10:00:00.000Z',
closedAt: null,
inbound: { tag: 'tproxy-in', type: 'tproxy' },
network: 'tcp',
protocol: 'tls',
source: { ip: device.ip, port: 54_000 },
destination: { domain: 'example.com', ip: null, port: 443, provenance: 'sing-box' },
origin: { kind: 'device', id, label: 'MacBook', provenance: 'source-ip' },
route: {
kind: 'vpn',
scope: 'local-sing-box',
outbound: 'channel-selector',
outboundType: 'selector',
chain: ['channel-primary', 'channel-selector'],
rule: 'final',
},
traffic: {
uploadBytes,
downloadBytes,
uploadBytesPerSecond: '0',
downloadBytesPerSecond: '0',
},
...overrides,
});
const nativeBatch = (connections, overrides = {}) => ({
epoch: 'sing-box-100',
observedAt: '2026-08-31T10:00:00.000Z',
reset: false,
connections,
closedIds: [],
...overrides,
});
test('sing-box route traffic keeps vpn, direct and unknown deltas separate', async () => {
let response = { connections: [
@@ -238,3 +272,73 @@ test('failover activity is zero-work while disabled and uses the existing connec
service.disableActivity();
assert.equal(service.activitySnapshot(500), null);
});
test('native lifecycle batches keep decimal precision and dedupe a same-epoch reconnect reset and final tail', () => {
let now = new Date('2026-08-31T10:00:00.000Z');
const service = createDomainTrafficService({
observe: () => ({ connections: [] }),
devices: () => [],
now: () => now,
});
const initial = nativeConnection('native', '9007199254740993', '10');
service.ingestNative(nativeBatch([initial], { reset: true }));
now = new Date('2026-08-31T10:00:01.000Z');
service.ingestNative(nativeBatch([initial], {
reset: true,
observedAt: now.toISOString(),
}));
assert.equal(service.snapshot().series[0].uploadBytes, '9007199254740993');
assert.equal(service.snapshot().source.activeConnections, 1);
service.enableActivity();
now = new Date('2026-08-31T10:00:02.000Z');
service.ingestNative(nativeBatch([
nativeConnection('native', '9007199254740998', '15', { closedAt: now.toISOString() }),
], {
observedAt: now.toISOString(),
closedIds: ['native'],
}));
assert.equal(service.snapshot().series[0].uploadBytes, '9007199254740998');
assert.equal(service.snapshot().series[0].downloadBytes, '15');
assert.equal(service.snapshot().source.activeConnections, 0);
assert.equal(service.activitySnapshot(0).state, 'active');
now = new Date('2026-08-31T10:00:03.000Z');
service.ingestNative(nativeBatch([
nativeConnection('native', '9007199254740998', '15'),
], {
reset: true,
observedAt: now.toISOString(),
}));
assert.equal(service.snapshot().series[0].uploadBytes, '9007199254740998');
assert.equal(service.snapshot().series[0].downloadBytes, '15');
assert.equal(service.snapshot().source.activeConnections, 1);
now = new Date('2026-08-31T10:00:14.000Z');
service.ingestNative(nativeBatch([], { observedAt: now.toISOString() }));
assert.equal(service.snapshot().observedAt, now.toISOString());
assert.equal(service.snapshot().source.activeConnections, 1);
assert.equal(service.activitySnapshot(0).state, 'quiet');
});
test('one native batch accounts every final tail before lifecycle and UI caps', () => {
const service = createDomainTrafficService({
observe: () => ({ connections: [] }),
devices: () => [],
});
const connections = Array.from({ length: 2_049 }, (_, index) => (
nativeConnection(`closed-${index}`, '1', '1', { closedAt: '2026-08-31T10:00:00.000Z' })
));
service.ingestNative(nativeBatch(connections, {
reset: true,
closedIds: connections.map(({ id: connectionId }) => connectionId),
}));
assert.equal(service.snapshot().series[0].uploadBytes, '2049');
assert.equal(service.snapshot().series[0].downloadBytes, '2049');
assert.equal(service.snapshot().source.activeConnections, 0);
});