UNPKG

over-the-wire

Version:

Network inspection library for Node

328 lines (285 loc) 8.25 kB
const { compile } = require('struct-compile'); const { checksums } = require('#lib/bindings'); const { OsiModelLayers } = require('./osi'); const { ICMPTypes } = require('./enums'); const child = require('./child'); const mixins = require('./mixins'); const { ICMPHeader } = compile(` //@NE struct ICMPHeader { uint8_t type; uint8_t code; uint16_t checksum; } __attribute__(packed); `); const { ICMPEchoHeader } = compile(` //@NE struct ICMPEchoHeader { uint16_t id; uint16_t sequence; uint64_t timestamp; } __attribute__(packed); `); const { ICMPTimestampHeader } = compile(` //@NE struct ICMPTimestampHeader { uint16_t id; uint16_t sequence; uint32_t originateTimestamp; uint32_t receiveTimestamp; uint32_t transmitTimestamp; } __attribute__(packed); `); const { ICMPDestUnreachableHeader } = compile(` //@NE struct ICMPDestUnreachableHeader { uint16_t unused; uint16_t nextHopMTU; } __attribute__(packed); `); const { ICMPTimeExceededHeader } = compile(` //@NE struct ICMPTimeExceededHeader { uint32_t unused; } __attribute__(packed); `); const { ICMPParamProblemHeader } = compile(` //@NE struct ICMPParamProblemHeader { uint8_t errorOctetPointer; uint8_t unused[3]; } __attribute__(packed); `); const childProto = {}; const lookupChild = child.lookupChild(childProto); const lookupKey = child.lookupKey(childProto); /** * ICMP protocol layer * @class * @implements {Layer} * @property {number} type - ICMP message type (see ICMPTypes) * @property {number} code - ICMP message code * @property {number} checksum - ICMP checksum */ class ICMP extends ICMPHeader { name = 'ICMP'; static osi = OsiModelLayers.Network; osi = OsiModelLayers.Network; /** * @param {Buffer|Object} data - Input buffer or object with protocol fields * @param {Object} opts - Options for the layer */ constructor(data = {}, opts = {}) { const isObj = typeof data == 'object' && !Buffer.isBuffer(data); if (isObj) { super(data, { toAlloc: ICMP.toAlloc(data) }); } else { super(data); } mixins.ctor(this, data, opts); if (isObj) { this.merge(data); } } _customPayload() { return this._buf.subarray(ICMPHeader.prototype.config.length); } merge(data) { super.merge(data); const tailBuffer = this._customPayload(); if (this.isEchoRequest || this.isEchoReply) { const echo = new ICMPEchoHeader(tailBuffer); echo.merge(data); } else if (this.isTimestampRequest || this.isTimestampReply) { const timestamp = new ICMPTimestampHeader(tailBuffer); timestamp.merge(data); } else if (this.isDestinationUnreachable) { const unreachable = new ICMPDestUnreachableHeader(tailBuffer); unreachable.merge(data); } else if (this.isTimeExceeded) { const timeExceeded = new ICMPTimeExceededHeader(tailBuffer); timeExceeded.merge(data); } else if (this.isParameterProblem) { const paramProblem = new ICMPParamProblemHeader(tailBuffer); paramProblem.merge(data); } } static _toAllocForType(type) { let customPayloadLength = 0; switch (type) { case ICMPTypes.EchoRequest: case ICMPTypes.EchoReply: customPayloadLength = ICMPEchoHeader.prototype.config.length; break; case ICMPTypes.TimestampRequest: case ICMPTypes.TimestampReply: customPayloadLength = ICMPTimestampHeader.prototype.config.length; break; case ICMPTypes.DestinationUnreachable: customPayloadLength = ICMPDestUnreachableHeader.prototype.config.length; break; case ICMPTypes.TimeExceeded: customPayloadLength = ICMPTimeExceededHeader.prototype.config.length; break; case ICMPTypes.ParameterProblem: customPayloadLength = ICMPParamProblemHeader.prototype.config.length; break; } return customPayloadLength + ICMPHeader.prototype.config.length; } /** * Get the required allocation size based on ICMP message type * @param {Object} data - The data object containing type and other fields * @returns {number} Required allocation size */ static toAlloc(data) { return ICMP._toAllocForType(data.type); } get length() { try { return ICMP._toAllocForType(this.type); } catch(err) { return super.length; } } /** * Get the name of the ICMP message type * @returns {string} Name of the ICMP message type */ get typeName() { return Object.entries(ICMPTypes).find(([_, value]) => value === this.type)?.[0] || 'Unknown'; } /** * Check if this is an Echo Request message * @returns {boolean} */ get isEchoRequest() { return this.type === ICMPTypes.EchoRequest; } /** * Check if this is an Echo Reply message * @returns {boolean} */ get isEchoReply() { return this.type === ICMPTypes.EchoReply; } /** * Check if this is a Destination Unreachable message * @returns {boolean} */ get isDestinationUnreachable() { return this.type === ICMPTypes.DestinationUnreachable; } /** * Check if this is a Time Exceeded message * @returns {boolean} */ get isTimeExceeded() { return this.type === ICMPTypes.TimeExceeded; } /** * Check if this is a Parameter Problem message * @returns {boolean} */ get isParameterProblem() { return this.type === ICMPTypes.ParameterProblem; } /** * Check if this is a Timestamp Request message * @returns {boolean} */ get isTimestampRequest() { return this.type === ICMPTypes.TimestampRequest; } /** * Check if this is a Timestamp Reply message * @returns {boolean} */ get isTimestampReply() { return this.type === ICMPTypes.TimestampReply; } toObject() { const base = { ...super.toObject(), typeName: this.typeName, isEchoRequest: this.isEchoRequest, isEchoReply: this.isEchoReply, isDestinationUnreachable: this.isDestinationUnreachable, isTimeExceeded: this.isTimeExceeded, isParameterProblem: this.isParameterProblem, isTimestampRequest: this.isTimestampRequest, isTimestampReply: this.isTimestampReply }; const tailBuffer = this._customPayload(); if (this.isEchoRequest || this.isEchoReply) { const echo = new ICMPEchoHeader(tailBuffer); return { ...base, id: echo.id, sequence: echo.sequence, timestamp: echo.timestamp }; } if (this.isTimestampRequest || this.isTimestampReply) { const timestamp = new ICMPTimestampHeader(tailBuffer); return { ...base, id: timestamp.id, sequence: timestamp.sequence, originateTimestamp: timestamp.originateTimestamp, receiveTimestamp: timestamp.receiveTimestamp, transmitTimestamp: timestamp.transmitTimestamp }; } if (this.isDestinationUnreachable) { const unreachable = new ICMPDestUnreachableHeader(tailBuffer); return { ...base, unused: unreachable.unused, nextHopMTU: unreachable.nextHopMTU }; } if (this.isTimeExceeded) { const timeExceeded = new ICMPTimeExceededHeader(tailBuffer); return { ...base, unused: timeExceeded.unused }; } if (this.isParameterProblem) { const paramProblem = new ICMPParamProblemHeader(tailBuffer); return { ...base, errorOctetPointer: paramProblem.errorOctetPointer, unused: paramProblem.unused }; } return base; } /** * Calculates and updates the checksum for the IPv4 layer. * This method mutates the object by setting the `checksum` property * based on the current state of the `buffer`. */ calculateChecksum() { this.checksum = checksums.ip(this.buffer.subarray(0, this.length)); } checksums(obj) { if (!obj.checksum) { this.calculateChecksum(); } } defaults(obj, layers) { if (!obj.type) { this.type = ICMPTypes.EchoRequest; } if (!obj.code) { this.code = 0; } } nextProto(layers) { return lookupChild(layers, this.type, this); } } module.exports = { ICMP };