TypeScript IP Toolkit for IPv4/IPv6 math, CIDR operations, ranges, allocation, and trie lookups.
- API Reference - Complete API documentation
- Usage Examples - Practical examples for all features
- IP Calculations Guide - Deep dive into the mathematical foundations and BigInt-based calculations
- Contributing Guide - Development setup and contribution guidelines
import { ip, cidr, IPv4, CIDR } from 'ip-kit';
// Parse IPs
const ipv4 = ip('192.168.1.1');
const ipv6 = ip('2001:db8::1');
// Parse CIDRs
const network = cidr('192.168.1.0/24');
// Get network info
console.log(network.network().toString()); // 192.168.1.0
console.log(network.broadcast().toString()); // 192.168.1.255
console.log(network.size()); // 256n
// Check containment
console.log(network.contains(ip('192.168.1.50'))); // true
// Iterate hosts
for (const host of network.hosts()) {
console.log(host.toString());
}
// Subnetting
// Prefer generator usage for large splits to avoid materializing large arrays:
// Good (generator): iterate lazily
let count = 0;
for (const s of network.subnets(26)) {
count++;
}
console.log(count); // 4
// Avoid using `split()` on very large networks — it returns an array and
// can allocate millions of CIDR objects for IPv6-sized spaces.
// If you really need all entries in memory, use `split()`:
// const subnets = network.split(26);
// console.log(subnets.length); // 4💡 See more examples in the
examples/directory!
- Node.js 18 or higher (BigInt support required)
- npm, pnpm or yarn
# Using npm
npm install @h3mantd/ip-kit
# Using pnpm
pnpm add @h3mantd/ip-kit
# Using yarn
yarn add @h3mantd/ip-kitgit clone https://github.com/h3mantD/ip-kit.git
cd ip-kit
npm install
npm run build- ✅ IPv4/IPv6 parsing and formatting (string, number, bigint, bytes)
- ✅ IPv6 mixed notation (IPv4-mapped/compatible addresses like
::ffff:192.0.2.1) - ✅ RFC 5952 IPv6 normalization and RFC 4291 mixed notation support
- ✅ CIDR operations (network, broadcast, contains, overlaps)
- ✅ Host iteration with proper /31 and /127 handling
- ✅ Subnetting and splitting
- ✅ IP ranges and CIDR conversion
- ✅ Range set operations (union, intersect, subtract)
- ✅ IP address allocation with conflict detection
- ✅ Radix trie for longest-prefix matching
- ✅ BigInt-based math for precision
- ✅ Lazy iterators for memory efficiency
- ✅ TypeScript strict mode with full type safety
- ✅ Dual ESM/CJS builds with TypeScript declarations
- ✅ Comprehensive test coverage (160+ tests)
type IPVersion = 4 | 6;class IPv4 extends IP<4> {
// Static methods
static parse(input: string | number | bigint | Uint8Array): IPv4;
static fromBigInt(value: bigint): IPv4;
// Instance methods
toBigInt(): bigint;
toBytes(): Uint8Array;
toString(): string;
equals(other: IP): boolean;
compare(other: IPv4): -1 | 0 | 1;
}class IPv6 extends IP<6> {
// Static methods
static parse(input: string | number | bigint | Uint8Array): IPv6;
static fromBigInt(value: bigint): IPv6;
// Instance methods
toBigInt(): bigint;
toBytes(): Uint8Array;
toString(): string; // RFC 5952 normalized
equals(other: IP): boolean;
compare(other: IPv6): -1 | 0 | 1;
}function ip(input: string | number | bigint | Uint8Array): IPv4 | IPv6;
function cidr(input: string): CIDR<4> | CIDR<6>;class CIDR<V extends IPVersion = IPVersion> {
readonly ip: V extends 4 ? IPv4 : IPv6;
readonly prefix: number;
readonly version: V;
// Static methods
static parse(s: string): CIDR<4> | CIDR<6>;
static from(ip: IPv4, prefix: number): CIDR<4>;
static from(ip: IPv6, prefix: number): CIDR<6>;
// Instance methods
bits(): 32 | 128;
network(): typeof this.ip;
broadcast(): IPv4; // IPv4 only
size(): bigint;
firstHost(opts?: { includeEdges?: boolean }): typeof this.ip;
lastHost(opts?: { includeEdges?: boolean }): typeof this.ip;
contains(x: IP | CIDR): boolean;
overlaps(other: CIDR<V>): boolean;
*hosts(opts?: { includeEdges?: boolean }): Generator<typeof this.ip>;
*subnets(newPrefix: number): Generator<CIDR<V>>;
split(parts: number): CIDR<V>[];
move(n: number): CIDR<V>;
toRange(): IPRange<V>;
toPtr(): string[];
toString(): string;
}class IPRange<V extends IPVersion = IPVersion> {
readonly start: V extends 4 ? IPv4 : IPv6;
readonly end: V extends 4 ? IPv4 : IPv6;
readonly version: V;
// Static methods
static parse(s: string): IPRange<4> | IPRange<6>;
static from(start: IPv4, end: IPv4): IPRange<4>;
static from(start: IPv6, end: IPv6): IPRange<6>;
// Instance methods
size(): bigint;
overlaps(other: IPRange<V>): boolean;
contains(ip: IP): boolean;
*ips(limit?: number): Generator<typeof this.start>;
toCIDRs(): CIDR<V>[];
toString(): string;
}class RangeSet<V extends IPVersion = IPVersion> {
// Static methods
static fromCIDRs<V extends IPVersion>(cidrs: Array<CIDR<V> | string>): RangeSet<V>;
static fromRanges<V extends IPVersion>(ranges: Array<IPRange<V>>): RangeSet<V>;
// Instance methods
isEmpty(): boolean;
size(): bigint;
union(other: RangeSet<V>): RangeSet<V>;
intersect(other: RangeSet<V>): RangeSet<V>;
subtract(other: RangeSet<V>): RangeSet<V>;
contains(ip: IP<V>): boolean;
containsCIDR(cidr: CIDR<V>): boolean;
*ips(limit?: number): Generator<IP<V>>;
toCIDRs(): CIDR<V>[];
toString(): string;
}class Allocator<V extends IPVersion = IPVersion> {
readonly parent: CIDR<V>;
readonly taken: RangeSet<V>;
readonly version: V;
constructor(parent: CIDR<V>, taken?: RangeSet<V>);
// Allocation methods
nextAvailable(from?: IP<V>): IP<V> | null;
allocateNext(): IP<V> | null;
allocateIP(ip: IP<V>): boolean;
allocateCIDR(cidr: CIDR<V>): boolean;
// Query methods
freeBlocks(opts?: { minPrefix?: number; maxResults?: number }): CIDR<V>[];
availableCount(): bigint;
utilization(): number;
}class RadixTrie<V extends IPVersion = IPVersion, T = unknown> {
readonly version: V;
constructor(version: V);
// Core methods
insert(cidr: CIDR<V>, value?: T): this;
remove(cidr: CIDR<V>): this;
longestMatch(ip: IP<V>): { cidr: CIDR<V>; value?: T } | null;
// Utility methods
isEmpty(): boolean;
size(): number;
getCIDRs(): CIDR<V>[];
}import { IPv4, CIDR } from 'ip-kit';
// Parse different formats
const ip1 = IPv4.parse('192.168.1.1');
const ip2 = IPv4.parse(3232235777); // number
const ip3 = IPv4.parse(3232235777n); // bigint
const ip4 = IPv4.parse(new Uint8Array([192, 168, 1, 1])); // bytes
// CIDR operations
const cidr = CIDR.parse('192.168.1.0/24');
console.log(cidr.network().toString()); // '192.168.1.0'
console.log(cidr.broadcast().toString()); // '192.168.1.255'
console.log(cidr.size()); // 256n
// Check containment
console.log(cidr.contains(IPv4.parse('192.168.1.100'))); // true
// Iterate hosts (excludes network/broadcast for /24)
for (const host of cidr.hosts()) {
console.log(host.toString());
}
// Subnet into /26 networks
const subnets = Array.from(cidr.subnets(26));
console.log(subnets.map((s) => s.toString()));
// ['192.168.1.0/26', '192.168.1.64/26', '192.168.1.128/26', '192.168.1.192/26']import { IPv6, CIDR } from 'ip-kit';
// Parse with compression (RFC 5952 normalization)
const ip = IPv6.parse('2001:0db8:0000:0000:0000:0000:0000:0001');
console.log(ip.toString()); // '2001:db8::1'
// CIDR operations
const cidr = CIDR.parse('2001:db8::/32');
console.log(cidr.size()); // 79228162514264337593543950336n
// IPv6 always includes all addresses in hosts()
// Use the generator form to avoid materializing huge arrays:
for (const h of cidr.hosts({ includeEdges: true })) {
// process host lazily
break; // example: stop after first
}import { IPv6, ip } from 'ip-kit';
// Parse IPv4-mapped IPv6 addresses (::ffff:0:0/96)
const mapped1 = ip('::ffff:192.0.2.128');
console.log(mapped1.toString()); // '::ffff:192.0.2.128' (preserves dotted notation)
const mapped2 = ip('::FFFF:192.168.1.1');
console.log(mapped2.toString()); // '::ffff:192.168.1.1' (lowercase per RFC 5952)
// Full form also works
const mapped3 = ip('0:0:0:0:0:ffff:192.168.1.1');
console.log(mapped3.toString()); // '::ffff:192.168.1.1' (compressed)
// Parse IPv4-compatible IPv6 addresses (::/96, deprecated but supported)
const compatible = ip('::192.0.2.1');
console.log(compatible.toString()); // '::192.0.2.1'
// Special cases preserve standard hex notation
const loopback = ip('::1');
console.log(loopback.toString()); // '::1' (not '::0.0.0.1')
const unspecified = ip('::');
console.log(unspecified.toString()); // '::' (not '::0.0.0.0')
// Mixed notation works in parsing, but non-mapped addresses output as hex
const custom = ip('2001:db8:1:1:1:1:192.168.0.10');
console.log(custom.toString()); // '2001:db8:1:1:1:1:c0a8:a' (hex output)
// Validation - all of these throw ParseError
try {
ip('::ffff:256.1.1.1'); // Octet > 255
} catch (e) { console.log(e.message); }
try {
ip('::192.168.1'); // Incomplete IPv4 (only 3 octets)
} catch (e) { console.log(e.message); }
try {
ip('::192.168.01.1'); // Leading zeros not allowed
} catch (e) { console.log(e.message); }
try {
ip(':::192.168.1.1'); // Too many consecutive colons
} catch (e) { console.log(e.message); }
try {
ip('::1::192.168.1.1'); // Multiple :: compressions
} catch (e) { console.log(e.message); }
// Convert between formats
const v4mapped = ip('::ffff:192.0.2.1');
const bytes = v4mapped.toBytes();
// Uint8Array(16) [0,0,0,0,0,0,0,0,0,0,0xff,0xff,192,0,2,1]
const bigint = v4mapped.toBigInt();
// 281473902969345n (0x0000_0000_0000_0000_0000_ffff_c000_0201)import { IPRange, IPv4 } from 'ip-kit';
// Parse range
const range = IPRange.parse('192.168.1.10 - 192.168.1.20');
console.log(range.size()); // 11n
// Check containment
console.log(range.contains(IPv4.parse('192.168.1.15'))); // true
// Iterate IPs
for (const ip of range.ips()) {
console.log(ip.toString());
}
// Convert to minimal CIDRs
const cidrs = range.toCIDRs();
console.log(cidrs.map((c) => c.toString()));import { RangeSet, CIDR, IPv4 } from 'ip-kit';
// Create range sets from CIDRs
const set1 = RangeSet.fromCIDRs(['192.168.1.0/25', '192.168.2.0/24']);
const set2 = RangeSet.fromCIDRs(['192.168.1.128/25', '192.168.3.0/24']);
// Union (combine ranges)
const union = set1.union(set2);
console.log(union.size()); // 512n + 256n = 768n
// Intersection (overlapping ranges)
const intersection = set1.intersect(set2);
console.log(intersection.size()); // 0n (no overlap)
// Subtraction (remove ranges)
const difference = set1.subtract(set2);
console.log(difference.size()); // 512n
// Check containment
console.log(set1.contains(IPv4.parse('192.168.1.50'))); // true
console.log(set1.containsCIDR(CIDR.parse('192.168.1.64/26'))); // true
// Convert to minimal CIDRs
const minimalCIDRs = union.toCIDRs();
console.log(minimalCIDRs.map((c) => c.toString()));
// ['192.168.1.0/24', '192.168.2.0/24', '192.168.3.0/24']import { Allocator, CIDR, IPv4 } from 'ip-kit';
// Create allocator for a /24 network
const parent = CIDR.parse('192.168.1.0/24');
const allocator = new Allocator(parent);
// Allocate next available IP
const ip1 = allocator.allocateNext();
console.log(ip1?.toString()); // '192.168.1.1'
// Allocate specific IP
const success = allocator.allocateIP(IPv4.parse('192.168.1.10'));
console.log(success); // true
// Allocate CIDR block
const cidrSuccess = allocator.allocateCIDR(CIDR.parse('192.168.1.64/26'));
console.log(cidrSuccess); // true
// Find next available IP
const next = allocator.nextAvailable();
console.log(next?.toString()); // '192.168.1.2'
// Get free blocks
const freeBlocks = allocator.freeBlocks({ minPrefix: 27 });
console.log(freeBlocks.map((b) => b.toString()));
// Check utilization
console.log(`Utilization: ${(allocator.utilization() * 100).toFixed(1)}%`);
// Get available count
console.log(`Available IPs: ${allocator.availableCount()}`);import { RadixTrie, CIDR, IPv4 } from 'ip-kit';
// Create routing table
const routingTable = new RadixTrie<4, string>(4);
// Add routes with associated interface/gateway info
routingTable
.insert(CIDR.parse('0.0.0.0/0'), 'default-gateway')
.insert(CIDR.parse('192.168.0.0/16'), 'lan-interface')
.insert(CIDR.parse('192.168.1.0/24'), 'server-subnet')
.insert(CIDR.parse('192.168.1.128/25'), 'dmz-subnet');
// Find best route for destination IP
const destIP = IPv4.parse('192.168.1.150');
const route = routingTable.longestMatch(destIP);
if (route) {
console.log(`Route: ${route.cidr.toString()}`);
console.log(`Next hop: ${route.value}`);
// Output: Route: 192.168.1.128/25, Next hop: dmz-subnet
}
// Remove a route
routingTable.remove(CIDR.parse('192.168.1.128/25'));
// Get all routes
const allRoutes = routingTable.getCIDRs();
console.log(`Total routes: ${routingTable.size()}`);import { IPv4, CIDR, ParseError } from 'ip-kit';
try {
const ip = IPv4.parse('256.1.1.1'); // Invalid
} catch (error) {
if (error instanceof ParseError) {
console.log('Parse error:', error.message);
}
}
try {
const cidr = CIDR.parse('192.168.1.0/33'); // Invalid prefix
} catch (error) {
console.log('Invalid CIDR:', error.message);
}-
This library throws a small set of custom errors exported from
src/core/errors.ts:ParseError— input parsing failuresInvariantError— internal invariant violation (logic error)OutOfRangeError— numeric or prefix out-of-rangeVersionMismatchError— operations attempted with mixed IP versions
-
Important: most address/size arithmetic uses
BigInt. Where counts are small (indexes, array sizes)numberis used, but IP math and sizes returnbigint.
Run these commands from the project root:
# install dependencies (use npm or pnpm as preferred)
npm ci
# typecheck
npm run typecheck
# run tests
npm test
# build
npm run buildimport { Allocator, RangeSet, CIDR, IPv4 } from 'ip-kit';
// Simulate IPAM for a data center
class IPAMSystem {
private allocators: Map<string, Allocator<4>> = new Map();
addSubnet(name: string, cidr: string, takenRanges: string[] = []) {
const parent = CIDR.parse(cidr) as CIDR<4>;
const taken = RangeSet.fromCIDRs(takenRanges);
this.allocators.set(name, new Allocator(parent, taken));
}
allocateIP(subnetName: string): IPv4 | null {
const allocator = this.allocators.get(subnetName);
return allocator?.allocateNext() || null;
}
getUtilization(subnetName: string): number {
const allocator = this.allocators.get(subnetName);
return allocator?.utilization() || 0;
}
findFreeBlocks(subnetName: string, minPrefix = 24) {
const allocator = this.allocators.get(subnetName);
return allocator?.freeBlocks({ minPrefix }) || [];
}
}
// Usage
const ipam = new IPAMSystem();
ipam.addSubnet('web-servers', '10.0.1.0/24', ['10.0.1.1/32', '10.0.1.2/32']);
ipam.addSubnet('database', '10.0.2.0/24');
const webIP = ipam.allocateIP('web-servers');
console.log(`Allocated web server IP: ${webIP?.toString()}`);
console.log(`Web subnet utilization: ${(ipam.getUtilization('web-servers') * 100).toFixed(1)}%`);
const freeBlocks = ipam.findFreeBlocks('database', 25);
console.log(`Available /25 blocks in database subnet: ${freeBlocks.length}`);import { RadixTrie, CIDR, IPv4 } from 'ip-kit';
interface RouteInfo {
interface: string;
gateway?: string;
metric: number;
}
class RoutingTable {
private ipv4Routes: RadixTrie<4, RouteInfo> = new RadixTrie(4);
addRoute(cidrStr: string, info: RouteInfo) {
const cidr = CIDR.parse(cidrStr);
if (cidr.version === 4) {
this.ipv4Routes.insert(cidr as CIDR<4>, info);
}
}
lookupRoute(destination: string): RouteInfo | null {
const ip = IPv4.parse(destination);
const result = this.ipv4Routes.longestMatch(ip);
return result?.value || null;
}
getAllRoutes(): Array<{ cidr: string; info: RouteInfo }> {
const routes: Array<{ cidr: string; info: RouteInfo }> = [];
for (const cidr of this.ipv4Routes.getCIDRs()) {
const result = this.ipv4Routes.longestMatch(cidr.network() as IPv4);
if (result?.value) {
routes.push({ cidr: cidr.toString(), info: result.value });
}
}
return routes;
}
}
// Usage
const routing = new RoutingTable();
routing.addRoute('0.0.0.0/0', { interface: 'eth0', gateway: '192.168.1.1', metric: 100 });
routing.addRoute('192.168.1.0/24', { interface: 'eth1', metric: 10 });
routing.addRoute('10.0.0.0/8', { interface: 'eth2', metric: 20 });
const route = routing.lookupRoute('192.168.1.50');
console.log(`Route to 192.168.1.50: ${route?.interface} (metric: ${route?.metric})`);
const allRoutes = routing.getAllRoutes();
console.log('All routes:', allRoutes);-
BigInt Usage: All IP math uses BigInt to avoid floating-point precision issues with large IPv6 addresses. See our IP Calculations Guide for detailed explanations.
-
Lazy Iterators: Methods like
hosts(),subnets(), andips()return generators to handle large ranges efficiently without memory issues. -
IPv4 /31 and /32 Handling: For point-to-point links (/31) and single-host (/32),
hosts()includes all addresses by default. Use{ includeEdges: false }to exclude network/broadcast. -
IPv6 Edge Inclusion: IPv6 ranges always include network and broadcast addresses in iterations, as there's no traditional broadcast concept.
-
RFC 5952 Normalization: IPv6 addresses are automatically normalized to the canonical compressed form (lowercase hex, longest zero run compressed with
::, etc.). -
RFC 4291 Mixed Notation: IPv6 addresses with dotted-decimal IPv4 tails are fully supported:
- IPv4-mapped (
::ffff:x.x.x.x) - Output preserves mixed notation - IPv4-compatible (
::x.x.x.x) - Output preserves mixed notation (except::and::1) - Other prefixes - Parsed correctly but output as standard hex notation
- Strict validation - Rejects malformed input (invalid octets, multiple
::, etc.)
- IPv4-mapped (
-
Type Safety: Strict TypeScript with generics ensures version-specific operations are type-checked.
-
split() materializes results:
CIDR.split(parts)returns an array of CIDR objects. For largeparts(especially with IPv6), this can allocate millions of objects and exhaust memory. Prefer iterating the generator returned bysubnets()for large or unbounded splits.
- Node.js 18+
- pnpm (recommended) or npm
git clone https://github.com/h3mantD/ip-kit.git
cd ip-kit
pnpm install# Build the library
pnpm build
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
# Lint code
pnpm lint
# Format code
pnpm format
# Type check
pnpm typecheck
# Development build with watch
pnpm dev
# Run examples
node examples/basic.jssrc/
├── core/ # Core utilities
│ ├── bigint.ts # BigInt math helpers
│ ├── errors.ts # Custom error classes
│ ├── normalize.ts # IPv6 normalization
│ └── ptr.ts # Reverse DNS utilities
├── domain/ # Domain models
│ ├── ip.ts # IP address classes
│ ├── cidr.ts # CIDR classes
│ ├── range.ts # IP range classes
│ ├── rangeset.ts # IP range set operations
│ ├── allocator.ts # IP address allocation
│ └── trie.ts # Radix trie for LPM
└── index.ts # Public exports
tests/ # Test files (160+ tests)
├── core/
│ ├── bigint.test.ts
│ ├── normalize.test.ts
│ ├── ptr.test.ts
│ └── ...
└── domain/
├── ip.test.ts
├── ipv6-mixed.test.ts
├── cidr.test.ts
├── range.test.ts
├── rangeset.test.ts
├── allocator.test.ts
└── trie.test.ts
See CONTRIBUTING.md for development guidelines and contribution process.
MIT
- Advanced range set operations (union, intersect, subtract) ✅
- IP allocation and free block finding ✅
- Radix trie for longest-prefix matching ✅
- Performance benchmarks
- WASM backend for high-performance operations
- CLI tool for common operations
- ASN/Geo lookups
- Database integration adapters