-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathipv4.js
More file actions
505 lines (505 loc) · 19.3 KB
/
Copy pathipv4.js
File metadata and controls
505 lines (505 loc) · 19.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
"use strict";
/* eslint-disable no-param-reassign */
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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Address4 = void 0;
const common = __importStar(require("./common"));
const constants = __importStar(require("./v4/constants"));
const address_error_1 = require("./address-error");
const isCorrect4 = common.isCorrect(constants.BITS);
/**
* Represents an IPv4 address
* @param {string} address - An IPv4 address string
*/
class Address4 {
constructor(address) {
this.addressMinusSuffix = '';
this.groups = constants.GROUPS;
this.parsedAddress = [];
this.parsedSubnet = '';
this.subnet = '/32';
this.subnetMask = 32;
this.v4 = true;
/**
* Returns true if the address is correct, false otherwise
* @returns {Boolean}
*/
this.isCorrect = isCorrect4;
/**
* Returns true if the given address is in the subnet of the current address
* @returns {boolean}
*/
this.isInSubnet = common.isInSubnet;
/**
* Returns true if this address's host bits fall inside the given subnet,
* ignoring this address's own subnet mask. Prefer this over `isInSubnet`
* when classifying a single address, so the answer doesn't change with the
* CIDR suffix the caller happened to write — notably when the address came
* from untrusted input and the result backs a trust-boundary decision.
* @returns {boolean}
*/
this.isHostInSubnet = common.isHostInSubnet;
this.address = address;
const subnet = constants.RE_SUBNET_STRING.exec(address);
if (subnet) {
this.parsedSubnet = subnet[0].replace('/', '');
this.subnetMask = parseInt(this.parsedSubnet, 10);
this.subnet = `/${this.subnetMask}`;
if (this.subnetMask < 0 || this.subnetMask > constants.BITS) {
throw new address_error_1.AddressError('Invalid subnet mask.');
}
address = address.replace(constants.RE_SUBNET_STRING, '');
}
this.addressMinusSuffix = address;
this.parsedAddress = this.parse(address);
}
/**
* Returns true if the given string is a valid IPv4 address (with optional
* CIDR subnet), false otherwise. Host bits in the subnet portion are
* allowed (e.g. `192.168.1.5/24` is valid); for strict network-address
* validation compare `correctForm()` to `startAddress().correctForm()`,
* or use `networkForm()`.
*/
static isValid(address) {
try {
// eslint-disable-next-line no-new
new Address4(address);
return true;
}
catch {
return false;
}
}
/**
* Parses an IPv4 address string into its four octet groups and stores the
* result on `this.parsedAddress`. Called automatically by the constructor;
* you typically don't need to call it directly. Throws `AddressError` if
* the input is not a valid IPv4 address.
*/
parse(address) {
const groups = address.split('.');
// Checked before the general match so the error names the actual problem.
// Address6 rejects the same notation on its v4-in-v6 path.
if (groups.some((group) => /^0\d/.test(group))) {
throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.");
}
if (!address.match(constants.RE_ADDRESS)) {
throw new address_error_1.AddressError('Invalid IPv4 address.');
}
return groups;
}
/**
* Returns the address in correct form: octets joined with `.` and any
* leading zeros stripped (e.g. `192.168.1.1`). For IPv4 this matches the
* canonical dotted-decimal representation.
*/
correctForm() {
return this.parsedAddress.map((part) => parseInt(part, 10)).join('.');
}
/**
* Construct an `Address4` from an address and a dotted-decimal subnet
* mask given as separate strings (e.g. as returned by Node's
* `os.networkInterfaces()`). Throws `AddressError` if the mask is
* non-contiguous (e.g. `255.0.255.0`).
* @example
* var address = Address4.fromAddressAndMask('192.168.1.1', '255.255.255.0');
* address.subnetMask; // 24
*/
static fromAddressAndMask(address, mask) {
const bits = common.prefixLengthFromMask(new Address4(mask).bigInt(), constants.BITS);
return new Address4(`${address}/${bits}`);
}
/**
* Construct an `Address4` from an address and a Cisco-style wildcard mask
* given as separate strings (e.g. `0.0.0.255` for a `/24`). The wildcard
* mask is the bitwise inverse of the subnet mask. Throws `AddressError`
* if the mask is non-contiguous (e.g. `0.255.0.255`).
* @example
* var address = Address4.fromAddressAndWildcardMask('10.0.0.1', '0.0.0.255');
* address.subnetMask; // 24
*/
static fromAddressAndWildcardMask(address, wildcardMask) {
const wildcard = new Address4(wildcardMask).bigInt();
const allOnes = (BigInt(1) << BigInt(constants.BITS)) - BigInt(1);
const mask = wildcard ^ allOnes;
const bits = common.prefixLengthFromMask(mask, constants.BITS);
return new Address4(`${address}/${bits}`);
}
/**
* Construct an `Address4` from a wildcard pattern with trailing `*`
* octets. The number of trailing wildcards determines the prefix
* length: each `*` represents 8 bits.
*
* Only trailing whole-octet wildcards are supported. Partial-octet
* wildcards (e.g. `192.168.0.1*`) and interior wildcards (e.g.
* `192.*.0.1`) throw `AddressError`.
* @example
* Address4.fromWildcard('192.168.0.*').subnet; // '/24'
* Address4.fromWildcard('192.168.*.*').subnet; // '/16'
* Address4.fromWildcard('*.*.*.*').subnet; // '/0'
*/
static fromWildcard(input) {
const groups = input.split('.');
if (groups.length !== constants.GROUPS) {
throw new address_error_1.AddressError('Wildcard pattern must have 4 octets');
}
let firstWildcard = -1;
for (let i = 0; i < groups.length; i++) {
if (groups[i] === '*') {
if (firstWildcard === -1) {
firstWildcard = i;
}
}
else if (firstWildcard !== -1) {
throw new address_error_1.AddressError('Wildcard `*` must only appear in trailing octets (e.g. `192.168.0.*`)');
}
}
const trailing = firstWildcard === -1 ? 0 : groups.length - firstWildcard;
const replaced = groups.map((g) => (g === '*' ? '0' : g));
const subnetBits = constants.BITS - trailing * 8;
return new Address4(`${replaced.join('.')}/${subnetBits}`);
}
/**
* Converts a hex string to an IPv4 address object. Accepts 8 hex digits
* with optional `:` separators (e.g. `'7f000001'` or `'7f:00:00:01'`).
* Throws `AddressError` for any other length or for non-hex characters.
* @param {string} hex - a hex string to convert
* @returns {Address4}
*/
static fromHex(hex) {
const stripped = hex.replace(/:/g, '');
if (!/^[0-9a-fA-F]{8}$/.test(stripped)) {
throw new address_error_1.AddressError('IPv4 hex must be exactly 8 hex digits');
}
const groups = [];
for (let i = 0; i < 8; i += 2) {
groups.push(parseInt(stripped.slice(i, i + 2), 16));
}
return new Address4(groups.join('.'));
}
/**
* Converts an integer into a IPv4 address object. The integer must be a
* non-negative safe integer in the range `[0, 2**32 - 1]`; otherwise
* `AddressError` is thrown.
* @param {integer} integer - a number to convert
* @returns {Address4}
*/
static fromInteger(integer) {
if (!Number.isInteger(integer) || integer < 0 || integer > 0xffffffff) {
throw new address_error_1.AddressError('IPv4 integer must be in the range 0 to 2**32 - 1');
}
return Address4.fromHex(integer.toString(16).padStart(8, '0'));
}
/**
* Return an address from in-addr.arpa form
* @param {string} arpaFormAddress - an 'in-addr.arpa' form ipv4 address
* @returns {Adress4}
* @example
* var address = Address4.fromArpa(42.2.0.192.in-addr.arpa.)
* address.correctForm(); // '192.0.2.42'
*/
static fromArpa(arpaFormAddress) {
// remove ending ".in-addr.arpa." or just "."
const leader = arpaFormAddress.replace(/(\.in-addr\.arpa)?\.$/, '');
const address = leader.split('.').reverse().join('.');
return new Address4(address);
}
/**
* Converts an IPv4 address object to a hex string
* @returns {String}
*/
toHex() {
return this.parsedAddress.map((part) => common.stringToPaddedHex(part)).join(':');
}
/**
* Converts an IPv4 address object to an array of bytes.
*
* To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toArray())`.
* @returns {Array}
*/
toArray() {
return this.parsedAddress.map((part) => parseInt(part, 10));
}
/**
* Converts an IPv4 address object to an IPv6 address group
* @returns {String}
*/
toGroup6() {
const output = [];
let i;
for (i = 0; i < constants.GROUPS; i += 2) {
output.push(`${common.stringToPaddedHex(this.parsedAddress[i])}${common.stringToPaddedHex(this.parsedAddress[i + 1])}`);
}
return output.join(':');
}
/**
* Returns the address as a `bigint`
* @returns {bigint}
*/
bigInt() {
return BigInt(`0x${this.parsedAddress.map((n) => common.stringToPaddedHex(n)).join('')}`);
}
/**
* Helper function getting start address.
* @returns {bigint}
*/
_startAddress() {
return BigInt(`0b${this.mask() + '0'.repeat(constants.BITS - this.subnetMask)}`);
}
/**
* The first address in the range given by this address' subnet.
* Often referred to as the Network Address.
* @returns {Address4}
*/
startAddress() {
return Address4.fromBigInt(this._startAddress());
}
/**
* The first host address in the range given by this address's subnet ie
* the first address after the Network Address
* @returns {Address4}
*/
startAddressExclusive() {
const adjust = BigInt('1');
return Address4.fromBigInt(this._startAddress() + adjust);
}
/**
* Helper function getting end address.
* @returns {bigint}
*/
_endAddress() {
return BigInt(`0b${this.mask() + '1'.repeat(constants.BITS - this.subnetMask)}`);
}
/**
* The last address in the range given by this address' subnet
* Often referred to as the Broadcast
* @returns {Address4}
*/
endAddress() {
return Address4.fromBigInt(this._endAddress());
}
/**
* The last host address in the range given by this address's subnet ie
* the last address prior to the Broadcast Address
* @returns {Address4}
*/
endAddressExclusive() {
const adjust = BigInt('1');
return Address4.fromBigInt(this._endAddress() - adjust);
}
/**
* The dotted-decimal form of the subnet mask, e.g. `255.255.240.0` for
* a `/20`. Returns an `Address4`; call `.correctForm()` for the string.
* @returns {Address4}
*/
subnetMaskAddress() {
return Address4.fromBigInt(BigInt(`0b${'1'.repeat(this.subnetMask)}${'0'.repeat(constants.BITS - this.subnetMask)}`));
}
/**
* The Cisco-style wildcard mask, e.g. `0.0.0.255` for a `/24`. This is
* the bitwise inverse of `subnetMaskAddress()`. Returns an `Address4`;
* call `.correctForm()` for the string.
* @returns {Address4}
*/
wildcardMask() {
return Address4.fromBigInt(BigInt(`0b${'0'.repeat(this.subnetMask)}${'1'.repeat(constants.BITS - this.subnetMask)}`));
}
/**
* The network address in CIDR string form, e.g. `192.168.1.0/24` for
* `192.168.1.5/24`. For an address with no explicit subnet the prefix is
* `/32`, e.g. `networkForm()` on `192.168.1.5` returns `192.168.1.5/32`.
* @returns {string}
*/
networkForm() {
return `${this.startAddress().correctForm()}/${this.subnetMask}`;
}
/**
* Converts a BigInt to a v4 address object. The value must be in the
* range `[0, 2**32 - 1]`; otherwise `AddressError` is thrown.
* @param {bigint} bigInt - a BigInt to convert
* @returns {Address4}
*/
static fromBigInt(bigInt) {
if (bigInt < BigInt(0) || bigInt > BigInt(0xffffffff)) {
throw new address_error_1.AddressError('IPv4 BigInt must be in the range 0 to 2**32 - 1');
}
return Address4.fromHex(bigInt.toString(16).padStart(8, '0'));
}
/**
* Convert a byte array to an Address4 object. Throws `AddressError` unless
* given exactly 4 integers from 0 to 255. Signed bytes are rejected, so
* this differs from `Address6.fromByteArray`, which folds them; the two
* contracts converge on this stricter form in the next major version.
*
* To convert from a Node.js `Buffer`, spread it: `Address4.fromByteArray([...buf])`.
* @param {Array<number>} bytes - an array of 4 bytes (0-255)
* @returns {Address4}
*/
static fromByteArray(bytes) {
common.assertByteArray(bytes, 4, 'IPv4', 0);
return this.fromUnsignedByteArray(bytes);
}
/**
* Convert an unsigned byte array to an Address4 object. Throws
* `AddressError` unless given exactly 4 bytes, and rejects values outside
* 0 to 255 when parsing the resulting address.
*
* To convert from a Node.js `Buffer`, spread it:
* `Address4.fromUnsignedByteArray([...buf])`.
* @param {Array<number>} bytes - an array of 4 unsigned bytes (0-255)
* @returns {Address4}
*/
static fromUnsignedByteArray(bytes) {
if (bytes.length !== 4) {
throw new address_error_1.AddressError('IPv4 addresses require exactly 4 bytes');
}
const address = bytes.join('.');
return new Address4(address);
}
/**
* Returns the first n bits of the address, defaulting to the
* subnet mask
* @returns {String}
*/
mask(mask) {
if (mask === undefined) {
mask = this.subnetMask;
}
return this.getBitsBase2(0, mask);
}
/**
* Returns the bits in the given range as a base-2 string
* @returns {string}
*/
getBitsBase2(start, end) {
return this.binaryZeroPad().slice(start, end);
}
/**
* Return the reversed in-addr.arpa form of the address, e.g.
* `42.2.0.192.in-addr.arpa.` for `192.0.2.42`.
* @param {Object} options
* @param {boolean} options.omitSuffix - omit the "in-addr.arpa" suffix
* @returns {String}
*/
reverseForm(options) {
if (!options) {
options = {};
}
const reversed = this.correctForm().split('.').reverse().join('.');
if (options.omitSuffix) {
return reversed;
}
return `${reversed}.in-addr.arpa.`;
}
/**
* Returns true if the given address is a multicast address
* @returns {boolean}
*/
isMulticast() {
return this.isHostInSubnet(MULTICAST_V4);
}
/**
* Returns true if the address is in one of the [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private address ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`).
* @returns {boolean}
*/
isPrivate() {
return PRIVATE_V4.some((subnet) => this.isHostInSubnet(subnet));
}
/**
* Returns true if the address is in the loopback range `127.0.0.0/8` ([RFC 1122](https://datatracker.ietf.org/doc/html/rfc1122)).
* @returns {boolean}
*/
isLoopback() {
return this.isHostInSubnet(LOOPBACK_V4);
}
/**
* Returns true if the address is in the link-local range `169.254.0.0/16` ([RFC 3927](https://datatracker.ietf.org/doc/html/rfc3927)).
* @returns {boolean}
*/
isLinkLocal() {
return this.isHostInSubnet(LINK_LOCAL_V4);
}
/**
* Returns true if the address is the unspecified address `0.0.0.0`.
* @returns {boolean}
*/
isUnspecified() {
return this.isHostInSubnet(UNSPECIFIED_V4);
}
/**
* Returns true if the address is the limited broadcast address `255.255.255.255` ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)).
* @returns {boolean}
*/
isBroadcast() {
return this.isHostInSubnet(BROADCAST_V4);
}
/**
* Returns true if the address is in the carrier-grade NAT range `100.64.0.0/10` ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)).
* @returns {boolean}
*/
isCGNAT() {
return this.isHostInSubnet(CGNAT_V4);
}
/**
* Returns a zero-padded base-2 string representation of the address
* @returns {string}
*/
binaryZeroPad() {
if (this._binaryZeroPad === undefined) {
this._binaryZeroPad = this.bigInt().toString(2).padStart(constants.BITS, '0');
}
return this._binaryZeroPad;
}
/**
* Groups an IPv4 address for inclusion at the end of an IPv6 address.
*
* Returns an HTML fragment: each half of the address is wrapped in a
* `<span>` carrying the group classes an address-inspector UI hovers on.
* The address content is HTML-escaped; anything you concatenate around it
* is your responsibility.
* @returns {String}
*/
groupForV6() {
const segments = this.parsedAddress;
return this.correctForm().replace(constants.RE_ADDRESS, `<span class="hover-group group-v4 group-6">${segments
.slice(0, 2)
.join('.')}</span>.<span class="hover-group group-v4 group-7">${segments
.slice(2, 4)
.join('.')}</span>`);
}
}
exports.Address4 = Address4;
const MULTICAST_V4 = new Address4('224.0.0.0/4');
const PRIVATE_V4 = [
new Address4('10.0.0.0/8'),
new Address4('172.16.0.0/12'),
new Address4('192.168.0.0/16'),
];
const LOOPBACK_V4 = new Address4('127.0.0.0/8');
const LINK_LOCAL_V4 = new Address4('169.254.0.0/16');
const UNSPECIFIED_V4 = new Address4('0.0.0.0/32');
const BROADCAST_V4 = new Address4('255.255.255.255/32');
const CGNAT_V4 = new Address4('100.64.0.0/10');
//# sourceMappingURL=ipv4.js.map