What this is
Torinoa Tools includes a subnet calculator that splits a CIDR block into subnets. Beyond simple "split into N equal subnets," it also supports VLSM (Variable Length Subnet Masking).
VLSM handles the case where each subnet needs a different number of hosts — say, HQ needs 100 hosts, Branch A needs 50, Branch B needs 25, and a DMZ needs 10 — and you want to allocate address space without wasting it. Here's how the packing algorithm is implemented.
What actually needs computing
For each subnet in a VLSM plan, you need:
- The smallest prefix length (subnet mask) that can fit the requested host count
- A starting address inside the parent network that doesn't overlap any other subnet
Part 1 is a straightforward formula. Part 2 requires processing requirements largest-first and packing them into the address space in order.
Computing the required host bits
In IPv4, usable hosts per subnet = total addresses in the subnet minus 2 (network address and broadcast address). So you need the smallest n (host bits) such that 2^n - 2 >= requested hosts.
const hostBits = ceilLog2(hosts + 2);
const subnetPrefix = 32 - hostBits;
const subnetSize = Math.pow(2, hostBits);
Passing hosts + 2 into ceilLog2 bakes in the "minus network and broadcast address" adjustment up front, rather than handling it separately later.
Pack largest requirements first (greedy)
The standard approach for VLSM address allocation is to place the largest subnets first. If you pack smallest-first, you end up with gaps when a later, larger subnet has to align to its own boundary — wasting address space that a different ordering wouldn't have wasted.
function calculateVlsm(networkNum: number, prefix: number, parentTotal: number) {
const reqs = vlsmRequirements.value;
// Sort descending for efficient packing
const sorted = [...reqs].sort((a, b) => b - a);
let currentNum = networkNum;
const subnets: SubnetResult[] = [];
for (const hosts of sorted) {
const hostBits = ceilLog2(hosts + 2);
const subnetPrefix = 32 - hostBits;
const subnetSize = Math.pow(2, hostBits);
// Align to subnet boundary
const alignedNum = (Math.ceil(currentNum / subnetSize) * subnetSize) >>> 0;
if (alignedNum + subnetSize - 1 > networkNum + parentTotal - 1) {
// Doesn't fit in the parent network
errors.value.value = t("errVlsmOverflow");
results.value = [];
return;
}
const subnetMask = maskFromPrefix(subnetPrefix);
const broadcastNum = (alignedNum + subnetSize - 1) >>> 0;
subnets.push({
network: numToIp(alignedNum),
mask: numToIp(subnetMask),
broadcast: numToIp(broadcastNum),
firstHost: numToIp(alignedNum + 1),
lastHost: numToIp(broadcastNum - 1),
usableHosts: subnetSize - 2,
totalAddresses: subnetSize,
prefix: subnetPrefix,
});
currentNum = alignedNum + subnetSize;
}
results.value = subnets;
}
Aligning to a subnet boundary
CIDR subnets can only start at an address that's a multiple of their own size (a /23, 512 addresses, can only start on an address divisible by 512). This line finds "the next valid boundary at or after currentNum":
const alignedNum = (Math.ceil(currentNum / subnetSize) * subnetSize) >>> 0;
Dividing currentNum (the address right after the previous subnet) by subnetSize, rounding up, then multiplying back by subnetSize gives the nearest aligned address that's >= currentNum. The >>> 0 coerces to an unsigned 32-bit integer — a common idiom when doing bitwise arithmetic on IPv4 addresses represented as numbers.
Overflowing the parent network is a hard error, not a silent truncation
if (alignedNum + subnetSize - 1 > networkNum + parentTotal - 1) {
errors.value.value = t("errVlsmOverflow");
results.value = [];
return;
}
Boundary alignment introduces gaps, so even if the sum of requested hosts is less than or equal to the parent network's total address count, alignment overhead can still make the plan not actually fit. Rather than silently dropping subnets or truncating, this returns an explicit error — so you find out right away whether your requirements genuinely fit, instead of getting a plan that's quietly wrong.
Wrap-up
VLSM itself isn't a hard algorithm, but getting three things right — sort descending, align to boundaries, and fail loudly on overflow — is enough for a correct, simple implementation. A small but genuinely useful tool if you've ever hand-calculated a subnet plan on paper.
Try it here: https://tools.torinoa.com/tools/subnet-calculator/
Top comments (1)
Really interesting approach! I'm curious if the packing algorithm prioritizes contiguous blocks or if it can