UNPKG

netkitty

Version:
324 lines (323 loc) 11.1 kB
"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;