Forum: Ruby Converting IP range to array of IP's

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Kris (Guest)
on 2006-04-20 20:05
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.
Peter E. (Guest)
on 2006-04-20 20:31
(Received via mailing list)
had some fun with that :-)

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
Matthew D. (Guest)
on 2006-04-20 20:53
(Received via mailing list)
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/
Paul B. (Guest)
on 2006-04-20 20:53
(Received via mailing list)
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.
Peter E. (Guest)
on 2006-04-20 21:08
(Received via mailing list)
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]
A LeDonne (Guest)
on 2006-04-20 21:11
(Received via mailing list)
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
unknown (Guest)
on 2006-04-20 21:20
(Received via mailing list)
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
Paul R. (Guest)
on 2006-04-20 21:30
(Received via mailing list)
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... :-)
Brian M. (Guest)
on 2006-04-20 21:30
(Received via mailing list)
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.
Brian M. (Guest)
on 2006-04-20 22:07
(Received via mailing list)
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.
Andy G. (Guest)
on 2006-04-21 01:18
(Received via mailing list)
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
Tom M. (Guest)
on 2006-04-21 02:08
(Received via mailing list)
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
Roy S. (Guest)
on 2006-04-21 02:23
(Received via mailing list)
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)
Brian M. (Guest)
on 2006-04-21 02:32
(Received via mailing list)
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.
This topic is locked and can not be replied to.