Subnet / CIDR Calculator — TypeScript 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 TypeScript 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.
export type IpClass = 'A' | 'B' | 'C' | '-';
export interface SubnetInfo {
network: string;
broadcast: string;
firstHost: string;
lastHost: string;
netmask: string;
wildcard: string;
hostCount: number;
prefix: number;
ipClass: IpClass;
isPrivate: boolean;
}
/** Convert a dotted-quad IPv4 string to an unsigned 32-bit integer. null if invalid. */
export function ipToInt(ip: string): number | null {
if (typeof ip !== 'string') return null;
const parts = ip.split('.');
if (parts.length !== 4) return null;
let result = 0;
for (const part of parts) {
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: number): string {
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: string): { ip: string; prefix: number } | null {
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;
return { ip: intToIp(ip), prefix };
}
/** Return the 32-bit netmask for a prefix length (0-32). */
function maskFromPrefix(prefix: number): number {
return prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
}
/** Full details for a CIDR block. null if the CIDR is invalid. */
export function subnetDetails(cidr: string): SubnetInfo | null {
const parsed = parseCidr(cidr);
if (!parsed) return null;
const { prefix } = parsed;
const mask = maskFromPrefix(prefix);
const wildcard = ~mask >>> 0;
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: number;
if (prefix >= 32) hostCount = 1;
else if (prefix === 31) hostCount = 2;
else hostCount = 2 ** (32 - prefix) - 2;
const firstOctet = (network >>> 24) & 255;
const ipClass: 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.
*/
export function splitSubnet(cidr: string, newPrefix: number): { network: string; prefix: number }[] {
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);
const blockSize = 2 ** (32 - newPrefix);
const out: { network: string; prefix: number }[] = [];
for (let i = 0; i < count; i++) {
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 →