From 5bf9ac2087aa0173f9fb8f59736b0faeb14cc8ee Mon Sep 17 00:00:00 2001 From: 7c <7c@github.com> Date: Wed, 24 Jun 2026 15:34:32 -0400 Subject: [PATCH] fix(ipdo): correct ipInRange for IPv6 and high-bit IPv4 ipv6ToBigInt expanded "::" to 9 groups (144 bits) instead of 8 due to an off-by-one and mishandling of the empty strings produced by split(":"). This corrupted CIDR comparisons, so addresses inside ranges like 2606:4700::/32 and 2000::/3 were reported as out of range. parseCIDR also computed the IPv4 network with signed 32-bit bitwise ops, yielding a negative start for addresses >= 128.0.0.0 and breaking ranges such as 192.168.0.0/24. Switched to arithmetic, which also supports /0. Fixes #4 --- packages/ipdo/src/index.ts | 47 +++++++++++++------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/packages/ipdo/src/index.ts b/packages/ipdo/src/index.ts index 69ae72e..b6f0832 100644 --- a/packages/ipdo/src/index.ts +++ b/packages/ipdo/src/index.ts @@ -17,39 +17,22 @@ export function ipv4ToInt(ip: string): number { * Convert IPv6 address to BigInt */ export function ipv6ToBigInt(ip: string): bigint { + let groups: string[]; + // Handle compressed IPv6 addresses with :: if (ip.includes("::")) { - const parts = ip.split(":"); - const compressedIndex = parts.indexOf(""); + const [headStr, tailStr = ""] = ip.split("::"); + const head = headStr ? headStr.split(":") : []; + const tail = tailStr ? tailStr.split(":") : []; - // Count how many empty parts after compression - let emptyCount = 0; - for (let i = compressedIndex; i < parts.length; i++) { - if (parts[i] === "") emptyCount++; - } - - // Calculate how many zeros to insert - const missingZeros = 8 - (parts.length - emptyCount) + 1; - - const expandedParts: string[] = []; - for (let i = 0; i < parts.length; i++) { - if (parts[i] === "") { - if (i === 0 || i === parts.length - 1) { - continue; // Skip empty at start/end (leading/trailing ::) - } - // Insert missing zeros - for (let j = 0; j < missingZeros; j++) { - expandedParts.push("0"); - } - } else { - expandedParts.push(parts[i]); - } - } - - ip = expandedParts.join(":"); + // Insert enough zero groups to total 8 groups + const missing = 8 - head.length - tail.length; + groups = [...head, ...Array(Math.max(missing, 0)).fill("0"), ...tail]; + } else { + groups = ip.split(":"); } - return ip.split(":").reduce((int, hex) => { + return groups.reduce((int, hex) => { return (int << 16n) + BigInt(Number.parseInt(hex || "0", 16)); }, 0n); } @@ -360,9 +343,11 @@ export function parseCIDR(cidr: string): { // Calculate start and end addresses if (version === 4) { const ipInt = ipv4ToInt(ip); - const mask = ~((1 << (32 - prefix)) - 1) >>> 0; - const start = ipInt & mask; - const end = start + ((1 << (32 - prefix)) - 1); + // Use arithmetic to avoid signed 32-bit bitwise overflow for addresses + // with the high bit set (>= 128.0.0.0) and to support a /0 prefix. + const size = 2 ** (32 - prefix); + const start = Math.floor(ipInt / size) * size; + const end = start + size - 1; return { start, end, prefix, version }; } else { const ipBigInt = ipv6ToBigInt(ip);