Skip to content

Commit a5a6c5b

Browse files
authored
feat(internet): improve ipv4 method (#2992)
1 parent 5b1c858 commit a5a6c5b

4 files changed

Lines changed: 282 additions & 11 deletions

File tree

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ export type { GitModule } from './modules/git';
6767
export type { HackerModule } from './modules/hacker';
6868
export type { HelpersModule, SimpleHelpersModule } from './modules/helpers';
6969
export type { ImageModule } from './modules/image';
70-
export type { InternetModule } from './modules/internet';
70+
export { IPv4Network } from './modules/internet';
71+
export type { IPv4NetworkType, InternetModule } from './modules/internet';
7172
export type { LocationModule } from './modules/location';
7273
export type { LoremModule } from './modules/lorem';
7374
export type { MusicModule } from './modules/music';

src/modules/internet/index.ts

Lines changed: 166 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FakerError } from '../../errors/faker-error';
12
import { ModuleBase } from '../../internal/module-base';
23
import { charMapping } from './char-mappings';
34
import * as random_ua from './user-agent';
@@ -23,6 +24,82 @@ export type HTTPStatusCodeType =
2324

2425
export type HTTPProtocolType = 'http' | 'https';
2526

27+
export enum IPv4Network {
28+
/**
29+
* Equivalent to: `0.0.0.0/0`
30+
*/
31+
Any = 'any',
32+
/**
33+
* Equivalent to: `127.0.0.0/8`
34+
*
35+
* @see [RFC1122](https://www.rfc-editor.org/rfc/rfc1122)
36+
*/
37+
Loopback = 'loopback',
38+
/**
39+
* Equivalent to: `10.0.0.0/8`
40+
*
41+
* @see [RFC1918](https://www.rfc-editor.org/rfc/rfc1918)
42+
*/
43+
PrivateA = 'private-a',
44+
/**
45+
* Equivalent to: `172.16.0.0/12`
46+
*
47+
* @see [RFC1918](https://www.rfc-editor.org/rfc/rfc1918)
48+
*/
49+
PrivateB = 'private-b',
50+
/**
51+
* Equivalent to: `192.168.0.0/16`
52+
*
53+
* @see [RFC1918](https://www.rfc-editor.org/rfc/rfc1918)
54+
*/
55+
PrivateC = 'private-c',
56+
/**
57+
* Equivalent to: `192.0.2.0/24`
58+
*
59+
* @see [RFC5737](https://www.rfc-editor.org/rfc/rfc5737)
60+
*/
61+
TestNet1 = 'test-net-1',
62+
/**
63+
* Equivalent to: `198.51.100.0/24`
64+
*
65+
* @see [RFC5737](https://www.rfc-editor.org/rfc/rfc5737)
66+
*/
67+
TestNet2 = 'test-net-2',
68+
/**
69+
* Equivalent to: `203.0.113.0/24`
70+
*
71+
* @see [RFC5737](https://www.rfc-editor.org/rfc/rfc5737)
72+
*/
73+
TestNet3 = 'test-net-3',
74+
/**
75+
* Equivalent to: `169.254.0.0/16`
76+
*
77+
* @see [RFC3927](https://www.rfc-editor.org/rfc/rfc3927)
78+
*/
79+
LinkLocal = 'link-local',
80+
/**
81+
* Equivalent to: `224.0.0.0/4`
82+
*
83+
* @see [RFC5771](https://www.rfc-editor.org/rfc/rfc5771)
84+
*/
85+
Multicast = 'multicast',
86+
}
87+
88+
export type IPv4NetworkType = `${IPv4Network}`;
89+
90+
const ipv4Networks: Record<IPv4Network, string> = {
91+
[IPv4Network.Any]: '0.0.0.0/0',
92+
[IPv4Network.Loopback]: '127.0.0.0/8',
93+
[IPv4Network.PrivateA]: '10.0.0.0/8',
94+
[IPv4Network.PrivateB]: '172.16.0.0/12',
95+
[IPv4Network.PrivateC]: '192.168.0.0/16',
96+
[IPv4Network.TestNet1]: '192.0.2.0/24',
97+
[IPv4Network.TestNet2]: '198.51.100.0/24',
98+
[IPv4Network.TestNet3]: '203.0.113.0/24',
99+
[IPv4Network.LinkLocal]: '169.254.0.0/16',
100+
[IPv4Network.Multicast]: '224.0.0.0/4',
101+
};
102+
26103
/**
27104
* Module to generate internet related entries.
28105
*
@@ -485,15 +562,100 @@ export class InternetModule extends ModuleBase {
485562
/**
486563
* Generates a random IPv4 address.
487564
*
565+
* @param options The optional options object.
566+
* @param options.cidrBlock The optional CIDR block to use. Must be in the format `x.x.x.x/y`. Defaults to `'0.0.0.0/0'`.
567+
*
488568
* @example
489569
* faker.internet.ipv4() // '245.108.222.0'
570+
* faker.internet.ipv4({ cidrBlock: '192.168.0.0/16' }) // '192.168.215.224'
490571
*
491572
* @since 6.1.1
492573
*/
493-
ipv4(): string {
494-
return Array.from({ length: 4 }, () => this.faker.number.int(255)).join(
495-
'.'
496-
);
574+
ipv4(options?: {
575+
/**
576+
* The optional CIDR block to use. Must be in the format `x.x.x.x/y`.
577+
*
578+
* @default '0.0.0.0/0'
579+
*/
580+
cidrBlock?: string;
581+
}): string;
582+
/**
583+
* Generates a random IPv4 address.
584+
*
585+
* @param options The optional options object.
586+
* @param options.network The optional network to use. This is intended as an alias for well-known `cidrBlock`s. Defaults to `'any'`.
587+
*
588+
* @example
589+
* faker.internet.ipv4() // '245.108.222.0'
590+
* faker.internet.ipv4({ network: 'private-a' }) // '10.199.154.205'
591+
*
592+
* @since 6.1.1
593+
*/
594+
ipv4(options?: {
595+
/**
596+
* The optional network to use. This is intended as an alias for well-known `cidrBlock`s.
597+
*
598+
* @default 'any'
599+
*/
600+
network?: IPv4NetworkType;
601+
}): string;
602+
/**
603+
* Generates a random IPv4 address.
604+
*
605+
* @param options The optional options object.
606+
* @param options.cidrBlock The optional CIDR block to use. Must be in the format `x.x.x.x/y`. Defaults to `'0.0.0.0/0'`.
607+
* @param options.network The optional network to use. This is intended as an alias for well-known `cidrBlock`s. Defaults to `'any'`.
608+
*
609+
* @example
610+
* faker.internet.ipv4() // '245.108.222.0'
611+
* faker.internet.ipv4({ cidrBlock: '192.168.0.0/16' }) // '192.168.215.224'
612+
* faker.internet.ipv4({ network: 'private-a' }) // '10.199.154.205'
613+
*
614+
* @since 6.1.1
615+
*/
616+
ipv4(
617+
options?:
618+
| {
619+
/**
620+
* The optional CIDR block to use. Must be in the format `x.x.x.x/y`.
621+
*
622+
* @default '0.0.0.0/0'
623+
*/
624+
cidrBlock?: string;
625+
}
626+
| {
627+
/**
628+
* The optional network to use. This is intended as an alias for well-known `cidrBlock`s.
629+
*
630+
* @default 'any'
631+
*/
632+
network?: IPv4NetworkType;
633+
}
634+
): string;
635+
ipv4(
636+
options: { cidrBlock?: string; network?: IPv4NetworkType } = {}
637+
): string {
638+
const { network = 'any', cidrBlock = ipv4Networks[network] } = options;
639+
640+
if (!/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/.test(cidrBlock)) {
641+
throw new FakerError(
642+
`Invalid CIDR block provided: ${cidrBlock}. Must be in the format x.x.x.x/y.`
643+
);
644+
}
645+
646+
const [ipText, subnet] = cidrBlock.split('/');
647+
const subnetMask = 0xffffffff >>> Number.parseInt(subnet);
648+
const [rawIp1, rawIp2, rawIp3, rawIp4] = ipText.split('.').map(Number);
649+
const rawIp = (rawIp1 << 24) | (rawIp2 << 16) | (rawIp3 << 8) | rawIp4;
650+
const networkIp = rawIp & ~subnetMask;
651+
const hostOffset = this.faker.number.int(subnetMask);
652+
const ip = networkIp | hostOffset;
653+
return [
654+
(ip >>> 24) & 0xff,
655+
(ip >>> 16) & 0xff,
656+
(ip >>> 8) & 0xff,
657+
ip & 0xff,
658+
].join('.');
497659
}
498660

499661
/**

test/modules/__snapshots__/internet.spec.ts.snap

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,13 @@ exports[`internet > 42 > httpStatusCode > noArgs 1`] = `226`;
6464

6565
exports[`internet > 42 > httpStatusCode > with options 1`] = `410`;
6666

67-
exports[`internet > 42 > ip 1`] = `"243.187.153.39"`;
67+
exports[`internet > 42 > ip 1`] = `"243.98.3.69"`;
6868

69-
exports[`internet > 42 > ipv4 1`] = `"95.243.187.153"`;
69+
exports[`internet > 42 > ipv4 > noArgs 1`] = `"95.225.220.121"`;
70+
71+
exports[`internet > 42 > ipv4 > with cidrBlock 1`] = `"192.168.13.95"`;
72+
73+
exports[`internet > 42 > ipv4 > with network 1`] = `"229.254.29.199"`;
7074

7175
exports[`internet > 42 > ipv6 1`] = `"8ead:331d:df0f:c444:6b96:d368:ab4b:d1d3"`;
7276

@@ -182,7 +186,11 @@ exports[`internet > 1211 > httpStatusCode > with options 1`] = `429`;
182186

183187
exports[`internet > 1211 > ip 1`] = `"d4fe:fa7f:baec:9dc4:c48f:a8eb:f46f:b7c8"`;
184188

185-
exports[`internet > 1211 > ipv4 1`] = `"237.228.57.255"`;
189+
exports[`internet > 1211 > ipv4 > noArgs 1`] = `"237.179.127.46"`;
190+
191+
exports[`internet > 1211 > ipv4 > with cidrBlock 1`] = `"192.168.13.237"`;
192+
193+
exports[`internet > 1211 > ipv4 > with network 1`] = `"238.219.55.242"`;
186194

187195
exports[`internet > 1211 > ipv6 1`] = `"ed4f:efa7:fbae:c9dc:4c48:fa8e:bf46:fb7c"`;
188196

@@ -296,9 +304,13 @@ exports[`internet > 1337 > httpStatusCode > noArgs 1`] = `201`;
296304

297305
exports[`internet > 1337 > httpStatusCode > with options 1`] = `407`;
298306

299-
exports[`internet > 1337 > ip 1`] = `"40.71.117.82"`;
307+
exports[`internet > 1337 > ip 1`] = `"40.159.131.70"`;
308+
309+
exports[`internet > 1337 > ipv4 > noArgs 1`] = `"67.20.12.145"`;
310+
311+
exports[`internet > 1337 > ipv4 > with cidrBlock 1`] = `"192.168.13.67"`;
300312

301-
exports[`internet > 1337 > ipv4 1`] = `"67.40.71.117"`;
313+
exports[`internet > 1337 > ipv4 > with network 1`] = `"228.49.64.201"`;
302314

303315
exports[`internet > 1337 > ipv6 1`] = `"536a:7b5f:a28d:2f9b:b79c:a46e:a394:bc4f"`;
304316

test/modules/internet.spec.ts

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import validator from 'validator';
22
import { describe, expect, it } from 'vitest';
33
import { allFakers, faker } from '../../src';
4+
import { FakerError } from '../../src/errors/faker-error';
5+
import { IPv4Network } from '../../src/modules/internet';
46
import { seededTests } from '../support/seeded-runs';
57
import { times } from './../support/times';
68

@@ -15,7 +17,6 @@ describe('internet', () => {
1517
'domainSuffix',
1618
'domainWord',
1719
'ip',
18-
'ipv4',
1920
'ipv6',
2021
'port',
2122
'userAgent'
@@ -133,6 +134,12 @@ describe('internet', () => {
133134
protocol: 'http',
134135
});
135136
});
137+
138+
t.describe('ipv4', (t) => {
139+
t.it('noArgs')
140+
.it('with cidrBlock', { cidrBlock: '192.168.13.37/24' })
141+
.it('with network', { network: IPv4Network.Multicast });
142+
});
136143
});
137144

138145
describe.each(times(NON_SEEDED_BASED_RUN).map(() => faker.seed()))(
@@ -595,6 +602,95 @@ describe('internet', () => {
595602
expect(+part).toBeLessThanOrEqual(255);
596603
}
597604
});
605+
606+
it('should return a random IPv4 for a given CIDR block', () => {
607+
const actual = faker.internet.ipv4({
608+
cidrBlock: '192.168.42.255/24',
609+
});
610+
611+
expect(actual).toBeTruthy();
612+
expect(actual).toBeTypeOf('string');
613+
expect(actual).toSatisfy((value: string) => validator.isIP(value, 4));
614+
expect(actual).toMatch(/^192\.168\.42\.\d{1,3}$/);
615+
});
616+
617+
it('should return a random IPv4 for a given CIDR block non-8ish network mask', () => {
618+
const actual = faker.internet.ipv4({
619+
cidrBlock: '192.168.0.255/20',
620+
});
621+
622+
expect(actual).toBeTruthy();
623+
expect(actual).toBeTypeOf('string');
624+
expect(actual).toSatisfy((value: string) => validator.isIP(value, 4));
625+
626+
const [first, second, third, fourth] = actual.split('.').map(Number);
627+
expect(first).toBe(192);
628+
expect(second).toBe(168);
629+
expect(third).toBeGreaterThanOrEqual(0);
630+
expect(third).toBeLessThanOrEqual(15);
631+
expect(fourth).toBeGreaterThanOrEqual(0);
632+
expect(fourth).toBeLessThanOrEqual(255);
633+
});
634+
635+
it.each([
636+
'',
637+
'...',
638+
'.../',
639+
'.0.0.0/0',
640+
'0..0.0/0',
641+
'0.0..0/0',
642+
'0.0.0./0',
643+
'0.0.0.0/',
644+
'a.0.0.0/0',
645+
'0.b.0.0/0',
646+
'0.0.c.0/0',
647+
'0.0.0.d/0',
648+
'0.0.0.0/e',
649+
])(
650+
'should throw an error if not following the x.x.x.x/y format',
651+
(cidrBlock) => {
652+
expect(() =>
653+
faker.internet.ipv4({
654+
cidrBlock,
655+
})
656+
).toThrow(
657+
new FakerError(
658+
`Invalid CIDR block provided: ${cidrBlock}. Must be in the format x.x.x.x/y.`
659+
)
660+
);
661+
}
662+
);
663+
664+
it.each([
665+
[IPv4Network.Any, /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/],
666+
[IPv4Network.Loopback, /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/],
667+
[IPv4Network.PrivateA, /^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/],
668+
[
669+
IPv4Network.PrivateB,
670+
/^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/,
671+
],
672+
[IPv4Network.PrivateC, /^192\.168\.\d{1,3}\.\d{1,3}$/],
673+
[IPv4Network.TestNet1, /^192\.0\.2\.\d{1,3}$/],
674+
[IPv4Network.TestNet2, /^198\.51\.100\.\d{1,3}$/],
675+
[IPv4Network.TestNet3, /^203\.0\.113\.\d{1,3}$/],
676+
[IPv4Network.LinkLocal, /^169\.254\.\d{1,3}\.\d{1,3}$/],
677+
[
678+
IPv4Network.Multicast,
679+
/^2(2[4-9]|3[0-9])\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
680+
],
681+
] as const)(
682+
'should return a random IPv4 for %s network',
683+
(network, regex) => {
684+
const actual = faker.internet.ipv4({ network });
685+
686+
expect(actual).toBeTruthy();
687+
expect(actual).toBeTypeOf('string');
688+
expect(actual).toSatisfy((value: string) =>
689+
validator.isIP(value, 4)
690+
);
691+
expect(actual).toMatch(regex);
692+
}
693+
);
598694
});
599695

600696
describe('ipv6()', () => {

0 commit comments

Comments
 (0)