Converting IP range to array of IP's


#1

I’m looking to convert an IP string (ranges, subnet masks or wild-carded
IP’s) in to an array of IP strings:

eg. “192.168.1.1 - 192.168.1.5” => [“192.168.1.1”, “192.168.1.2”,
“192.168.1.3”, “192.168.1.4”, “192.168.1.5”]

Before I embark on this, is there already a ruby class that does it or
some of this?

I’ve checked out the NET classes already.

Many thanks, K.


#2

had some fun with that :slight_smile:

require ‘pp’

def ip_range(range, &block)
start, delta = range.split("-").map do |ipstr|
ipstr.split(".").map do |number| number.to_i end
end

(0…3).each do |pos|
delta[pos] -= start[pos]
end

(0…delta[0]).each do |d0|
(0…delta[1]).each do |d1|
(0…delta[2]).each do |d2|
(0…delta[3]).each do |d3|
yield [start[0] + d0, start[1] + d1, start[2] + d2, start[3] +
d3]
end
end
end
end
end

ip_range(“192.168.1.1 - 192.169.2.5”) do |ip|
pp ip
end


#3

Kris wrote:

Many thanks, K.

Hi,

If we could turn back the clock 8 days or so I would invite you to join
us for this month’s new haven rubyists meeting. Al Gordon demonstrated
the IPAdmin[1] library for the group. I’d guess that this might do what
you want.

Regards,
Matthew D.

[1] http://rubyforge.org/projects/ipadmin/


#4

On 20/04/06, Kris removed_email_address@domain.invalid wrote:

I’m looking to convert an IP string (ranges, subnet masks or wild-carded
IP’s) in to an array of IP strings:

The dotted notation is just a human-readable version of a 32-bit
value, so you can do this:

def dq_to_int(dq)
parts = dq.split(’.’).map{ |s| s.to_i }.reverse
(0…3).inject(0){ |r,i| r + parts[i] * (0x100**i) }
end

def int_to_dq(int)
(0…3).map{ |i| ((int / (0x100**(3-i))) & 0xFF).to_s }.join(’.’)
end

(dq_to_int(‘192.168.1.1’)…dq_to_int(‘192.168.1.5’)).map{ |i|
int_to_dq(i) }

=> [“192.168.1.1”, “192.168.1.2”, “192.168.1.3”, “192.168.1.4”,

“192.168.1.5”]

Paul.


#5

the code it’s broken / working fine depending on how you define a
range…

lets say you have a 192.168.0.0/10 subnet:

192.168.0.1 - 192.168.3.254

will do a ‘wraparound’ and emit addresses like

192.168.1.0 (which is a subnet but no host)
192.168.1.255 (which is the broadcast)

but these are not legal ip addresses.

this is inconvenient if you want to address all hosts in a subnet

I would define range in our case:

“all ips between ip1 and ip2 where each of the digits is between the
corresponding digit in ip1 and ip2”

example:
192.168.1.3 - 192.168.2.4:

[192, 168, 1, 3]
[192, 168, 1, 4]
[192, 168, 2, 3]
[192, 168, 2, 4]


#6

On 4/20/06, Kris removed_email_address@domain.invalid wrote:

I’m looking to convert an IP string (ranges, subnet masks or wild-carded
IP’s) in to an array of IP strings:

eg. “192.168.1.1 - 192.168.1.5” => [“192.168.1.1”, “192.168.1.2”,
“192.168.1.3”, “192.168.1.4”, “192.168.1.5”]

Before I embark on this, is there already a ruby class that does it or
some of this?

Well, starting with ranges, we can make ruby ranges work just fine for
IPAddr objects by defining succ and <=> …

