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
+46
View File
@@ -40,6 +40,28 @@ const snapshot = {
},
domainTraffic: {
observedAt,
source: {
error: null,
mode: 'shadow',
writer: 'snapshot',
activeConnections: 4,
native: {
state: 'degraded',
epoch: 'epoch-1',
sequence: 7,
observedAt,
active: 5,
unattributedUploadBytes: '11',
unattributedDownloadBytes: '22',
},
shadow: {
activeDifference: 1,
uploadDifferenceBytes: '-30',
downloadDifferenceBytes: '40',
routeMismatches: 2,
deviceMismatches: 3,
},
},
overflowConnections: '2',
attributionEvents: {
unresolved_host: '3',
@@ -97,6 +119,15 @@ test('Prometheus exposition keeps exact counters, stable identity and escaped na
assert.match(output, /harbor_domain_traffic_attribution_events_total\{outcome="unresolved_host"\} 3/);
assert.match(output, /harbor_domain_traffic_attribution_events_total\{outcome="unknown_device"\} 4/);
assert.match(output, /harbor_domain_traffic_attribution_events_total\{outcome="unsupported_source"\} 5/);
assert.match(output, /harbor_traffic_collector_info\{mode="shadow",writer="snapshot"\} 1/);
assert.match(output, /harbor_traffic_collector_state\{state="degraded"\} 1/);
assert.match(output, /harbor_traffic_collector_unattributed_bytes\{direction="download"\} 22/);
assert.match(output, /harbor_traffic_collector_unattributed_bytes\{direction="upload"\} 11/);
assert.match(output, /harbor_traffic_shadow_active_difference 1/);
assert.match(output, /harbor_traffic_shadow_difference_bytes\{direction="download"\} 40/);
assert.match(output, /harbor_traffic_shadow_difference_bytes\{direction="upload"\} -30/);
assert.match(output, /harbor_traffic_shadow_route_mismatches 2/);
assert.match(output, /harbor_traffic_shadow_device_mismatches 3/);
assert.doesNotMatch(output, /harbor_device_domain_traffic_bytes_total\{[^\n]*name=/);
assert.doesNotMatch(output, /harbor_device_(?:direct_ipv4_packet|singbox_tracked)_bytes_total\{[^\n]*(?:name|ip|mac|server)=/);
assert.equal(output.endsWith('\n'), true);
@@ -122,6 +153,15 @@ test('Prometheus response uses the negotiated legacy text contract without mutat
assert.deepEqual(snapshot, before);
});
test('legacy combined Gateway snapshots keep existing metrics without collector diagnostics', () => {
const legacy = structuredClone(snapshot);
legacy.domainTraffic.source = { error: null, activeConnections: 0 };
const output = renderPrometheusMetrics(legacy);
assert.match(output, /harbor_singbox_tracked_bytes_total/);
assert.doesNotMatch(output, /harbor_traffic_collector_info/);
});
test('invalid canonical counters fail the scrape instead of publishing corrupt values', () => {
const invalid = { traffic: { gatewayBytes: 'broken', proxyBytes: '0' }, devices: [] };
assert.throws(
@@ -135,6 +175,12 @@ test('invalid canonical counters fail the scrape instead of publishing corrupt v
const invalidRoute = structuredClone(snapshot);
invalidRoute.domainTraffic.routes[0].outbound = 'vpn-server-tag';
assert.throws(() => renderPrometheusMetrics(invalidRoute), /Invalid sing-box outbound labels/);
const invalidCollector = structuredClone(snapshot);
invalidCollector.domainTraffic.source.mode = 'future';
assert.throws(() => renderPrometheusMetrics(invalidCollector), /Invalid traffic collector labels/);
const invalidShadow = structuredClone(snapshot);
invalidShadow.domainTraffic.source.shadow.uploadDifferenceBytes = '1.5';
assert.throws(() => renderPrometheusMetrics(invalidShadow), /Invalid Prometheus gauge/);
});
function routeResponse() {