@kar-mi/spirit-vale-tools-capture 2.4.2 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capture/adapter-selection.d.ts +1 -1
- package/dist/capture/adapter-selection.d.ts.map +1 -1
- package/dist/capture/npcap.d.ts +2 -1
- package/dist/capture/npcap.d.ts.map +1 -1
- package/dist/capture/target-tracker.d.ts +2 -8
- package/dist/capture/target-tracker.d.ts.map +1 -1
- package/dist/capture/win32-system.d.ts +11 -0
- package/dist/capture/win32-system.d.ts.map +1 -0
- package/dist/capture.js +234 -137
- package/package.json +1 -1
|
@@ -5,5 +5,5 @@ export interface AdapterResolution {
|
|
|
5
5
|
detail?: string;
|
|
6
6
|
}
|
|
7
7
|
export declare function resolveCaptureDevice(devices: readonly NpcapDevice[], requestedName?: string): Promise<AdapterResolution>;
|
|
8
|
-
export declare function
|
|
8
|
+
export declare function chooseDeviceByRouteAddress(devices: readonly NpcapDevice[], address: string): NpcapDevice | undefined;
|
|
9
9
|
//# sourceMappingURL=adapter-selection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter-selection.d.ts","sourceRoot":"","sources":["../../src/capture/adapter-selection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter-selection.d.ts","sourceRoot":"","sources":["../../src/capture/adapter-selection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAa5B;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,OAAO,EAAE,MAAM,GACd,WAAW,GAAG,SAAS,CAEzB"}
|
package/dist/capture/npcap.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type NpcapAvailability = "ready" | "missing" | "
|
|
1
|
+
export type NpcapAvailability = "ready" | "missing" | "error";
|
|
2
2
|
export interface NpcapStatus {
|
|
3
3
|
availability: NpcapAvailability;
|
|
4
4
|
detail: string;
|
|
@@ -32,6 +32,7 @@ export declare class SystemNpcapRuntime implements NpcapRuntime {
|
|
|
32
32
|
private readyStatus?;
|
|
33
33
|
status(): Promise<NpcapStatus>;
|
|
34
34
|
listDevices(): Promise<NpcapDevice[]>;
|
|
35
|
+
private enumerateDevices;
|
|
35
36
|
open(device: NpcapDevice, filter: string): Promise<NpcapSession>;
|
|
36
37
|
private load;
|
|
37
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"npcap.d.ts","sourceRoot":"","sources":["../../src/capture/npcap.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,
|
|
1
|
+
{"version":3,"file":"npcap.d.ts","sourceRoot":"","sources":["../../src/capture/npcap.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9D,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,iBAAiB,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,IAAI,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,UAAU,IAAI,WAAW,GAAG,SAAS,CAAC;IACtC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/B,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAClE;AAKD,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,GAAG,CAAC,CAAc;IAC1B,OAAO,CAAC,WAAW,CAAC,CAAc;IAE5B,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,CA4BnC;IAEK,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAI1C;IAED,OAAO,CAAC,gBAAgB;IAgClB,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAsCrE;IAED,OAAO,CAAC,IAAI;CAyBb"}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { CaptureProtocol, CapturedTransportPacket, CaptureTargetStatus } from "../types.ts";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
address: string;
|
|
5
|
-
port: number;
|
|
6
|
-
processId: number;
|
|
7
|
-
}
|
|
2
|
+
import type { WindowsOwnedEndpoint } from "./win32-system.ts";
|
|
3
|
+
type OwnedEndpoint = WindowsOwnedEndpoint;
|
|
8
4
|
export interface TargetSnapshotProvider {
|
|
9
5
|
snapshot(processName: string, protocols: readonly CaptureProtocol[]): Promise<{
|
|
10
6
|
processIds: number[];
|
|
@@ -31,7 +27,5 @@ export declare class WindowsTargetTracker {
|
|
|
31
27
|
private matches;
|
|
32
28
|
private refresh;
|
|
33
29
|
}
|
|
34
|
-
export declare function parseTaskList(output: string): number[];
|
|
35
|
-
export declare function parseNetstat(output: string, protocol: CaptureProtocol): OwnedEndpoint[];
|
|
36
30
|
export {};
|
|
37
31
|
//# sourceMappingURL=target-tracker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"target-tracker.d.ts","sourceRoot":"","sources":["../../src/capture/target-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEjG,
|
|
1
|
+
{"version":3,"file":"target-tracker.d.ts","sourceRoot":"","sources":["../../src/capture/target-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEjG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,KAAK,aAAa,GAAG,oBAAoB,CAAC;AAI1C,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC;QAC5E,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,SAAS,EAAE,aAAa,EAAE,CAAC;KAC5B,CAAC,CAAC;CACJ;AAED,qBAAa,oBAAoB;IAS7B,QAAQ,CAAC,WAAW,EAAE,MAAM;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAbpC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,KAAK,CAAC,CAAiC;IAC/C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,YACW,WAAW,EAAE,MAAM,EACX,SAAS,EAAE,SAAS,eAAe,EAAE,EACrC,QAAQ,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,EAC/C,QAAQ,GAAE,sBAA4D,EACtE,SAAS,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,EAC/C,iBAAiB,SAA6B,EAC7D;IAEE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3B;IAED,IAAI,IAAI,IAAI,CAGX;IAED,QAAQ,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAM5E;IAED,OAAO,CAAC,OAAO;YAMD,OAAO;CAgCtB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CaptureProtocol } from "../types.ts";
|
|
2
|
+
export interface WindowsOwnedEndpoint {
|
|
3
|
+
protocol: CaptureProtocol;
|
|
4
|
+
address: string;
|
|
5
|
+
port: number;
|
|
6
|
+
processId: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function findProcessIdsByName(processName: string): number[];
|
|
9
|
+
export declare function listOwnedEndpoints(protocols: readonly CaptureProtocol[]): WindowsOwnedEndpoint[];
|
|
10
|
+
export declare function defaultRouteIpv4Address(): string | undefined;
|
|
11
|
+
//# sourceMappingURL=win32-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"win32-system.d.ts","sourceRoot":"","sources":["../../src/capture/win32-system.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAmDD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAsBlE;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,GAAG,oBAAoB,EAAE,CAIhG;AAED,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,SAAS,CAe5D"}
|
package/dist/capture.js
CHANGED
|
@@ -15235,53 +15235,8 @@ function decodeFishNetBundle(payload, options = { reliable: false }) {
|
|
|
15235
15235
|
return new FishNetSessionDecoder(options.rpcMap).decode(payload, options);
|
|
15236
15236
|
}
|
|
15237
15237
|
|
|
15238
|
-
// src/capture/
|
|
15239
|
-
|
|
15240
|
-
if (requestedName) {
|
|
15241
|
-
const requested = devices.find((device) => device.name === requestedName);
|
|
15242
|
-
if (requested)
|
|
15243
|
-
return { device: requested, usedFallback: false };
|
|
15244
|
-
}
|
|
15245
|
-
const automatic = await defaultRouteDevice(devices) ?? devices.find(isUsable) ?? devices[0];
|
|
15246
|
-
return {
|
|
15247
|
-
device: automatic,
|
|
15248
|
-
usedFallback: Boolean(requestedName),
|
|
15249
|
-
...requestedName && automatic ? { detail: "The saved adapter is unavailable; capture is using the automatically selected adapter" } : {}
|
|
15250
|
-
};
|
|
15251
|
-
}
|
|
15252
|
-
function chooseDeviceByRouteOutput(devices, output) {
|
|
15253
|
-
const routes = output.split(/\r?\n/).flatMap((line) => {
|
|
15254
|
-
const columns = line.trim().split(/\s+/);
|
|
15255
|
-
if (columns[0] !== "0.0.0.0" || columns[1] !== "0.0.0.0" || columns.length < 5)
|
|
15256
|
-
return [];
|
|
15257
|
-
const metric = Number(columns[4]);
|
|
15258
|
-
return [{ address: columns[3], metric: Number.isFinite(metric) ? metric : Number.MAX_SAFE_INTEGER }];
|
|
15259
|
-
}).sort((left, right) => left.metric - right.metric);
|
|
15260
|
-
for (const route of routes) {
|
|
15261
|
-
const match = devices.find((device) => device.addresses.includes(route.address));
|
|
15262
|
-
if (match)
|
|
15263
|
-
return match;
|
|
15264
|
-
}
|
|
15265
|
-
return;
|
|
15266
|
-
}
|
|
15267
|
-
async function defaultRouteDevice(devices) {
|
|
15268
|
-
try {
|
|
15269
|
-
const child = Bun.spawn({
|
|
15270
|
-
cmd: ["route.exe", "PRINT", "0.0.0.0"],
|
|
15271
|
-
stdout: "pipe",
|
|
15272
|
-
stderr: "ignore",
|
|
15273
|
-
windowsHide: true
|
|
15274
|
-
});
|
|
15275
|
-
const output = await new Response(child.stdout).text();
|
|
15276
|
-
return await child.exited === 0 ? chooseDeviceByRouteOutput(devices, output) : undefined;
|
|
15277
|
-
} catch {
|
|
15278
|
-
return;
|
|
15279
|
-
}
|
|
15280
|
-
}
|
|
15281
|
-
function isUsable(device) {
|
|
15282
|
-
const label = `${device.name} ${device.description}`.toLowerCase();
|
|
15283
|
-
return !device.loopback && device.addresses.length > 0 && !["bluetooth", "wan miniport", "vmware", "hyper-v", "zerotier"].some((value) => label.includes(value));
|
|
15284
|
-
}
|
|
15238
|
+
// src/capture/win32-system.ts
|
|
15239
|
+
import { dlopen, FFIType } from "bun:ffi";
|
|
15285
15240
|
|
|
15286
15241
|
// src/capture/ip-address.ts
|
|
15287
15242
|
function formatIpv6(data) {
|
|
@@ -15309,6 +15264,194 @@ function formatIpv6(data) {
|
|
|
15309
15264
|
return `${before}::${after}`;
|
|
15310
15265
|
}
|
|
15311
15266
|
|
|
15267
|
+
// src/capture/win32-system.ts
|
|
15268
|
+
var ERROR_SUCCESS = 0;
|
|
15269
|
+
var ERROR_INSUFFICIENT_BUFFER = 122;
|
|
15270
|
+
var TH32CS_SNAPPROCESS = 2;
|
|
15271
|
+
var AF_INET = 2;
|
|
15272
|
+
var AF_INET6 = 23;
|
|
15273
|
+
var TCP_TABLE_OWNER_PID_ALL = 5;
|
|
15274
|
+
var UDP_TABLE_OWNER_PID = 1;
|
|
15275
|
+
var ENDPOINT_LAYOUTS = [
|
|
15276
|
+
{ protocol: "tcp", family: AF_INET, rowSize: 24, addressOffset: 4, portOffset: 8, processIdOffset: 20 },
|
|
15277
|
+
{ protocol: "tcp", family: AF_INET6, rowSize: 56, addressOffset: 0, portOffset: 20, processIdOffset: 52 },
|
|
15278
|
+
{ protocol: "udp", family: AF_INET, rowSize: 12, addressOffset: 0, portOffset: 4, processIdOffset: 8 },
|
|
15279
|
+
{ protocol: "udp", family: AF_INET6, rowSize: 28, addressOffset: 0, portOffset: 20, processIdOffset: 24 }
|
|
15280
|
+
];
|
|
15281
|
+
var ROUTE_SIZE = 56;
|
|
15282
|
+
var ROUTE_INTERFACE_INDEX_OFFSET = 16;
|
|
15283
|
+
var ADDRESS_ROW_SIZE = 24;
|
|
15284
|
+
var ADDRESS_ROW_INTERFACE_INDEX_OFFSET = 4;
|
|
15285
|
+
var PROCESS_ENTRY_SIZE = process.arch === "ia32" ? 556 : 568;
|
|
15286
|
+
var PROCESS_ENTRY_ID_OFFSET = 8;
|
|
15287
|
+
var PROCESS_ENTRY_NAME_OFFSET = process.arch === "ia32" ? 36 : 44;
|
|
15288
|
+
var kernel32;
|
|
15289
|
+
var ipHelper;
|
|
15290
|
+
function findProcessIdsByName(processName) {
|
|
15291
|
+
requireWindows();
|
|
15292
|
+
const api = loadKernel32();
|
|
15293
|
+
const snapshot = api.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
15294
|
+
if (snapshot === null || snapshot === -1)
|
|
15295
|
+
throw new Error("Windows could not create a process snapshot");
|
|
15296
|
+
const entry = new Uint8Array(PROCESS_ENTRY_SIZE);
|
|
15297
|
+
const view = new DataView(entry.buffer);
|
|
15298
|
+
view.setUint32(0, PROCESS_ENTRY_SIZE, true);
|
|
15299
|
+
const expected = processName.toLowerCase();
|
|
15300
|
+
const processIds = [];
|
|
15301
|
+
try {
|
|
15302
|
+
let hasEntry = api.Process32FirstW(snapshot, entry) !== 0;
|
|
15303
|
+
while (hasEntry) {
|
|
15304
|
+
if (decodeWideString(entry, PROCESS_ENTRY_NAME_OFFSET).toLowerCase() === expected) {
|
|
15305
|
+
processIds.push(view.getUint32(PROCESS_ENTRY_ID_OFFSET, true));
|
|
15306
|
+
}
|
|
15307
|
+
hasEntry = api.Process32NextW(snapshot, entry) !== 0;
|
|
15308
|
+
}
|
|
15309
|
+
} finally {
|
|
15310
|
+
api.CloseHandle(snapshot);
|
|
15311
|
+
}
|
|
15312
|
+
return processIds.sort((left, right) => left - right);
|
|
15313
|
+
}
|
|
15314
|
+
function listOwnedEndpoints(protocols) {
|
|
15315
|
+
requireWindows();
|
|
15316
|
+
const selected = new Set(protocols);
|
|
15317
|
+
return ENDPOINT_LAYOUTS.filter((layout) => selected.has(layout.protocol)).flatMap(readEndpoints);
|
|
15318
|
+
}
|
|
15319
|
+
function defaultRouteIpv4Address() {
|
|
15320
|
+
requireWindows();
|
|
15321
|
+
const api = loadIpHelper();
|
|
15322
|
+
const route = new Uint8Array(ROUTE_SIZE);
|
|
15323
|
+
if (api.GetBestRoute(0, 0, route) !== ERROR_SUCCESS)
|
|
15324
|
+
return;
|
|
15325
|
+
const interfaceIndex = new DataView(route.buffer).getUint32(ROUTE_INTERFACE_INDEX_OFFSET, true);
|
|
15326
|
+
const table = readVariableTable((buffer, size) => api.GetIpAddrTable(buffer, size, 0));
|
|
15327
|
+
const view = tableView(table, ADDRESS_ROW_SIZE);
|
|
15328
|
+
for (let index = 0;index < view.count; index += 1) {
|
|
15329
|
+
const offset = 4 + index * ADDRESS_ROW_SIZE;
|
|
15330
|
+
if (view.data.getUint32(offset + ADDRESS_ROW_INTERFACE_INDEX_OFFSET, true) === interfaceIndex) {
|
|
15331
|
+
return formatIpv4(table, offset);
|
|
15332
|
+
}
|
|
15333
|
+
}
|
|
15334
|
+
return;
|
|
15335
|
+
}
|
|
15336
|
+
function readEndpoints(layout) {
|
|
15337
|
+
const api = loadIpHelper();
|
|
15338
|
+
const table = readVariableTable((buffer, size) => layout.protocol === "tcp" ? api.GetExtendedTcpTable(buffer, size, 0, layout.family, TCP_TABLE_OWNER_PID_ALL, 0) : api.GetExtendedUdpTable(buffer, size, 0, layout.family, UDP_TABLE_OWNER_PID, 0));
|
|
15339
|
+
const view = tableView(table, layout.rowSize);
|
|
15340
|
+
const endpoints = [];
|
|
15341
|
+
for (let index = 0;index < view.count; index += 1) {
|
|
15342
|
+
const offset = 4 + index * layout.rowSize;
|
|
15343
|
+
endpoints.push({
|
|
15344
|
+
protocol: layout.protocol,
|
|
15345
|
+
address: readAddress(table, offset + layout.addressOffset, layout.family),
|
|
15346
|
+
port: view.data.getUint16(offset + layout.portOffset, false),
|
|
15347
|
+
processId: view.data.getUint32(offset + layout.processIdOffset, true)
|
|
15348
|
+
});
|
|
15349
|
+
}
|
|
15350
|
+
return endpoints;
|
|
15351
|
+
}
|
|
15352
|
+
function readVariableTable(call) {
|
|
15353
|
+
const size = new Uint32Array(1);
|
|
15354
|
+
const initialResult = call(null, size);
|
|
15355
|
+
if (initialResult !== ERROR_INSUFFICIENT_BUFFER || !size[0]) {
|
|
15356
|
+
throw new Error(`Windows table size query failed with error ${initialResult}`);
|
|
15357
|
+
}
|
|
15358
|
+
for (let attempt = 0;attempt < 3; attempt += 1) {
|
|
15359
|
+
const capacity = size[0] ?? 0;
|
|
15360
|
+
const buffer = new Uint8Array(capacity);
|
|
15361
|
+
size[0] = buffer.byteLength;
|
|
15362
|
+
const result = call(buffer, size);
|
|
15363
|
+
if (result === ERROR_SUCCESS)
|
|
15364
|
+
return buffer.subarray(0, size[0]);
|
|
15365
|
+
if (result !== ERROR_INSUFFICIENT_BUFFER || size[0] === 0) {
|
|
15366
|
+
throw new Error(`Windows table query failed with error ${result}`);
|
|
15367
|
+
}
|
|
15368
|
+
}
|
|
15369
|
+
throw new Error("Windows table changed too frequently to read consistently");
|
|
15370
|
+
}
|
|
15371
|
+
function tableView(table, rowSize) {
|
|
15372
|
+
if (table.byteLength < 4)
|
|
15373
|
+
throw new Error("Windows returned a truncated table");
|
|
15374
|
+
const data = new DataView(table.buffer, table.byteOffset, table.byteLength);
|
|
15375
|
+
const count = data.getUint32(0, true);
|
|
15376
|
+
if (4 + count * rowSize > table.byteLength)
|
|
15377
|
+
throw new Error("Windows returned an invalid table size");
|
|
15378
|
+
return { count, data };
|
|
15379
|
+
}
|
|
15380
|
+
function readAddress(buffer, offset, family) {
|
|
15381
|
+
return family === AF_INET ? formatIpv4(buffer, offset) : formatIpv6(Buffer.from(buffer.subarray(offset, offset + 16)));
|
|
15382
|
+
}
|
|
15383
|
+
function formatIpv4(buffer, offset) {
|
|
15384
|
+
return `${buffer[offset]}.${buffer[offset + 1]}.${buffer[offset + 2]}.${buffer[offset + 3]}`;
|
|
15385
|
+
}
|
|
15386
|
+
function decodeWideString(buffer, offset) {
|
|
15387
|
+
let end = offset;
|
|
15388
|
+
while (end + 1 < buffer.byteLength && (buffer[end] !== 0 || buffer[end + 1] !== 0))
|
|
15389
|
+
end += 2;
|
|
15390
|
+
return new TextDecoder("utf-16le").decode(buffer.subarray(offset, end));
|
|
15391
|
+
}
|
|
15392
|
+
function requireWindows() {
|
|
15393
|
+
if (process.platform !== "win32")
|
|
15394
|
+
throw new Error("Windows system APIs are unavailable on this platform");
|
|
15395
|
+
}
|
|
15396
|
+
function loadKernel32() {
|
|
15397
|
+
if (kernel32)
|
|
15398
|
+
return kernel32;
|
|
15399
|
+
kernel32 = dlopen("kernel32.dll", {
|
|
15400
|
+
CreateToolhelp32Snapshot: { args: [FFIType.u32, FFIType.u32], returns: FFIType.ptr },
|
|
15401
|
+
Process32FirstW: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
|
|
15402
|
+
Process32NextW: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
|
|
15403
|
+
CloseHandle: { args: [FFIType.ptr], returns: FFIType.i32 }
|
|
15404
|
+
}).symbols;
|
|
15405
|
+
return kernel32;
|
|
15406
|
+
}
|
|
15407
|
+
function loadIpHelper() {
|
|
15408
|
+
if (ipHelper)
|
|
15409
|
+
return ipHelper;
|
|
15410
|
+
ipHelper = dlopen("iphlpapi.dll", {
|
|
15411
|
+
GetBestRoute: { args: [FFIType.u32, FFIType.u32, FFIType.ptr], returns: FFIType.u32 },
|
|
15412
|
+
GetIpAddrTable: { args: [FFIType.ptr, FFIType.ptr, FFIType.i32], returns: FFIType.u32 },
|
|
15413
|
+
GetExtendedTcpTable: {
|
|
15414
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.i32, FFIType.u32, FFIType.u32, FFIType.u32],
|
|
15415
|
+
returns: FFIType.u32
|
|
15416
|
+
},
|
|
15417
|
+
GetExtendedUdpTable: {
|
|
15418
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.i32, FFIType.u32, FFIType.u32, FFIType.u32],
|
|
15419
|
+
returns: FFIType.u32
|
|
15420
|
+
}
|
|
15421
|
+
}).symbols;
|
|
15422
|
+
return ipHelper;
|
|
15423
|
+
}
|
|
15424
|
+
|
|
15425
|
+
// src/capture/adapter-selection.ts
|
|
15426
|
+
async function resolveCaptureDevice(devices, requestedName) {
|
|
15427
|
+
if (requestedName) {
|
|
15428
|
+
const requested = devices.find((device) => device.name === requestedName);
|
|
15429
|
+
if (requested)
|
|
15430
|
+
return { device: requested, usedFallback: false };
|
|
15431
|
+
}
|
|
15432
|
+
const automatic = defaultRouteDevice(devices) ?? devices.find(isUsable) ?? devices[0];
|
|
15433
|
+
return {
|
|
15434
|
+
device: automatic,
|
|
15435
|
+
usedFallback: Boolean(requestedName),
|
|
15436
|
+
...requestedName && automatic ? { detail: "The saved adapter is unavailable; capture is using the automatically selected adapter" } : {}
|
|
15437
|
+
};
|
|
15438
|
+
}
|
|
15439
|
+
function chooseDeviceByRouteAddress(devices, address) {
|
|
15440
|
+
return devices.find((device) => device.addresses.includes(address));
|
|
15441
|
+
}
|
|
15442
|
+
function defaultRouteDevice(devices) {
|
|
15443
|
+
try {
|
|
15444
|
+
const address = defaultRouteIpv4Address();
|
|
15445
|
+
return address ? chooseDeviceByRouteAddress(devices, address) : undefined;
|
|
15446
|
+
} catch {
|
|
15447
|
+
return;
|
|
15448
|
+
}
|
|
15449
|
+
}
|
|
15450
|
+
function isUsable(device) {
|
|
15451
|
+
const label = `${device.name} ${device.description}`.toLowerCase();
|
|
15452
|
+
return !device.loopback && device.addresses.length > 0 && !["bluetooth", "wan miniport", "vmware", "hyper-v", "zerotier"].some((value) => label.includes(value));
|
|
15453
|
+
}
|
|
15454
|
+
|
|
15312
15455
|
// src/capture/link-layer.ts
|
|
15313
15456
|
var DATA_LINK = {
|
|
15314
15457
|
NULL: 0,
|
|
@@ -15352,8 +15495,9 @@ function ipVersion(data) {
|
|
|
15352
15495
|
// src/capture/npcap.ts
|
|
15353
15496
|
import { existsSync } from "fs";
|
|
15354
15497
|
import path from "path";
|
|
15355
|
-
import { CString, dlopen, FFIType, read, toArrayBuffer } from "bun:ffi";
|
|
15498
|
+
import { CString, dlopen as dlopen2, FFIType as FFIType2, read, toArrayBuffer } from "bun:ffi";
|
|
15356
15499
|
var ERROR_BUFFER_SIZE = 512;
|
|
15500
|
+
var PCAP_ERROR_PERM_DENIED = -8;
|
|
15357
15501
|
|
|
15358
15502
|
class SystemNpcapRuntime {
|
|
15359
15503
|
api;
|
|
@@ -15368,18 +15512,20 @@ class SystemNpcapRuntime {
|
|
|
15368
15512
|
if (!existsSync(dllPath)) {
|
|
15369
15513
|
return { availability: "missing", detail: "Npcap is not installed" };
|
|
15370
15514
|
}
|
|
15371
|
-
if (await isAdminOnlyInstall()) {
|
|
15372
|
-
return {
|
|
15373
|
-
availability: "admin-only",
|
|
15374
|
-
detail: "Npcap is installed for administrators only; reinstall it with that restriction unchecked"
|
|
15375
|
-
};
|
|
15376
|
-
}
|
|
15377
15515
|
try {
|
|
15378
15516
|
const api = this.load();
|
|
15379
15517
|
const version = String(api.symbols.pcap_lib_version());
|
|
15380
15518
|
if (!version.toLowerCase().includes("npcap")) {
|
|
15381
15519
|
return { availability: "error", detail: "The loaded packet capture library is not Npcap" };
|
|
15382
15520
|
}
|
|
15521
|
+
const devices = this.enumerateDevices(api);
|
|
15522
|
+
if (devices.length === 0) {
|
|
15523
|
+
return {
|
|
15524
|
+
availability: "error",
|
|
15525
|
+
detail: "Npcap reported no captureable network adapters; access may require elevation",
|
|
15526
|
+
version
|
|
15527
|
+
};
|
|
15528
|
+
}
|
|
15383
15529
|
this.readyStatus = { availability: "ready", detail: "Npcap is ready", version };
|
|
15384
15530
|
return this.readyStatus;
|
|
15385
15531
|
} catch (error) {
|
|
@@ -15390,7 +15536,9 @@ class SystemNpcapRuntime {
|
|
|
15390
15536
|
const status = await this.status();
|
|
15391
15537
|
if (status.availability !== "ready")
|
|
15392
15538
|
throw new Error(status.detail);
|
|
15393
|
-
|
|
15539
|
+
return this.enumerateDevices(this.load());
|
|
15540
|
+
}
|
|
15541
|
+
enumerateDevices(api) {
|
|
15394
15542
|
const resultPointer = new Uint8Array(8);
|
|
15395
15543
|
const errorBuffer = new Uint8Array(ERROR_BUFFER_SIZE);
|
|
15396
15544
|
const result = api.symbols.pcap_findalldevs(resultPointer, errorBuffer);
|
|
@@ -15440,6 +15588,9 @@ class SystemNpcapRuntime {
|
|
|
15440
15588
|
check(api.symbols.pcap_set_buffer_size(handle, 10 * 1024 * 1024), handle, api, "set capture buffer size");
|
|
15441
15589
|
check(api.symbols.pcap_set_immediate_mode(handle, 1), handle, api, "enable immediate mode");
|
|
15442
15590
|
const activated = api.symbols.pcap_activate(handle);
|
|
15591
|
+
if (activated === PCAP_ERROR_PERM_DENIED) {
|
|
15592
|
+
throw new Error(`Npcap denied access to ${device.description}; run elevated or reinstall Npcap without administrator-only access: ${pcapError(api, handle)}`);
|
|
15593
|
+
}
|
|
15443
15594
|
if (activated < 0)
|
|
15444
15595
|
throw new Error(`Npcap could not activate ${device.description}: ${pcapError(api, handle)}`);
|
|
15445
15596
|
const dataLink = api.symbols.pcap_datalink(handle);
|
|
@@ -15463,25 +15614,25 @@ class SystemNpcapRuntime {
|
|
|
15463
15614
|
load() {
|
|
15464
15615
|
if (this.api)
|
|
15465
15616
|
return this.api;
|
|
15466
|
-
const library =
|
|
15467
|
-
pcap_lib_version: { args: [], returns:
|
|
15468
|
-
pcap_findalldevs: { args: [
|
|
15469
|
-
pcap_freealldevs: { args: [
|
|
15470
|
-
pcap_create: { args: [
|
|
15471
|
-
pcap_set_snaplen: { args: [
|
|
15472
|
-
pcap_set_promisc: { args: [
|
|
15473
|
-
pcap_set_timeout: { args: [
|
|
15474
|
-
pcap_set_buffer_size: { args: [
|
|
15475
|
-
pcap_set_immediate_mode: { args: [
|
|
15476
|
-
pcap_activate: { args: [
|
|
15477
|
-
pcap_datalink: { args: [
|
|
15478
|
-
pcap_compile: { args: [
|
|
15479
|
-
pcap_setfilter: { args: [
|
|
15480
|
-
pcap_freecode: { args: [
|
|
15481
|
-
pcap_setnonblock: { args: [
|
|
15482
|
-
pcap_next_ex: { args: [
|
|
15483
|
-
pcap_geterr: { args: [
|
|
15484
|
-
pcap_close: { args: [
|
|
15617
|
+
const library = dlopen2(npcapDllPath(), {
|
|
15618
|
+
pcap_lib_version: { args: [], returns: FFIType2.cstring },
|
|
15619
|
+
pcap_findalldevs: { args: [FFIType2.ptr, FFIType2.ptr], returns: FFIType2.i32 },
|
|
15620
|
+
pcap_freealldevs: { args: [FFIType2.ptr], returns: FFIType2.void },
|
|
15621
|
+
pcap_create: { args: [FFIType2.cstring, FFIType2.ptr], returns: FFIType2.ptr },
|
|
15622
|
+
pcap_set_snaplen: { args: [FFIType2.ptr, FFIType2.i32], returns: FFIType2.i32 },
|
|
15623
|
+
pcap_set_promisc: { args: [FFIType2.ptr, FFIType2.i32], returns: FFIType2.i32 },
|
|
15624
|
+
pcap_set_timeout: { args: [FFIType2.ptr, FFIType2.i32], returns: FFIType2.i32 },
|
|
15625
|
+
pcap_set_buffer_size: { args: [FFIType2.ptr, FFIType2.i32], returns: FFIType2.i32 },
|
|
15626
|
+
pcap_set_immediate_mode: { args: [FFIType2.ptr, FFIType2.i32], returns: FFIType2.i32 },
|
|
15627
|
+
pcap_activate: { args: [FFIType2.ptr], returns: FFIType2.i32 },
|
|
15628
|
+
pcap_datalink: { args: [FFIType2.ptr], returns: FFIType2.i32 },
|
|
15629
|
+
pcap_compile: { args: [FFIType2.ptr, FFIType2.ptr, FFIType2.cstring, FFIType2.i32, FFIType2.u32], returns: FFIType2.i32 },
|
|
15630
|
+
pcap_setfilter: { args: [FFIType2.ptr, FFIType2.ptr], returns: FFIType2.i32 },
|
|
15631
|
+
pcap_freecode: { args: [FFIType2.ptr], returns: FFIType2.void },
|
|
15632
|
+
pcap_setnonblock: { args: [FFIType2.ptr, FFIType2.i32, FFIType2.ptr], returns: FFIType2.i32 },
|
|
15633
|
+
pcap_next_ex: { args: [FFIType2.ptr, FFIType2.ptr, FFIType2.ptr], returns: FFIType2.i32 },
|
|
15634
|
+
pcap_geterr: { args: [FFIType2.ptr], returns: FFIType2.cstring },
|
|
15635
|
+
pcap_close: { args: [FFIType2.ptr], returns: FFIType2.void }
|
|
15485
15636
|
});
|
|
15486
15637
|
this.api = library;
|
|
15487
15638
|
return this.api;
|
|
@@ -15575,20 +15726,6 @@ function readCStringBuffer(buffer) {
|
|
|
15575
15726
|
function npcapDllPath() {
|
|
15576
15727
|
return path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "Npcap", "wpcap.dll");
|
|
15577
15728
|
}
|
|
15578
|
-
async function isAdminOnlyInstall() {
|
|
15579
|
-
try {
|
|
15580
|
-
const child = Bun.spawn({
|
|
15581
|
-
cmd: ["reg.exe", "query", "HKLM\\SYSTEM\\CurrentControlSet\\Services\\npcap\\Parameters", "/v", "AdminOnly"],
|
|
15582
|
-
stdout: "pipe",
|
|
15583
|
-
stderr: "ignore",
|
|
15584
|
-
windowsHide: true
|
|
15585
|
-
});
|
|
15586
|
-
const output = await new Response(child.stdout).text();
|
|
15587
|
-
return await child.exited === 0 && /AdminOnly\s+REG_DWORD\s+0x1\b/i.test(output);
|
|
15588
|
-
} catch {
|
|
15589
|
-
return false;
|
|
15590
|
-
}
|
|
15591
|
-
}
|
|
15592
15729
|
function errorMessage(error) {
|
|
15593
15730
|
return error instanceof Error ? error.message : String(error);
|
|
15594
15731
|
}
|
|
@@ -15614,7 +15751,7 @@ function parseIpv4(data, context) {
|
|
|
15614
15751
|
const declaredLength = data.readUInt16BE(2);
|
|
15615
15752
|
if (declaredLength < headerLength)
|
|
15616
15753
|
return;
|
|
15617
|
-
return parseTransport(data, headerLength, Math.min(declaredLength, data.length), declaredLength > data.length, data[9], 4,
|
|
15754
|
+
return parseTransport(data, headerLength, Math.min(declaredLength, data.length), declaredLength > data.length, data[9], 4, formatIpv42(data, 12), formatIpv42(data, 16), context);
|
|
15618
15755
|
}
|
|
15619
15756
|
function parseIpv6(data, context) {
|
|
15620
15757
|
if (data.length < 40)
|
|
@@ -15685,7 +15822,7 @@ function parseTransport(data, offset, packetEnd, truncated, protocol, ipVersion2
|
|
|
15685
15822
|
}
|
|
15686
15823
|
return;
|
|
15687
15824
|
}
|
|
15688
|
-
function
|
|
15825
|
+
function formatIpv42(data, offset) {
|
|
15689
15826
|
return `${data[offset]}.${data[offset + 1]}.${data[offset + 2]}.${data[offset + 3]}`;
|
|
15690
15827
|
}
|
|
15691
15828
|
|
|
@@ -15705,7 +15842,7 @@ class WindowsTargetTracker {
|
|
|
15705
15842
|
refreshing = false;
|
|
15706
15843
|
published = false;
|
|
15707
15844
|
lastRefreshError;
|
|
15708
|
-
constructor(processName, protocols, onStatus, provider = new
|
|
15845
|
+
constructor(processName, protocols, onStatus, provider = new WindowsTargetSnapshotProvider, onWarning = () => {}, refreshIntervalMs = TARGET_REFRESH_INTERVAL_MS) {
|
|
15709
15846
|
this.processName = processName;
|
|
15710
15847
|
this.protocols = protocols;
|
|
15711
15848
|
this.onStatus = onStatus;
|
|
@@ -15766,58 +15903,18 @@ class WindowsTargetTracker {
|
|
|
15766
15903
|
}
|
|
15767
15904
|
}
|
|
15768
15905
|
|
|
15769
|
-
class
|
|
15906
|
+
class WindowsTargetSnapshotProvider {
|
|
15770
15907
|
async snapshot(processName, protocols) {
|
|
15771
|
-
const
|
|
15772
|
-
cmd: ["tasklist.exe", "/FI", `IMAGENAME eq ${processName}`, "/FO", "CSV", "/NH"],
|
|
15773
|
-
stdout: "pipe",
|
|
15774
|
-
stderr: "ignore",
|
|
15775
|
-
windowsHide: true
|
|
15776
|
-
});
|
|
15777
|
-
const processOutput = await new Response(process2.stdout).text();
|
|
15778
|
-
const processIds = await process2.exited === 0 ? parseTaskList(processOutput) : [];
|
|
15908
|
+
const processIds = findProcessIdsByName(processName);
|
|
15779
15909
|
if (processIds.length === 0)
|
|
15780
15910
|
return { processIds, endpoints: [] };
|
|
15781
|
-
const outputs = await Promise.all(protocols.map(async (protocol) => {
|
|
15782
|
-
const child = Bun.spawn({
|
|
15783
|
-
cmd: ["netstat.exe", "-ano", "-p", protocol.toUpperCase()],
|
|
15784
|
-
stdout: "pipe",
|
|
15785
|
-
stderr: "ignore",
|
|
15786
|
-
windowsHide: true
|
|
15787
|
-
});
|
|
15788
|
-
const output = await new Response(child.stdout).text();
|
|
15789
|
-
return await child.exited === 0 ? parseNetstat(output, protocol) : [];
|
|
15790
|
-
}));
|
|
15791
15911
|
const selected = new Set(processIds);
|
|
15792
|
-
return {
|
|
15912
|
+
return {
|
|
15913
|
+
processIds,
|
|
15914
|
+
endpoints: listOwnedEndpoints(protocols).filter((endpoint) => selected.has(endpoint.processId))
|
|
15915
|
+
};
|
|
15793
15916
|
}
|
|
15794
15917
|
}
|
|
15795
|
-
function parseTaskList(output) {
|
|
15796
|
-
return output.split(/\r?\n/).flatMap((line) => {
|
|
15797
|
-
const match = line.match(/^"(?:[^"]|"")*","(\d+)"/);
|
|
15798
|
-
return match ? [Number(match[1])] : [];
|
|
15799
|
-
}).sort((left, right) => left - right);
|
|
15800
|
-
}
|
|
15801
|
-
function parseNetstat(output, protocol) {
|
|
15802
|
-
return output.split(/\r?\n/).flatMap((line) => {
|
|
15803
|
-
const columns = line.trim().split(/\s+/);
|
|
15804
|
-
if (columns[0]?.toLowerCase() !== protocol || columns.length < (protocol === "tcp" ? 5 : 4))
|
|
15805
|
-
return [];
|
|
15806
|
-
const local = parseEndpoint(columns[1]);
|
|
15807
|
-
const processId = Number(columns[protocol === "tcp" ? 4 : 3]);
|
|
15808
|
-
return local && Number.isInteger(processId) ? [{ protocol, ...local, processId }] : [];
|
|
15809
|
-
});
|
|
15810
|
-
}
|
|
15811
|
-
function parseEndpoint(value) {
|
|
15812
|
-
const bracketed = value.match(/^\[(.*)]:(\d+)$/);
|
|
15813
|
-
if (bracketed)
|
|
15814
|
-
return { address: bracketed[1], port: Number(bracketed[2]) };
|
|
15815
|
-
const split = value.lastIndexOf(":");
|
|
15816
|
-
if (split < 0)
|
|
15817
|
-
return;
|
|
15818
|
-
const port = Number(value.slice(split + 1));
|
|
15819
|
-
return Number.isInteger(port) ? { address: value.slice(0, split), port } : undefined;
|
|
15820
|
-
}
|
|
15821
15918
|
function endpointKey(protocol, address, port) {
|
|
15822
15919
|
return `${protocol}|${address}|${port}`;
|
|
15823
15920
|
}
|