irb(main):001:0> require ‘ipaddr’
=> true
irb(main):002:0> class IPAddr
irb(main):003:1> def succ()
irb(main):004:2> return self.clone.set(@addr + 1)
irb(main):005:2> end
irb(main):006:1> def <=>(other)
irb(main):007:2> return @addr <=> other.to_i
irb(main):008:2> end
irb(main):009:1> end
=> nil
irb(main):010:0> start = IPAddr.new(“192.168.1.1”)
=> #<IPAddr: IPv4:192.168.1.1/255.255.255.255>
irb(main):011:0> endy = IPAddr.new(“192.168.1.5”)
=> #<IPAddr: IPv4:192.168.1.5/255.255.255.255>
irb(main):012:0> (start…endy).to_a
=> [#<IPAddr: IPv4:192.168.1.1/255.255.255.255>, #<IPAddr:
IPv4:192.168.1.2/255.255.255.255>, #<IPAddr:
IPv4:192.168.1.3/255.255.255.255>, #<IPAddr:
IPv4:192.168.1.4/255.255.255.255>, #<IPAddr:
IPv4:192.168.1.5/255.255.255.255>]

Maybe that should be added to ipaddr.rb…

-A


#7

On 20 Apr 2006, at 18:08, Peter E. wrote:

lets say you have a 192.168.0.0/10 subnet:

That’s CIDR.

192.168.1.0 (which is a subnet but no host)
192.168.1.255 (which is the broadcast)

That isn’t.

It’s a long time since I last did any proper CIDR subnetting, but if
you really are setting things up so flat that you have a /10 then .
1.255 isn’t a broadcast address, no? You’re mixing up a CIDR subnet
mask with the notion of an old Class C there aren’t you? Or am I
completely wrong on this and my memory is playing tricks? The largest
subnet I’ve dealt with in the last 5 years is a /27…

“all ips between ip1 and ip2 where each of the digits is between the
corresponding digit in ip1 and ip2”

example:
192.168.1.3 - 192.168.2.4:

[192, 168, 1, 3]
[192, 168, 1, 4]
[192, 168, 2, 3]
[192, 168, 2, 4]

What you have there is two ranges - .1.3-.1.4 and .2.3-.2.4 so I
think if expressed as one range, I’d want to go all the way up - be
consistent with how ranges work elsewhere in Ruby:

(‘bar’…‘bbs’).to_a
=> [“bar”, “bas”, “bat”, “bau”, “bav”, “baw”, “bax”, “bay”, “baz”,
“bba”, “bbb”, “bbc”, “bbd”, “bbe”, “bbf”, “bbg”, “bbh”, “bbi”, “bbj”,
“bbk”, “bbl”, “bbm”, “bbn”, “bbo”, “bbp”, “bbq”, “bbr”, “bbs”]

Incidentally, my distant memory of the subject would suggest going
straight from a bar to a BBS often resulted in deep regret the
following morning - best to stop when you get to BBC or even better a
BBQ… :slight_smile:


#8

On Fri, 21 Apr 2006, A LeDonne wrote:

irb(main):007:2> return @addr <=> other.to_i
IPv4:192.168.1.3/255.255.255.255>, #<IPAddr:
IPv4:192.168.1.4/255.255.255.255>, #<IPAddr:
IPv4:192.168.1.5/255.255.255.255>]

Maybe that should be added to ipaddr.rb…

ipadmin seems to do this now:

 harp:~ > cat a.rb
 require 'rubygems'
 require_gem 'ipadmin'

 class String
   def to_cidr
     IPAdmin::CIDR.new :CIDR => self
   end
 end
 class Array
   def to_cidr
     map{|elem| elem.to_s.to_cidr}
   end
 end

 list = IPAdmin.range :Boundaries => %w( 192.168.0.0 192.168.0.255 

).to_cidr

 p list

