Skip to content

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

// subnet-cidr — C# port: IPv4 subnet/CIDR math (dotted-quad ⇄ uint32, subnet details, CIDR splitting). (.NET 7+.) Port of src/lib/subnet-cidr.ts — same logic as this dir's python.py / go.go; invalid input yields null rather than throwing.
using System;
using System.Collections.Generic;
using System.Linq;

namespace SubnetCidr;

public static class SubnetCidr
{
    // The TS /^\d+$/ check — rejects signs, hex, whitespace, and the empty string.
    private static bool AllDigits(string s) => s.Length > 0 && s.All(char.IsAsciiDigit);

    // "a.b.c.d" → uint. null for a wrong octet count, junk octets, or an octet > 255.
    public static uint? IpToInt(string ip)
    {
        string[] parts = ip.Split('.');
        if (parts.Length != 4) return null;
        uint result = 0;
        foreach (string part in parts)
        {
            if (!AllDigits(part) || part.Length > 3) return null;
            uint octet = uint.Parse(part);
            if (octet > 255) return null;
            result = result * 256 + octet;
        }
        return result;
    }

    public static string IntToIp(uint v) => $"{v >> 24}.{(v >> 16) & 255}.{(v >> 8) & 255}.{v & 255}";
    private static uint MaskFromPrefix(int p) => p == 0 ? 0 : 0xFFFFFFFFu << (32 - p);

    // "ip/prefix" → (canonical ip, prefix). A second slash or a prefix > 32 is invalid.
    private static (string ip, int prefix)? ParseCidr(string cidr)
    {
        string[] halves = cidr.Split('/', 2);
        if (halves.Length != 2 || !AllDigits(halves[1]) || halves[1].Length > 2) return null;
        if (!int.TryParse(halves[1], out int prefix) || prefix > 32) return null;
        return IpToInt(halves[0]) is { } n ? (IntToIp(n), prefix) : null;
    }

    public sealed record SubnetInfo(string Network, string Broadcast, string FirstHost, string LastHost,
        ulong HostCount, int Prefix, char IpClass, bool IsPrivate);

    public static SubnetInfo SubnetDetails(string cidr) // "ip/prefix" → full block info
    {
        var parsed = ParseCidr(cidr);
        if (parsed is null) return null;
        uint mask = MaskFromPrefix(parsed.Value.prefix);
        uint network = IpToInt(parsed.Value.ip)!.Value & mask, broadcast = network | ~mask;
        // /31 (point-to-point) and /32 (single host) have no host/broadcast split.
        uint first = parsed.Value.prefix >= 31 ? network : network + 1;
        uint last = parsed.Value.prefix >= 31 ? broadcast : broadcast - 1;
        ulong hosts = parsed.Value.prefix switch { >= 32 => 1, 31 => 2, var p => (1ul << (32 - p)) - 2 };
        uint o1 = network >> 24, o2 = (network >> 16) & 255;
        char klass = o1 < 128 ? 'A' : o1 < 192 ? 'B' : o1 < 224 ? 'C' : '-'; // D/E → '-'
        bool priv = o1 == 10 || (o1 == 172 && o2 is >= 16 and <= 31) || (o1 == 192 && o2 == 168);
        return new SubnetInfo(IntToIp(network), IntToIp(broadcast), IntToIp(first), IntToIp(last),
            hosts, parsed.Value.prefix, klass, priv);
    }

    // Split into 2^(newPrefix-prefix) children; empty list for invalid input or range.
    public static List<string> SplitSubnet(string cidr, int newPrefix)
    {
        var parsed = ParseCidr(cidr);
        if (parsed is null || newPrefix < parsed.Value.prefix || newPrefix > 32) return new();
        uint network = IpToInt(parsed.Value.ip)!.Value & MaskFromPrefix(parsed.Value.prefix);
        uint block = 1u << (32 - newPrefix);
        return Enumerable.Range(0, 1 << (newPrefix - parsed.Value.prefix))
            .Select(i => $"{IntToIp(network + (uint)i * block)}/{newPrefix}").ToList();
    }

    public static void Main()
    {
        SubnetInfo d = SubnetDetails("192.168.1.100/24");
        Console.WriteLine($"192.168.1.100/24 net={d.Network} bcast={d.Broadcast} first={d.FirstHost} " +
                          $"last={d.LastHost} hosts={d.HostCount} class={d.IpClass} private={d.IsPrivate}");
        foreach (string kid in SplitSubnet("192.168.1.0/24", 26)) Console.WriteLine($"  split /26 → {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 →