UNPKG

icmp

Version:

Internet Control Message Protocol in Node

172 lines (131 loc) 4.58 kB
const raw = require('raw-socket'); const net = require('net'); const dns = require('dns'); class ICMP { constructor(host) { this.host = host; this.ip = ''; this.socket = raw.createSocket({ protocol: raw.Protocol.ICMP }); this.open = false; this.type = ''; this.code = ''; } close() { this.socket.close(); if (this._timeout) { clearTimeout(this._timeout); } } _resolveIP() { return new Promise((resolve, reject) => { if (!this.ip) { if (net.isIPv4(this.host)) { this.ip = this.host; return resolve(); } dns.resolve4(this.host, (err, ips) => { if (err) return reject(err); this.ip = ips[0]; resolve(); }); } else { resolve(); } }); } _queue(header, timeout = 5000) { return new Promise(async (resolve, reject) => { try { await this._resolveIP(); } catch (e) { return reject(e); } this.socket.send(header, 0, header.length, this.ip, (err, source) => { if (err) { return reject(err); } this._timeout = setTimeout(() => { resolve(); this.close(); }, timeout); this.start = process.hrtime(); }); this.socket.on('message', (buffer, source) => { if (source === this.ip) { const NS_PER_SEC = 1e9; this.diff = process.hrtime(this.start); this.elapsed = (this.diff[0] + this.diff[1] / NS_PER_SEC) * 1000; const offset = 20; const type = buffer.readUInt8(offset); const code = buffer.readUInt8(offset + 1); this.parse(type, code); this.close(); resolve(); } }); this.socket.on('error', err => { reject(err); }); }); } createHeader(data) { const datastr = String(data); /** * We need 8 bytes (4 octets) for ICMP headers and 1 byte more per data's char */ const header = Buffer.alloc(8 + datastr.length); /** * Fill data part to prevent network leaking */ header.fill(0, 8); /** * Type */ header.writeUInt8(8, 0); header.writeUInt8(0, 1); header.writeUInt16BE(0, 2); header.writeUInt16LE(process.pid % 65535, 4); header.write(datastr, 8); return raw.writeChecksum(header, 2, raw.createChecksum(header)); } async send(data = "", timeout = 5000) { const header = this.createHeader(data); return await this._queue(header, timeout); } static async send(host, data = "", timeout = 5000) { const obj = new ICMP(host); await obj.send(data, timeout); return obj; } ping(timeout = 5000) { return this.send('', timeout); } static ping(host, timeout = 5000) { return this.send(host, '', timeout); } listen(cb = (buffer, source) => { }) { return this.socket.on('message', cb); } static listen(cb = (buffer, source) => { }) { const obj = new ICMP(null); return obj.listen(cb); } parse(type, code) { const ECHOMessageType = ['REPLY', 'NA', 'NA', 'DESTINATION_UNREACHABLE', 'SOURCE_QUENCH', 'REDIRECT']; const DestinationUnreachableCode = ['NET', 'HOST', 'PROTOCOL', 'PORT', 'FRAGMENTATION', 'ROUTE_FAILED', 'NET_UNKNOWN', 'HOST_UNKNOWN', 'HOST_ISOLATED', 'NET_PROHIBITED', 'HOST_PROHIBITED', 'NET_UNREACHABLE', 'HOST_UNREACHABLE', 'COMM_PROHIBITED', 'HOST_PRECEDENCE', 'PRECEDENCE_CUTOFF']; const RedirectCode = ['NETWORK', 'HOST', 'SERVICE_NETWORK', 'HOST_NETWORK']; this.type = 'OTHER'; this.code = 'NO_CODE'; this.open = (type === 0); if (type < ECHOMessageType.length) { this.type = ECHOMessageType[type]; } switch (type) { case 3: this.code = DestinationUnreachableCode[code]; break; case 5: this.code = RedirectCode[code]; break; } } } module.exports = ICMP;