harp:~ > ruby a.rb
[“192.168.0.1”, “192.168.0.2”, “192.168.0.3”, “192.168.0.4”,
“192.168.0.5”, “192.168.0.6”, “192.168.0.7”, “192.168.0.8”,
“192.168.0.9”, “192.168.0.10”, “192.168.0.11”, “192.168.0.12”,
“192.168.0.13”, “192.168.0.14”, “192.168.0.15”, “192.168.0.16”,
“192.168.0.17”, “192.168.0.18”, “192.168.0.19”, “192.168.0.20”,
“192.168.0.21”, “192.168.0.22”, “192.168.0.23”, “192.168.0.24”,
“192.168.0.25”, “192.168.0.26”, “192.168.0.27”, “192.168.0.28”,
“192.168.0.29”, “192.168.0.30”, “192.168.0.31”, “192.168.0.32”,
“192.168.0.33”, “192.168.0.34”, “192.168.0.35”, “192.168.0.36”,
“192.168.0.37”, “192.168.0.38”, “192.168.0.39”, “192.168.0.40”,
“192.168.0.41”, “192.168.0.42”, “192.168.0.43”, “192.168.0.44”,
“192.168.0.45”, “192.168.0.46”, “192.168.0.47”, “192.168.0.48”,
“192.168.0.49”, “192.168.0.50”, “192.168.0.51”, “192.168.0.52”,
“192.168.0.53”, “192.168.0.54”, “192.168.0.55”, “192.168.0.56”,
“192.168.0.57”, “192.168.0.58”, “192.168.0.59”, “192.168.0.60”,
“192.168.0.61”, “192.168.0.62”, “192.168.0.63”, “192.168.0.64”,
“192.168.0.65”, “192.168.0.66”, “192.168.0.67”, “192.168.0.68”,
“192.168.0.69”, “192.168.0.70”, “192.168.0.71”, “192.168.0.72”,
“192.168.0.73”, “192.168.0.74”, “192.168.0.75”, “192.168.0.76”,
“192.168.0.77”, “192.168.0.78”, “192.168.0.79”, “192.168.0.80”,
“192.168.0.81”, “192.168.0.82”, “192.168.0.83”, “192.168.0.84”,
“192.168.0.85”, “192.168.0.86”, “192.168.0.87”, “192.168.0.88”,
“192.168.0.89”, “192.168.0.90”, “192.168.0.91”, “192.168.0.92”,
“192.168.0.93”, “192.168.0.94”, “192.168.0.95”, “192.168.0.96”,
“192.168.0.97”, “192.168.0.98”, “192.168.0.99”, “192.168.0.100”,
“192.168.0.101”, “192.168.0.102”, “192.168.0.103”, “192.168.0.104”,
“192.168.0.105”, “192.168.0.106”, “192.168.0.107”, “192.168.0.108”,
“192.168.0.109”, “192.168.0.110”, “192.168.0.111”, “192.168.0.112”,
“192.168.0.113”, “192.168.0.114”, “192.168.0.115”, “192.168.0.116”,
“192.168.0.117”, “192.168.0.118”, “192.168.0.119”, “192.168.0.120”,
“192.168.0.121”, “192.168.0.122”, “192.168.0.123”, “192.168.0.124”,
“192.168.0.125”, “192.168.0.126”, “192.168.0.127”, “192.168.0.128”,
“192.168.0.129”, “192.168.0.130”, “192.168.0.131”, “192.168.0.132”,
“192.168.0.133”, “192.168.0.134”, “192.168.0.135”, “192.168.0.136”,
“192.168.0.137”, “192.168.0.138”, “192.168.0.139”, “192.168.0.140”,
“192.168.0.141”, “192.168.0.142”, “192.168.0.143”, “192.168.0.144”,
“192.168.0.145”, “192.168.0.146”, “192.168.0.147”, “192.168.0.148”,
“192.168.0.149”, “192.168.0.150”, “192.168.0.151”, “192.168.0.152”,
“192.168.0.153”, “192.168.0.154”, “192.168.0.155”, “192.168.0.156”,
“192.168.0.157”, “192.168.0.158”, “192.168.0.159”, “192.168.0.160”,
“192.168.0.161”, “192.168.0.162”, “192.168.0.163”, “192.168.0.164”,
“192.168.0.165”, “192.168.0.166”, “192.168.0.167”, “192.168.0.168”,
“192.168.0.169”, “192.168.0.170”, “192.168.0.171”, “192.168.0.172”,
“192.168.0.173”, “192.168.0.174”, “192.168.0.175”, “192.168.0.176”,
“192.168.0.177”, “192.168.0.178”, “192.168.0.179”, “192.168.0.180”,
“192.168.0.181”, “192.168.0.182”, “192.168.0.183”, “192.168.0.184”,
“192.168.0.185”, “192.168.0.186”, “192.168.0.187”, “192.168.0.188”,
“192.168.0.189”, “192.168.0.190”, “192.168.0.191”, “192.168.0.192”,
“192.168.0.193”, “192.168.0.194”, “192.168.0.195”, “192.168.0.196”,
“192.168.0.197”, “192.168.0.198”, “192.168.0.199”, “192.168.0.200”,
“192.168.0.201”, “192.168.0.202”, “192.168.0.203”, “192.168.0.204”,
“192.168.0.205”, “192.168.0.206”, “192.168.0.207”, “192.168.0.208”,
“192.168.0.209”, “192.168.0.210”, “192.168.0.211”, “192.168.0.212”,
“192.168.0.213”, “192.168.0.214”, “192.168.0.215”, “192.168.0.216”,
“192.168.0.217”, “192.168.0.218”, “192.168.0.219”, “192.168.0.220”,
“192.168.0.221”, “192.168.0.222”, “192.168.0.223”, “192.168.0.224”,
“192.168.0.225”, “192.168.0.226”, “192.168.0.227”, “192.168.0.228”,
“192.168.0.229”, “192.168.0.230”, “192.168.0.231”, “192.168.0.232”,
“192.168.0.233”, “192.168.0.234”, “192.168.0.235”, “192.168.0.236”,
“192.168.0.237”, “192.168.0.238”, “192.168.0.239”, “192.168.0.240”,
“192.168.0.241”, “192.168.0.242”, “192.168.0.243”, “192.168.0.244”,
“192.168.0.245”, “192.168.0.246”, “192.168.0.247”, “192.168.0.248”,
“192.168.0.249”, “192.168.0.250”, “192.168.0.251”, “192.168.0.252”,
“192.168.0.253”, “192.168.0.254”]

