Skip to content

Subnet / CIDR Calculator — Zig 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 Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// subnet-cidr — Zig port: IPv4 subnet/CIDR math (dotted-quad ⇄ u32, subnet details, CIDR splitting). Language: Zig (0.13, stdlib only). Port of src/lib/subnet-cidr.ts — same logic as this dir's python.py / go.go; invalid input yields null rather than erroring.
const std = @import("std");

const SubnetInfo = struct {
    network: []const u8, broadcast: []const u8, first_host: []const u8, last_host: []const u8,
    host_count: u64, prefix: u6, ip_class: u8, is_private: bool,
};

fn allDigits(s: []const u8) bool { // the TS /^\d+$/ check — no signs, hex, whitespace, or empty
    var ok = s.len > 0; for (s) |c| ok = ok and c >= '0' and c <= '9';
    return ok;
}
fn ipToInt(ip: []const u8) ?u32 { // "a.b.c.d" → u32; null for wrong octet count, junk, or > 255
    var it = std.mem.splitScalar(u8, ip, '.');
    var result: u32 = 0; var octets: usize = 0;
    while (it.next()) |part| {
        if (!allDigits(part) or part.len > 3) return null;
        var octet: u32 = 0;
        for (part) |c| octet = octet * 10 + (c - '0');
        if (octet > 255) return null;
        result = result * 256 + octet;
        octets += 1;
    }
    return if (octets == 4) result else null;
}
fn intToIp(v: u32, buf: []u8) []const u8 {
    return std.fmt.bufPrint(buf, "{d}.{d}.{d}.{d}", .{ v >> 24, (v >> 16) & 255, (v >> 8) & 255, v & 255 }) catch unreachable;
}
fn maskFromPrefix(p: u6) u32 {
    return if (p == 0) 0 else 0xFFFFFFFF << @intCast(32 - p);
}
fn subnetDetails(cidr: []const u8, buf: *[4][16]u8) ?SubnetInfo { // "ip/prefix" → full block details
    const slash = std.mem.indexOfScalar(u8, cidr, '/') orelse return null;
    const prefixPart = cidr[slash + 1 ..];
    if (std.mem.indexOfScalar(u8, prefixPart, '/') != null or !allDigits(prefixPart) or prefixPart.len > 2) return null;
    var prefix: u32 = 0; for (prefixPart) |c| prefix = prefix * 10 + (c - '0'); // decimal — no octal autodetect
    if (prefix > 32) return null;
    const ipInt = ipToInt(cidr[0..slash]) orelse return null;
    const mask = maskFromPrefix(@intCast(prefix));
    const network = ipInt & mask; const broadcast = network | ~mask;
    // /31 (point-to-point) and /32 (single host) have no host/broadcast split.
    const first = if (prefix >= 31) network else network + 1;
    const last = if (prefix >= 31) broadcast else broadcast - 1;
    const host_count: u64 = if (prefix >= 32) 1 else if (prefix == 31) 2 else (@as(u64, 1) << @intCast(32 - prefix)) - 2;
    const o1: u32 = network >> 24;
    const o2: u32 = (network >> 16) & 255;
    return .{
        .network = intToIp(network, &buf[0]),
        .broadcast = intToIp(broadcast, &buf[1]),
        .first_host = intToIp(first, &buf[2]),
        .last_host = intToIp(last, &buf[3]),
        .host_count = host_count,
        .prefix = @intCast(prefix),
        .ip_class = if (o1 < 128) 'A' else if (o1 < 192) 'B' else if (o1 < 224) 'C' else '-', // D/E → '-'
        .is_private = o1 == 10 or (o1 == 172 and o2 >= 16 and o2 <= 31) or (o1 == 192 and o2 == 168),
    };
}
fn splitSubnet(cidr: []const u8, new_prefix: u6, bufs: [][24]u8) usize { // writes "ip/prefix" per slot; count returned
    var detail_buf: [4][16]u8 = undefined;
    const d = subnetDetails(cidr, &detail_buf) orelse return 0;
    if (new_prefix < d.prefix or bufs.len == 0) return 0;
    const network = ipToInt(d.network) orelse return 0;
    const count = @as(usize, 1) << @intCast(new_prefix - d.prefix);
    const block = @as(u32, 1) << @intCast(32 - new_prefix);
    var n: usize = 0;
    for (0..count) |i| {
        if (n == bufs.len) break;
        const ip = intToIp(network + @as(u32, @intCast(i)) * block, bufs[n]);
        _ = std.fmt.bufPrint(bufs[n][ip.len..], "{s}/{d}", .{ ip, new_prefix }) catch unreachable;
        n += 1;
    }
    return n;
}
pub fn main() !void {
    var buf: [4][16]u8 = undefined;
    const d = subnetDetails("192.168.1.100/24", &buf) orelse return;
    std.debug.print("192.168.1.100/24 net={s} bcast={s} first={s} last={s} hosts={d} class={c} private={}\n", .{ d.network, d.broadcast, d.first_host, d.last_host, d.host_count, d.ip_class, d.is_private });
    var bufs: [8][24]u8 = undefined; const kids = splitSubnet("192.168.1.0/24", 26, &bufs);
    for (bufs[0..kids]) |kid| std.debug.print("  split /26 → {s}\n", .{kid});
}

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 →