UNPKG

over-the-wire

Version:

Network inspection library for Node

203 lines (172 loc) 5.34 kB
const { Duplex } = require('stream'); const { PcapDevice: LiveDeviceCxx } = require('#lib/bindings'); const { pick } = require('#lib/pick'); const { Packet } = require('#lib/packet'); const optionsKeys = [ 'capture', 'parse', 'mode', 'direction', 'packetBufferTimeoutMs', 'packetBufferSize', 'snapshotLength', 'nflogGroup', ]; const manualOptionsKeys = ['filter', 'iface']; const getOptions = obj => pick(obj, ...optionsKeys, ...manualOptionsKeys); /** * @typedef {Object} LiveDeviceOptions * @property {string} [mode] - The mode of the device, either "promiscuous" or "normal". * @property {string} [direction] - The direction of packet capture, either "inout", "in", or "out". * @property {number} [packetBufferTimeoutMs] - The packet buffer timeout in milliseconds. * @property {number} [packetBufferSize] - The size of the packet buffer. * @property {number} [snapshotLength] - The snapshot length for packet capture. * @property {number} [nflogGroup] - The NFLOG group. * @property {string} [iface] - The network interface name. * @property {string} [filter] - The filter string for packet capture. */ /** * @typedef {Object} DeviceStats * @property {number} packetsDrop - The number of packets dropped. * @property {number} packetsDropByInterface - The number of packets dropped by the interface. * @property {number} packetsRecv - The number of packets received. */ /** * @typedef {Object} InterfaceInfo * @property {string} name - The name of the network interface. * @property {string} description - The description of the network interface. * @property {string} mac - The MAC address of the network interface. * @property {string} gateway - The gateway address of the network interface. * @property {number} mtu - The Maximum Transmission Unit size. * @property {string} linktype - The link type of the network interface. * @property {string[]} dnsServers - The DNS servers associated with the network interface. * @property {string[]} addresses - The IP addresses associated with the network interface. */ /** * Duplex stream for capturing and injecting packets on a specific device. * @extends Duplex */ class LiveDevice extends Duplex { /** * Creates an instance of LiveDevice. * @param {LiveDeviceOptions} options - The options for the LiveDevice instance. */ constructor(options = {}) { super({ objectMode: true }); this.options = getOptions(options); this.isOpen = false; this.capturing = false; this.optionsChanged = false; optionsKeys.forEach(opt => { Object.defineProperty(this, opt, { get() { return this.options[opt]; }, set(val) { this.optionsChanged = true; return this.options[opt] = val; }, }); }); this.options.push = (buffer) => { if (!this._ifaceCached) { this._ifaceCached = this.iface; } const res = this.push(new Packet({ buffer, iface: this._iface })); if (!res) { this.pcapInternal.stopCapture(); this.capturing = false; } }; this.pcapInternal = new LiveDeviceCxx(this.options); } _construct(callback) { if (this.optionsChanged) { this.pcapInternal.setConfig(this.options); } if (!this.pcapInternal) { return callback(); } try { this.pcapInternal.open(); } catch (err) { return callback(err); } this.isOpen = true; if (this.options.filter) { this.pcapInternal.setFilter(this.options.filter); } callback(); } _read(size) { if (this.options.capture === false) { this.push(null); } else if (!this.capturing) { this.pcapInternal.startCapture(); this.capturing = true; } } _write(chunk, encoding, callback) { if (chunk instanceof Packet) { return this.pcapInternal._write(chunk.buffer, callback); } if (Buffer.isBuffer(chunk) || ArrayBuffer.isView(chunk)) { if (chunk.length > 0) { return this.pcapInternal._write(chunk, callback); } else { callback(); } } else { callback(new Error('Invalid argument - expected Packet | Buffer | TypedArray')); } } _writev(chunks, callback) { return this.pcapInternal._write(chunks.map(e => e.chunk instanceof Packet ? e.chunk.buffer : e.chunk), callback); } _destroy(err, callback) { if (this.pcapInternal) { this.pcapInternal._destroy(); } callback(err); } _final(callback) { this.pcapInternal._destroy(); delete this.pcapInternal; } /** * The statistics of the device. * @throws {Error} If the device is not open. * @type {DeviceStats} */ get stats() { if (!this.isOpen) { throw new Error('Device is not open'); } return this.pcapInternal.stats; } /** * The filter for the device. * @type {string} */ set filter(filter) { this.options.filter = filter; if (this.isOpen) { this.pcapInternal.setFilter(filter); } return filter; } get filter() { return this.options.filter; } set iface(iface) { this.options.iface = iface; } /** * The interface information for the device. * @type {InterfaceInfo} */ get iface() { return this.pcapInternal.interfaceInfo; } } module.exports = { LiveDevice };