fyi.

-a


#9

On 4/20/06, Brian M. removed_email_address@domain.invalid wrote:

Other’s solutions should work great but just for fun and enlightenment

[192] >>= \a ->

}.inject([]) {|m, a| m + a}

end
end

A few redundancies to fix (my original code didn’t use map but a
custom method Array#bind. The to_a is no longer needed:

class Array
def expand(i = 0, *a)
return [a] if i == size
self[i].map {|x|
expand(i+1, *(a + [x]))
}.inject([]) {|m, x| m + x}
end
end

This is what it used to look like:

class Array
def bind(&blk)
map(&blk).inject([]) {|m, x| m+x}
end

def expand(i = 0, *a)
return [a] if i == size
self[i].bind {|x| expand(i+1, *(a + [x]))}
end
end

Note that bind could have the inject part split out into what Haskell
calls a join but that just lead to name issues so I just inlined it.

Brian.


#10

On Fri, Apr 21, 2006 at 02:08:11AM +0900, Peter E. wrote:

192.168.1.0 (which is a subnet but no host)
192.168.1.255 (which is the broadcast)

but these are not legal ip addresses.

Yes they are. They might not be usable for hosts in the given
example, but they’re still valid IP addresses (since an IP address is
just a 32 bit number).

I’d say you’re confusing “a range of IP addresses” with “a range of
valid host addresses within a subnet”. They’re different things.

-Andy


#11

On 4/20/06, Kris removed_email_address@domain.invalid wrote:

I’m looking to convert an IP string (ranges, subnet masks or wild-carded
IP’s) in to an array of IP strings:

eg. “192.168.1.1 - 192.168.1.5” => [“192.168.1.1”, “192.168.1.2”,
“192.168.1.3”, “192.168.1.4”, “192.168.1.5”]

Before I embark on this, is there already a ruby class that does it or
some of this?

Other’s solutions should work great but just for fun and enlightenment
I wrote my own little implementation of a simple list comprehension in
Haskell and then ported it to Ruby. This simple piece of code allows
you to expand more arbitrary ranges and might be good for more things
than just IP addresses.

Condensed ruby version of this haskell code

[[a,b,c,d] | a <- [192], b <- [168], c <- [0,1], d <- [1…10]]

non-sugared notation:

[192] >>= \a ->

[168] >>= \b ->

[0,1] >>= \c ->

[1…10] >>= \d ->

return [a,b,c,d]

class Array
def expand(i = 0, *a)
return [a] if i == size
self[i].to_a.map {|x|
expand(i+1, *(a + [x]))
}.inject([]) {|m, a| m + a}
end
end

[[192], [168], [0,1], (1…10)].expand

#=> [[192, 168, 0, 1], [192, 168, 0, 2], [192, 168, 0, 3], [192, 168,
0, 4], [192, 168, 0, 5], [192, 168, 0, 6], [192, 168, 0, 7], [192,
168, 0, 8], [192, 168, 0, 9], [192, 168, 0, 10], [192, 168, 1, 1],
[192, 168, 1, 2], [192, 168, 1, 3], [192, 168, 1, 4], [192, 168, 1,
5], [192, 168, 1, 6], [192, 168, 1, 7], [192, 168, 1, 8], [192, 168,
1, 9], [192, 168, 1, 10]]

