Universal CIDR notation parsing, stringifying, and validation.

This module provides parseCidr, stringifyCidr, cidrContains, cidrContainsCidr, cidrOverlaps, cidrIntersect, cidrSubtract, cidrMerge, cidrSize, cidrFirstAddress, cidrLastAddress, cidrAddresses, and compareCidr that auto-detect IPv4 vs IPv6 and delegate to the appropriate version-specific function. The Cidr and ParsedCidr type aliases and the isCidrv4/isCidrv6 type guards are also exported for working with version-polymorphic CIDR values.

For version-specific functions, see:

Examples

Parse and stringify any CIDR block

import { assertEquals } from "@std/assert";
import { parseCidr, stringifyCidr } from "@hertzg/ip/cidr";

// IPv4
const v4 = parseCidr("192.168.1.0/24");
assertEquals(v4, { address: 3232235776, prefixLength: 24 });
assertEquals(stringifyCidr(v4), "192.168.1.0/24");

// IPv6, with a zone ID
const v6 = parseCidr("fe80::%ether1/64");
assertEquals(v6, { address: 0xfe80n << 112n, prefixLength: 64, zoneId: "ether1" });
assertEquals(stringifyCidr(v6), "fe80::%ether1/64");

// The mask dialect is kept as written
const masked = parseCidr("10.0.0.0/255.0.0.0");
assertEquals(masked, { address: 167772160, mask: 0xFF000000 });
assertEquals(stringifyCidr(masked), "10.0.0.0/255.0.0.0");

Functions

f
cidrContains(
cidr: Cidr,
address: Address
): boolean

Checks if a CIDR block contains an address.

f
cidrContainsCidr(
outer: Cidr,
inner: Cidr
): boolean
1 overloads

Checks if one CIDR block fully contains another.

f
cidrFirstAddress(cidr: Cidr): Address
1 overloads

Returns the first address of a CIDR block.

f
cidrIntersect(
a: Cidr,
b: Cidr
): Cidr | null
1 overloads

Returns the intersection of two CIDR blocks.

f
cidrLastAddress(cidr: Cidr): Address
1 overloads

Returns the last address of a CIDR block.

f
cidrMerge(cidrs: readonly Cidr[]): Cidr[]
1 overloads

Merges CIDR blocks into the minimal covering set.

f
cidrOverlaps(
a: Cidr,
b: Cidr
): boolean
1 overloads

Checks if two CIDR blocks overlap (share at least one address).

f
cidrSize(cidr: Cidr): number | bigint
1 overloads

Returns the total number of addresses in a CIDR block.

f
cidrSubtract(
a: Cidr,
b: Cidr
): Cidr[]
1 overloads

Subtracts one CIDR block from another.

f
compareCidr(
a: Cidr,
b: Cidr
): -1 | 0 | 1

Compares two CIDR blocks of either version for sorting.

f
isCidrv4(cidr: Cidr): cidr is Cidrv4

Type guard that checks whether a Cidr is an IPv4 CIDR block.

f
isCidrv6(cidr: Cidr): cidr is Cidrv6

Type guard that checks whether a Cidr is an IPv6 CIDR block.

f
parseCidr(
cidr: string,
options?: ParseOptions
): ParsedCidr

Parses IPv4 or IPv6 CIDR notation, in either dialect and with an optional zone ID.

f
stringifyCidr(cidr: Address | ParsedAddress | ParsedCidr): string

Stringifies a CIDR block of either version, or an address, to CIDR notation.

Type Aliases

T
Address = Addressv4 | Addressv6

A plain IP address of either IP version.

T
Addressv4 = number

An IPv4 address as a 32-bit unsigned integer, 0 to 4294967295. The primitive type is what carries the version: a number is IPv4, a bigint is IPv6 (ADR 0001).

T
Addressv6 = bigint

An IPv6 address as a 128-bit unsigned bigint, 0n to 2n ** 128n - 1n. The primitive type is what carries the version: a bigint is IPv6, a number is IPv4 (ADR 0001).

T
Cidr = Cidrv4 | Cidrv6

A CIDR block of either IP version.

T
Cidrv4 = PrefixedCidrv4 | MaskedCidrv4

Represents an IPv4 CIDR block.

T
Cidrv6 = PrefixedCidrv6 | MaskedCidrv6

Represents an IPv6 CIDR block.

T
Mask = Maskv4 | Maskv6

A network mask of either IP version: a number for IPv4, a bigint for IPv6, the same split as Address.

T
MaskedCidrv4 = { readonly address: Addressv4; readonly mask: Maskv4; readonly prefixLength?: never; }

