Skip to content

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 →