Kind of a fun thing to think about and try to wrap your head around
sometime ;-)… maybe I should bundle a more complete monadic
collections lib into a gem sometime.

Brian.


#12

Peter E. wrote:

delta[pos] -= start[pos]

end
end

ip_range(“192.168.1.1 - 192.169.2.5”) do |ip|
pp ip
end
Not to rain on your parade but I believe that range should include a few
thousand more IP addresses (e.g. 192.169.1.254)


#13

On 4/20/06, Tom M. removed_email_address@domain.invalid wrote:

 Prelude> mapM_ print $ sequence [[192], [168], [0,1], [1..10]]
 [192,168,1,1]
 [192,168,1,2]
 [192,168,1,3]
 [192,168,1,4]
 [192,168,1,5]
 [192,168,1,6]
 [192,168,1,7]
 [192,168,1,8]
 [192,168,1,9]
 [192,168,1,10]

Thanks. I was already aware of that but didn’t think of naming my
example method that name. Good to have that pointed out to me every
once in a while (for some reason I have not gotten used to using
monadic functions on lists yet).

After writing this I think it might be fun to write a monadic
extension library and maybe add a collection of monad transformers.
This would bring a very powerful set of tools to Ruby. Maybe someone
can come up with some nice DSL like Haskell’s do notation. I haven’t
found one yet.

Brian.


#14

Brian M. wrote:

Other’s solutions should work great but just for fun and enlightenment
I wrote my own little implementation of a simple list comprehension in
Haskell …

You might also find fun in what ‘sequence’ does in the List monad:

 Prelude> :t sequence
 sequence :: (Monad m) => [m a] -> m [a]

 Prelude> mapM_ print $ sequence [[192], [168], [0,1], [1..10]]
 [192,168,0,1]
 [192,168,0,2]
 [192,168,0,3]
 [192,168,0,4]
 [192,168,0,5]
 [192,168,0,6]
 [192,168,0,7]
 [192,168,0,8]
 [192,168,0,9]
 [192,168,0,10]
 [192,168,1,1]
 [192,168,1,2]
 [192,168,1,3]
 [192,168,1,4]
 [192,168,1,5]
 [192,168,1,6]
 [192,168,1,7]
 [192,168,1,8]
 [192,168,1,9]
 [192,168,1,10]

Cheers,
Tom