netkitty
Version:
Network tools kit
324 lines (323 loc) • 11.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Capture = void 0;
const GetNetworkInterfaces_1 = require("./GetNetworkInterfaces");
const node_fs_1 = require("node:fs");
const GetDeviceCaptureTemporaryFilename_1 = require("../GetDeviceCaptureTemporaryFilename");
const node_path_1 = __importDefault(require("node:path"));
const is_electron_1 = __importDefault(require("is-electron"));
const childProcess = __importStar(require("child_process"));
const electron_1 = require("electron");
const PipeServer_1 = require("../pipe/PipeServer");
const crypto_1 = require("crypto");
const promises_1 = require("node:fs/promises");
const events_1 = __importDefault(require("events"));
const DeviceNotFoundError_1 = require("../../errors/DeviceNotFoundError");
const node_os_1 = require("node:os");
const PCAPGenerator_1 = require("../pcap/PCAPGenerator");
class Capture extends events_1.default {
#workerModule = node_path_1.default.resolve(__dirname, './workers/CaptureWorker.js');
#device;
#tmpDir;
#temporaryFilename;
#pipeServer;
#hasWorker = false;
#worker;
#workerId;
#workerSocket;
#workerDestroying = false;
#started = false;
#paused = false;
#operating = false;
#filter = '';
#count = 0;
get filter() {
return this.#filter;
}
get temporaryFilename() {
return this.#temporaryFilename;
}
get count() {
return this.#count;
}
/**
* Constructor
* @param options
*/
constructor(options) {
super();
this.#workerModule = options.workerModule ? options.workerModule : this.#workerModule;
this.#device = options.device;
this.#filter = options.filter ? options.filter : '';
this.#tmpDir = options.tmpDir ? options.tmpDir : node_path_1.default.resolve((0, node_os_1.tmpdir)(), 'netkitty-tmp');
if (!options.workerModule) {
//Use origin worker module, check capture device is available
if (!(0, GetNetworkInterfaces_1.GetNetworkInterfaces)().filter((availableDevice) => availableDevice.name === this.#device).length)
throw new DeviceNotFoundError_1.DeviceNotFoundError(`Device ${this.#device} not found`);
}
this.#temporaryFilename = options.temporaryFilename ? options.temporaryFilename : (0, GetDeviceCaptureTemporaryFilename_1.GetDeviceCaptureTemporaryFilename)(this.#device, this.#tmpDir);
this.#pipeServer = new PipeServer_1.PipeServer();
this.cleanTemporaryFile();
(0, node_fs_1.writeFileSync)(this.#temporaryFilename, (0, PCAPGenerator_1.GeneratePCAPHeader)());
}
/**
* Create and initialize capture worker
* @protected
*/
createCaptureWorker() {
this.#hasWorker = true;
this.#workerId = `CW_${Date.now().toString(16)}${(0, crypto_1.randomInt)(32).toString(16)}`;
const env = {
captureWorkerId: this.#workerId,
captureDevice: this.#device,
captureFilter: this.#filter,
captureTemporaryFilename: this.#temporaryFilename,
socketPath: this.#pipeServer.socketPath
};
this.getWorkerSocket().then((socket) => {
socket.on('packet', (packetInfo) => {
this.emit('rawPacket', packetInfo.index, packetInfo.packet, packetInfo.seconds, packetInfo.microseconds);
this.emit('packet', packetInfo);
this.#count += 1;
});
});
this.#worker = (0, is_electron_1.default)() ? electron_1.utilityProcess.fork(this.#workerModule, [], { env: env }) : childProcess.fork(this.#workerModule, [], { env: env });
}
/**
* Destroy capture worker
* @protected
*/
async destroyCaptureWorker() {
if (!this.#hasWorker)
return;
if (this.#workerDestroying) {
while (this.#workerDestroying)
await new Promise(resolve => setTimeout(resolve, 10));
return;
}
this.#workerDestroying = true;
await new Promise(resolve => {
if (this.#workerSocket && this.#worker) {
const disconnectHandler = () => {
clearTimeout(notifyExitTimeout);
return resolve();
};
this.#workerSocket.once('disconnect', disconnectHandler);
this.#workerSocket.notify('exit');
const notifyExitTimeout = setTimeout(() => {
this.#workerSocket?.off('disconnect', disconnectHandler);
this.#worker?.kill('SIGINT');
return resolve();
}, 10000);
}
});
this.#workerSocket = undefined;
this.#worker = undefined;
this.#workerId = undefined;
this.#workerDestroying = false;
this.#hasWorker = false;
}
/**
* Get worker socket
* @protected
*/
async getWorkerSocket() {
return new Promise(resolve => {
if (!this.#workerSocket) {
this.#pipeServer.once('connect', (clientSocket) => {
this.#workerSocket = clientSocket;
return resolve(this.#workerSocket);
});
}
else {
return resolve(this.#workerSocket);
}
});
}
/**
* Clean resources before start capture
* @param removeTmpFile
* @protected
*/
async cleanResources(removeTmpFile = true) {
//Clean cached capture data
if ((0, node_fs_1.existsSync)(this.#temporaryFilename) && removeTmpFile)
this.cleanTemporaryFile();
//Clean old worker resources
if (this.#worker && this.#workerSocket)
await this.destroyCaptureWorker();
}
/**
* Clean temporary pcap file
* @protected
*/
cleanTemporaryFile() {
(0, node_fs_1.rmSync)(this.#temporaryFilename, {
recursive: true,
force: true
});
}
/**
* Wait other operation done
* @protected
*/
async waitOperationDone() {
while (this.#operating)
await new Promise(resolve => setTimeout(resolve, 10));
}
/**
* Start capture packets
*/
async start() {
if (this.#paused)
return await this.resume();
if (this.#started)
return;
await this.waitOperationDone();
this.#operating = true;
try {
this.#started = true;
await this.cleanResources(false);
this.createCaptureWorker();
const workerSocket = await this.getWorkerSocket();
await workerSocket.invoke('start');
}
catch (e) {
this.#started = false;
this.#operating = false;
throw e;
}
this.#operating = false;
}
/**
* Stop capture packets
*/
async stop() {
if (!this.#started)
return;
if (this.#workerDestroying)
return;
await this.waitOperationDone();
this.#operating = true;
const workerSocket = await this.getWorkerSocket();
await workerSocket.invoke('stop');
while (await workerSocket.invoke('count') !== this.count)
await new Promise(resolve => setTimeout(resolve, 10));
await this.destroyCaptureWorker();
this.#started = false;
this.#operating = false;
}
/**
* Pause capture packets
*/
async pause() {
if (!this.#started)
return;
if (!this.#hasWorker)
return;
if (this.#workerDestroying)
return;
await this.waitOperationDone();
this.#operating = true;
const workerSocket = await this.getWorkerSocket();
await workerSocket.invoke('stop');
while (await workerSocket.invoke('count') !== this.count)
await new Promise(resolve => setTimeout(resolve, 10));
this.#paused = true;
this.#operating = false;
}
/**
* Resume capture packets
*/
async resume() {
if (!this.#started)
return;
if (!this.#hasWorker)
return;
if (this.#workerDestroying)
return;
await this.waitOperationDone();
this.#operating = true;
const workerSocket = await this.getWorkerSocket();
await workerSocket.invoke('start');
this.#paused = false;
this.#operating = false;
}
/**
* Set capture filter
* @param filter
*/
async setFilter(filter) {
if (!this.#started)
return;
if (!this.#hasWorker)
return;
if (this.#workerDestroying)
return;
await this.waitOperationDone();
this.#operating = true;
const workerSocket = await this.getWorkerSocket();
await workerSocket.invoke('setFilter', filter);
this.#filter = filter;
this.#operating = false;
}
/**
* Save captured pcap to destination
* @param destination
*/
async saveTo(destination) {
await (0, promises_1.cp)(this.temporaryFilename, destination);
}
/**
* Dispose capture
*/
async dispose() {
await this.stop();
await this.cleanResources();
}
on(eventName, listener) {
super.on(eventName, listener);
return this;
}
once(eventName, listener) {
super.once(eventName, listener);
return this;
}
}
exports.Capture = Capture;