Subnet / CIDR Calculator — JavaScript source
Compute IPv4 subnet details from a CIDR block - network, broadcast, host range, netmask, wildcard, host count, IP class, and private-range detection. Split a block into smaller subnets, all in your browser.
This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
/**
* IPv4 subnet / CIDR math - pure, deterministic, never throws.
* Invalid input returns null (or [] for the splitter) so the UI can show a
* graceful error.
*
* Language: JavaScript (ES module)
* CosmoDev polyglot showcase port of `subnet-cidr`.
* Ported from src/lib/subnet-cidr.ts - functionally equivalent.
*
* Display source - part of CosmoDev's polyglot tool pages.
*/
// IPv4 addresses are handled as unsigned 32-bit integers. JavaScript numbers
// hold these exactly (well below Number.MAX_SAFE_INTEGER), and the `>>> 0`
// idiom folds any bitwise result back into the unsigned 32-bit range -
// important because `&` and `~` otherwise return a *signed* 32-bit value.
/**
* @typedef {'A' | 'B' | 'C' | '-'} IpClass
*/
/**
* @typedef {Object} SubnetInfo
* @property {string} network Network address (ip AND mask).
* @property {string} broadcast Broadcast address (network OR wildcard).
* @property {string} firstHost First usable host (network+1, except /31,/32).
* @property {string} lastHost Last usable host (broadcast-1, except /31,/32).
* @property {string} netmask Dotted-quad netmask.
* @property {string} wildcard Dotted-quad wildcard (host mask).
* @property {number} hostCount Usable host count (1 for /32, 2 for /31).
* @property {number} prefix CIDR prefix length (0-32).
* @property {IpClass} ipClass Classful letter.
* @property {boolean} isPrivate RFC 1918 private range?
*/
/** Convert a dotted-quad IPv4 string to an unsigned 32-bit integer. null if invalid. */
export function ipToInt(ip) {
if (typeof ip !== 'string') return null;
const parts = ip.split('.');
if (parts.length !== 4) return null;
let result = 0;
for (const part of parts) {
// Only plain decimal digits - no whitespace, signs, or hex.
if (!/^\d+$/.test(part)) return null;
const octet = Number(part);
if (octet > 255) return null;
result = result * 256 + octet;
}
return result >>> 0;
}
/** Convert an unsigned 32-bit integer to a dotted-quad IPv4 string. */
export function intToIp(n) {
// Math.floor tolerates a float argument; `>>> 0` coerces to uint32.
const v = Math.floor(n) >>> 0;
return [`${(v >>> 24) & 255}`, `${(v >>> 16) & 255}`, `${(v >>> 8) & 255}`, `${v & 255}`].join('.');
}
/** Parse "ip/prefix" into { ip, prefix } with prefix in 0-32. null if invalid. */
export function parseCidr(cidr) {
if (typeof cidr !== 'string') return null;
const slash = cidr.indexOf('/');
if (slash === -1) return null;
// Reject multiple slashes ("a.b.c.d/24/16").
if (cidr.indexOf('/', slash + 1) !== -1) return null;
const ipPart = cidr.slice(0, slash);
const prefixPart = cidr.slice(slash + 1);
if (!/^\d+$/.test(prefixPart)) return null;
const prefix = Number(prefixPart);
if (prefix > 32) return null;
const ip = ipToInt(ipPart);
if (ip === null) return null;
// Re-emit through intToIp so the address is in canonical form.
return { ip: intToIp(ip), prefix };
}
/**
* Return the 32-bit netmask for a prefix length (0-32).
* Guards the prefix===0 case: `<< 32` would fold to `<< 0` under JS's 32-bit
* shift semantics, so it must be handled explicitly.
*/
function maskFromPrefix(prefix) {
return prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
}
/** Full details for a CIDR block. null if the CIDR is invalid. */
export function subnetDetails(cidr) {
const parsed = parseCidr(cidr);
if (!parsed) return null;
const { prefix } = parsed;
const mask = maskFromPrefix(prefix);
const wildcard = ~mask >>> 0;
// Parentheses matter: `>>>` binds tighter than `&`, and the final `>>> 0`
// undoes the signed result `&` would otherwise produce.
const network = (ipToInt(parsed.ip) & mask) >>> 0;
const broadcast = (network | wildcard) >>> 0;
// /31 (point-to-point) and /32 (single host) have no host/broadcast split.
const firstHost = prefix >= 31 ? network : (network + 1) >>> 0;
const lastHost = prefix >= 31 ? broadcast : (broadcast - 1) >>> 0;
let hostCount;
if (prefix >= 32) hostCount = 1;
else if (prefix === 31) hostCount = 2;
else hostCount = 2 ** (32 - prefix) - 2;
const firstOctet = (network >>> 24) & 255;
const ipClass =
firstOctet < 128 ? 'A' : firstOctet < 192 ? 'B' : firstOctet < 224 ? 'C' : '-';
const secondOctet = (network >>> 16) & 255;
const isPrivate =
firstOctet === 10 ||
(firstOctet === 172 && secondOctet >= 16 && secondOctet <= 31) ||
(firstOctet === 192 && secondOctet === 168);
return {
network: intToIp(network),
broadcast: intToIp(broadcast),
firstHost: intToIp(firstHost),
lastHost: intToIp(lastHost),
netmask: intToIp(mask),
wildcard: intToIp(wildcard),
hostCount,
prefix,
ipClass,
isPrivate,
};
}
/**
* Split a CIDR block into smaller subnets of `newPrefix`.
* Returns [] if the CIDR is invalid or newPrefix is outside (prefix, 32].
* The base IP is normalized to the network address before splitting.
*
* @returns {{ network: string, prefix: number }[]}
*/
export function splitSubnet(cidr, newPrefix) {
const parsed = parseCidr(cidr);
if (!parsed) return [];
if (!Number.isInteger(newPrefix) || newPrefix < parsed.prefix || newPrefix > 32) return [];
const mask = maskFromPrefix(parsed.prefix);
const network = (ipToInt(parsed.ip) & mask) >>> 0;
const count = 2 ** (newPrefix - parsed.prefix); // number of child blocks
const blockSize = 2 ** (32 - newPrefix); // addresses per child block
const out = [];
for (let i = 0; i < count; i++) {
// Addition wraps via the final `>>> 0`, matching uint32 semantics.
out.push({ network: intToIp(network + i * blockSize), prefix: newPrefix });
}
return out;
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →