Format: name, host count (one per line)
Quick Examples
Allocation Summary
02 / ALLOCATIONS

The allocated subnets, ready to deploy.

Name Requested CIDR Mask Range Usable
→ Export to Terraform
03 / EQUAL SPLIT

Split a CIDR into N equal subnets.

Specify either N subnets or a target prefix size. The other is calculated.
04 / DIFF VISUALIZER

Compare two CIDRs on a number line.

CODE EXAMPLES

VLSM allocation in code

A VLSM allocator from scratch is about twenty lines: sort the host requirements largest-first, round each up to the smallest prefix that fits, then pack them contiguously starting at the parent network. The example allocates three subnets (120, 50, 25 hosts) inside 10.0.0.0/23.

import ipaddress, math

parent = ipaddress.IPv4Network("10.0.0.0/23")
demands = [120, 50, 25]

# Sort largest-first
demands.sort(reverse=True)

cursor = int(parent.network_address)
print(f"Allocations from {parent}:")
for need in demands:
    # smallest prefix that fits "need" hosts (RFC standard: subtract 2)
    prefix = 32 - math.ceil(math.log2(need + 2))
    size = 1 << (32 - prefix)
    # align cursor up to the prefix boundary
    if cursor % size:
        cursor += size - (cursor % size)
    subnet = ipaddress.IPv4Network((cursor, prefix))
    usable = size - 2
    print(f"  {need:>3} hosts -> {subnet.network_address}/{prefix:<2} ({usable} usable)")
    cursor += size
package main

import (
	"fmt"
	"math"
	"net"
	"sort"
)

func main() {
	_, parent, _ := net.ParseCIDR("10.0.0.0/23")
	parentInt := uint32(parent.IP[0])<<24 | uint32(parent.IP[1])<<16 |
		uint32(parent.IP[2])<<8 | uint32(parent.IP[3])

	demands := []int{120, 50, 25}
	sort.Sort(sort.Reverse(sort.IntSlice(demands)))

	cursor := parentInt
	fmt.Printf("Allocations from %s:\n", parent.String())
	for _, need := range demands {
		prefix := 32 - int(math.Ceil(math.Log2(float64(need+2))))
		size := uint32(1) << (32 - prefix)
		if cursor%size != 0 {
			cursor += size - (cursor % size)
		}
		netIP := net.IPv4(byte(cursor>>24), byte(cursor>>16),
			byte(cursor>>8), byte(cursor)).To4()
		usable := int(size) - 2
		fmt.Printf("  %3d hosts -> %s/%-2d (%d usable)\n",
			need, netIP, prefix, usable)
		cursor += size
	}
}
function ip2int(s) {
  return s.split('.').reduce((a, o) => (a << 8) + +o, 0) >>> 0;
}
function int2ip(n) {
  return [24, 16, 8, 0].map(s => (n >>> s) & 0xff).join('.');
}

const [parentIp, parentPrefix] = "10.0.0.0/23".split('/');
const demands = [120, 50, 25].sort((a, b) => b - a);

let cursor = ip2int(parentIp);
console.log(`Allocations from ${parentIp}/${parentPrefix}:`);
for (const need of demands) {
  const prefix = 32 - Math.ceil(Math.log2(need + 2));
  const size = 2 ** (32 - prefix);
  if (cursor % size) cursor += size - (cursor % size);
  const usable = size - 2;
  console.log(`  ${String(need).padStart(3)} hosts -> ${int2ip(cursor)}/${String(prefix).padEnd(2)} (${usable} usable)`);
  cursor += size;
}
#!/usr/bin/env bash
# Variable-length subnet allocation, largest-first, inside a parent CIDR.

PARENT="10.0.0.0/23"
DEMANDS=(120 50 25)

ip2int() { local IFS=.; local -a o=($1)
  echo $(( (o[0]<<24)|(o[1]<<16)|(o[2]<<8)|o[3] )); }
int2ip() { printf "%d.%d.%d.%d" \
  $(( ($1>>24)&0xff )) $(( ($1>>16)&0xff )) \
  $(( ($1>>8)&0xff )) $(( $1&0xff )); }

# Smallest prefix that fits N hosts (RFC standard: subtract 2)
prefix_for() {
  local need=$1 bits=1
  while (( (1 << bits) - 2 < need )); do ((bits++)); done
  echo $((32 - bits))
}

# Largest-first sort
IFS=$'\n' SORTED=($(sort -nr <<< "${DEMANDS[*]}" | tr ' ' '\n'))
unset IFS

cursor=$(ip2int "${PARENT%/*}")
echo "Allocations from $PARENT:"
for need in "${SORTED[@]}"; do
  pfx=$(prefix_for "$need")
  size=$(( 1 << (32 - pfx) ))
  rem=$(( cursor % size ))
  if (( rem != 0 )); then cursor=$(( cursor + size - rem )); fi
  usable=$(( size - 2 ))
  printf "  %3d hosts -> %s/%-2d (%d usable)\n" \
    "$need" "$(int2ip $cursor)" "$pfx" "$usable"
  cursor=$(( cursor + size ))
done
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class VlsmAlloc {
    static String int2ip(long n) {
        return String.format("%d.%d.%d.%d",
            (n >> 24) & 0xff, (n >> 16) & 0xff,
            (n >> 8) & 0xff, n & 0xff);
    }
    static long ip2int(String s) {
        String[] p = s.split("\\.");
        return ((Long.parseLong(p[0]) << 24)
              | (Long.parseLong(p[1]) << 16)
              | (Long.parseLong(p[2]) <<  8)
              |  Long.parseLong(p[3])) & 0xFFFFFFFFL;
    }
    public static void main(String[] args) {
        String parent = "10.0.0.0/23";
        List<Integer> demands = new java.util.ArrayList<>(Arrays.asList(120, 50, 25));
        demands.sort(Collections.reverseOrder());

        long cursor = ip2int(parent.split("/")[0]);
        System.out.printf("Allocations from %s:%n", parent);
        for (int need : demands) {
            int prefix = 32 - (int) Math.ceil(Math.log(need + 2) / Math.log(2));
            long size = 1L << (32 - prefix);
            if (cursor % size != 0) cursor += size - (cursor % size);
            long usable = size - 2;
            System.out.printf("  %3d hosts -> %s/%-2d (%d usable)%n",
                need, int2ip(cursor), prefix, usable);
            cursor += size;
        }
    }
}
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>

static void int2ip(uint32_t n, char *out) {
    sprintf(out, "%u.%u.%u.%u",
        (n >> 24) & 0xff, (n >> 16) & 0xff,
        (n >> 8)  & 0xff,  n        & 0xff);
}
static uint32_t ip2int(const char *s) {
    unsigned a, b, c, d;
    sscanf(s, "%u.%u.%u.%u", &a, &b, &c, &d);
    return (a << 24) | (b << 16) | (c << 8) | d;
}
static int cmp_desc(const void *a, const void *b) {
    return *(const int*)b - *(const int*)a;
}

int main(void) {
    const char *parent = "10.0.0.0/23";
    int demands[] = {120, 50, 25};
    int n = sizeof(demands) / sizeof(demands[0]);

    qsort(demands, n, sizeof(int), cmp_desc);

    uint32_t cursor = ip2int("10.0.0.0");
    printf("Allocations from %s:\n", parent);
    for (int i = 0; i < n; i++) {
        int need = demands[i];
        int prefix = 32 - (int)ceil(log2(need + 2));
        uint32_t size = 1u << (32 - prefix);
        if (cursor % size) cursor += size - (cursor % size);
        char buf[20];
        int2ip(cursor, buf);
        printf("  %3d hosts -> %s/%-2d (%u usable)\n",
               need, buf, prefix, size - 2);
        cursor += size;
    }
    return 0;
}
Allocations from 10.0.0.0/23:
  120 hosts -> 10.0.0.0/25 (126 usable)
   50 hosts -> 10.0.0.128/26 (62 usable)
   25 hosts -> 10.0.0.192/27 (30 usable)
FAQ

Frequently asked questions

What is VLSM (Variable-Length Subnet Masking)?

VLSM is the practice of using different subnet sizes within one parent CIDR. Instead of splitting a /22 into four equal /24s, you allocate a /24 for one tier, a /25 for the next, a /26 for the smallest — sized to actual need. This saves address space dramatically in heterogeneous networks.

Why allocate largest-first in VLSM?

Largest-first prevents fragmentation. If you allocate a small subnet at the start of the parent block, you may leave gaps that aren't big enough for the larger subnets you need later. Allocating largest-first guarantees alignment and minimizes wasted space.

Does this VLSM planner handle cloud reserved IPs?

Yes. Switch to AWS, Azure, GCP, or OCI mode and the planner adds the reserved-IP overhead to each subnet's host requirement, so the resulting plan still meets your actual host count after the cloud provider takes its IPs.

Can I export the VLSM plan?

Yes — copy the plan as JSON, or send it to IaC Export to generate Terraform / CloudFormation / Pulumi modules for the whole VPC layout.

REFERENCES

Common prefix references