#!/usr/bin/ruby

# =============================================================================
# Converts arbitrary ip range (from..to) to set of CIDR network addresses
# -----------------------------------------------------------------------------
#
# Examples:
#
# $ ./cidr.rb 10.0.0.1 10.0.1.33
# 10.0.0.1/32
# 10.0.0.2/31
# 10.0.0.4/30
# 10.0.0.8/29
# 10.0.0.16/28
# 10.0.0.32/27
# 10.0.0.64/26
# 10.0.0.128/25
# 10.0.1.0/27
# 10.0.1.32/31
#
# $ ./cidr.rb 10.0.0.2 10.0.0.7
# 10.0.0.2/31
# 10.0.0.4/30
#
# -----------------------------------------------------------------------------
# Author: Michal Safranek (wejn at box dot cz)
# License: MIT
# Date: 20110214004000
# =============================================================================

module CIDRCompress
	def inet_ntoa(n)
		[n].pack("N").unpack("C*").join "."
	end

	def inet_aton(ip)
		return ip.to_i if /^\d+$/ =~ ip
		raise ArgumentError, "invalid IP: #{ip}" unless /^(\d+\.){3}\d+$/ =~ ip
		ip.split(/\./).map{|c| c.to_i}.pack("C*").unpack("N").first
	end

	def ranges_for(f, l, &b)
		# adapted from: http://phix.me/geodns/#script
		# as my previous version was much less elegant

		raise LocalJumpError, "no block given" unless block_given?

		# auto-convert IPs
		f = inet_aton(f) unless f.kind_of?(Integer)
		l = inet_aton(l) unless l.kind_of?(Integer)

		# flip first,last if first > last
		f, l = l, f if f > l

		# do the magic
		log = (Math.log(l-f+1)/Math.log(2)).to_i # log10(x)/log10(2) == log2(x)
		mask = 2**32 - 2**log

		if f&mask == l&mask
			b.call(inet_ntoa(f), 32-log)
		else
			ranges_for(f, (l&mask)-1, &b)
			ranges_for(l&mask, l, &b)
		end
	end

	# make it both includable and directly callable
	class <<self; include CIDRCompress; end
end

if $0 == __FILE__
	unless ARGV.size == 2
		STDERR.puts "Usage: #{File.basename($0)} <first IP> <last IP>"
		exit 1
	end

	CIDRCompress.ranges_for(ARGV.first, ARGV.last) do |a,m|
		puts [a,m].join('/')
	end
end
