Subnet / CIDR Calculator — Ruby 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 Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# subnet-cidr — Ruby port: IPv4 subnet/CIDR math (dotted-quad ⇄ uint32, subnet details, CIDR splitting). Language: Ruby (3.1+, stdlib only). Port of src/lib/subnet-cidr.ts — same logic as this dir's python.py / go.go; invalid input yields nil rather than raising.
Details = Struct.new(:network, :broadcast, :first_host, :last_host, :host_count,
:prefix, :ip_class, :is_private)
# The TS /^\d+$/ check — rejects signs, hex, whitespace, and the empty string.
def all_digits?(s) = !s.empty? && s.match?(/\A\d+\z/)
# "a.b.c.d" → uint32. nil for a wrong octet count, junk octets, or an octet > 255.
def ip_to_int(ip)
parts = ip.split('.', -1)
return nil unless parts.length == 4
result = 0
parts.each do |part|
return nil unless all_digits?(part)
octet = part.to_i # decimal on purpose — no octal autodetect
return nil if octet > 255
result = result * 256 + octet
end
result & 0xFFFFFFFF
end
def int_to_ip(v)
"#{(v >> 24) & 255}.#{(v >> 16) & 255}.#{(v >> 8) & 255}.#{v & 255}"
end
def mask_from_prefix(p) = p.zero? ? 0 : (0xFFFFFFFF << (32 - p)) & 0xFFFFFFFF
# "ip/prefix" → full block details; nil for an invalid CIDR.
def subnet_details(cidr)
ip_part, prefix_part = cidr.split('/', 2)
return nil if prefix_part.nil? || prefix_part.include?('/') || !all_digits?(prefix_part)
prefix = prefix_part.to_i
ip_int = ip_to_int(ip_part)
return nil if prefix > 32 || ip_int.nil?
mask = mask_from_prefix(prefix)
wildcard = (~mask) & 0xFFFFFFFF
network = ip_int & mask
broadcast = network | wildcard
# /31 (point-to-point) and /32 (single host) have no host/broadcast split.
first_host = prefix >= 31 ? network : network + 1
last_host = prefix >= 31 ? broadcast : broadcast - 1
host_count = prefix >= 32 ? 1 : prefix == 31 ? 2 : (1 << (32 - prefix)) - 2
o1 = network >> 24
o2 = (network >> 16) & 255
ip_class = o1 < 128 ? 'A' : o1 < 192 ? 'B' : o1 < 224 ? 'C' : '-' # D/E → '-'
is_private = o1 == 10 || (o1 == 172 && o2.between?(16, 31)) || (o1 == 192 && o2 == 168)
Details.new(int_to_ip(network), int_to_ip(broadcast), int_to_ip(first_host),
int_to_ip(last_host), host_count, prefix, ip_class, is_private)
end
# Split into 2^(new_prefix-prefix) children; [] for invalid input or range.
def split_subnet(cidr, new_prefix)
details = subnet_details(cidr)
return [] if details.nil? || new_prefix < details.prefix || new_prefix > 32
network = ip_to_int(details.network) & mask_from_prefix(details.prefix)
count = 1 << (new_prefix - details.prefix)
block = 1 << (32 - new_prefix)
(0...count).map { |i| "#{int_to_ip(network + i * block)}/#{new_prefix}" }
end
if $PROGRAM_NAME == __FILE__
d = subnet_details('192.168.1.100/24')
puts "192.168.1.100/24 net=#{d.network} bcast=#{d.broadcast} first=#{d.first_host} " \
"last=#{d.last_host} hosts=#{d.host_count} class=#{d.ip_class} private=#{d.is_private}"
split_subnet('192.168.1.0/24', 26).each { |kid| puts " split /26 → #{kid}" }
end
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 →