An IPv4 CIDR block written with a network mask, as in 10.0.0.0/255.0.0.0.

T
MaskedCidrv6 = { readonly address: Addressv6; readonly mask: Maskv6; readonly prefixLength?: never; }

An IPv6 CIDR block written with a network mask, as in 2001:db8::/ffff:ffff::.

T
Maskv4 = number

An IPv4 network mask as a 32-bit unsigned integer, e.g. 0xFFFFFF00 for /24.

T
Maskv6 = bigint

An IPv6 network mask as a 128-bit unsigned bigint, e.g. 0xFFFFFFFFFFFFFFFF0000000000000000n for /64.

T
ParsedAddress = ParsedAddressv4 | ParsedAddressv6

What parseAddress returns and what stringifyAddress accepts: a ParsedAddressv4 or a ParsedAddressv6. Read .address for the bare Address and narrow with typeof; the zone never touches the value, and no operation in this package reads it.

T
ParsedAddressv4 = { readonly address: Addressv4; readonly zoneId?: ZoneId; }

What parseAddressv4 returns and what stringifyAddressv4 accepts: the address, plus the zone ID if the notation had one. Read .address for the bare Addressv4; the zone never touches the value, and no operation in this package reads it.

T
ParsedAddressv6 = { readonly address: Addressv6; readonly zoneId?: ZoneId; }

What parseAddressv6 returns and what stringifyAddressv6 accepts: the address, plus the zone ID if the notation had one. Read .address for the bare Addressv6; the zone never touches the value, and no operation in this package reads it.

T
ParsedCidr = ParsedCidrv4 | ParsedCidrv6

What parseCidr returns and what stringifyCidr accepts: a ParsedCidrv4 or a ParsedCidrv6, the block in the dialect it was written in plus an optional zone ID. Assignable to Cidr, so a parse result goes straight into every cidr* operation; none of them reads the zone.

T
ParsedCidrv4 = Cidrv4 & { readonly zoneId?: ZoneId; }

What parseCidrv4 returns and what stringifyCidrv4 accepts: a Cidrv4 in the dialect it was written in, plus the zone ID if the notation had one (fe80::%ether1/64 has one; RouterOS emits that form for connected routes). Assignable to Cidrv4, so a parse result goes straight into every cidrv4* operation; none of them reads the zone.

T
ParsedCidrv6 = Cidrv6 & { readonly zoneId?: ZoneId; }

What parseCidrv6 returns and what stringifyCidrv6 accepts: a Cidrv6 in the dialect it was written in, plus the zone ID if the notation had one (fe80::%ether1/64, the form RouterOS and netstat -rn emit for link-local routes). Assignable to Cidrv6, so a parse result goes straight into every cidrv6* operation; none of them reads the zone.

T
ParseOptions = { readonly unmapToV4?: boolean; }

Options for the universal parsers, parseAddress and parseCidr. The version-specific parsers take none: they return their own version and nothing else.

  • unmapToV4: boolean

    Whether an IPv4-mapped IPv6 address (::ffff:a.b.c.d) is returned as the IPv4 value it carries. Defaults to true (ADR 0004): dual-stack listeners report IPv4 clients in the mapped form, and almost every caller wants the IPv4 view. Set to false to keep the bigint.

T
PrefixedCidrv4 = { readonly address: Addressv4; readonly prefixLength: PrefixLengthv4; readonly mask?: never; }

An IPv4 CIDR block written with a prefix length, as in 10.0.0.0/8.

T
PrefixedCidrv6 = { readonly address: Addressv6; readonly prefixLength: PrefixLengthv6; readonly mask?: never; }

An IPv6 CIDR block written with a prefix length, as in 2001:db8::/32.

T
PrefixLength = number

A prefix length of either IP version. Being a bare number it cannot say which version it belongs to, which is why there is no universal mask-from-prefix-length function: 24 is /24 in both, and the masks differ.

T
PrefixLengthv4 = number

An IPv4 prefix length, the 24 in /24. The range is 0 to 32; it is documented rather than encoded in the type, so prefixLength + 1 stays a PrefixLengthv4 (ADR 0002).

T
PrefixLengthv6 = number

An IPv6 prefix length, the 64 in /64. The range is 0 to 128; it is documented rather than encoded in the type, so prefixLength + 1 stays a PrefixLengthv6 (ADR 0002).

T
ZoneId = string

The zone ID of an address, the interface tail after % in fe80::1%eth0 (RFC 4007 section 11). Carried verbatim by the Parsed* types; never percent-decoded, so %25eth0 is the zone 25eth0.