UNPKG

over-the-wire

Version:

Network inspection library for Node

244 lines (205 loc) 7 kB
const { compile } = require('struct-compile'); const { OsiModelLayers } = require('./osi'); const { IPProtocolTypes, IPv4OptionTypes } = require('./enums'); const { inetPton, inetNtop, ntohs } = require('#lib/converters'); const { AF_INET } = require('#lib/socket'); const { checksums } = require('#lib/bindings'); const child = require('./child'); const mixins = require('./mixins'); const IP_DONT_FRAGMENT = 0x40; const IP_MORE_FRAGMENTS = 0x20; const { IPv4Header } = compile(` //@NE struct IPv4Header { //IP header length, has the value of 5 for IPv4 uint8_t headerLength:4; //IP version number, has the value of 4 for IPv4 uint8_t version:4; //type of service, same as Differentiated Services Code Point (DSCP) uint8_t typeOfService; //Entire packet (fragment) size, including header and data, in bytes uint16_t totalLength; //Identification field. Primarily used for uniquely identifying the group of fragments of a single IP datagram uint16_t id; //Fragment offset field, measured in units of eight-byte blocks (64 bits) @LE uint16_t fragmentOffsetRaw; //An eight-bit time to live field helps prevent datagrams from persisting (e.g. going in circles) on an internet. In practice, the field has become a hop count uint8_t timeToLive; //Defines the protocol used in the data portion of the IP datagram. Must be one of ::IPProtocolTypes uint8_t protocol; //Error-checking of the header uint16_t checksum; //@LE IPv4 address of the sender of the packet uint32_t src; //@LE IPv4 address of the receiver of the packet uint32_t dst; /*The options start here.*/ } __attribute__(packed); `); const { length: baseLength } = IPv4Header.prototype.config; const childProto = { [IPProtocolTypes.UDP]: 'UDP', [IPProtocolTypes.TCP]: 'TCP', [IPProtocolTypes.ICMP]: 'ICMP', [IPProtocolTypes.GRE]: 'GRE', [IPProtocolTypes.IGMP]: 'IGMP', [IPProtocolTypes.AH]: 'AuthenticationHeader', [IPProtocolTypes.ESP]: 'ESP', [IPProtocolTypes.IPV6]: 'IPv6', [IPProtocolTypes.VRRP]: 'VRRP', }; const lookupChild = child.lookupChild(childProto); const lookupKey = child.lookupKey(childProto); /** * IPv4 protocol layer * @class * @implements {Layer} * @property {number} headerLength - IP header length, has the value of 5 for IPv4. * @property {number} version - IP version number, has the value of 4 for IPv4. * @property {number} typeOfService - type of service, same as Differentiated Services Code Point (DSCP) * @property {number} totalLength - Entire packet (fragment) size, including header and data, in bytes * @property {number} id - Identification field. Primarily used for uniquely identifying the group of fragments of a single IP datagram * @property {number} fragmentOffsetRaw - Fragment offset field, measured in units of eight-byte blocks (64 bits) @LE * @property {number} timeToLive - An eight-bit time to live field helps prevent datagrams from persisting (e.g. going in circles) on an internet. In practice, the field has become a hop count * @property {number} protocol - Defines the protocol used in the data portion of the IP datagram. Must be one of ::IPProtocolTypes * @property {number} checksum - Error-checking of the header * @property {string} src - IPv4 address of the sender of the packet * @property {string} dst - IPv4 address of the receiver of the packet * @implements {Layer} */ class IPv4 extends IPv4Header { name = 'IPv4'; /** * @param {Buffer|Object} data - Input buffer or object with protocol fields. * @param {Object} opts - Options for the layer. */ constructor(data = {}, opts = {}) { super(data); mixins.ctor(this, data, opts); this.length = opts.allocated ?? this.headerLength * 4; } static toAlloc = (data) => baseLength + IPv4.prototype.optionsLength(data.options); static OptionTypes = IPv4OptionTypes; osi = OsiModelLayers.Network; /** * The source IP address in human-readable format. * @type {string} */ get src() { return inetNtop(AF_INET, super.src); } set src(val) { super.src = inetPton(AF_INET, val); } /** * The destination IP address in human-readable format. * @type {string} */ get dst() { return inetNtop(AF_INET, super.dst); } set dst(val) { super.dst = inetPton(AF_INET, val); } /** * 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)); } /** * @type {number} */ get fragmentOffsetFlags() { return super.fragmentOffsetRaw & 0xE0; } /** * @type {boolean} */ get isFragment() { return ((this.fragmentOffsetFlags & IP_MORE_FRAGMENTS) != 0 || this.fragmentOffsetValue != 0); } /** * @type {boolean} */ get isFirstFragment() { return isFragment() && (this.fragmentOffsetValue == 0); } /** * @type {boolean} */ get isLastFragment() { return this.isFragment && ((this.fragmentOffsetFlags & IP_MORE_FRAGMENTS) == 0); } /** * @type {number} */ get fragmentOffsetValue() { return ntohs(super.fragmentOffsetRaw & 0xFF1F) * 8; } toObject() { return { ...super.toObject(), fragmentInfo: { isFragment: this.isFragment, value: this.fragmentOffsetValue, flags: this.fragmentOffsetFlags, }, options: [...this.options], }; } defaults(obj = {}, layers) { if (!obj.headerLength) { this.headerLength = IPv4.toAlloc(obj) / 4; this.length = this.headerLength * 4; } if (!obj.version) { this.version = 4; } if (!obj.typeOfService) { this.typeOfService = 0; } if (!obj.id) { this.id = 0; } if (!obj.timeToLive) { this.timeToLive = 64; } if (!obj.totalLength) { this.totalLength = this.length + (this.next?.length ?? 0); } if (!obj.protocol) { if (!this.next) { this.protocol = IPProtocolTypes.RAW; } this.protocol = lookupKey(layers, this.next) ?? IPProtocolTypes.RAW; } } checksums(obj) { if (!obj.checksum) { this.calculateChecksum(); } } nextProto(layers) { if (this.isFragment) { return new layers.Payload(this._buf.subarray(this.length), this); } if (this.protocol == IPProtocolTypes.IPIP) { const { version } = this; if (version == 4) { return new layers.IPv4(this._buf.subarray(this.length), this); } else if (version == 6) { return new layers.IPv6(this._buf.subarray(this.length), this); } else { throw new Error(`Invalid IP version ${version}`); } } return lookupChild(layers, this.protocol, this); } }; mixins.withOptions(IPv4.prototype, { baseLength }); module.exports = { IPv4 };