From: "James F. Hranicky" Date: 2003-07-02T02:24:15+09:00 Subject: Re: IP arithmetic This is a multi-part message in MIME format. --Multipart_Tue__1_Jul_2003_13:24:06_-0400_002e1cf0 Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit On Wed, 2 Jul 2003 01:12:15 +0900 Brian Candler wrote: > Are there any functions in the Ruby standard library which convert > dotted-decimal IP addresses to numeric form, *without* doing any forward or > reverse DNS lookups? > > I want to do some address subnet matching: e.g. > > if ip_match("1.2.3.4", "1.2.3.0/24") > ... etc > end > > and the efficient way of doing this would be to do bitwise operations on the > numeric form. Attached is something I've thrown together over the past year or so. ruby -I. -rnetaddr -e 'p IP4NetAddr.new("128.227.0.0/16").contains("128.127.215.35")' More can be done I'm sure. Feel free to add. Jim --Multipart_Tue__1_Jul_2003_13:24:06_-0400_002e1cf0 Content-Type: text/plain; name="netaddr.rb.txt" Content-Disposition: attachment; filename="netaddr.rb.txt" Content-Transfer-Encoding: 7bit class IP4NetAddr attr_accessor :network, :mask, :maskBits, :broadcast, :numIPs, :numHostIPs attr_accessor :netn, :maskn, :wildcardn, :broadcastn def initialize(*argv) network, mask = argv network, mask = network.split("/") unless mask mask = mask.to_i raise "Invalid net or mask" unless (network =~ /\d+\.\d+\.\d+\.\d+/ && (mask.to_s =~ /^\d{1,2}$/ || mask =~ /\d+\.\d+\.\d+\.\d+/)) @network = network if (mask.to_s =~ /^\d{1,2}$/) @maskBits = mask @mask = bitsToMask(mask) else @mask = mask @maskBits = maskToBits(mask) end @numIPs = 2 ** (32 - maskBits) @numHostIPs = @numIPs - 2 @netn = inet_aton(@network) @maskn = inet_aton(@mask) @wildcardn = @maskn ^ ((2 ** 32) - 1) @broadcastn = @netn ^ @wildcardn if (@netn & @wildcardn != 0) raise "Invalid netmask" end end def maskToBits(mask) bits = 0 mask.scan(/\d+/) { |octet| 7.downto(0) { |i| bits += 1 if (octet.to_i & ( 1 << i)) != 0 } } bits end def bitsToMask(bits) octets = Array.new 4.times { if ( bits > 8 ) octets << 255 elsif ( bits <= 0 ) octets << 0 else octets << (((2 ** bits) - 1) << 8 - bits) end bits -= 8 } octets.join(".") end def eachIPAddr @netn.upto(@broadcastn) { |ipn| yield inet_ntoa(ipn) } end def eachIPNumber @netn.upto(@broadcastn) { |ipn| yield ipn } end def eachHostIPAddr (@netn + 1).upto(@broadcastn - 1) { |ipn| yield inet_ntoa(ipn) } end def eachHostIPNumber (@netn + 1).upto(@broadcastn - 1) { |ipn| yield ipn } end def contains(ip) ipn = inet_aton(ip) return true if ipn >= @netn && ipn <= broadcastn return false end alias :all :eachIPAddr alias :each :eachHostIPAddr end def inet_aton(ip) raise "Invalid ip: #{ip}" unless (ip =~ /\d+\.\d+\.\d+\.\d+/) n = 0 shiftlen = 24 ip.scan(/\d+/) { |octet| n += (octet.to_i << shiftlen) shiftlen -= 8 } n end def inet_ntoa(n) raise "Invalid numeric IP value: #{n}" unless n >= 0 && n <= 2**32 octets = Array.new shiftlen = 24 4.times { octets << ( (n & (255 << shiftlen)) >> shiftlen) shiftlen -= 8 } octets.join(".") end if $0 == __FILE__ n = IP4NetAddr.new("128.227.205.0", 29) puts "Net: #{n.network}" puts "Mask: #{n.mask}" puts "MaskBits: #{n.maskBits}" puts sprintf("netn: %x", n.netn) print "ntoa netn: ", inet_ntoa(n.netn), "\n" puts sprintf("nmaskn: %x", n.maskn) puts sprintf("nwildcardn: %08x", n.wildcardn) print "ntoa broadcastn: ", inet_ntoa(n.broadcastn), "\n" n.eachIPAddr { |ip| p ip } puts "Host IPS:" n.eachHostIPAddr { |ip| p ip } end --Multipart_Tue__1_Jul_2003_13:24:06_-0400_002e1cf0--