-
Notifications
You must be signed in to change notification settings - Fork 2
Utils IpUtils
The ipUtils module provides comprehensive utilities for working with IPv4 and IPv6 addresses. It includes:
- Validation: Check IP address format and structure
- Conversion: Transform IPs between different representations (binary, hex, numeric)
- Expansion: Convert compressed IPv6 to full form
- Range Checking: Determine if IPs fall within CIDR ranges
- Binary Operations: Work with IP addresses at the bit level
These utilities are essential for network programming, security rules, access control, and IP address management.
Matches valid IPv4 address format (dotted decimal notation).
const IPV4_REGEX: RegExp; // /^(\d{1,3}\.){3}\d{1,3}$/Basic IPv6 character validation (hex digits, colons, dots).
const IPV6_REGEX: RegExp; // /^[0-9a-fA-F:.]+$/Validates individual IPv4 octets (0-255).
const IPV4_SEGMENT: RegExp; // /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/Validates individual IPv6 hexadecimal segments (1-4 hex digits).
const IPV6_SEGMENT: RegExp; // /^[0-9A-Fa-f]{1,4}$/-
IPV4_MAX_SUBNET: 32- Maximum CIDR mask for IPv4 -
IPV6_MAX_SUBNET: 128- Maximum CIDR mask for IPv6
-
IPV4_BITS: 32- Total bits in IPv4 address -
IPV6_BITS: 128- Total bits in IPv6 address -
OCTET_BITS: 8- Bits per IPv4 octet -
IPV6_SEGMENT_BITS: 16- Bits per IPv6 segment
Validates if a string is a properly formatted IPv4 address with octets in range 0-255.
import { isValidIPv4 } from '@tundralibs/utils';
isValidIPv4('192.168.1.1'); // true
isValidIPv4('255.255.255.0'); // true
isValidIPv4('256.1.1.1'); // false (octet > 255)
isValidIPv4('192.168.1'); // false (incomplete)Validates IPv6 address structure including compression, IPv4-mapped, and mixed notation.
import { isValidIPv6Structure } from '@tundralibs/utils';
isValidIPv6Structure('2001:db8::1'); // true
isValidIPv6Structure('::1'); // true (loopback)
isValidIPv6Structure('::ffff:192.168.1.1'); // true (IPv4-mapped)
isValidIPv6Structure('fe80::'); // true (compressed)
isValidIPv6Structure('gggg::1'); // false (invalid hex)Expands any valid IPv6 form (compressed, full, or with a trailing dotted IPv4
part) to the canonical 8-group form. Groups are not zero-padded. The ::
zero-fill is placed exactly where the token appears, so mixed forms with hex
groups on both sides of :: expand correctly. Returns null for invalid
input — including a bare IPv4 address or a leading single colon — rather than
throwing.
import { expandIPv6 } from '@tundralibs/utils';
expandIPv6('2001:db8::1');
// Returns: '2001:db8:0:0:0:0:0:1'
expandIPv6('::1');
// Returns: '0:0:0:0:0:0:0:1'
expandIPv6('::ffff:192.168.1.1');
// Returns: '0:0:0:0:0:ffff:c0a8:101'
expandIPv6('64:ff9b::1:1.2.3.4'); // hex groups on both sides of ::
// Returns: '64:ff9b:0:0:0:1:102:304'
expandIPv6('192.168.1.1'); // bare IPv4 is not IPv6
// Returns: null
expandIPv6(':ffff:1.2.3.4'); // leading single colon is not valid IPv6
// Returns: null
expandIPv6('invalid');
// Returns: nullConverts IPv4 address to 32-bit binary string representation.
import { ipv4ToBinary } from '@tundralibs/utils';
ipv4ToBinary('192.168.1.1');
// Returns: '11000000101010000000000100000001'
ipv4ToBinary('255.255.255.0');
// Returns: '11111111111111111111111100000000'
ipv4ToBinary('10.0.0.1');
// Returns: '00001010000000000000000000000001'Converts IPv6 address to 128-bit binary string representation.
import { ipv6ToBinary } from '@tundralibs/utils';
ipv6ToBinary('2001:db8::1');
// Returns: '00100000000000010000110110111000...' (128 bits)
ipv6ToBinary('::1');
// Returns: '00000000000000000000000000000000...' (127 zeros + '1')Converts IPv4 address to 32-bit unsigned integer.
import { ipv4ToLong } from '@tundralibs/utils';
ipv4ToLong('192.168.1.1'); // Returns: 3232235777
ipv4ToLong('10.0.0.1'); // Returns: 167772161
ipv4ToLong('0.0.0.0'); // Returns: 0
ipv4ToLong('255.255.255.255'); // Returns: 4294967295Useful for sorting IPs numerically:
import { ipv4ToLong } from '@tundralibs/utils';
const ips = ['192.168.1.10', '192.168.1.1', '10.0.0.1'];
ips.sort((a, b) => ipv4ToLong(a) - ipv4ToLong(b));
// Result: ['10.0.0.1', '192.168.1.1', '192.168.1.10']Converts IPv4 to hexadecimal segments for IPv6-mapped addresses.
Each segment is .toString(16) — not zero-padded to 4 digits.
import { ipv4ToHexSegments } from '@tundralibs/utils';
ipv4ToHexSegments('192.168.1.1');
// Returns: ['c0a8', '101'] ← not '0101'; no zero-padding
ipv4ToHexSegments('10.0.0.1');
// Returns: ['a00', '1']
// Use for creating IPv6-mapped address:
const segments = ipv4ToHexSegments('192.168.1.1');
const ipv6Mapped = `::ffff:${segments[0]}:${segments[1]}`;
// Result: '::ffff:c0a8:101'Checks if IPv4 address falls within a CIDR range using efficient
bitwise operations. rangeStart is a plain dotted-decimal IPv4
address (the network address), not CIDR notation — the prefix length
is the separate cidr argument.
import { isIPv4InRange } from '@tundralibs/utils';
isIPv4InRange('192.168.1.10', '192.168.0.0', 16); // true
isIPv4InRange('192.168.1.10', '192.168.1.0', 24); // true
isIPv4InRange('10.0.0.1', '192.168.0.0', 16); // false
isIPv4InRange('192.169.1.1', '192.168.0.0', 16); // falseimport { isValidIPv4, isValidIPv6Structure } from '@tundralibs/utils';
function validateIPAddress(ip: string): 'IPv4' | 'IPv6' | 'Invalid' {
if (isValidIPv4(ip)) return 'IPv4';
if (isValidIPv6Structure(ip)) return 'IPv6';
return 'Invalid';
}
console.log(validateIPAddress('192.168.1.1')); // 'IPv4'
console.log(validateIPAddress('2001:db8::1')); // 'IPv6'
console.log(validateIPAddress('invalid')); // 'Invalid'This is what isInSubnet does internally for
IPv4 — shown here with the low-level primitives, using a different
name so it doesn't shadow the real export (which takes a single CIDR
string, not separate network/mask arguments):
import { ipv4ToBinary } from '@tundralibs/utils';
function checkSubnetMembership(
ip: string,
network: string,
mask: number,
): boolean {
const ipBinary = ipv4ToBinary(ip);
const networkBinary = ipv4ToBinary(network);
return ipBinary.substring(0, mask) === networkBinary.substring(0, mask);
}
checkSubnetMembership('192.168.1.10', '192.168.0.0', 16); // true
checkSubnetMembership('10.0.0.1', '192.168.0.0', 16); // falseFor real subnet checks, prefer the actual export — it validates
input, never throws, and supports IPv6 too:
isInSubnet('192.168.1.10', '192.168.0.0/16').
import { ipv4ToLong } from '@tundralibs/utils';
const serverIPs = [
'192.168.1.100',
'192.168.1.10',
'192.168.1.1',
'10.0.0.1',
];
// Sort numerically
const sorted = serverIPs.sort((a, b) => ipv4ToLong(a) - ipv4ToLong(b));
// ['10.0.0.1', '192.168.1.1', '192.168.1.10', '192.168.1.100']import { expandIPv6, isValidIPv6Structure } from '@tundralibs/utils';
function normalizeIPv6(ip: string): string | null {
if (!isValidIPv6Structure(ip)) {
return null;
}
return expandIPv6(ip);
}
normalizeIPv6('2001:db8::1');
// '2001:db8:0:0:0:0:0:1'
normalizeIPv6('::1');
// '0:0:0:0:0:0:0:1'import { ipv4ToHexSegments, isValidIPv4 } from '@tundralibs/utils';
function ipv4ToIPv6Mapped(ipv4: string): string | null {
if (!isValidIPv4(ipv4)) return null;
const hexSegments = ipv4ToHexSegments(ipv4);
return `::ffff:${hexSegments[0]}:${hexSegments[1]}`;
}
ipv4ToIPv6Mapped('192.168.1.1'); // '::ffff:c0a8:101'
ipv4ToIPv6Mapped('10.0.0.1'); // '::ffff:a00:1'import { ipv4ToLong, isIPv4InRange } from '@tundralibs/utils';
function getNetworkRange(network: string, mask: number) {
const networkLong = ipv4ToLong(network);
const hostBits = 32 - mask;
const totalHosts = Math.pow(2, hostBits);
return {
network,
mask,
firstIP: longToIPv4(networkLong + 1),
lastIP: longToIPv4(networkLong + totalHosts - 2),
broadcast: longToIPv4(networkLong + totalHosts - 1),
totalHosts: totalHosts - 2, // Exclude network and broadcast
};
}
function longToIPv4(num: number): string {
return [
(num >>> 24) & 255,
(num >>> 16) & 255,
(num >>> 8) & 255,
num & 255,
].join('.');
}
const range = getNetworkRange('192.168.1.0', 24);
// {
// network: '192.168.1.0',
// mask: 24,
// firstIP: '192.168.1.1',
// lastIP: '192.168.1.254',
// broadcast: '192.168.1.255',
// totalHosts: 254
// }import { isIPv4InRange } from '@tundralibs/utils';
interface FirewallRule {
name: string;
network: string;
mask: number;
action: 'allow' | 'deny';
}
function checkFirewallRules(ip: string, rules: FirewallRule[]): string {
for (const rule of rules) {
if (isIPv4InRange(ip, rule.network, rule.mask)) {
return `${rule.action} (matched ${rule.name})`;
}
}
return 'deny (no match)';
}
const rules: FirewallRule[] = [
{
name: 'Internal Network',
network: '192.168.0.0',
mask: 16,
action: 'allow',
},
{ name: 'DMZ', network: '10.0.0.0', mask: 8, action: 'allow' },
{ name: 'Blocked Range', network: '172.16.0.0', mask: 12, action: 'deny' },
];
checkFirewallRules('192.168.1.10', rules); // 'allow (matched Internal Network)'
checkFirewallRules('172.16.1.1', rules); // 'deny (matched Blocked Range)'
checkFirewallRules('8.8.8.8', rules); // 'deny (no match)'import { ipv4ToBinary, ipv6ToBinary } from '@tundralibs/utils';
function compareSubnets(
ip1: string,
ip2: string,
mask: number,
isIPv6 = false,
): boolean {
const binary1 = isIPv6 ? ipv6ToBinary(ip1) : ipv4ToBinary(ip1);
const binary2 = isIPv6 ? ipv6ToBinary(ip2) : ipv4ToBinary(ip2);
return binary1.substring(0, mask) === binary2.substring(0, mask);
}
// IPv4
compareSubnets('192.168.1.10', '192.168.1.20', 24); // true (same /24)
compareSubnets('192.168.1.10', '192.168.2.10', 24); // false (different /24)
// IPv6
compareSubnets('2001:db8::1', '2001:db8::2', 64, true); // true (same /64)
compareSubnets('2001:db8::1', '2001:db9::1', 64, true); // false (different /64)✅ Validate before converting:
import { ipv4ToBinary, isValidIPv4 } from '@tundralibs/utils';
declare const ip: string;
if (isValidIPv4(ip)) {
const binary = ipv4ToBinary(ip);
// ... use binary
}✅ Use constants instead of magic numbers:
import { IPV4_BITS } from '@tundralibs/utils';
declare const mask: number;
if (mask > 0 && mask <= IPV4_BITS) {
// Valid IPv4 mask
}✅ Normalize IPv6 before comparing:
import { expandIPv6 } from '@tundralibs/utils';
declare const ip1: string;
declare const ip2: string;
const normalized1 = expandIPv6(ip1);
const normalized2 = expandIPv6(ip2);
if (normalized1 === normalized2) {
// Same address
}✅ Use binary operations for subnet checks:
import { isIPv4InRange } from '@tundralibs/utils';
declare const ip: string;
declare const network: string;
declare const mask: number;
// Efficient bit-level comparison
const inRange = isIPv4InRange(ip, network, mask);❌ Don't convert without validation:
import { ipv4ToBinary, isValidIPv4 } from '@tundralibs/utils';
declare const userInput: string;
// BAD: Could throw or return garbage
const binary = ipv4ToBinary(userInput);
// GOOD: Validate first
if (isValidIPv4(userInput)) {
const validated = ipv4ToBinary(userInput);
}❌ Don't compare compressed IPv6 directly:
import { expandIPv6 } from '@tundralibs/utils';
declare const ip1: string;
declare const ip2: string;
// BAD: '2001:db8::1' !== '2001:0db8::0001'
if (ip1 === ip2) {}
// GOOD: Normalize first
if (expandIPv6(ip1) === expandIPv6(ip2)) {}❌ Don't use string operations for ranges:
import { isIPv4InRange } from '@tundralibs/utils';
declare const ip: string;
// BAD: String comparison doesn't work for IPs
if (ip >= '192.168.0.0' && ip <= '192.168.255.255') {}
// GOOD: Use proper range checking
if (isIPv4InRange(ip, '192.168.0.0', 16)) {}Benched on Apple M2 Max / Deno 2.9.5:
| Function | Time (avg) |
|---|---|
isValidIPv4 |
~66 ns |
ipv4ToLong |
~119 ns |
ipv4ToBinary |
~293 ns |
isIPv4InRange |
~370 ns |
expandIPv6 (compressed input) |
~444 ns |
ipv6ToBinary (compressed input) |
~1.4 µs |
All well under a microsecond except full IPv6 binary conversion,
which pays for expandIPv6 plus a 128-bit walk. For high-performance
scenarios:
- Cache validation results for frequently checked IPs
- Use
isIPv4InRangeinstead of binary string comparison - Pre-normalize IPv6 addresses in data structures
| Use Case | Functions | Example |
|---|---|---|
| Firewall Rules |
isIPv4InRange, isValidIPv4
|
ACL, security groups |
| Load Balancing |
ipv4ToLong, sorting |
Consistent hashing |
| Network Discovery |
isValidIPv4, isValidIPv6Structure
|
IP scanning |
| Subnet Planning |
ipv4ToBinary, ipv4ToLong
|
CIDR calculations |
| IPv6 Migration |
expandIPv6, ipv4ToHexSegments
|
Dual-stack support |
| Access Control | isIPv4InRange |
Geographic restrictions |
- Is In Subnet - High-level subnet membership checking
- Is Public IP - Detect public vs private addresses
- Is Subnet - CIDR notation validation
- Get Free Port - Network port allocation