diff options
author | bluemonk <ceresa@gmail.com> | 2011-04-21 11:19:24 +0200 |
---|---|---|
committer | bluemonk <ceresa@gmail.com> | 2011-04-21 11:19:24 +0200 |
commit | 9caa3c8505e0a3563f6e54077d9674628e04864c (patch) | |
tree | 6a9a5e3aba5ca511ac0d6e6905a6cfa2b0be5e96 | |
parent | c95e3e6a5dde0aadc7d433efe372c282fbb3913b (diff) | |
download | ipaddress-ruby-stdlib.tar.gz |
All the library in one file to facilitate ruby-stdlib integrationruby-stdlib
-rw-r--r-- | CHANGELOG.rdoc | 87 | ||||
-rw-r--r-- | LICENSE | 20 | ||||
-rw-r--r-- | README.rdoc | 935 | ||||
-rw-r--r-- | VERSION | 1 | ||||
-rw-r--r-- | ipaddress.gemspec | 65 | ||||
-rw-r--r-- | lib/ipaddress.rb | 2039 | ||||
-rw-r--r-- | lib/ipaddress/extensions/extensions.rb | 22 | ||||
-rw-r--r-- | lib/ipaddress/ipv4.rb | 996 | ||||
-rw-r--r-- | lib/ipaddress/ipv6.rb | 776 | ||||
-rw-r--r-- | lib/ipaddress/prefix.rb | 252 | ||||
-rw-r--r-- | test/ipaddress/extensions/extensions_test.rb | 18 | ||||
-rw-r--r-- | test/ipaddress/ipv4_test.rb | 510 | ||||
-rw-r--r-- | test/ipaddress/ipv6_test.rb | 356 | ||||
-rw-r--r-- | test/ipaddress/prefix_test.rb | 159 | ||||
-rw-r--r-- | test/ipaddress_test.rb | 1024 |
15 files changed, 3058 insertions, 4202 deletions
diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc deleted file mode 100644 index 19a86c4..0000000 --- a/CHANGELOG.rdoc +++ /dev/null @@ -1,87 +0,0 @@ -== ipaddress 0.7.5 - -CHANGED:: IPAddress::IPv4#each_host to improve speed -FIXED:: IPAddress::IPv4::summarize bug (summarization should now work properly) -NEW:: IPAddress::IPv4#include_all? -NEW:: #ipv4? and #ipv6? - -== ipaddress 0.7.0 - -NEW:: IPAddress::IPv6#include? -NEW:: IPAddress::IPv6#network_u128 -NEW:: Modified IPAddress::IPv6::Mapped to accept IPv4 mapped addresses in IPv6 format -NEW:: IPAddress::IPv4#private? -NEW:: IPAddress::IPv4::parse_classful - -== ipaddress 0.6.0 - -=== API changes -* IPv4#to_s now returns the address portion only, - to retain compatibility with IPAddr. Example: - - IPAddress("172.16.10.1/24").to_s - #=> "172.16.10.1" # ipaddress 0.6.0 - - IPAddress("172.16.10.1/24").to_s - #=> "172.16.10.1/24" # ipaddress 0.5.0 - -* IPv6#to_s now returns the address portion only, - to retain compatibility with IPAddr. Example: - - IPAddress "2001:db8::8:800:200c:417a/64".to_s - #=> "2001:db8::8:800:200c:417a" # ipaddress 0.6.0 - - IPAddress "2001:db8::8:800:200c:417a/64".to_s - #=> "2001:db8::8:800:200c:417a/64" # ipaddress 0.6.0 - -* IPv6::Unspecified#to_s, IPv6::Loopback and - IPv6::Mapped#to_s now return the address portion only, - to retain compatibility with IPAddr. -* IPv4::summarize now returns an array even if the - result is a single subnet, to keep consistency - and avoid confusion - -=== New methods -* IPv4#to_string and IPv6#to_string: print the address - with the prefix portion, like the #to_s method in - ipaddress 0.5.0 -* IPAddress::parse, for those who don't like the wrapper - method IPAddress() -* IPv6#to_string_uncompressed, returns a string with the - uncompressed IPv6 and the prefix -* IPv6::Mapped#to_string, returns the IPv6 Mapped address - with IPv4 notation and the prefix -* IPv6#reverse, returns the ip6.arpa DNS reverse lookup - string -* IPv4#arpa and IPv6#arpa, alias of the respective #reverse - methods -* Prefix#+, Prefix#- - -=== Library structure -* Moved all the IPAddress module methods from - lib/ipaddress/ipbase.rb to lib/ipaddress.rb -* Removed IPBase superclass -* IPv4 and IPv6 classes no longer inherit from IPBase -* Removed lib/ipaddress/ipbase.rb -* Removed test/ipaddress/ipbase_test.rb - -=== Minor fixes -* Replaced Ruby 1.9 deprecated Hash#index with Hash#key -* Removed require ruby-prof from tests which was causing - users to install ruby-prof or manually remove the line -* Removed "must" method from tests, replaced by normal - Test::Unit methods -* Removed duplicate Jeweler entry in Rakefile -* Made Integer#closest_power_of_2 more general by adding - an optional limit parameter -* Fixed summarization algorithm (thanks to nicolas fevrier) -* Fixed bug in prefix_from_ip (thanks to jdpace) - -=== Documentation -* Normalized README rdoc headers -* Added documentation for IPAddress::Prefix -* Added documentation for IPAddress::IPv4 and - IPAddress::IPv6 -* Fixed formatting -* Fixed lots of typos - diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 25def2b..0000000 --- a/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2009-2011 Marco Ceresa - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index 3026119..0000000 --- a/README.rdoc +++ /dev/null @@ -1,935 +0,0 @@ -= IPAddress - -IPAddress is a Ruby library designed to make the use of IPv4 and IPv6 -addresses simple, powerful and enjoyable. It provides a complete set of -methods to handle IP addresses for any need, from simple scripting to -full network design. - -IPAddress is written with a full OO interface, and its code is easy to -read, maintain and extend. The documentation is full of examples, to -let you start being productive immediately. - -This document provides a brief introduction to the library and -examples of typical usage. - -== Requirements - -* Ruby >= 1.8.6 (not tested with previous versions) - -IPAddress works perfectly on: - -* Ruby 1.8.6 (2007-03-13 patchlevel 0) -* Ruby 1.8.7 -* Ruby 1.9.1 -* Ruby 1.9.2dev (2010-06-08 revision 28230) -* Ruby 1.9.2dev (2010-07-15 revision 28653) -* Rubinius 1.0.1 (1.8.7 release 2010-06-03 JI) -* Ironruby >= 1.0 - -It hasn't yet been tested on any other platform, so if you want to collaborate feel -free to send a small report to my email address, or -{join the discussion}[http://groups.google.com/group/ruby-ipaddress]. - -== Why not using IPAddr? - -IPAddr is the IP addresses library that comes with Ruby standard -lib. We found this library, although very well written, not very -suitable for all our needs, and not very flexible. - -Some quick examples of things you can't do with IPAddr: - -* store both the address and the prefix information -* quickly find the broadcast address of a network -* iterate over hosts -* perform subnetting or network aggregation - -Many methods and procedures are so old that they have been -declared deprecated by the IETF, and some others have bugs in their -implementation. - -Moreover, IPAddress is more robust and is already around 50% faster than IPAddr, -in addition to provide an organic API with logical separation and OO structure. - -We hope that IPAddress will address all these issues and meet all your -needs in network programming. - -== Installation - -Install the library using rubygems - - $ gem install ipaddress - -You can then use it in your programs: - - require 'rubygems' # optional - require 'ipaddress' - -Another way would be to clone the git repository - - $ git clone git://github.com/bluemonk/ipaddress.git - -And then install the library - - $ cd ipaddress - ipaddress$ rake install - -== Documentation - -The code is fully documented with RDoc. You can generate the -documentation with Rake: - - ipaddress$ rake rdoc - -The latest documentation can be found online at -{this address}[http://rubydoc.info/gems/ipaddress/0.7.0/frames] - -== IPv4 - -Class IPAddress::IPv4 is used to handle IPv4 type addresses. IPAddress -is similar to other IP Addresses libraries, like Ruby's own -IPAddr. However it works slightly different, as we will see. - -=== Create a new IPv4 address - -The usual way to express an IP Address is using its dotted decimal -form, such as 172.16.10.1, and a prefix, such as 24, separated by a -slash. - - 172.16.10.1/24 - -To create a new IPv4 object, you can use IPv4 own class - - ip = IPAddress::IPv4.new "172.16.10.1/24" - -or, in a easier way, using the IPAddress parse method - - ip = IPAddress.parse "172.16.10.1/24" - -which accepts and parses any kind of IP (IPv4, IPV6 and -IPv4 IPv6 Mapped addresses). - -If you like syntactic sugar, you can use the wrapper method -IPAddress(), which is built around IPAddress::parse: - - ip = IPAddress "172.16.10.1/24" - -You can specify an IPv4 address in any of two ways: - - IPAddress "172.16.10.1/24" - IPAddress "172.16.10.1/255.255.255.0" - -In this example, prefix /24 and netmask 255.255.255.0 are the same and -you have the flexibility to use either one of them. - -If you don't explicitly specify the prefix (or the subnet mask), -IPAddress thinks you're dealing with host addresses and not with -networks. Therefore, the default prefix will be /32, or -255.255.255.255. For example: - - # let's declare an host address - host = IPAddress::IPv4.new "10.1.1.1." - -The new created object will have prefix /32, which is the same -as we created the following: - - host = IPAddress::IPv4.new "10.1.1.1/32" - -=== Handling the IPv4 address - -Once created, you can obtain the attributes for an IPv4 object: - - ip = IPAddress("172.16.10.1/24") - - ip.address - #=> "172.16.10.1" - ip.prefix - #=> 24 - -In case you need to retrieve the netmask in IPv4 format, you can use -the IPv4#netmask method: - - ip.netmask - #=> "255.255.255.0" - -A special attribute, IPv4#octets, is available to get the four -decimal octets from the IP address: - - ip.octets - #=> [172,16,10,1] - -Shortcut method IPv4#[], provides access to a given octet whithin the -range: - - ip[1] - #=> 16 - -If you need to print out the IPv4 address in a canonical form, you can -use IPv4#to_string - - ip.to_string - #=> "172.16.10.l/24" - -=== Changing netmask - -You can set a new prefix (netmask) after creating an IPv4 -object. For example: - - ip.prefix = 25 - - ip.to_string - #=> "172.16.10.l/25" - -If you need to use a netmask in IPv4 format, you can achive so by -using the IPv4#netmask= method - - ip.netmask = "255.255.255.252" - - ip.to_string - #=> "172.16.10.1/30" - -=== Working with networks, broadcasts and addresses - -Some very important topics in dealing with IP addresses are the -concepts of +network+ and +broadcast+, as well as the addresses -included in a range. - -When you specify an IPv4 address such as "172.16.10.1/24", you are -actually handling two different information: - -* The IP address itself, "172.16.10.1" -* The subnet mask which indicates the network - -The network number is the IP which has all zeroes in the host -portion. In our example, because the prefix is 24, we identify our -network number to have the last 8 (32-24) bits all zeroes. Thus, IP -address "172.16.10.1/24" belongs to network "172.16.10.0/24". - -This is very important because, for instance, IP "172.16.10.1/16" is -very different to the previous one, belonging to the very different -network "172.16.0.0/16". - -==== Networks - -With IPAddress it's very easy to calculate the network for an IP -address: - - ip = IPAddress "172.16.10.1/24" - - net = ip.network - #=> #<IPAddress::IPv4:0xb7a5ab24 @octets=[172, 16, 10, 0], - @prefix=24, - @address="172.16.10.0"> - net.to_string - #=> "172.16.10.0/24" - -The IPv4#network method creates a new IPv4 object from the network -number, calculated after the original object. We want to outline here -that the network address is a perfect legitimate IPv4 address, which -just happen to have all zeroes in the host portion. - -You can use method IPv4#network? to check whether an IP address is a -network or not: - - ip1 = IPAddress "172.16.10.1/24" - ip2 = IPAddress "172.16.10.4/30" - - ip1.network? - #=> false - ip2.network? - #=> true - -==== Broadcast - -The broadcast address is the contrary than the network number: where -the network number has all zeroes in the host portion, the broadcast -address has all one's. For example, ip "172.16.10.1/24" has broadcast -"172.16.10.255/24", where ip "172.16.10.1/16" has broadcast -"172.16.255.255/16". - -Method IPv4#broadcast has the same behavior as is #network -counterpart: it creates a new IPv4 object to handle the broadcast -address: - - ip = IPAddress "172.16.10.1/24" - - bcast = ip.broadcast - #=> #<IPAddress::IPv4:0xb7a406fc @octets=[172, 16, 10, 255], - @prefix=24, - @address="172.16.10.255"> - bcast.to_string - #=> "172.16.10.255/24" - -==== Addresses, ranges and iterators - -So we see that the netmask essentially specifies a range for IP -addresses that are included in a network: all the addresses between -the network number and the broadcast. IPAddress has many methods to -iterate between those addresses. Let's start with IPv4#each, which -iterates over all addresses in a range - - ip = IPAddress "172.16.10.1/24" - - ip.each do |addr| - puts addr - end - -It is important to note that it doesn't matter if the original IP is a -host IP or a network number (or a broadcast address): the #each method -only considers the range that the original IP specifies. - -If you only want to iterate over hosts IP, use the IPv4#each_host -method: - - ip = IPAddress "172.16.10.1/24" - - ip.each_host do |host| - puts host - end - -Methods IPv4#first and IPv4#last return a new object containing -respectively the first and the last host address in the range - - ip = IPAddress "172.16.10.100/24" - - ip.first.to_string - #=> "172.16.10.1/24" - - ip.last.to_string - #=> "172.16.10.254/24" - -=== IP special formats - -The IPAddress library provides a complete set of methods to access an -IPv4 address in special formats, such as binary, 32 bits unsigned int, -data and hexadecimal. - -Let's take the following IPv4 as an example: - - ip = IPAddress "172.16.10.1/24" - - ip.address - #=> "172.16.10.1" - -The first thing to highlight here is that all these conversion methods -only take into consideration the address portion of an IPv4 object and -not the prefix (netmask). - -So, to express the address in binary format, use the IPv4#bits method: - - ip.bits - #=> "10101100000100000000101000000001" - -To calculate the 32 bits unsigned int format of the ip address, use -the IPv4#to_u32 method - - ip.to_u32 - #=> 2886732289 - -This method is the equivalent of the Unix call pton(), expressing an -IP address in the so called +network byte order+ notation. However, if -you want to transmit your IP over a network socket, you might need to -transform it in data format using the IPv4#data method: - - ip.data - #=> "\254\020\n\001" - -Finally, you can transform an IPv4 address into a format which is -suitable to use in IPv4-IPv6 mapped addresses: - - ip.to_ipv6 - #=> "ac10:0a01" - -=== Classful networks - -IPAddress allows you to create and manipulate objects using the old -and deprecated (but apparently still popular) classful networks concept. - -Classful networks and addresses don't have a prefix: their subnet mask -is univocally identified by their address, and therefore diveded in classes. -As per RFC 791, these classes are: - -* Class A, from 0.0.0.0 to 127.255.255.255 -* Class B, from 128.0.0.0 to 191.255.255.255 -* Class C, from 192.0.0.0 to 255.255.255.255 - -Since classful networks here are only considered to calculate the default -prefix number, classes D and E are not considered. - -To create a classful IP and prefix from an IP address, use the -IPv4::parse_classful method: - - # classful ip - ip = IPAddress::IPv4::parse_classful "10.1.1.1" - - ip.prefix - #=> 8 - -The method automatically created a new IPv4 object and assigned it -the correct prefix. - -You can easily check which CLASSFUL network an IPv4 object belongs: - - ip = IPAddress("10.0.0.1/24") - ip.a? - #=> true - - ip = IPAddress("172.16.10.1/24") - ip.b? - #=> true - - ip = IPAddress("192.168.1.1/30") - ip.c? - #=> true - -Remember that these methods are only checking the address portion of an IP, and are -independent from its prefix, as classful networks have no concept of prefix. - -For more information on CLASSFUL networks visit the -{Wikipedia page}[http://en.wikipedia.org/wiki/Classful_network] - -=== Network design with IPAddress - -IPAddress includes a lot of useful methods to manipulate IPv4 and IPv6 -networks and do some basic network design. - -==== Subnetting - -The process of subnetting is the division of a network into smaller -(in terms of hosts capacity) networks, called subnets, so that they -all share a common root, which is the starting network. - -For example, if you have network "172.16.10.0/24", we can subnet it -into 4 smaller subnets. The new prefix will be /26, because 4 is 2^2 -and therefore we add 2 bits to the network prefix (24+2=26). - -Subnetting is easy with IPAddress. Let's work out the last example: - - network = IPAddress("172.16.10.0/24") - - subnets = network / 4 - #=> [#<IPAddress::IPv4:0xb7b10e10 @octets=[172,16,10,0] [...] - #<IPAddress::IPv4:0xb7b0f1b4 @octets=[172,16,10,64] [...] - #<IPAddress::IPv4:0xb7b0e5ac @octets=[172,16,10,128] [...] - #<IPAddress::IPv4:0xb7b0e0c0 @octets=[172,16,10,192] [...]] - - subnets.map{|i| i.to_string} - #=> ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26", - "172.16.10.192/26"] - -You can also use method IPv4#subnets, which is an alias for -IPv4#/. Please note that you don't have to specify a network to -calculate a subnet: if the IPv4 object is a host IPv4, the method will -calculate the network number for that network and then subnet it. For -example: - - ip = IPAddress("172.16.10.58/24") - - ip.subnet(4).map{|i| i.to_string} - #=> ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26", - "172.16.10.192/26"] - -Usually, subnetting implies dividing a network to a number of subnets -which is a power of two: in this way, you can be sure that the network -will be divided evenly, and all the subnets will have the same number -of hosts. - -==== Uneven subnetting - -IPAddress also handles un-even subnetting: if you specify any number -(up to the prefix limit), the network will be divided so that the -first power-of-two networks will be even, and all the rest will just -fill out the space. - -As an example, let's divide network 172.16.10.0/24 into 3 different subnets: - - network = IPAddress("172.16.10.0/24") - - network.subnet(3).map{|i| i.to_string} - #=> ["172.16.10.0/26", - "172.16.10.64/26", - "172.16.10.128/25"] - -We can go even further and divide into 11 subnets: - - network = IPAddress("172.16.10.0/24") - - network.subnet(11).map{|i| i.to_string} - #=> ["172.16.10.0/28", "172.16.10.16/28", "172.16.10.32/28", - "172.16.10.48/28", "172.16.10.64/28", "172.16.10.80/28", - "172.16.10.96/28", "172.16.10.112/28", "172.16.10.128/27", - "172.16.10.160/27", "172.16.10.192/26"] - -As you can see, most of the networks are /28, with a few /27 and one -/26 to fill up the remaining space. - -==== Summarization - -Summarization (or aggregation) is the process when two or more -networks are taken together to check if a supernet, including -all and only these networks, exists. If it exists then this supernet -is called the summarized (or aggregated) network. -It is very important to understand that summarization can only -occur if there are no holes in the aggregated network, or, in -other words, if the given networks fill completely the address space -of the supernet. So the two rules are: - -1) The aggregate network must contain +all+ the IP addresses of the -original networks; - -2) The aggregate network must contain +only+ the IP addresses of the -original networks; - -A few examples will help clarify the above. Let's consider for -instance the following two networks: - - ip1 = IPAddress("172.16.10.0/24") - ip2 = IPAddress("172.16.11.0/24") - -These two networks can be expressed using only one IP address -network if we change the prefix. Let Ruby do the work: - - IPAddress::IPv4::summarize(ip1,ip2).to_string - #=> "172.16.10.0/23" - -We note how the network "172.16.10.0/23" includes all the -addresses specified in the above networks, and (more important) includes -ONLY those addresses. - -If we summarized +ip1+ and +ip2+ with the following network: - - "172.16.0.0/16" - -we would have satisfied rule #1 above, but not rule #2. So - - "172.16.0.0/16" - -is not an aggregate network for +ip1+ and +ip2+. - -If it's not possible to compute a single aggregated network for -all the original networks, the method returns an array with all the -aggregate networks found. For example, the following four networks can be -aggregated in a single /22: - - ip1 = IPAddress("10.0.0.1/24") - ip2 = IPAddress("10.0.1.1/24") - ip3 = IPAddress("10.0.2.1/24") - ip4 = IPAddress("10.0.3.1/24") - - IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} - #=> ["10.0.0.0/22"] - -But the following networks can't be summarized in a single -network: - - ip1 = IPAddress("10.0.1.1/24") - ip2 = IPAddress("10.0.2.1/24") - ip3 = IPAddress("10.0.3.1/24") - ip4 = IPAddress("10.0.4.1/24") - - IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} - #=> ["10.0.1.0/24","10.0.2.0/23","10.0.4.0/24"] - -In this case, the two summarizables networks have been aggregated into -a single /23, while the other two networks have been left untouched. - -==== Supernetting - -Supernetting is a different operation than aggregation, as it only -works on a single network and returns a new single IPv4 object, -representing the supernet. - -Supernetting is similar to subnetting, except that you getting as a -result a network with a smaller prefix (bigger host space). For -example, given the network - - ip = IPAddress("172.16.10.0/24") - -you can supernet it with a new /23 prefix - - ip.supernet(23).to_string - #=> "172.16.10.0/23" - -However if you supernet it with a /22 prefix, the network address will -change: - - ip.supernet(22).to_string - #=> "172.16.8.0/22" - -This is because "172.16.10.0/22" is not a network anymore, but an host -address. - -== IPv6 - -IPAddress is not only fantastic for IPv4 addresses, it's also great to -handle IPv6 addresses family! Let's discover together how to use it in -our projects. - -=== IPv6 addresses - -IPv6 addresses are 128 bits long, in contrast with IPv4 addresses -which are only 32 bits long. An IPv6 address is generally written as -eight groups of four hexadecimal digits, each group representing 16 -bits or two octet. For example, the following is a valid IPv6 -address: - - 1080:0000:0000:0000:0008:0800:200c:417a - -Letters in an IPv6 address are usually written downcase, as per -RFC. You can create a new IPv6 object using uppercase letters, but -they will be converted. - -==== Compression - -Since IPv6 addresses are very long to write, there are some -simplifications and compressions that you can use to shorten them. - -* Leading zeroes: all the leading zeroes within a group can be - omitted: "0008" would become "8" - -* A string of consecutive zeroes can be replaced by the string - "::". This can be only applied once. - -Using compression, the IPv6 address written above can be shorten into -the following, equivalent, address - - 1080::8:800:200c:417a - -This short version is often used in human representation. - -==== Network Mask - -As we used to do with IPv4 addresses, an IPv6 address can be written -using the prefix notation to specify the subnet mask: - - 1080::8:800:200c:417a/64 - -The /64 part means that the first 64 bits of the address are -representing the network portion, and the last 64 bits are the host -portion. - -=== Using IPAddress with IPv6 addresses - -All the IPv6 representations we've just seen are perfectly fine when -you want to create a new IPv6 address: - - ip6 = IPAddress "1080:0000:0000:0000:0008:0800:200C:417A" - - ip6 = IPAddress "1080:0:0:0:8:800:200C:417A" - - ip6 = IPAddress "1080::8:800:200C:417A" - -All three are giving out the same IPv6 object. The default subnet mask -for an IPv6 is 128, as IPv6 addresses don't have classes like IPv4 -addresses. If you want a different mask, you can go ahead and explicit -it: - - ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - -Access the address portion and the prefix by using the respective -methods: - - ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - - ip6.address - #=> "2001:0db8:0000:0000:0008:0800:200c:417a" - - ip6.prefix - #=> 64 - -A compressed version of the IPv6 address can be obtained with the -IPv6#compressed method: - - ip6 = IPAddress "2001:0db8:0000:0000:0008:200c:417a:00ab/64" - - ip6.compressed - #=> "2001:db8::8:800:200c:417a" - -=== Handling the IPv6 address - -Accessing the groups that form an IPv6 address is very easy with the -IPv6#groups method: - - ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - - ip6.groups - #=> [8193, 3512, 0, 0, 8, 2048, 8204, 16762] - -As with IPv4 addresses, each individual group can be accessed using -the IPv6#[] shortcut method: - - ip6[0] - #=> 8193 - ip6[1] - #=> 3512 - ip6[2] - #=> 0 - ip6[3] - #=> 0 - -Note that each 16 bits group is expressed in its decimal form. You can -also obtain the groups into hexadecimal format using the IPv6#hexs -method: - - ip6.hexs - #=> => ["2001", "0db8", "0000", "0000", "0008", "0800", "200c", "417a"] - -A few other methods are available to transform an IPv6 address into -decimal representation, with IPv6.to_i - - ip6.to_i - #=> 42540766411282592856906245548098208122 - -or to hexadecimal representation - - ip6.to_hex - #=> "20010db80000000000080800200c417a" - -To print out an IPv6 address in human readable form, use the IPv6#to_s, IPv6#to_string -and IPv6#to_string_uncompressed methods - - ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - - ip6.to_string - #=> "2001:db8::8:800:200c:417a/96" - - ip6.to_string_uncompressed - #=> "2001:0db8:0000:0000:0008:0800:200c:417a/96" - -As you can see, IPv6.to_string prints out the compressed form, while -IPv6.to_string_uncompressed uses the expanded version. - -==== Compressing and uncompressing - -If you have a string representing an IPv6 address, you can easily -compress it and uncompress it using the two class methods IPv6::expand -and IPv6::compress. - -For example, let's say you have the following uncompressed IPv6 -address: - - ip6str = "2001:0DB8:0000:CD30:0000:0000:0000:0000" - -Here is the compressed version: - - IPAddress::IPv6.compress ip6str - #=> "2001:db8:0:cd30::" - -The other way works as well: - - ip6str = "2001:db8:0:cd30::" - - IPAddress::IPv6.expand ip6str - #=> "2001:0DB8:0000:CD30:0000:0000:0000:0000" - -These methods can be used when you don't want to create a new object -just for expanding or compressing an address (although a new object is -actually created internally). - -=== New IPv6 address from other formats - -You can create a new IPv6 address from different formats than just a -string representing the colon-hex groups. - -For instance, if you have a data stream, you can use IPv6::parse_data, -like in the following example: - - data = " \001\r\270\000\000\000\000\000\b\b\000 \fAz" - - ip6 = IPAddress::IPv6::parse_data data - ip6.prefix = 64 - - ip6.to_string - #=> "2001:db8::8:800:200c:417a/64" - -A new IPv6 address can also be created from an unsigned 128 bits -integer: - - u128 = 21932261930451111902915077091070067066 - - ip6 = IPAddress::IPv6::parse_u128 u128 - ip6.prefix = 64 - - ip6.to_string - #=> "1080::8:800:200c:417a/64" - -Finally, a new IPv6 address can be created from an hex string: - - hex = "20010db80000000000080800200c417a" - - ip6 = IPAddress::IPv6::parse_hex hex - ip6.prefix = 64 - - ip6.to_string - #=> "2001:db8::8:800:200c:417a/64" - -=== Special IPv6 addresses - -Some IPv6 have a special meaning and are expressed in a special form, -quite different than an usual IPv6 address. IPAddress has built-in -support for unspecified, loopback and mapped IPv6 addresses. - -==== Unspecified address - -The address with all zero bits is called the +unspecified+ address -(corresponding to 0.0.0.0 in IPv4). It should be something like this: - - 0000:0000:0000:0000:0000:0000:0000:0000 - -but, with the use of compression, it is usually written as just two -colons: - - :: - -or, specifying the netmask: - - ::/128 - -With IPAddress, create a new unspecified IPv6 address using its own -subclass: - - ip = IPAddress::IPv6::Unspecified.new - - ip.to_string - #=> "::/128" - -You can easily check if an IPv6 object is an unspecified address by -using the IPv6#unspecified? method - - ip.unspecified? - #=> true - -An unspecified IPv6 address can also be created with the wrapper -method, like we've seen before - - ip = IPAddress "::" - - ip.unspecified? - #=> true - -This address must never be assigned to an interface and is to be used -only in software before the application has learned its host's source -address appropriate for a pending connection. Routers must not forward -packets with the unspecified address. - -==== Loopback address - -The loopback address is a unicast localhost address. If an -application in a host sends packets to this address, the IPv6 stack -will loop these packets back on the same virtual interface. - -Loopback addresses are expressed in the following form: - - ::1 - -or, with their appropriate prefix, - - ::1/128 - -As for the unspecified addresses, IPv6 loopbacks can be created with -IPAddress calling their own class: - - ip = IPAddress::IPv6::Loopback.new - - ip.to_string - #=> "::1/128" - -or by using the wrapper: - - ip = IPAddress "::1" - - ip.to_string - #=> "::1/128" - -Checking if an address is loopback is easy with the IPv6#loopback? -method: - - ip.loopback? - #=> true - -The IPv6 loopback address corresponds to 127.0.0.1 in IPv4. - -==== Mapped address - -It is usually identified as a IPv4 mapped IPv6 address, a particular -IPv6 address which aids the transition from IPv4 to IPv6. The -structure of the address is - - ::ffff:w.y.x.z - -where w.x.y.z is a normal IPv4 address. For example, the following is -a mapped IPv6 address: - - ::ffff:192.168.100.1 - -IPAddress is very powerful in handling mapped IPv6 addresses, as the -IPv4 portion is stored internally as a normal IPv4 object. Let's have -a look at some examples. To create a new mapped address, just use the -class builder itself - - ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128" - -or just use the wrapper method - - ip6 = IPAddress "::ffff:172.16.10.1/128" - -Let's check it's really a mapped address: - - ip6.mapped? - #=> true - - ip6.to_string - #=> "::ffff:172.16.10.1/128" - -Now with the +ipv4+ attribute, we can easily access the IPv4 portion -of the mapped IPv6 address: - - ip6.ipv4.address - #=> "172.16.10.1" - -Internally, the IPv4 address is stored as two 16 bits -groups. Therefore all the usual methods for an IPv6 address are -working perfectly fine: - - ip6.to_hex - #=> "00000000000000000000ffffac100a01" - - ip6.address - #=> "0000:0000:0000:0000:0000:ffff:ac10:0a01" - -A mapped IPv6 can also be created just by specify the address in the -following format: - - ip6 = IPAddress "::172.16.10.1" - -That is, two colons and the IPv4 address. However, as by RFC, the ffff -group will be automatically added at the beginning - - ip6.to_string - => "::ffff:172.16.10.1/128" - -making it a mapped IPv6 compatible address. - -== Community - -Want to join the community? - -* {IPAddress google group}[http://groups.google.com/group/ruby-ipaddress] - -We've created a group to discuss about -IPAddress future development, features and provide some kind of support. -Feel free to join us and tell us what you think! - -== Thanks to - -Thanks to Luca Russo (vargolo) and Simone Carletti -(weppos) for all the support and technical review. Thanks to Marco Beri, -Bryan T. Richardson, Nicolas Fevrier, jdpace, Daniele Alessandri, jrdioko, -Ghislain Charrier, Pawel Krzesniak, Mark Sullivan, Erik Ahlström and -Steve Rawlinson for their support, feedback and bug reports. - -== Copyright - -Copyright (c) 2009-2011 Marco Ceresa. See LICENSE for details. - - -
\ No newline at end of file diff --git a/VERSION b/VERSION deleted file mode 100644 index da2ac9c..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.7.5
\ No newline at end of file diff --git a/ipaddress.gemspec b/ipaddress.gemspec deleted file mode 100644 index 00c8b16..0000000 --- a/ipaddress.gemspec +++ /dev/null @@ -1,65 +0,0 @@ -# Generated by jeweler -# DO NOT EDIT THIS FILE DIRECTLY -# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' -# -*- encoding: utf-8 -*- - -Gem::Specification.new do |s| - s.name = %q{ipaddress} - s.version = "0.7.5" - - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Marco Ceresa"] - s.date = %q{2011-04-08} - s.description = %q{ IPAddress is a Ruby library designed to make manipulation - of IPv4 and IPv6 addresses both powerful and simple. It mantains - a layer of compatibility with Ruby's own IPAddr, while - addressing many of its issues. -} - s.email = %q{ceresa@gmail.com} - s.extra_rdoc_files = [ - "LICENSE", - "README.rdoc" - ] - s.files = [ - ".document", - "CHANGELOG.rdoc", - "LICENSE", - "README.rdoc", - "Rakefile", - "VERSION", - "lib/ipaddress.rb", - "lib/ipaddress/extensions/extensions.rb", - "lib/ipaddress/ipv4.rb", - "lib/ipaddress/ipv6.rb", - "lib/ipaddress/prefix.rb", - "test/ipaddress/extensions/extensions_test.rb", - "test/ipaddress/ipv4_test.rb", - "test/ipaddress/ipv6_test.rb", - "test/ipaddress/prefix_test.rb", - "test/ipaddress_test.rb", - "test/test_helper.rb" - ] - s.homepage = %q{http://github.com/bluemonk/ipaddress} - s.require_paths = ["lib"] - s.rubygems_version = %q{1.3.7} - s.summary = %q{IPv4/IPv6 addresses manipulation library} - s.test_files = [ - "test/ipaddress/extensions/extensions_test.rb", - "test/ipaddress/ipv4_test.rb", - "test/ipaddress/ipv6_test.rb", - "test/ipaddress/prefix_test.rb", - "test/ipaddress_test.rb", - "test/test_helper.rb" - ] - - if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION - s.specification_version = 3 - - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - else - end - else - end -end - diff --git a/lib/ipaddress.rb b/lib/ipaddress.rb index 3627782..cfe6f14 100644 --- a/lib/ipaddress.rb +++ b/lib/ipaddress.rb @@ -12,11 +12,6 @@ # #++ -require 'ipaddress/ipv4' -require 'ipaddress/ipv6' -require 'ipaddress/extensions/extensions' - - module IPAddress NAME = "IPAddress" @@ -185,4 +180,2038 @@ def IPAddress(str) IPAddress::parse str end +module IPAddress + + # + # =NAME + # + # IPAddress::Prefix + # + # =SYNOPSIS + # + # Parent class for Prefix32 and Prefix128 + # + # =DESCRIPTION + # + # IPAddress::Prefix is the parent class for IPAddress::Prefix32 + # and IPAddress::Prefix128, defining some modules in common for + # both the subclasses. + # + # IPAddress::Prefix shouldn't be accesses directly, unless + # for particular needs. + # + class Prefix + + include Comparable + + attr_reader :prefix + + # + # Creates a new general prefix + # + def initialize(num) + @prefix = num.to_i + end + + # + # Returns a string with the prefix + # + def to_s + "#@prefix" + end + alias_method :inspect, :to_s + + # + # Returns the prefix + # + def to_i + @prefix + end + + # + # Compare the prefix + # + def <=>(oth) + @prefix <=> oth.to_i + end + + # + # Sums two prefixes or a prefix to a + # number, returns a Fixnum + # + def +(oth) + if oth.is_a? Fixnum + self.prefix + oth + else + self.prefix + oth.prefix + end + end + + # + # Returns the difference between two + # prefixes, or a prefix and a number, + # as a Fixnum + # + def -(oth) + if oth.is_a? Fixnum + self.prefix - oth + else + (self.prefix - oth.prefix).abs + end + end + + end # class Prefix + + + class Prefix32 < Prefix + + IN4MASK = 0xffffffff + + # + # Creates a new prefix object for 32 bits IPv4 addresses + # + # prefix = IPAddress::Prefix32.new 24 + # #=> 24 + # + def initialize(num) + unless (0..32).include? num + raise ArgumentError, "Prefix must be in range 0..32, got: #{num}" + end + super(num) + end + + # + # Returns the length of the host portion + # of a netmask. + # + # prefix = Prefix32.new 24 + # + # prefix.host_prefix + # #=> 8 + # + def host_prefix + 32 - @prefix + end + + # + # Transforms the prefix into a string of bits + # representing the netmask + # + # prefix = IPAddress::Prefix32.new 24 + # + # prefix.bits + # #=> "11111111111111111111111100000000" + # + def bits + "%.32b" % to_u32 + end + + # + # Gives the prefix in IPv4 dotted decimal format, + # i.e. the canonical netmask we're all used to + # + # prefix = IPAddress::Prefix32.new 24 + # + # prefix.to_ip + # #=> "255.255.255.0" + # + def to_ip + [bits].pack("B*").unpack("CCCC").join(".") + end + + # + # An array of octets of the IPv4 dotted decimal + # format + # + # prefix = IPAddress::Prefix32.new 24 + # + # prefix.octets + # #=> [255, 255, 255, 0] + # + def octets + to_ip.split(".").map{|i| i.to_i} + end + + # + # Unsigned 32 bits decimal number representing + # the prefix + # + # prefix = IPAddress::Prefix32.new 24 + # + # prefix.to_u32 + # #=> 4294967040 + # + def to_u32 + (IN4MASK >> host_prefix) << host_prefix + end + + # + # Shortcut for the octecs in the dotted decimal + # representation + # + # prefix = IPAddress::Prefix32.new 24 + # + # prefix[2] + # #=> 255 + # + def [](index) + octets[index] + end + + # + # The hostmask is the contrary of the subnet mask, + # as it shows the bits that can change within the + # hosts + # + # prefix = IPAddress::Prefix32.new 24 + # + # prefix.hostmask + # #=> "0.0.0.255" + # + def hostmask + [~to_u32].pack("N").unpack("CCCC").join(".") + end + + # + # Creates a new prefix by parsing a netmask in + # dotted decimal form + # + # prefix = IPAddress::Prefix32::parse_netmask "255.255.255.0" + # #=> 24 + # + def self.parse_netmask(netmask) + octets = netmask.split(".").map{|i| i.to_i} + num = octets.pack("C"*octets.size).unpack("B*").first.count "1" + return self.new(num) + end + + end # class Prefix32 < Prefix + + class Prefix128 < Prefix + + # + # Creates a new prefix object for 128 bits IPv6 addresses + # + # prefix = IPAddress::Prefix128.new 64 + # #=> 64 + # + def initialize(num=128) + unless (1..128).include? num.to_i + raise ArgumentError, "Prefix must be in range 1..128, got: #{num}" + end + super(num.to_i) + end + + # + # Transforms the prefix into a string of bits + # representing the netmask + # + # prefix = IPAddress::Prefix128.new 64 + # + # prefix.bits + # #=> "1111111111111111111111111111111111111111111111111111111111111111" + # "0000000000000000000000000000000000000000000000000000000000000000" + # + def bits + "1" * @prefix + "0" * (128 - @prefix) + end + + # + # Unsigned 128 bits decimal number representing + # the prefix + # + # prefix = IPAddress::Prefix128.new 64 + # + # prefix.to_u128 + # #=> 340282366920938463444927863358058659840 + # + def to_u128 + bits.to_i(2) + end + + end # class Prefix123 < Prefix + +end # module IPAddress + +module IPAddress; + # + # =Name + # + # IPAddress::IPv4 - IP version 4 address manipulation library + # + # =Synopsis + # + # require 'ipaddress' + # + # =Description + # + # Class IPAddress::IPv4 is used to handle IPv4 type addresses. + # + class IPv4 + + include IPAddress + include Enumerable + include Comparable + + # + # This Hash contains the prefix values for Classful networks + # + # Note that classes C, D and E will all have a default + # prefix of /24 or 255.255.255.0 + # + CLASSFUL = { + /^0../ => 8, # Class A, from 0.0.0.0 to 127.255.255.255 + /^10./ => 16, # Class B, from 128.0.0.0 to 191.255.255.255 + /^110/ => 24 # Class C, D and E, from 192.0.0.0 to 255.255.255.254 + } + + # + # Regular expression to match an IPv4 address + # + REGEXP = Regexp.new(/((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)/) + + # + # Creates a new IPv4 address object. + # + # An IPv4 address can be expressed in any of the following forms: + # + # * "10.1.1.1/24": ip +address+ and +prefix+. This is the common and + # suggested way to create an object . + # * "10.1.1.1/255.255.255.0": ip +address+ and +netmask+. Although + # convenient sometimes, this format is less clear than the previous + # one. + # * "10.1.1.1": if the address alone is specified, the prefix will be + # set as default 32, also known as the host prefix + # + # Examples: + # + # # These two are the same + # ip = IPAddress::IPv4.new("10.0.0.1/24") + # ip = IPAddress("10.0.0.1/24") + # + # # These two are the same + # IPAddress::IPv4.new "10.0.0.1/8" + # IPAddress::IPv4.new "10.0.0.1/255.0.0.0" + # + def initialize(str) + ip, netmask = str.split("/") + + # Check the ip and remove white space + if IPAddress.valid_ipv4?(ip) + @address = ip.strip + else + raise ArgumentError, "Invalid IP #{ip.inspect}" + end + + # Check the netmask + if netmask # netmask is defined + netmask.strip! + if netmask =~ /^\d{1,2}$/ # netmask in cidr format + @prefix = Prefix32.new(netmask.to_i) + elsif IPAddress.valid_ipv4_netmask?(netmask) # netmask in IP format + @prefix = Prefix32.parse_netmask(netmask) + else # invalid netmask + raise ArgumentError, "Invalid netmask #{netmask}" + end + else # netmask is nil, reverting to defaul classful mask + @prefix = Prefix32.new(32) + end + + # Array formed with the IP octets + @octets = @address.split(".").map{|i| i.to_i} + # 32 bits interger containing the address + @u32 = (@octets[0]<< 24) + (@octets[1]<< 16) + (@octets[2]<< 8) + (@octets[3]) + + end # def initialize + + # + # Returns the address portion of the IPv4 object + # as a string. + # + # ip = IPAddress("172.16.100.4/22") + # + # ip.address + # #=> "172.16.100.4" + # + def address + @address + end + + # + # Returns the prefix portion of the IPv4 object + # as a IPAddress::Prefix32 object + # + # ip = IPAddress("172.16.100.4/22") + # + # ip.prefix + # #=> 22 + # + # ip.prefix.class + # #=> IPAddress::Prefix32 + # + def prefix + @prefix + end + + # + # Set a new prefix number for the object + # + # This is useful if you want to change the prefix + # to an object created with IPv4::parse_u32 or + # if the object was created using the classful + # mask. + # + # ip = IPAddress("172.16.100.4") + # + # puts ip + # #=> 172.16.100.4/16 + # + # ip.prefix = 22 + # + # puts ip + # #=> 172.16.100.4/22 + # + def prefix=(num) + @prefix = Prefix32.new(num) + end + + # + # Returns the address as an array of decimal values + # + # ip = IPAddress("172.16.100.4") + # + # ip.octets + # #=> [172, 16, 100, 4] + # + def octets + @octets + end + + # + # Returns a string with the address portion of + # the IPv4 object + # + # ip = IPAddress("172.16.100.4/22") + # + # ip.to_s + # #=> "172.16.100.4" + # + def to_s + @address + end + + # + # Returns a string with the IP address in canonical + # form. + # + # ip = IPAddress("172.16.100.4/22") + # + # ip.to_string + # #=> "172.16.100.4/22" + # + def to_string + "#@address/#@prefix" + end + + # + # Returns the prefix as a string in IP format + # + # ip = IPAddress("172.16.100.4/22") + # + # ip.netmask + # #=> "255.255.252.0" + # + def netmask + @prefix.to_ip + end + + # + # Like IPv4#prefix=, this method allow you to + # change the prefix / netmask of an IP address + # object. + # + # ip = IPAddress("172.16.100.4") + # + # puts ip + # #=> 172.16.100.4/16 + # + # ip.netmask = "255.255.252.0" + # + # puts ip + # #=> 172.16.100.4/22 + # + def netmask=(addr) + @prefix = Prefix32.parse_netmask(addr) + end + + # + # Returns the address portion in unsigned + # 32 bits integer format. + # + # This method is identical to the C function + # inet_pton to create a 32 bits address family + # structure. + # + # ip = IPAddress("10.0.0.0/8") + # + # ip.to_i + # #=> 167772160 + # + def u32 + @u32 + end + alias_method :to_i, :u32 + alias_method :to_u32, :u32 + + # + # Returns the address portion of an IPv4 object + # in a network byte order format. + # + # ip = IPAddress("172.16.10.1/24") + # + # ip.data + # #=> "\254\020\n\001" + # + # It is usually used to include an IP address + # in a data packet to be sent over a socket + # + # a = Socket.open(params) # socket details here + # ip = IPAddress("10.1.1.0/24") + # binary_data = ["Address: "].pack("a*") + ip.data + # + # # Send binary data + # a.puts binary_data + # + def data + [@u32].pack("N") + end + + # + # Returns the octet specified by index + # + # ip = IPAddress("172.16.100.50/24") + # + # ip[0] + # #=> 172 + # ip[1] + # #=> 16 + # ip[2] + # #=> 100 + # ip[3] + # #=> 50 + # + def [](index) + @octets[index] + end + alias_method :octet, :[] + + # + # Returns the address portion of an IP in binary format, + # as a string containing a sequence of 0 and 1 + # + # ip = IPAddress("127.0.0.1") + # + # ip.bits + # #=> "01111111000000000000000000000001" + # + def bits + data.unpack("B*").first + end + + # + # Returns the broadcast address for the given IP. + # + # ip = IPAddress("172.16.10.64/24") + # + # ip.broadcast.to_s + # #=> "172.16.10.255" + # + def broadcast + self.class.parse_u32(broadcast_u32, @prefix) + end + + # + # Checks if the IP address is actually a network + # + # ip = IPAddress("172.16.10.64/24") + # + # ip.network? + # #=> false + # + # ip = IPAddress("172.16.10.64/26") + # + # ip.network? + # #=> true + # + def network? + @u32 | @prefix.to_u32 == @prefix.to_u32 + end + + # + # Returns a new IPv4 object with the network number + # for the given IP. + # + # ip = IPAddress("172.16.10.64/24") + # + # ip.network.to_s + # #=> "172.16.10.0" + # + def network + self.class.parse_u32(network_u32, @prefix) + end + + # + # Returns a new IPv4 object with the + # first host IP address in the range. + # + # Example: given the 192.168.100.0/24 network, the first + # host IP address is 192.168.100.1. + # + # ip = IPAddress("192.168.100.0/24") + # + # ip.first.to_s + # #=> "192.168.100.1" + # + # The object IP doesn't need to be a network: the method + # automatically gets the network number from it + # + # ip = IPAddress("192.168.100.50/24") + # + # ip.first.to_s + # #=> "192.168.100.1" + # + def first + self.class.parse_u32(network_u32+1, @prefix) + end + + # + # Like its sibling method IPv4#first, this method + # returns a new IPv4 object with the + # last host IP address in the range. + # + # Example: given the 192.168.100.0/24 network, the last + # host IP address is 192.168.100.254 + # + # ip = IPAddress("192.168.100.0/24") + # + # ip.last.to_s + # #=> "192.168.100.254" + # + # The object IP doesn't need to be a network: the method + # automatically gets the network number from it + # + # ip = IPAddress("192.168.100.50/24") + # + # ip.last.to_s + # #=> "192.168.100.254" + # + def last + self.class.parse_u32(broadcast_u32-1, @prefix) + end + + # + # Iterates over all the hosts IP addresses for the given + # network (or IP address). + # + # ip = IPAddress("10.0.0.1/29") + # + # ip.each_host do |i| + # p i.to_s + # end + # #=> "10.0.0.1" + # #=> "10.0.0.2" + # #=> "10.0.0.3" + # #=> "10.0.0.4" + # #=> "10.0.0.5" + # #=> "10.0.0.6" + # + def each_host + (network_u32+1..broadcast_u32-1).each do |i| + yield self.class.parse_u32(i, @prefix) + end + end + + # + # Iterates over all the IP addresses for the given + # network (or IP address). + # + # The object yielded is a new IPv4 object created + # from the iteration. + # + # ip = IPAddress("10.0.0.1/29") + # + # ip.each do |i| + # p i.address + # end + # #=> "10.0.0.0" + # #=> "10.0.0.1" + # #=> "10.0.0.2" + # #=> "10.0.0.3" + # #=> "10.0.0.4" + # #=> "10.0.0.5" + # #=> "10.0.0.6" + # #=> "10.0.0.7" + # + def each + (network_u32..broadcast_u32).each do |i| + yield self.class.parse_u32(i, @prefix) + end + end + + # + # Spaceship operator to compare IP addresses + # + # An IP address is considered to be minor if it + # has a greater prefix (thus smaller hosts + # portion) and a smaller u32 value. + # + # For example, "10.100.100.1/8" is smaller than + # "172.16.0.1/16", but it's bigger than "10.100.100.1/16". + # + # Example: + # + # ip1 = IPAddress "10.100.100.1/8" + # ip2 = IPAddress "172.16.0.1/16" + # ip3 = IPAddress "10.100.100.1/16" + # + # ip1 < ip2 + # #=> true + # ip1 < ip3 + # #=> false + # + def <=>(oth) + if to_u32 > oth.to_u32 + return 1 + elsif to_u32 < oth.to_u32 + return -1 + else + if prefix < oth.prefix + return 1 + elsif prefix > oth.prefix + return -1 + end + end + return 0 + end + + # + # Returns the number of IP addresses included + # in the network. It also counts the network + # address and the broadcast address. + # + # ip = IPAddress("10.0.0.1/29") + # + # ip.size + # #=> 8 + # + def size + 2 ** @prefix.host_prefix + end + + # + # Returns an array with the IP addresses of + # all the hosts in the network. + # + # ip = IPAddress("10.0.0.1/29") + # + # ip.hosts.map {|i| i.address} + # #=> ["10.0.0.1", + # #=> "10.0.0.2", + # #=> "10.0.0.3", + # #=> "10.0.0.4", + # #=> "10.0.0.5", + # #=> "10.0.0.6"] + # + def hosts + to_a[1..-2] + end + + # + # Returns the network number in Unsigned 32bits format + # + # ip = IPAddress("10.0.0.1/29") + # + # ip.network_u32 + # #=> 167772160 + # + def network_u32 + @u32 & @prefix.to_u32 + end + + # + # Returns the broadcast address in Unsigned 32bits format + # + # ip = IPaddress("10.0.0.1/29") + # + # ip.broadcast_u32 + # #=> 167772167 + # + def broadcast_u32 + network_u32 + size - 1 + end + + # + # Checks whether a subnet includes the given IP address. + # + # Accepts an IPAddress::IPv4 object. + # + # ip = IPAddress("192.168.10.100/24") + # + # addr = IPAddress("192.168.10.102/24") + # + # ip.include? addr + # #=> true + # + # ip.include? IPAddress("172.16.0.48/16") + # #=> false + # + def include?(oth) + @prefix <= oth.prefix and network_u32 == (oth.to_u32 & @prefix.to_u32) + end + + # + # Checks whether a subnet includes all the + # given IPv4 objects. + # + # ip = IPAddress("192.168.10.100/24") + # + # addr1 = IPAddress("192.168.10.102/24") + # addr2 = IPAddress("192.168.10.103/24") + # + # ip.include_all?(addr1,addr2) + # #=> true + # + def include_all?(*others) + others.all? {|oth| include?(oth)} + end + + # + # True if the object is an IPv4 address + # + # ip = IPAddress("192.168.10.100/24") + # + # ip.ipv4? + # #-> true + # +# def ipv4? +# true +# end + + # + # True if the object is an IPv6 address + # + # ip = IPAddress("192.168.10.100/24") + # + # ip.ipv6? + # #-> false + # +# def ipv6? +# false +# end + + # + # Checks if an IPv4 address objects belongs + # to a private network RFC1918 + # + # Example: + # + # ip = IPAddress "10.1.1.1/24" + # ip.private? + # #=> true + # + def private? + [self.class.new("10.0.0.0/8"), + self.class.new("172.16.0.0/12"), + self.class.new("192.168.0.0/16")].any? {|i| i.include? self} + end + + # + # Returns the IP address in in-addr.arpa format + # for DNS lookups + # + # ip = IPAddress("172.16.100.50/24") + # + # ip.reverse + # #=> "50.100.16.172.in-addr.arpa" + # + def reverse + @octets.reverse.join(".") + ".in-addr.arpa" + end + alias_method :arpa, :reverse + + # + # Subnetting a network + # + # If the IP Address is a network, it can be divided into + # multiple networks. If +self+ is not a network, the + # method will calculate the network from the IP and then + # subnet it. + # + # If +subnets+ is an power of two number, the resulting + # networks will be divided evenly from the supernet. + # + # network = IPAddress("172.16.10.0/24") + # + # network / 4 # implies map{|i| i.to_string} + # #=> ["172.16.10.0/26", + # "172.16.10.64/26", + # "172.16.10.128/26", + # "172.16.10.192/26"] + # + # If +num+ is any other number, the supernet will be + # divided into some networks with a even number of hosts and + # other networks with the remaining addresses. + # + # network = IPAddress("172.16.10.0/24") + # + # network / 3 # implies map{|i| i.to_string} + # #=> ["172.16.10.0/26", + # "172.16.10.64/26", + # "172.16.10.128/25"] + # + # Returns an array of IPAddress objects + # + def subnet(subnets=2) + unless (1..(2**@prefix.host_prefix)).include? subnets + raise ArgumentError, "Value #{subnets} out of range" + end + calculate_subnets(subnets) + end + alias_method :/, :subnet + + # + # Returns a new IPv4 object from the supernetting + # of the instance network. + # + # Supernetting is similar to subnetting, except + # that you getting as a result a network with a + # smaller prefix (bigger host space). For example, + # given the network + # + # ip = IPAddress("172.16.10.0/24") + # + # you can supernet it with a new /23 prefix + # + # ip.supernet(23).to_string + # #=> "172.16.10.0/23" + # + # However if you supernet it with a /22 prefix, the + # network address will change: + # + # ip.supernet(22).to_string + # #=> "172.16.8.0/22" + # + def supernet(new_prefix) + raise ArgumentError, "Can't supernet a /1 network" if new_prefix < 1 + raise ArgumentError, "New prefix must be smaller than existing prefix" if new_prefix >= @prefix.to_i + self.class.new(@address+"/#{new_prefix}").network + end + + # + # Returns the difference between two IP addresses + # in unsigned int 32 bits format + # + # Example: + # + # ip1 = IPAddress("172.16.10.0/24") + # ip2 = IPAddress("172.16.11.0/24") + # + # puts ip1 - ip2 + # #=> 256 + # + def -(oth) + return (to_u32 - oth.to_u32).abs + end + + # + # Returns a new IPv4 object which is the result + # of the summarization, if possible, of the two + # objects + # + # Example: + # + # ip1 = IPAddress("172.16.10.1/24") + # ip2 = IPAddress("172.16.11.2/24") + # + # p (ip1 + ip2).map {|i| i.to_string} + # #=> ["172.16.10.0/23"] + # + # If the networks are not contiguous, returns + # the two network numbers from the objects + # + # ip1 = IPAddress("10.0.0.1/24") + # ip2 = IPAddress("10.0.2.1/24") + # + # p (ip1 + ip2).map {|i| i.to_string} + # #=> ["10.0.0.0/24","10.0.2.0/24"] + # + def +(oth) + aggregate(*[self,oth].sort.map{|i| i.network}) + end + + # + # Checks whether the ip address belongs to a + # RFC 791 CLASS A network, no matter + # what the subnet mask is. + # + # Example: + # + # ip = IPAddress("10.0.0.1/24") + # + # ip.a? + # #=> true + # + def a? + CLASSFUL.key(8) === bits + end + + # + # Checks whether the ip address belongs to a + # RFC 791 CLASS B network, no matter + # what the subnet mask is. + # + # Example: + # + # ip = IPAddress("172.16.10.1/24") + # + # ip.b? + # #=> true + # + def b? + CLASSFUL.key(16) === bits + end + + # + # Checks whether the ip address belongs to a + # RFC 791 CLASS C network, no matter + # what the subnet mask is. + # + # Example: + # + # ip = IPAddress("192.168.1.1/30") + # + # ip.c? + # #=> true + # + def c? + CLASSFUL.key(24) === bits + end + + # + # Return the ip address in a format compatible + # with the IPv6 Mapped IPv4 addresses + # + # Example: + # + # ip = IPAddress("172.16.10.1/24") + # + # ip.to_ipv6 + # #=> "ac10:0a01" + # + def to_ipv6 + "%.4x:%.4x" % [to_u32].pack("N").unpack("nn") + end + + # + # Creates a new IPv4 object from an + # unsigned 32bits integer. + # + # ip = IPAddress::IPv4::parse_u32(167772160) + # + # ip.prefix = 8 + # ip.to_string + # #=> "10.0.0.0/8" + # + # The +prefix+ parameter is optional: + # + # ip = IPAddress::IPv4::parse_u32(167772160, 8) + # + # ip.to_string + # #=> "10.0.0.0/8" + # + def self.parse_u32(u32, prefix=32) + self.new([u32].pack("N").unpack("C4").join(".")+"/#{prefix}") + end + + # + # Creates a new IPv4 object from binary data, + # like the one you get from a network stream. + # + # For example, on a network stream the IP 172.16.0.1 + # is represented with the binary "\254\020\n\001". + # + # ip = IPAddress::IPv4::parse_data "\254\020\n\001" + # ip.prefix = 24 + # + # ip.to_string + # #=> "172.16.10.1/24" + # + def self.parse_data(str, prefix=32) + self.new(str.unpack("C4").join(".")+"/#{prefix}") + end + + # + # Extract an IPv4 address from a string and + # returns a new object + # + # Example: + # + # str = "foobar172.16.10.1barbaz" + # ip = IPAddress::IPv4::extract str + # + # ip.to_s + # #=> "172.16.10.1" + # + def self.extract(str) + self.new REGEXP.match(str).to_s + end + + # + # Summarization (or aggregation) is the process when two or more + # networks are taken together to check if a supernet, including all + # and only these networks, exists. If it exists then this supernet + # is called the summarized (or aggregated) network. + # + # It is very important to understand that summarization can only + # occur if there are no holes in the aggregated network, or, in other + # words, if the given networks fill completely the address space + # of the supernet. So the two rules are: + # + # 1) The aggregate network must contain +all+ the IP addresses of the + # original networks; + # 2) The aggregate network must contain +only+ the IP addresses of the + # original networks; + # + # A few examples will help clarify the above. Let's consider for + # instance the following two networks: + # + # ip1 = IPAddress("172.16.10.0/24") + # ip2 = IPAddress("172.16.11.0/24") + # + # These two networks can be expressed using only one IP address + # network if we change the prefix. Let Ruby do the work: + # + # IPAddress::IPv4::summarize(ip1,ip2).to_s + # #=> "172.16.10.0/23" + # + # We note how the network "172.16.10.0/23" includes all the addresses + # specified in the above networks, and (more important) includes + # ONLY those addresses. + # + # If we summarized +ip1+ and +ip2+ with the following network: + # + # "172.16.0.0/16" + # + # we would have satisfied rule #1 above, but not rule #2. So "172.16.0.0/16" + # is not an aggregate network for +ip1+ and +ip2+. + # + # If it's not possible to compute a single aggregated network for all the + # original networks, the method returns an array with all the aggregate + # networks found. For example, the following four networks can be + # aggregated in a single /22: + # + # ip1 = IPAddress("10.0.0.1/24") + # ip2 = IPAddress("10.0.1.1/24") + # ip3 = IPAddress("10.0.2.1/24") + # ip4 = IPAddress("10.0.3.1/24") + # + # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).to_string + # #=> "10.0.0.0/22", + # + # But the following networks can't be summarized in a single network: + # + # ip1 = IPAddress("10.0.1.1/24") + # ip2 = IPAddress("10.0.2.1/24") + # ip3 = IPAddress("10.0.3.1/24") + # ip4 = IPAddress("10.0.4.1/24") + # + # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} + # #=> ["10.0.1.0/24","10.0.2.0/23","10.0.4.0/24"] + # + def self.summarize(*args) + # one network? no need to summarize + return [args.first.network] if args.size == 1 + + i = 0 + result = args.dup.sort.map{|ip| ip.network} + while i < result.size-1 + sum = result[i] + result[i+1] + result[i..i+1] = sum.first if sum.size == 1 + i += 1 + end + + result.flatten! + if result.size == args.size + # nothing more to summarize + return result + else + # keep on summarizing + return self.summarize(*result) + end + end + + # + # Creates a new IPv4 address object by parsing the + # address in a classful way. + # + # Classful addresses have a fixed netmask based on the + # class they belong to: + # + # * Class A, from 0.0.0.0 to 127.255.255.255 + # * Class B, from 128.0.0.0 to 191.255.255.255 + # * Class C, D and E, from 192.0.0.0 to 255.255.255.254 + # + # Note that classes C, D and E will all have a default + # prefix of /24 or 255.255.255.0 + # + # Example: + # + # ip = IPAddress::IPv4.parse_classful "10.0.0.1" + # + # ip.netmask + # #=> "255.0.0.0" + # ip.a? + # #=> true + # + def self.parse_classful(ip) + if IPAddress.valid_ipv4?(ip) + address = ip.strip + else + raise ArgumentError, "Invalid IP #{ip.inspect}" + end + prefix = CLASSFUL.find{|h,k| h === ("%.8b" % address.to_i)}.last + self.new "#{address}/#{prefix}" + end + + # + # private methods + # + private + + def calculate_subnets(subnets) + po2 = closest_power_of_2(subnets) + new_prefix = @prefix + log2(po2).to_i + networks = Array.new + (0..po2-1).each do |i| + mul = i * (2**(32-new_prefix)) + networks << IPAddress::IPv4.parse_u32(network_u32+mul, new_prefix) + end + until networks.size == subnets + networks = sum_first_found(networks) + end + return networks + end + + def sum_first_found(arr) + dup = arr.dup.reverse + dup.each_with_index do |obj,i| + a = [IPAddress::IPv4.summarize(obj,dup[i+1])].flatten + if a.size == 1 + dup[i..i+1] = a + return dup.reverse + end + end + return dup.reverse + end + + def aggregate(ip1,ip2) + return [ip1] if ip1.include? ip2 + + snet = ip1.supernet(ip1.prefix-1) + if snet.include_all?(ip1, ip2) && ((ip1.size + ip2.size) == snet.size) + return [snet] + else + return [ip1, ip2] + end + end + + def log2(n); Math::log(n) / Math::log(2); end + + def power_of_2?(int) + log2(int).to_i == log2(int) + end + + def closest_power_of_2(int, limit=32) + int.upto(limit) do |i| + return i if power_of_2?(i) + end + end + + end # class IPv4 +end # module IPAddress + +module IPAddress; + # + # =Name + # + # IPAddress::IPv6 - IP version 6 address manipulation library + # + # =Synopsis + # + # require 'ipaddress' + # + # =Description + # + # Class IPAddress::IPv6 is used to handle IPv6 type addresses. + # + # == IPv6 addresses + # + # IPv6 addresses are 128 bits long, in contrast with IPv4 addresses + # which are only 32 bits long. An IPv6 address is generally written as + # eight groups of four hexadecimal digits, each group representing 16 + # bits or two octect. For example, the following is a valid IPv6 + # address: + # + # 1080:0000:0000:0000:0008:0800:200c:417a + # + # Letters in an IPv6 address are usually written downcase, as per + # RFC. You can create a new IPv6 object using uppercase letters, but + # they will be converted. + # + # === Compression + # + # Since IPv6 addresses are very long to write, there are some + # semplifications and compressions that you can use to shorten them. + # + # * Leading zeroes: all the leading zeroes within a group can be + # omitted: "0008" would become "8" + # + # * A string of consecutive zeroes can be replaced by the string + # "::". This can be only applied once. + # + # Using compression, the IPv6 address written above can be shorten into + # the following, equivalent, address + # + # 1080::8:800:200c:417a + # + # This short version is often used in human representation. + # + # === Network Mask + # + # As we used to do with IPv4 addresses, an IPv6 address can be written + # using the prefix notation to specify the subnet mask: + # + # 1080::8:800:200c:417a/64 + # + # The /64 part means that the first 64 bits of the address are + # representing the network portion, and the last 64 bits are the host + # portion. + # + # + class IPv6 + + include IPAddress + include Enumerable + include Comparable + + + # + # Format string to pretty print IPv6 addresses + # + IN6FORMAT = ("%.4x:"*8).chop + + # + # Creates a new IPv6 address object. + # + # An IPv6 address can be expressed in any of the following forms: + # + # * "1080:0000:0000:0000:0008:0800:200C:417A": IPv6 address with no compression + # * "1080:0:0:0:8:800:200C:417A": IPv6 address with leading zeros compression + # * "1080::8:800:200C:417A": IPv6 address with full compression + # + # In all these 3 cases, a new IPv6 address object will be created, using the default + # subnet mask /128 + # + # You can also specify the subnet mask as with IPv4 addresses: + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + def initialize(str) + ip, netmask = str.split("/") + + if str =~ /:.+\./ + raise ArgumentError, "Please use #{self.class}::Mapped for IPv4 mapped addresses" + end + + if IPAddress.valid_ipv6?(ip) + @groups = self.class.groups(ip) + @address = IN6FORMAT % @groups + @compressed = compress_address + else + raise ArgumentError, "Invalid IP #{ip.inspect}" + end + + @prefix = Prefix128.new(netmask ? netmask : 128) + + end # def initialize + + # + # Returns the IPv6 address in uncompressed form: + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.address + # #=> "2001:0db8:0000:0000:0008:0800:200c:417a" + # + def address + @address + end + + # + # Returns an array with the 16 bits groups in decimal + # format: + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.groups + # #=> [8193, 3512, 0, 0, 8, 2048, 8204, 16762] + # + def groups + @groups + end + + # + # Returns an instance of the prefix object + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.prefix + # #=> 64 + # + def prefix + @prefix + end + + # + # Set a new prefix number for the object + # + # This is useful if you want to change the prefix + # to an object created with IPv6::parse_u128 or + # if the object was created using the default prefix + # of 128 bits. + # + # ip6 = IPAddress("2001:db8::8:800:200c:417a") + # + # puts ip6.to_string + # #=> "2001:db8::8:800:200c:417a/128" + # + # ip6.prefix = 64 + # puts ip6.to_string + # #=> "2001:db8::8:800:200c:417a/64" + # + def prefix=(num) + @prefix = Prefix128.new(num) + end + + # + # Unlike its counterpart IPv6#to_string method, IPv6#to_string_uncompressed + # returns the whole IPv6 address and prefix in an uncompressed form + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.to_string_uncompressed + # #=> "2001:0db8:0000:0000:0008:0800:200c:417a/64" + # + def to_string_uncompressed + "#@address/#@prefix" + end + + # + # Returns the IPv6 address in a human readable form, + # using the compressed address. + # + # ip6 = IPAddress "2001:0db8:0000:0000:0008:0800:200c:417a/64" + # + # ip6.to_string + # #=> "2001:db8::8:800:200c:417a/64" + # + def to_string + "#@compressed/#@prefix" + end + + # + # Returns the IPv6 address in a human readable form, + # using the compressed address. + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.to_s + # #=> "2001:db8::8:800:200c:417a" + # + def to_s + @compressed + end + + # + # Returns a decimal format (unsigned 128 bit) of the + # IPv6 address + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.to_i + # #=> 42540766411282592856906245548098208122 + # + def to_i + to_hex.hex + end + alias_method :to_u128, :to_i + + # + # True if the IPv6 address is a network + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.network? + # #=> false + # + # ip6 = IPAddress "2001:db8:8:800::/64" + # + # ip6.network? + # #=> true + # + def network? + to_u128 | @prefix.to_u128 == @prefix.to_u128 + end + + # + # Returns the 16-bits value specified by index + # + # ip = IPAddress("2001:db8::8:800:200c:417a/64") + # + # ip[0] + # #=> 8193 + # ip[1] + # #=> 3512 + # ip[2] + # #=> 0 + # ip[3] + # #=> 0 + # + def [](index) + @groups[index] + end + alias_method :group, :[] + + # + # Returns a Base16 number representing the IPv6 + # address + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.to_hex + # #=> "20010db80000000000080800200c417a" + # + def to_hex + hexs.join("") + end + + # Returns the address portion of an IPv6 object + # in a network byte order format. + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.data + # #=> " \001\r\270\000\000\000\000\000\b\b\000 \fAz" + # + # It is usually used to include an IP address + # in a data packet to be sent over a socket + # + # a = Socket.open(params) # socket details here + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # binary_data = ["Address: "].pack("a*") + ip.data + # + # # Send binary data + # a.puts binary_data + # + def data + @groups.pack("n8") + end + + # + # Returns an array of the 16 bits groups in hexdecimal + # format: + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.hexs + # #=> ["2001", "0db8", "0000", "0000", "0008", "0800", "200c", "417a"] + # + # Not to be confused with the similar IPv6#to_hex method. + # + def hexs + @address.split(":") + end + + # + # Returns the IPv6 address in a DNS reverse lookup + # string, as per RFC3172 and RFC2874. + # + # ip6 = IPAddress "3ffe:505:2::f" + # + # ip6.reverse + # #=> "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa" + # + def reverse + to_hex.reverse.gsub(/./){|c| c+"."} + "ip6.arpa" + end + alias_method :arpa, :reverse + + # + # Returns the network number in Unsigned 128bits format + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.network_u128 + # #=> 42540766411282592856903984951653826560 + # + def network_u128 + to_u128 & @prefix.to_u128 + end + + # + # Checks whether a subnet includes the given IP address. + # + # Example: + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # addr = IPAddress "2001:db8::8:800:200c:1/128" + # + # ip6.include? addr + # #=> true + # + # ip6.include? IPAddress("2001:db8:1::8:800:200c:417a/76") + # #=> false + # + def include?(oth) + @prefix <= oth.prefix and network_u128 == self.class.new(oth.address+"/#@prefix").network_u128 + end + + # + # Compressed form of the IPv6 address + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.compressed + # #=> "2001:db8::8:800:200c:417a" + # + def compressed + @compressed + end + + # + # Returns true if the address is an unspecified address + # + # See IPAddress::IPv6::Unspecified for more information + # + def unspecified? + @prefix == 128 and @compressed == "::" + end + + # + # Returns true if the address is a loopback address + # + # See IPAddress::IPv6::Loopback for more information + # + def loopback? + @prefix == 128 and @compressed == "::1" + end + + # + # Returns true if the address is a mapped address + # + # See IPAddress::IPv6::Mapped for more information + # + def mapped? + to_u128 >> 32 == 0xffff + end + + # + # Returns the address portion of an IP in binary format, + # as a string containing a sequence of 0 and 1 + # + # ip6 = IPAddress("2001:db8::8:800:200c:417a") + # + # ip6.bits + # #=> "0010000000000001000011011011100000 [...] " + # + def bits + data.unpack("B*").first + end + + # + # Expands an IPv6 address in the canocical form + # + # IPAddress::IPv6.expand "2001:0DB8:0:CD30::" + # #=> "2001:0DB8:0000:CD30:0000:0000:0000:0000" + # + def self.expand(str) + self.new(str).address + end + + # + # Compress an IPv6 address in its compressed form + # + # IPAddress::IPv6.compress "2001:0DB8:0000:CD30:0000:0000:0000:0000" + # #=> "2001:db8:0:cd30::" + # + def self.compress(str) + self.new(str).compressed + end + + # + # Literal version of the IPv6 address + # + # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" + # + # ip6.literal + # #=> "2001-0db8-0000-0000-0008-0800-200c-417a.ipv6-literal.net" + # + def literal + @address.gsub(":","-") + ".ipv6-literal.net" + end + + # + # Extract 16 bits groups from a string + # + def self.groups(str) + l, r = if str =~ /^(.*)::(.*)$/ + [$1,$2].map {|i| i.split ":"} + else + [str.split(":"),[]] + end + (l + Array.new(8-l.size-r.size, '0') + r).map {|i| i.hex} + end + + # + # Creates a new IPv6 object from binary data, + # like the one you get from a network stream. + # + # For example, on a network stream the IP + # + # "2001:db8::8:800:200c:417a" + # + # is represented with the binary data + # + # " \001\r\270\000\000\000\000\000\b\b\000 \fAz" + # + # With that data you can create a new IPv6 object: + # + # ip6 = IPAddress::IPv6::parse_data " \001\r\270\000\000\000\000\000\b\b\000 \fAz" + # ip6.prefix = 64 + # + # ip6.to_s + # #=> "2001:db8::8:800:200c:417a/64" + # + def self.parse_data(str) + self.new(IN6FORMAT % str.unpack("n8")) + end + + # + # Creates a new IPv6 object from an + # unsigned 128 bits integer. + # + # ip6 = IPAddress::IPv6::parse_u128(21932261930451111902915077091070067066) + # ip6.prefix = 64 + # + # ip6.to_s + # #=> "1080::8:800:200c:417a/64" + # + # The +prefix+ parameter is optional: + # + # ip6 = IPAddress::IPv6::parse_u128(21932261930451111902915077091070067066, 64) + # + # ip6.to_s + # #=> "1080::8:800:200c:417a/64" + # + def self.parse_u128(u128, prefix=128) + str = IN6FORMAT % (0..7).map{|i| (u128>>(112-16*i))&0xffff} + self.new(str + "/#{prefix}") + end + + # + # Creates a new IPv6 object from a number expressed in + # hexdecimal format: + # + # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a") + # ip6.prefix = 64 + # + # ip6.to_s + # #=> "2001:db8::8:800:200c:417a/64" + # + # The +prefix+ parameter is optional: + # + # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a", 64) + # + # ip6.to_s + # #=> "1080::8:800:200c:417a/64" + # + def self.parse_hex(hex, prefix=128) + self.parse_u128(hex.hex, prefix) + end + + private + + def compress_address + str = @groups.map{|i| i.to_s 16}.join ":" + loop do + break if str.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::') + break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':') + break if str.sub!(/\b0:0:0:0:0:0\b/, ':') + break if str.sub!(/\b0:0:0:0:0\b/, ':') + break if str.sub!(/\b0:0:0:0\b/, ':') + break if str.sub!(/\b0:0:0\b/, ':') + break if str.sub!(/\b0:0\b/, ':') + break + end + str.sub(/:{3,}/, '::') + end + + end # class IPv6 + + # + # The address with all zero bits is called the +unspecified+ address + # (corresponding to 0.0.0.0 in IPv4). It should be something like this: + # + # 0000:0000:0000:0000:0000:0000:0000:0000 + # + # but, with the use of compression, it is usually written as just two + # colons: + # + # :: + # + # or, specifying the netmask: + # + # ::/128 + # + # With IPAddress, create a new unspecified IPv6 address using its own + # subclass: + # + # ip = IPAddress::IPv6::Unspecified.new + # + # ip.to_s + # #=> => "::/128" + # + # You can easily check if an IPv6 object is an unspecified address by + # using the IPv6#unspecified? method + # + # ip.unspecified? + # #=> true + # + # An unspecified IPv6 address can also be created with the wrapper + # method, like we've seen before + # + # ip = IPAddress "::" + # + # ip.unspecified? + # #=> true + # + # This address must never be assigned to an interface and is to be used + # only in software before the application has learned its host's source + # address appropriate for a pending connection. Routers must not forward + # packets with the unspecified address. + # + class IPAddress::IPv6::Unspecified < IPAddress::IPv6 + # + # Creates a new IPv6 unspecified address + # + # ip = IPAddress::IPv6::Unspecified.new + # + # ip.to_s + # #=> => "::/128" + # + def initialize + @address = ("0000:"*8).chop + @groups = Array.new(8,0) + @prefix = Prefix128.new(128) + @compressed = compress_address + end + end # class IPv6::Unspecified + + # + # The loopback address is a unicast localhost address. If an + # application in a host sends packets to this address, the IPv6 stack + # will loop these packets back on the same virtual interface. + # + # Loopback addresses are expressed in the following form: + # + # ::1 + # + # or, with their appropriate prefix, + # + # ::1/128 + # + # As for the unspecified addresses, IPv6 loopbacks can be created with + # IPAddress calling their own class: + # + # ip = IPAddress::IPv6::Loopback.new + # + # ip.to_s + # #=> "::1/128" + # + # or by using the wrapper: + # + # ip = IPAddress "::1" + # + # ip.to_s + # #=> "::1/128" + # + # Checking if an address is loopback is easy with the IPv6#loopback? + # method: + # + # ip.loopback? + # #=> true + # + # The IPv6 loopback address corresponds to 127.0.0.1 in IPv4. + # + class IPAddress::IPv6::Loopback < IPAddress::IPv6 + # + # Creates a new IPv6 unspecified address + # + # ip = IPAddress::IPv6::Loopback.new + # + # ip.to_s + # #=> "::1/128" + # + def initialize + @address = ("0000:"*7)+"0001" + @groups = Array.new(7,0).push(1) + @prefix = Prefix128.new(128) + @compressed = compress_address + end + end # class IPv6::Loopback + + # + # It is usually identified as a IPv4 mapped IPv6 address, a particular + # IPv6 address which aids the transition from IPv4 to IPv6. The + # structure of the address is + # + # ::ffff:w.y.x.z + # + # where w.x.y.z is a normal IPv4 address. For example, the following is + # a mapped IPv6 address: + # + # ::ffff:192.168.100.1 + # + # IPAddress is very powerful in handling mapped IPv6 addresses, as the + # IPv4 portion is stored internally as a normal IPv4 object. Let's have + # a look at some examples. To create a new mapped address, just use the + # class builder itself + # + # ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128" + # + # or just use the wrapper method + # + # ip6 = IPAddress "::ffff:172.16.10.1/128" + # + # Let's check it's really a mapped address: + # + # ip6.mapped? + # #=> true + # + # ip6.to_s + # #=> "::FFFF:172.16.10.1/128" + # + # Now with the +ipv4+ attribute, we can easily access the IPv4 portion + # of the mapped IPv6 address: + # + # ip6.ipv4.address + # #=> "172.16.10.1" + # + # Internally, the IPv4 address is stored as two 16 bits + # groups. Therefore all the usual methods for an IPv6 address are + # working perfectly fine: + # + # ip6.to_hex + # #=> "00000000000000000000ffffac100a01" + # + # ip6.address + # #=> "0000:0000:0000:0000:0000:ffff:ac10:0a01" + # + # A mapped IPv6 can also be created just by specify the address in the + # following format: + # + # ip6 = IPAddress "::172.16.10.1" + # + # That is, two colons and the IPv4 address. However, as by RFC, the ffff + # group will be automatically added at the beginning + # + # ip6.to_s + # => "::ffff:172.16.10.1/128" + # + # making it a mapped IPv6 compatible address. + # + class IPAddress::IPv6::Mapped < IPAddress::IPv6 + + # Access the internal IPv4 address + attr_reader :ipv4 + + # + # Creates a new IPv6 IPv4-mapped address + # + # ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128" + # + # ipv6.ipv4.class + # #=> IPAddress::IPv4 + # + # An IPv6 IPv4-mapped address can also be created using the + # IPv6 only format of the address: + # + # ip6 = IPAddress::IPv6::Mapped.new "::0d01:4403" + # + # ip6.to_s + # #=> "::ffff:13.1.68.3" + # + def initialize(str) + string, netmask = str.split("/") + if string =~ /\./ # IPv4 in dotted decimal form + @ipv4 = IPAddress::IPv4.extract(string) + else # IPv4 in hex form + groups = IPAddress::IPv6.groups(string) + @ipv4 = IPAddress::IPv4.parse_u32((groups[-2]<< 16)+groups[-1]) + end + super("::ffff:#{@ipv4.to_ipv6}/#{netmask}") + end + + # + # Similar to IPv6#to_s, but prints out the IPv4 address + # in dotted decimal format + # + # ip6 = IPAddress "::ffff:172.16.10.1/128" + # + # ip6.to_s + # #=> "::ffff:172.16.10.1" + # + def to_s + "::ffff:#{@ipv4.address}" + end + + # + # Similar to IPv6#to_string, but prints out the IPv4 address + # in dotted decimal format + # + # + # ip6 = IPAddress "::ffff:172.16.10.1/128" + # + # ip6.to_string + # #=> "::ffff:172.16.10.1/128" + # + def to_string + "::ffff:#{@ipv4.address}/#@prefix" + end + + # + # Checks if the IPv6 address is IPv4 mapped + # + # ip6 = IPAddress "::ffff:172.16.10.1/128" + # + # ip6.mapped? + # #=> true + # + def mapped? + true + end + end # class IPv6::Mapped + +end # module IPAddress + diff --git a/lib/ipaddress/extensions/extensions.rb b/lib/ipaddress/extensions/extensions.rb deleted file mode 100644 index 4e0bc88..0000000 --- a/lib/ipaddress/extensions/extensions.rb +++ /dev/null @@ -1,22 +0,0 @@ -class << Math # :nodoc: - def log2(n); log(n) / log(2); end -end - -if RUBY_VERSION =~ /1\.8/ - class Hash # :nodoc: - alias :key :index - end -end - -class Integer # :nodoc: - def power_of_2? - Math::log2(self).to_i == Math::log2(self) - end - - def closest_power_of_2(limit=32) - self.upto(limit) do |i| - return i if i.power_of_2? - end - end -end - diff --git a/lib/ipaddress/ipv4.rb b/lib/ipaddress/ipv4.rb deleted file mode 100644 index 2a0ca65..0000000 --- a/lib/ipaddress/ipv4.rb +++ /dev/null @@ -1,996 +0,0 @@ -require 'ipaddress/prefix' - -module IPAddress; - # - # =Name - # - # IPAddress::IPv4 - IP version 4 address manipulation library - # - # =Synopsis - # - # require 'ipaddress' - # - # =Description - # - # Class IPAddress::IPv4 is used to handle IPv4 type addresses. - # - class IPv4 - - include IPAddress - include Enumerable - include Comparable - - # - # This Hash contains the prefix values for Classful networks - # - # Note that classes C, D and E will all have a default - # prefix of /24 or 255.255.255.0 - # - CLASSFUL = { - /^0../ => 8, # Class A, from 0.0.0.0 to 127.255.255.255 - /^10./ => 16, # Class B, from 128.0.0.0 to 191.255.255.255 - /^110/ => 24 # Class C, D and E, from 192.0.0.0 to 255.255.255.254 - } - - # - # Regular expression to match an IPv4 address - # - REGEXP = Regexp.new(/((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)/) - - # - # Creates a new IPv4 address object. - # - # An IPv4 address can be expressed in any of the following forms: - # - # * "10.1.1.1/24": ip +address+ and +prefix+. This is the common and - # suggested way to create an object . - # * "10.1.1.1/255.255.255.0": ip +address+ and +netmask+. Although - # convenient sometimes, this format is less clear than the previous - # one. - # * "10.1.1.1": if the address alone is specified, the prefix will be - # set as default 32, also known as the host prefix - # - # Examples: - # - # # These two are the same - # ip = IPAddress::IPv4.new("10.0.0.1/24") - # ip = IPAddress("10.0.0.1/24") - # - # # These two are the same - # IPAddress::IPv4.new "10.0.0.1/8" - # IPAddress::IPv4.new "10.0.0.1/255.0.0.0" - # - def initialize(str) - ip, netmask = str.split("/") - - # Check the ip and remove white space - if IPAddress.valid_ipv4?(ip) - @address = ip.strip - else - raise ArgumentError, "Invalid IP #{ip.inspect}" - end - - # Check the netmask - if netmask # netmask is defined - netmask.strip! - if netmask =~ /^\d{1,2}$/ # netmask in cidr format - @prefix = Prefix32.new(netmask.to_i) - elsif IPAddress.valid_ipv4_netmask?(netmask) # netmask in IP format - @prefix = Prefix32.parse_netmask(netmask) - else # invalid netmask - raise ArgumentError, "Invalid netmask #{netmask}" - end - else # netmask is nil, reverting to defaul classful mask - @prefix = Prefix32.new(32) - end - - # Array formed with the IP octets - @octets = @address.split(".").map{|i| i.to_i} - # 32 bits interger containing the address - @u32 = (@octets[0]<< 24) + (@octets[1]<< 16) + (@octets[2]<< 8) + (@octets[3]) - - end # def initialize - - # - # Returns the address portion of the IPv4 object - # as a string. - # - # ip = IPAddress("172.16.100.4/22") - # - # ip.address - # #=> "172.16.100.4" - # - def address - @address - end - - # - # Returns the prefix portion of the IPv4 object - # as a IPAddress::Prefix32 object - # - # ip = IPAddress("172.16.100.4/22") - # - # ip.prefix - # #=> 22 - # - # ip.prefix.class - # #=> IPAddress::Prefix32 - # - def prefix - @prefix - end - - # - # Set a new prefix number for the object - # - # This is useful if you want to change the prefix - # to an object created with IPv4::parse_u32 or - # if the object was created using the classful - # mask. - # - # ip = IPAddress("172.16.100.4") - # - # puts ip - # #=> 172.16.100.4/16 - # - # ip.prefix = 22 - # - # puts ip - # #=> 172.16.100.4/22 - # - def prefix=(num) - @prefix = Prefix32.new(num) - end - - # - # Returns the address as an array of decimal values - # - # ip = IPAddress("172.16.100.4") - # - # ip.octets - # #=> [172, 16, 100, 4] - # - def octets - @octets - end - - # - # Returns a string with the address portion of - # the IPv4 object - # - # ip = IPAddress("172.16.100.4/22") - # - # ip.to_s - # #=> "172.16.100.4" - # - def to_s - @address - end - - # - # Returns a string with the IP address in canonical - # form. - # - # ip = IPAddress("172.16.100.4/22") - # - # ip.to_string - # #=> "172.16.100.4/22" - # - def to_string - "#@address/#@prefix" - end - - # - # Returns the prefix as a string in IP format - # - # ip = IPAddress("172.16.100.4/22") - # - # ip.netmask - # #=> "255.255.252.0" - # - def netmask - @prefix.to_ip - end - - # - # Like IPv4#prefix=, this method allow you to - # change the prefix / netmask of an IP address - # object. - # - # ip = IPAddress("172.16.100.4") - # - # puts ip - # #=> 172.16.100.4/16 - # - # ip.netmask = "255.255.252.0" - # - # puts ip - # #=> 172.16.100.4/22 - # - def netmask=(addr) - @prefix = Prefix32.parse_netmask(addr) - end - - # - # Returns the address portion in unsigned - # 32 bits integer format. - # - # This method is identical to the C function - # inet_pton to create a 32 bits address family - # structure. - # - # ip = IPAddress("10.0.0.0/8") - # - # ip.to_i - # #=> 167772160 - # - def u32 - @u32 - end - alias_method :to_i, :u32 - alias_method :to_u32, :u32 - - # - # Returns the address portion of an IPv4 object - # in a network byte order format. - # - # ip = IPAddress("172.16.10.1/24") - # - # ip.data - # #=> "\254\020\n\001" - # - # It is usually used to include an IP address - # in a data packet to be sent over a socket - # - # a = Socket.open(params) # socket details here - # ip = IPAddress("10.1.1.0/24") - # binary_data = ["Address: "].pack("a*") + ip.data - # - # # Send binary data - # a.puts binary_data - # - def data - [@u32].pack("N") - end - - # - # Returns the octet specified by index - # - # ip = IPAddress("172.16.100.50/24") - # - # ip[0] - # #=> 172 - # ip[1] - # #=> 16 - # ip[2] - # #=> 100 - # ip[3] - # #=> 50 - # - def [](index) - @octets[index] - end - alias_method :octet, :[] - - # - # Returns the address portion of an IP in binary format, - # as a string containing a sequence of 0 and 1 - # - # ip = IPAddress("127.0.0.1") - # - # ip.bits - # #=> "01111111000000000000000000000001" - # - def bits - data.unpack("B*").first - end - - # - # Returns the broadcast address for the given IP. - # - # ip = IPAddress("172.16.10.64/24") - # - # ip.broadcast.to_s - # #=> "172.16.10.255" - # - def broadcast - self.class.parse_u32(broadcast_u32, @prefix) - end - - # - # Checks if the IP address is actually a network - # - # ip = IPAddress("172.16.10.64/24") - # - # ip.network? - # #=> false - # - # ip = IPAddress("172.16.10.64/26") - # - # ip.network? - # #=> true - # - def network? - @u32 | @prefix.to_u32 == @prefix.to_u32 - end - - # - # Returns a new IPv4 object with the network number - # for the given IP. - # - # ip = IPAddress("172.16.10.64/24") - # - # ip.network.to_s - # #=> "172.16.10.0" - # - def network - self.class.parse_u32(network_u32, @prefix) - end - - # - # Returns a new IPv4 object with the - # first host IP address in the range. - # - # Example: given the 192.168.100.0/24 network, the first - # host IP address is 192.168.100.1. - # - # ip = IPAddress("192.168.100.0/24") - # - # ip.first.to_s - # #=> "192.168.100.1" - # - # The object IP doesn't need to be a network: the method - # automatically gets the network number from it - # - # ip = IPAddress("192.168.100.50/24") - # - # ip.first.to_s - # #=> "192.168.100.1" - # - def first - self.class.parse_u32(network_u32+1, @prefix) - end - - # - # Like its sibling method IPv4#first, this method - # returns a new IPv4 object with the - # last host IP address in the range. - # - # Example: given the 192.168.100.0/24 network, the last - # host IP address is 192.168.100.254 - # - # ip = IPAddress("192.168.100.0/24") - # - # ip.last.to_s - # #=> "192.168.100.254" - # - # The object IP doesn't need to be a network: the method - # automatically gets the network number from it - # - # ip = IPAddress("192.168.100.50/24") - # - # ip.last.to_s - # #=> "192.168.100.254" - # - def last - self.class.parse_u32(broadcast_u32-1, @prefix) - end - - # - # Iterates over all the hosts IP addresses for the given - # network (or IP address). - # - # ip = IPAddress("10.0.0.1/29") - # - # ip.each_host do |i| - # p i.to_s - # end - # #=> "10.0.0.1" - # #=> "10.0.0.2" - # #=> "10.0.0.3" - # #=> "10.0.0.4" - # #=> "10.0.0.5" - # #=> "10.0.0.6" - # - def each_host - (network_u32+1..broadcast_u32-1).each do |i| - yield self.class.parse_u32(i, @prefix) - end - end - - # - # Iterates over all the IP addresses for the given - # network (or IP address). - # - # The object yielded is a new IPv4 object created - # from the iteration. - # - # ip = IPAddress("10.0.0.1/29") - # - # ip.each do |i| - # p i.address - # end - # #=> "10.0.0.0" - # #=> "10.0.0.1" - # #=> "10.0.0.2" - # #=> "10.0.0.3" - # #=> "10.0.0.4" - # #=> "10.0.0.5" - # #=> "10.0.0.6" - # #=> "10.0.0.7" - # - def each - (network_u32..broadcast_u32).each do |i| - yield self.class.parse_u32(i, @prefix) - end - end - - # - # Spaceship operator to compare IP addresses - # - # An IP address is considered to be minor if it - # has a greater prefix (thus smaller hosts - # portion) and a smaller u32 value. - # - # For example, "10.100.100.1/8" is smaller than - # "172.16.0.1/16", but it's bigger than "10.100.100.1/16". - # - # Example: - # - # ip1 = IPAddress "10.100.100.1/8" - # ip2 = IPAddress "172.16.0.1/16" - # ip3 = IPAddress "10.100.100.1/16" - # - # ip1 < ip2 - # #=> true - # ip1 < ip3 - # #=> false - # - def <=>(oth) - if to_u32 > oth.to_u32 - return 1 - elsif to_u32 < oth.to_u32 - return -1 - else - if prefix < oth.prefix - return 1 - elsif prefix > oth.prefix - return -1 - end - end - return 0 - end - - # - # Returns the number of IP addresses included - # in the network. It also counts the network - # address and the broadcast address. - # - # ip = IPAddress("10.0.0.1/29") - # - # ip.size - # #=> 8 - # - def size - 2 ** @prefix.host_prefix - end - - # - # Returns an array with the IP addresses of - # all the hosts in the network. - # - # ip = IPAddress("10.0.0.1/29") - # - # ip.hosts.map {|i| i.address} - # #=> ["10.0.0.1", - # #=> "10.0.0.2", - # #=> "10.0.0.3", - # #=> "10.0.0.4", - # #=> "10.0.0.5", - # #=> "10.0.0.6"] - # - def hosts - to_a[1..-2] - end - - # - # Returns the network number in Unsigned 32bits format - # - # ip = IPAddress("10.0.0.1/29") - # - # ip.network_u32 - # #=> 167772160 - # - def network_u32 - @u32 & @prefix.to_u32 - end - - # - # Returns the broadcast address in Unsigned 32bits format - # - # ip = IPaddress("10.0.0.1/29") - # - # ip.broadcast_u32 - # #=> 167772167 - # - def broadcast_u32 - network_u32 + size - 1 - end - - # - # Checks whether a subnet includes the given IP address. - # - # Accepts an IPAddress::IPv4 object. - # - # ip = IPAddress("192.168.10.100/24") - # - # addr = IPAddress("192.168.10.102/24") - # - # ip.include? addr - # #=> true - # - # ip.include? IPAddress("172.16.0.48/16") - # #=> false - # - def include?(oth) - @prefix <= oth.prefix and network_u32 == (oth.to_u32 & @prefix.to_u32) - end - - # - # Checks whether a subnet includes all the - # given IPv4 objects. - # - # ip = IPAddress("192.168.10.100/24") - # - # addr1 = IPAddress("192.168.10.102/24") - # addr2 = IPAddress("192.168.10.103/24") - # - # ip.include_all?(addr1,addr2) - # #=> true - # - def include_all?(*others) - others.all? {|oth| include?(oth)} - end - - # - # True if the object is an IPv4 address - # - # ip = IPAddress("192.168.10.100/24") - # - # ip.ipv4? - # #-> true - # -# def ipv4? -# true -# end - - # - # True if the object is an IPv6 address - # - # ip = IPAddress("192.168.10.100/24") - # - # ip.ipv6? - # #-> false - # -# def ipv6? -# false -# end - - # - # Checks if an IPv4 address objects belongs - # to a private network RFC1918 - # - # Example: - # - # ip = IPAddress "10.1.1.1/24" - # ip.private? - # #=> true - # - def private? - [self.class.new("10.0.0.0/8"), - self.class.new("172.16.0.0/12"), - self.class.new("192.168.0.0/16")].any? {|i| i.include? self} - end - - # - # Returns the IP address in in-addr.arpa format - # for DNS lookups - # - # ip = IPAddress("172.16.100.50/24") - # - # ip.reverse - # #=> "50.100.16.172.in-addr.arpa" - # - def reverse - @octets.reverse.join(".") + ".in-addr.arpa" - end - alias_method :arpa, :reverse - - # - # Subnetting a network - # - # If the IP Address is a network, it can be divided into - # multiple networks. If +self+ is not a network, the - # method will calculate the network from the IP and then - # subnet it. - # - # If +subnets+ is an power of two number, the resulting - # networks will be divided evenly from the supernet. - # - # network = IPAddress("172.16.10.0/24") - # - # network / 4 # implies map{|i| i.to_string} - # #=> ["172.16.10.0/26", - # "172.16.10.64/26", - # "172.16.10.128/26", - # "172.16.10.192/26"] - # - # If +num+ is any other number, the supernet will be - # divided into some networks with a even number of hosts and - # other networks with the remaining addresses. - # - # network = IPAddress("172.16.10.0/24") - # - # network / 3 # implies map{|i| i.to_string} - # #=> ["172.16.10.0/26", - # "172.16.10.64/26", - # "172.16.10.128/25"] - # - # Returns an array of IPAddress objects - # - def subnet(subnets=2) - unless (1..(2**@prefix.host_prefix)).include? subnets - raise ArgumentError, "Value #{subnets} out of range" - end - calculate_subnets(subnets) - end - alias_method :/, :subnet - - # - # Returns a new IPv4 object from the supernetting - # of the instance network. - # - # Supernetting is similar to subnetting, except - # that you getting as a result a network with a - # smaller prefix (bigger host space). For example, - # given the network - # - # ip = IPAddress("172.16.10.0/24") - # - # you can supernet it with a new /23 prefix - # - # ip.supernet(23).to_string - # #=> "172.16.10.0/23" - # - # However if you supernet it with a /22 prefix, the - # network address will change: - # - # ip.supernet(22).to_string - # #=> "172.16.8.0/22" - # - def supernet(new_prefix) - raise ArgumentError, "Can't supernet a /1 network" if new_prefix < 1 - raise ArgumentError, "New prefix must be smaller than existing prefix" if new_prefix >= @prefix.to_i - self.class.new(@address+"/#{new_prefix}").network - end - - # - # Returns the difference between two IP addresses - # in unsigned int 32 bits format - # - # Example: - # - # ip1 = IPAddress("172.16.10.0/24") - # ip2 = IPAddress("172.16.11.0/24") - # - # puts ip1 - ip2 - # #=> 256 - # - def -(oth) - return (to_u32 - oth.to_u32).abs - end - - # - # Returns a new IPv4 object which is the result - # of the summarization, if possible, of the two - # objects - # - # Example: - # - # ip1 = IPAddress("172.16.10.1/24") - # ip2 = IPAddress("172.16.11.2/24") - # - # p (ip1 + ip2).map {|i| i.to_string} - # #=> ["172.16.10.0/23"] - # - # If the networks are not contiguous, returns - # the two network numbers from the objects - # - # ip1 = IPAddress("10.0.0.1/24") - # ip2 = IPAddress("10.0.2.1/24") - # - # p (ip1 + ip2).map {|i| i.to_string} - # #=> ["10.0.0.0/24","10.0.2.0/24"] - # - def +(oth) - aggregate(*[self,oth].sort.map{|i| i.network}) - end - - # - # Checks whether the ip address belongs to a - # RFC 791 CLASS A network, no matter - # what the subnet mask is. - # - # Example: - # - # ip = IPAddress("10.0.0.1/24") - # - # ip.a? - # #=> true - # - def a? - CLASSFUL.key(8) === bits - end - - # - # Checks whether the ip address belongs to a - # RFC 791 CLASS B network, no matter - # what the subnet mask is. - # - # Example: - # - # ip = IPAddress("172.16.10.1/24") - # - # ip.b? - # #=> true - # - def b? - CLASSFUL.key(16) === bits - end - - # - # Checks whether the ip address belongs to a - # RFC 791 CLASS C network, no matter - # what the subnet mask is. - # - # Example: - # - # ip = IPAddress("192.168.1.1/30") - # - # ip.c? - # #=> true - # - def c? - CLASSFUL.key(24) === bits - end - - # - # Return the ip address in a format compatible - # with the IPv6 Mapped IPv4 addresses - # - # Example: - # - # ip = IPAddress("172.16.10.1/24") - # - # ip.to_ipv6 - # #=> "ac10:0a01" - # - def to_ipv6 - "%.4x:%.4x" % [to_u32].pack("N").unpack("nn") - end - - # - # Creates a new IPv4 object from an - # unsigned 32bits integer. - # - # ip = IPAddress::IPv4::parse_u32(167772160) - # - # ip.prefix = 8 - # ip.to_string - # #=> "10.0.0.0/8" - # - # The +prefix+ parameter is optional: - # - # ip = IPAddress::IPv4::parse_u32(167772160, 8) - # - # ip.to_string - # #=> "10.0.0.0/8" - # - def self.parse_u32(u32, prefix=32) - self.new([u32].pack("N").unpack("C4").join(".")+"/#{prefix}") - end - - # - # Creates a new IPv4 object from binary data, - # like the one you get from a network stream. - # - # For example, on a network stream the IP 172.16.0.1 - # is represented with the binary "\254\020\n\001". - # - # ip = IPAddress::IPv4::parse_data "\254\020\n\001" - # ip.prefix = 24 - # - # ip.to_string - # #=> "172.16.10.1/24" - # - def self.parse_data(str, prefix=32) - self.new(str.unpack("C4").join(".")+"/#{prefix}") - end - - # - # Extract an IPv4 address from a string and - # returns a new object - # - # Example: - # - # str = "foobar172.16.10.1barbaz" - # ip = IPAddress::IPv4::extract str - # - # ip.to_s - # #=> "172.16.10.1" - # - def self.extract(str) - self.new REGEXP.match(str).to_s - end - - # - # Summarization (or aggregation) is the process when two or more - # networks are taken together to check if a supernet, including all - # and only these networks, exists. If it exists then this supernet - # is called the summarized (or aggregated) network. - # - # It is very important to understand that summarization can only - # occur if there are no holes in the aggregated network, or, in other - # words, if the given networks fill completely the address space - # of the supernet. So the two rules are: - # - # 1) The aggregate network must contain +all+ the IP addresses of the - # original networks; - # 2) The aggregate network must contain +only+ the IP addresses of the - # original networks; - # - # A few examples will help clarify the above. Let's consider for - # instance the following two networks: - # - # ip1 = IPAddress("172.16.10.0/24") - # ip2 = IPAddress("172.16.11.0/24") - # - # These two networks can be expressed using only one IP address - # network if we change the prefix. Let Ruby do the work: - # - # IPAddress::IPv4::summarize(ip1,ip2).to_s - # #=> "172.16.10.0/23" - # - # We note how the network "172.16.10.0/23" includes all the addresses - # specified in the above networks, and (more important) includes - # ONLY those addresses. - # - # If we summarized +ip1+ and +ip2+ with the following network: - # - # "172.16.0.0/16" - # - # we would have satisfied rule #1 above, but not rule #2. So "172.16.0.0/16" - # is not an aggregate network for +ip1+ and +ip2+. - # - # If it's not possible to compute a single aggregated network for all the - # original networks, the method returns an array with all the aggregate - # networks found. For example, the following four networks can be - # aggregated in a single /22: - # - # ip1 = IPAddress("10.0.0.1/24") - # ip2 = IPAddress("10.0.1.1/24") - # ip3 = IPAddress("10.0.2.1/24") - # ip4 = IPAddress("10.0.3.1/24") - # - # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).to_string - # #=> "10.0.0.0/22", - # - # But the following networks can't be summarized in a single network: - # - # ip1 = IPAddress("10.0.1.1/24") - # ip2 = IPAddress("10.0.2.1/24") - # ip3 = IPAddress("10.0.3.1/24") - # ip4 = IPAddress("10.0.4.1/24") - # - # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} - # #=> ["10.0.1.0/24","10.0.2.0/23","10.0.4.0/24"] - # - def self.summarize(*args) - # one network? no need to summarize - return [args.first.network] if args.size == 1 - - i = 0 - result = args.dup.sort.map{|ip| ip.network} - while i < result.size-1 - sum = result[i] + result[i+1] - result[i..i+1] = sum.first if sum.size == 1 - i += 1 - end - - result.flatten! - if result.size == args.size - # nothing more to summarize - return result - else - # keep on summarizing - return self.summarize(*result) - end - end - - # - # Creates a new IPv4 address object by parsing the - # address in a classful way. - # - # Classful addresses have a fixed netmask based on the - # class they belong to: - # - # * Class A, from 0.0.0.0 to 127.255.255.255 - # * Class B, from 128.0.0.0 to 191.255.255.255 - # * Class C, D and E, from 192.0.0.0 to 255.255.255.254 - # - # Note that classes C, D and E will all have a default - # prefix of /24 or 255.255.255.0 - # - # Example: - # - # ip = IPAddress::IPv4.parse_classful "10.0.0.1" - # - # ip.netmask - # #=> "255.0.0.0" - # ip.a? - # #=> true - # - def self.parse_classful(ip) - if IPAddress.valid_ipv4?(ip) - address = ip.strip - else - raise ArgumentError, "Invalid IP #{ip.inspect}" - end - prefix = CLASSFUL.find{|h,k| h === ("%.8b" % address.to_i)}.last - self.new "#{address}/#{prefix}" - end - - # - # private methods - # - private - - def calculate_subnets(subnets) - po2 = subnets.closest_power_of_2 - new_prefix = @prefix + Math::log2(po2).to_i - networks = Array.new - (0..po2-1).each do |i| - mul = i * (2**(32-new_prefix)) - networks << IPAddress::IPv4.parse_u32(network_u32+mul, new_prefix) - end - until networks.size == subnets - networks = sum_first_found(networks) - end - return networks - end - - def sum_first_found(arr) - dup = arr.dup.reverse - dup.each_with_index do |obj,i| - a = [IPAddress::IPv4.summarize(obj,dup[i+1])].flatten - if a.size == 1 - dup[i..i+1] = a - return dup.reverse - end - end - return dup.reverse - end - - def aggregate(ip1,ip2) - return [ip1] if ip1.include? ip2 - - snet = ip1.supernet(ip1.prefix-1) - if snet.include_all?(ip1, ip2) && ((ip1.size + ip2.size) == snet.size) - return [snet] - else - return [ip1, ip2] - end - end - end # class IPv4 -end # module IPAddress - diff --git a/lib/ipaddress/ipv6.rb b/lib/ipaddress/ipv6.rb deleted file mode 100644 index 78ebe3d..0000000 --- a/lib/ipaddress/ipv6.rb +++ /dev/null @@ -1,776 +0,0 @@ -require 'ipaddress/prefix' - -module IPAddress; - # - # =Name - # - # IPAddress::IPv6 - IP version 6 address manipulation library - # - # =Synopsis - # - # require 'ipaddress' - # - # =Description - # - # Class IPAddress::IPv6 is used to handle IPv6 type addresses. - # - # == IPv6 addresses - # - # IPv6 addresses are 128 bits long, in contrast with IPv4 addresses - # which are only 32 bits long. An IPv6 address is generally written as - # eight groups of four hexadecimal digits, each group representing 16 - # bits or two octect. For example, the following is a valid IPv6 - # address: - # - # 1080:0000:0000:0000:0008:0800:200c:417a - # - # Letters in an IPv6 address are usually written downcase, as per - # RFC. You can create a new IPv6 object using uppercase letters, but - # they will be converted. - # - # === Compression - # - # Since IPv6 addresses are very long to write, there are some - # semplifications and compressions that you can use to shorten them. - # - # * Leading zeroes: all the leading zeroes within a group can be - # omitted: "0008" would become "8" - # - # * A string of consecutive zeroes can be replaced by the string - # "::". This can be only applied once. - # - # Using compression, the IPv6 address written above can be shorten into - # the following, equivalent, address - # - # 1080::8:800:200c:417a - # - # This short version is often used in human representation. - # - # === Network Mask - # - # As we used to do with IPv4 addresses, an IPv6 address can be written - # using the prefix notation to specify the subnet mask: - # - # 1080::8:800:200c:417a/64 - # - # The /64 part means that the first 64 bits of the address are - # representing the network portion, and the last 64 bits are the host - # portion. - # - # - class IPv6 - - include IPAddress - include Enumerable - include Comparable - - - # - # Format string to pretty print IPv6 addresses - # - IN6FORMAT = ("%.4x:"*8).chop - - # - # Creates a new IPv6 address object. - # - # An IPv6 address can be expressed in any of the following forms: - # - # * "1080:0000:0000:0000:0008:0800:200C:417A": IPv6 address with no compression - # * "1080:0:0:0:8:800:200C:417A": IPv6 address with leading zeros compression - # * "1080::8:800:200C:417A": IPv6 address with full compression - # - # In all these 3 cases, a new IPv6 address object will be created, using the default - # subnet mask /128 - # - # You can also specify the subnet mask as with IPv4 addresses: - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - def initialize(str) - ip, netmask = str.split("/") - - if str =~ /:.+\./ - raise ArgumentError, "Please use #{self.class}::Mapped for IPv4 mapped addresses" - end - - if IPAddress.valid_ipv6?(ip) - @groups = self.class.groups(ip) - @address = IN6FORMAT % @groups - @compressed = compress_address - else - raise ArgumentError, "Invalid IP #{ip.inspect}" - end - - @prefix = Prefix128.new(netmask ? netmask : 128) - - end # def initialize - - # - # Returns the IPv6 address in uncompressed form: - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.address - # #=> "2001:0db8:0000:0000:0008:0800:200c:417a" - # - def address - @address - end - - # - # Returns an array with the 16 bits groups in decimal - # format: - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.groups - # #=> [8193, 3512, 0, 0, 8, 2048, 8204, 16762] - # - def groups - @groups - end - - # - # Returns an instance of the prefix object - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.prefix - # #=> 64 - # - def prefix - @prefix - end - - # - # Set a new prefix number for the object - # - # This is useful if you want to change the prefix - # to an object created with IPv6::parse_u128 or - # if the object was created using the default prefix - # of 128 bits. - # - # ip6 = IPAddress("2001:db8::8:800:200c:417a") - # - # puts ip6.to_string - # #=> "2001:db8::8:800:200c:417a/128" - # - # ip6.prefix = 64 - # puts ip6.to_string - # #=> "2001:db8::8:800:200c:417a/64" - # - def prefix=(num) - @prefix = Prefix128.new(num) - end - - # - # Unlike its counterpart IPv6#to_string method, IPv6#to_string_uncompressed - # returns the whole IPv6 address and prefix in an uncompressed form - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.to_string_uncompressed - # #=> "2001:0db8:0000:0000:0008:0800:200c:417a/64" - # - def to_string_uncompressed - "#@address/#@prefix" - end - - # - # Returns the IPv6 address in a human readable form, - # using the compressed address. - # - # ip6 = IPAddress "2001:0db8:0000:0000:0008:0800:200c:417a/64" - # - # ip6.to_string - # #=> "2001:db8::8:800:200c:417a/64" - # - def to_string - "#@compressed/#@prefix" - end - - # - # Returns the IPv6 address in a human readable form, - # using the compressed address. - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.to_s - # #=> "2001:db8::8:800:200c:417a" - # - def to_s - @compressed - end - - # - # Returns a decimal format (unsigned 128 bit) of the - # IPv6 address - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.to_i - # #=> 42540766411282592856906245548098208122 - # - def to_i - to_hex.hex - end - alias_method :to_u128, :to_i - - # - # True if the IPv6 address is a network - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.network? - # #=> false - # - # ip6 = IPAddress "2001:db8:8:800::/64" - # - # ip6.network? - # #=> true - # - def network? - to_u128 | @prefix.to_u128 == @prefix.to_u128 - end - - # - # Returns the 16-bits value specified by index - # - # ip = IPAddress("2001:db8::8:800:200c:417a/64") - # - # ip[0] - # #=> 8193 - # ip[1] - # #=> 3512 - # ip[2] - # #=> 0 - # ip[3] - # #=> 0 - # - def [](index) - @groups[index] - end - alias_method :group, :[] - - # - # Returns a Base16 number representing the IPv6 - # address - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.to_hex - # #=> "20010db80000000000080800200c417a" - # - def to_hex - hexs.join("") - end - - # Returns the address portion of an IPv6 object - # in a network byte order format. - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.data - # #=> " \001\r\270\000\000\000\000\000\b\b\000 \fAz" - # - # It is usually used to include an IP address - # in a data packet to be sent over a socket - # - # a = Socket.open(params) # socket details here - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # binary_data = ["Address: "].pack("a*") + ip.data - # - # # Send binary data - # a.puts binary_data - # - def data - @groups.pack("n8") - end - - # - # Returns an array of the 16 bits groups in hexdecimal - # format: - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.hexs - # #=> ["2001", "0db8", "0000", "0000", "0008", "0800", "200c", "417a"] - # - # Not to be confused with the similar IPv6#to_hex method. - # - def hexs - @address.split(":") - end - - # - # Returns the IPv6 address in a DNS reverse lookup - # string, as per RFC3172 and RFC2874. - # - # ip6 = IPAddress "3ffe:505:2::f" - # - # ip6.reverse - # #=> "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa" - # - def reverse - to_hex.reverse.gsub(/./){|c| c+"."} + "ip6.arpa" - end - alias_method :arpa, :reverse - - # - # Returns the network number in Unsigned 128bits format - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.network_u128 - # #=> 42540766411282592856903984951653826560 - # - def network_u128 - to_u128 & @prefix.to_u128 - end - - # - # Checks whether a subnet includes the given IP address. - # - # Example: - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # addr = IPAddress "2001:db8::8:800:200c:1/128" - # - # ip6.include? addr - # #=> true - # - # ip6.include? IPAddress("2001:db8:1::8:800:200c:417a/76") - # #=> false - # - def include?(oth) - @prefix <= oth.prefix and network_u128 == self.class.new(oth.address+"/#@prefix").network_u128 - end - - # - # Compressed form of the IPv6 address - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.compressed - # #=> "2001:db8::8:800:200c:417a" - # - def compressed - @compressed - end - - # - # Returns true if the address is an unspecified address - # - # See IPAddress::IPv6::Unspecified for more information - # - def unspecified? - @prefix == 128 and @compressed == "::" - end - - # - # Returns true if the address is a loopback address - # - # See IPAddress::IPv6::Loopback for more information - # - def loopback? - @prefix == 128 and @compressed == "::1" - end - - # - # Returns true if the address is a mapped address - # - # See IPAddress::IPv6::Mapped for more information - # - def mapped? - to_u128 >> 32 == 0xffff - end - - # - # Returns the address portion of an IP in binary format, - # as a string containing a sequence of 0 and 1 - # - # ip6 = IPAddress("2001:db8::8:800:200c:417a") - # - # ip6.bits - # #=> "0010000000000001000011011011100000 [...] " - # - def bits - data.unpack("B*").first - end - - # - # Expands an IPv6 address in the canocical form - # - # IPAddress::IPv6.expand "2001:0DB8:0:CD30::" - # #=> "2001:0DB8:0000:CD30:0000:0000:0000:0000" - # - def self.expand(str) - self.new(str).address - end - - # - # Compress an IPv6 address in its compressed form - # - # IPAddress::IPv6.compress "2001:0DB8:0000:CD30:0000:0000:0000:0000" - # #=> "2001:db8:0:cd30::" - # - def self.compress(str) - self.new(str).compressed - end - - # - # Literal version of the IPv6 address - # - # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" - # - # ip6.literal - # #=> "2001-0db8-0000-0000-0008-0800-200c-417a.ipv6-literal.net" - # - def literal - @address.gsub(":","-") + ".ipv6-literal.net" - end - - # - # Extract 16 bits groups from a string - # - def self.groups(str) - l, r = if str =~ /^(.*)::(.*)$/ - [$1,$2].map {|i| i.split ":"} - else - [str.split(":"),[]] - end - (l + Array.new(8-l.size-r.size, '0') + r).map {|i| i.hex} - end - - # - # Creates a new IPv6 object from binary data, - # like the one you get from a network stream. - # - # For example, on a network stream the IP - # - # "2001:db8::8:800:200c:417a" - # - # is represented with the binary data - # - # " \001\r\270\000\000\000\000\000\b\b\000 \fAz" - # - # With that data you can create a new IPv6 object: - # - # ip6 = IPAddress::IPv6::parse_data " \001\r\270\000\000\000\000\000\b\b\000 \fAz" - # ip6.prefix = 64 - # - # ip6.to_s - # #=> "2001:db8::8:800:200c:417a/64" - # - def self.parse_data(str) - self.new(IN6FORMAT % str.unpack("n8")) - end - - # - # Creates a new IPv6 object from an - # unsigned 128 bits integer. - # - # ip6 = IPAddress::IPv6::parse_u128(21932261930451111902915077091070067066) - # ip6.prefix = 64 - # - # ip6.to_s - # #=> "1080::8:800:200c:417a/64" - # - # The +prefix+ parameter is optional: - # - # ip6 = IPAddress::IPv6::parse_u128(21932261930451111902915077091070067066, 64) - # - # ip6.to_s - # #=> "1080::8:800:200c:417a/64" - # - def self.parse_u128(u128, prefix=128) - str = IN6FORMAT % (0..7).map{|i| (u128>>(112-16*i))&0xffff} - self.new(str + "/#{prefix}") - end - - # - # Creates a new IPv6 object from a number expressed in - # hexdecimal format: - # - # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a") - # ip6.prefix = 64 - # - # ip6.to_s - # #=> "2001:db8::8:800:200c:417a/64" - # - # The +prefix+ parameter is optional: - # - # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a", 64) - # - # ip6.to_s - # #=> "1080::8:800:200c:417a/64" - # - def self.parse_hex(hex, prefix=128) - self.parse_u128(hex.hex, prefix) - end - - private - - def compress_address - str = @groups.map{|i| i.to_s 16}.join ":" - loop do - break if str.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::') - break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':') - break if str.sub!(/\b0:0:0:0:0:0\b/, ':') - break if str.sub!(/\b0:0:0:0:0\b/, ':') - break if str.sub!(/\b0:0:0:0\b/, ':') - break if str.sub!(/\b0:0:0\b/, ':') - break if str.sub!(/\b0:0\b/, ':') - break - end - str.sub(/:{3,}/, '::') - end - - end # class IPv6 - - # - # The address with all zero bits is called the +unspecified+ address - # (corresponding to 0.0.0.0 in IPv4). It should be something like this: - # - # 0000:0000:0000:0000:0000:0000:0000:0000 - # - # but, with the use of compression, it is usually written as just two - # colons: - # - # :: - # - # or, specifying the netmask: - # - # ::/128 - # - # With IPAddress, create a new unspecified IPv6 address using its own - # subclass: - # - # ip = IPAddress::IPv6::Unspecified.new - # - # ip.to_s - # #=> => "::/128" - # - # You can easily check if an IPv6 object is an unspecified address by - # using the IPv6#unspecified? method - # - # ip.unspecified? - # #=> true - # - # An unspecified IPv6 address can also be created with the wrapper - # method, like we've seen before - # - # ip = IPAddress "::" - # - # ip.unspecified? - # #=> true - # - # This address must never be assigned to an interface and is to be used - # only in software before the application has learned its host's source - # address appropriate for a pending connection. Routers must not forward - # packets with the unspecified address. - # - class IPAddress::IPv6::Unspecified < IPAddress::IPv6 - # - # Creates a new IPv6 unspecified address - # - # ip = IPAddress::IPv6::Unspecified.new - # - # ip.to_s - # #=> => "::/128" - # - def initialize - @address = ("0000:"*8).chop - @groups = Array.new(8,0) - @prefix = Prefix128.new(128) - @compressed = compress_address - end - end # class IPv6::Unspecified - - # - # The loopback address is a unicast localhost address. If an - # application in a host sends packets to this address, the IPv6 stack - # will loop these packets back on the same virtual interface. - # - # Loopback addresses are expressed in the following form: - # - # ::1 - # - # or, with their appropriate prefix, - # - # ::1/128 - # - # As for the unspecified addresses, IPv6 loopbacks can be created with - # IPAddress calling their own class: - # - # ip = IPAddress::IPv6::Loopback.new - # - # ip.to_s - # #=> "::1/128" - # - # or by using the wrapper: - # - # ip = IPAddress "::1" - # - # ip.to_s - # #=> "::1/128" - # - # Checking if an address is loopback is easy with the IPv6#loopback? - # method: - # - # ip.loopback? - # #=> true - # - # The IPv6 loopback address corresponds to 127.0.0.1 in IPv4. - # - class IPAddress::IPv6::Loopback < IPAddress::IPv6 - # - # Creates a new IPv6 unspecified address - # - # ip = IPAddress::IPv6::Loopback.new - # - # ip.to_s - # #=> "::1/128" - # - def initialize - @address = ("0000:"*7)+"0001" - @groups = Array.new(7,0).push(1) - @prefix = Prefix128.new(128) - @compressed = compress_address - end - end # class IPv6::Loopback - - # - # It is usually identified as a IPv4 mapped IPv6 address, a particular - # IPv6 address which aids the transition from IPv4 to IPv6. The - # structure of the address is - # - # ::ffff:w.y.x.z - # - # where w.x.y.z is a normal IPv4 address. For example, the following is - # a mapped IPv6 address: - # - # ::ffff:192.168.100.1 - # - # IPAddress is very powerful in handling mapped IPv6 addresses, as the - # IPv4 portion is stored internally as a normal IPv4 object. Let's have - # a look at some examples. To create a new mapped address, just use the - # class builder itself - # - # ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128" - # - # or just use the wrapper method - # - # ip6 = IPAddress "::ffff:172.16.10.1/128" - # - # Let's check it's really a mapped address: - # - # ip6.mapped? - # #=> true - # - # ip6.to_s - # #=> "::FFFF:172.16.10.1/128" - # - # Now with the +ipv4+ attribute, we can easily access the IPv4 portion - # of the mapped IPv6 address: - # - # ip6.ipv4.address - # #=> "172.16.10.1" - # - # Internally, the IPv4 address is stored as two 16 bits - # groups. Therefore all the usual methods for an IPv6 address are - # working perfectly fine: - # - # ip6.to_hex - # #=> "00000000000000000000ffffac100a01" - # - # ip6.address - # #=> "0000:0000:0000:0000:0000:ffff:ac10:0a01" - # - # A mapped IPv6 can also be created just by specify the address in the - # following format: - # - # ip6 = IPAddress "::172.16.10.1" - # - # That is, two colons and the IPv4 address. However, as by RFC, the ffff - # group will be automatically added at the beginning - # - # ip6.to_s - # => "::ffff:172.16.10.1/128" - # - # making it a mapped IPv6 compatible address. - # - class IPAddress::IPv6::Mapped < IPAddress::IPv6 - - # Access the internal IPv4 address - attr_reader :ipv4 - - # - # Creates a new IPv6 IPv4-mapped address - # - # ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128" - # - # ipv6.ipv4.class - # #=> IPAddress::IPv4 - # - # An IPv6 IPv4-mapped address can also be created using the - # IPv6 only format of the address: - # - # ip6 = IPAddress::IPv6::Mapped.new "::0d01:4403" - # - # ip6.to_s - # #=> "::ffff:13.1.68.3" - # - def initialize(str) - string, netmask = str.split("/") - if string =~ /\./ # IPv4 in dotted decimal form - @ipv4 = IPAddress::IPv4.extract(string) - else # IPv4 in hex form - groups = IPAddress::IPv6.groups(string) - @ipv4 = IPAddress::IPv4.parse_u32((groups[-2]<< 16)+groups[-1]) - end - super("::ffff:#{@ipv4.to_ipv6}/#{netmask}") - end - - # - # Similar to IPv6#to_s, but prints out the IPv4 address - # in dotted decimal format - # - # ip6 = IPAddress "::ffff:172.16.10.1/128" - # - # ip6.to_s - # #=> "::ffff:172.16.10.1" - # - def to_s - "::ffff:#{@ipv4.address}" - end - - # - # Similar to IPv6#to_string, but prints out the IPv4 address - # in dotted decimal format - # - # - # ip6 = IPAddress "::ffff:172.16.10.1/128" - # - # ip6.to_string - # #=> "::ffff:172.16.10.1/128" - # - def to_string - "::ffff:#{@ipv4.address}/#@prefix" - end - - # - # Checks if the IPv6 address is IPv4 mapped - # - # ip6 = IPAddress "::ffff:172.16.10.1/128" - # - # ip6.mapped? - # #=> true - # - def mapped? - true - end - end # class IPv6::Mapped - -end # module IPAddress - diff --git a/lib/ipaddress/prefix.rb b/lib/ipaddress/prefix.rb deleted file mode 100644 index aac2090..0000000 --- a/lib/ipaddress/prefix.rb +++ /dev/null @@ -1,252 +0,0 @@ -module IPAddress - - # - # =NAME - # - # IPAddress::Prefix - # - # =SYNOPSIS - # - # Parent class for Prefix32 and Prefix128 - # - # =DESCRIPTION - # - # IPAddress::Prefix is the parent class for IPAddress::Prefix32 - # and IPAddress::Prefix128, defining some modules in common for - # both the subclasses. - # - # IPAddress::Prefix shouldn't be accesses directly, unless - # for particular needs. - # - class Prefix - - include Comparable - - attr_reader :prefix - - # - # Creates a new general prefix - # - def initialize(num) - @prefix = num.to_i - end - - # - # Returns a string with the prefix - # - def to_s - "#@prefix" - end - alias_method :inspect, :to_s - - # - # Returns the prefix - # - def to_i - @prefix - end - - # - # Compare the prefix - # - def <=>(oth) - @prefix <=> oth.to_i - end - - # - # Sums two prefixes or a prefix to a - # number, returns a Fixnum - # - def +(oth) - if oth.is_a? Fixnum - self.prefix + oth - else - self.prefix + oth.prefix - end - end - - # - # Returns the difference between two - # prefixes, or a prefix and a number, - # as a Fixnum - # - def -(oth) - if oth.is_a? Fixnum - self.prefix - oth - else - (self.prefix - oth.prefix).abs - end - end - - end # class Prefix - - - class Prefix32 < Prefix - - IN4MASK = 0xffffffff - - # - # Creates a new prefix object for 32 bits IPv4 addresses - # - # prefix = IPAddress::Prefix32.new 24 - # #=> 24 - # - def initialize(num) - unless (0..32).include? num - raise ArgumentError, "Prefix must be in range 0..32, got: #{num}" - end - super(num) - end - - # - # Returns the length of the host portion - # of a netmask. - # - # prefix = Prefix32.new 24 - # - # prefix.host_prefix - # #=> 8 - # - def host_prefix - 32 - @prefix - end - - # - # Transforms the prefix into a string of bits - # representing the netmask - # - # prefix = IPAddress::Prefix32.new 24 - # - # prefix.bits - # #=> "11111111111111111111111100000000" - # - def bits - "%.32b" % to_u32 - end - - # - # Gives the prefix in IPv4 dotted decimal format, - # i.e. the canonical netmask we're all used to - # - # prefix = IPAddress::Prefix32.new 24 - # - # prefix.to_ip - # #=> "255.255.255.0" - # - def to_ip - [bits].pack("B*").unpack("CCCC").join(".") - end - - # - # An array of octets of the IPv4 dotted decimal - # format - # - # prefix = IPAddress::Prefix32.new 24 - # - # prefix.octets - # #=> [255, 255, 255, 0] - # - def octets - to_ip.split(".").map{|i| i.to_i} - end - - # - # Unsigned 32 bits decimal number representing - # the prefix - # - # prefix = IPAddress::Prefix32.new 24 - # - # prefix.to_u32 - # #=> 4294967040 - # - def to_u32 - (IN4MASK >> host_prefix) << host_prefix - end - - # - # Shortcut for the octecs in the dotted decimal - # representation - # - # prefix = IPAddress::Prefix32.new 24 - # - # prefix[2] - # #=> 255 - # - def [](index) - octets[index] - end - - # - # The hostmask is the contrary of the subnet mask, - # as it shows the bits that can change within the - # hosts - # - # prefix = IPAddress::Prefix32.new 24 - # - # prefix.hostmask - # #=> "0.0.0.255" - # - def hostmask - [~to_u32].pack("N").unpack("CCCC").join(".") - end - - # - # Creates a new prefix by parsing a netmask in - # dotted decimal form - # - # prefix = IPAddress::Prefix32::parse_netmask "255.255.255.0" - # #=> 24 - # - def self.parse_netmask(netmask) - octets = netmask.split(".").map{|i| i.to_i} - num = octets.pack("C"*octets.size).unpack("B*").first.count "1" - return self.new(num) - end - - end # class Prefix32 < Prefix - - class Prefix128 < Prefix - - # - # Creates a new prefix object for 128 bits IPv6 addresses - # - # prefix = IPAddress::Prefix128.new 64 - # #=> 64 - # - def initialize(num=128) - unless (1..128).include? num.to_i - raise ArgumentError, "Prefix must be in range 1..128, got: #{num}" - end - super(num.to_i) - end - - # - # Transforms the prefix into a string of bits - # representing the netmask - # - # prefix = IPAddress::Prefix128.new 64 - # - # prefix.bits - # #=> "1111111111111111111111111111111111111111111111111111111111111111" - # "0000000000000000000000000000000000000000000000000000000000000000" - # - def bits - "1" * @prefix + "0" * (128 - @prefix) - end - - # - # Unsigned 128 bits decimal number representing - # the prefix - # - # prefix = IPAddress::Prefix128.new 64 - # - # prefix.to_u128 - # #=> 340282366920938463444927863358058659840 - # - def to_u128 - bits.to_i(2) - end - - end # class Prefix123 < Prefix - -end # module IPAddress diff --git a/test/ipaddress/extensions/extensions_test.rb b/test/ipaddress/extensions/extensions_test.rb deleted file mode 100644 index 06b0a2c..0000000 --- a/test/ipaddress/extensions/extensions_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'test_helper' - -class ExtensionsTest < Test::Unit::TestCase - - def test_method_power_of_2? - assert_equal true, 16.power_of_2? - assert_equal false, 20.power_of_2? - end - - def test_method_closest_power_of_2 - assert_equal 8, 6.closest_power_of_2 - assert_equal 16, 13.closest_power_of_2 - assert_equal 32, 24.closest_power_of_2 - end - -end - - diff --git a/test/ipaddress/ipv4_test.rb b/test/ipaddress/ipv4_test.rb deleted file mode 100644 index 8cb3b8f..0000000 --- a/test/ipaddress/ipv4_test.rb +++ /dev/null @@ -1,510 +0,0 @@ -require 'test_helper' - -class IPv4Test < Test::Unit::TestCase - - def setup - @klass = IPAddress::IPv4 - - @valid_ipv4 = { - "0.0.0.0/0" => ["0.0.0.0", 0], - "10.0.0.0" => ["10.0.0.0", 32], - "10.0.0.1" => ["10.0.0.1", 32], - "10.0.0.1/24" => ["10.0.0.1", 24], - "10.0.0.1/255.255.255.0" => ["10.0.0.1", 24]} - - @invalid_ipv4 = ["10.0.0.256", - "10.0.0.0.0", - "10.0.0", - "10.0"] - - @valid_ipv4_range = ["10.0.0.1-254", - "10.0.1-254.0", - "10.1-254.0.0"] - - @netmask_values = { - "0.0.0.0/0" => "0.0.0.0", - "10.0.0.0/8" => "255.0.0.0", - "172.16.0.0/16" => "255.255.0.0", - "192.168.0.0/24" => "255.255.255.0", - "192.168.100.4/30" => "255.255.255.252"} - - @decimal_values ={ - "0.0.0.0/0" => 0, - "10.0.0.0/8" => 167772160, - "172.16.0.0/16" => 2886729728, - "192.168.0.0/24" => 3232235520, - "192.168.100.4/30" => 3232261124} - - @ip = @klass.new("172.16.10.1/24") - @network = @klass.new("172.16.10.0/24") - - @broadcast = { - "10.0.0.0/8" => "10.255.255.255/8", - "172.16.0.0/16" => "172.16.255.255/16", - "192.168.0.0/24" => "192.168.0.255/24", - "192.168.100.4/30" => "192.168.100.7/30"} - - @networks = { - "10.5.4.3/8" => "10.0.0.0/8", - "172.16.5.4/16" => "172.16.0.0/16", - "192.168.4.3/24" => "192.168.4.0/24", - "192.168.100.5/30" => "192.168.100.4/30"} - - @class_a = @klass.new("10.0.0.1/8") - @class_b = @klass.new("172.16.0.1/16") - @class_c = @klass.new("192.168.0.1/24") - - @classful = { - "10.1.1.1" => 8, - "150.1.1.1" => 16, - "200.1.1.1" => 24 } - - end - - def test_initialize - @valid_ipv4.keys.each do |i| - ip = @klass.new(i) - assert_instance_of @klass, ip - end - assert_instance_of IPAddress::Prefix32, @ip.prefix - assert_raise (ArgumentError) do - @klass.new - end - assert_nothing_raised do - @klass.new "10.0.0.0/8" - end - end - - def test_initialize_format_error - @invalid_ipv4.each do |i| - assert_raise(ArgumentError) {@klass.new(i)} - end - assert_raise (ArgumentError) {@klass.new("10.0.0.0/asd")} - end - - def test_initialize_without_prefix - assert_nothing_raised do - @klass.new("10.10.0.0") - end - ip = @klass.new("10.10.0.0") - assert_instance_of IPAddress::Prefix32, ip.prefix - assert_equal 32, ip.prefix.to_i - end - - def test_attributes - @valid_ipv4.each do |arg,attr| - ip = @klass.new(arg) - assert_equal attr.first, ip.address - assert_equal attr.last, ip.prefix.to_i - end - end - - def test_octets - ip = @klass.new("10.1.2.3/8") - assert_equal ip.octets, [10,1,2,3] - end - - def test_initialize_should_require_ip - assert_raise(ArgumentError) { @klass.new } - end - - def test_method_data - assert_equal "\254\020\n\001", @ip.data - end - - def test_method_to_string - @valid_ipv4.each do |arg,attr| - ip = @klass.new(arg) - assert_equal attr.join("/"), ip.to_string - end - end - - def test_method_to_s - @valid_ipv4.each do |arg,attr| - ip = @klass.new(arg) - assert_equal attr.first, ip.to_s - end - end - - def test_netmask - @netmask_values.each do |addr,mask| - ip = @klass.new(addr) - assert_equal mask, ip.netmask - end - end - - def test_method_to_u32 - @decimal_values.each do |addr,int| - ip = @klass.new(addr) - assert_equal int, ip.to_u32 - end - end - - def test_method_network? - assert_equal true, @network.network? - assert_equal false, @ip.network? - end - - def test_method_broadcast - @broadcast.each do |addr,bcast| - ip = @klass.new(addr) - assert_instance_of @klass, ip.broadcast - assert_equal bcast, ip.broadcast.to_string - end - end - - def test_method_network - @networks.each do |addr,net| - ip = @klass.new addr - assert_instance_of @klass, ip.network - assert_equal net, ip.network.to_string - end - end - - def test_method_bits - ip = @klass.new("127.0.0.1") - assert_equal "01111111000000000000000000000001", ip.bits - end - - def test_method_first - ip = @klass.new("192.168.100.0/24") - assert_instance_of @klass, ip.first - assert_equal "192.168.100.1", ip.first.to_s - ip = @klass.new("192.168.100.50/24") - assert_instance_of @klass, ip.first - assert_equal "192.168.100.1", ip.first.to_s - end - - def test_method_last - ip = @klass.new("192.168.100.0/24") - assert_instance_of @klass, ip.last - assert_equal "192.168.100.254", ip.last.to_s - ip = @klass.new("192.168.100.50/24") - assert_instance_of @klass, ip.last - assert_equal "192.168.100.254", ip.last.to_s - end - - def test_method_each_host - ip = @klass.new("10.0.0.1/29") - arr = [] - ip.each_host {|i| arr << i.to_s} - expected = ["10.0.0.1","10.0.0.2","10.0.0.3", - "10.0.0.4","10.0.0.5","10.0.0.6"] - assert_equal expected, arr - end - - def test_method_each - ip = @klass.new("10.0.0.1/29") - arr = [] - ip.each {|i| arr << i.to_s} - expected = ["10.0.0.0","10.0.0.1","10.0.0.2", - "10.0.0.3","10.0.0.4","10.0.0.5", - "10.0.0.6","10.0.0.7"] - assert_equal expected, arr - end - - def test_method_size - ip = @klass.new("10.0.0.1/29") - assert_equal 8, ip.size - end - - def test_method_hosts - ip = @klass.new("10.0.0.1/29") - expected = ["10.0.0.1","10.0.0.2","10.0.0.3", - "10.0.0.4","10.0.0.5","10.0.0.6"] - assert_equal expected, ip.hosts.map {|i| i.to_s} - end - - def test_method_network_u32 - assert_equal 2886732288, @ip.network_u32 - end - - def test_method_broadcast_u32 - assert_equal 2886732543, @ip.broadcast_u32 - end - - def test_method_include? - ip = @klass.new("192.168.10.100/24") - addr = @klass.new("192.168.10.102/24") - assert_equal true, ip.include?(addr) - assert_equal false, ip.include?(@klass.new("172.16.0.48")) - ip = @klass.new("10.0.0.0/8") - assert_equal true, ip.include?(@klass.new("10.0.0.0/9")) - assert_equal true, ip.include?(@klass.new("10.1.1.1/32")) - assert_equal true, ip.include?(@klass.new("10.1.1.1/9")) - assert_equal false, ip.include?(@klass.new("172.16.0.0/16")) - assert_equal false, ip.include?(@klass.new("10.0.0.0/7")) - assert_equal false, ip.include?(@klass.new("5.5.5.5/32")) - assert_equal false, ip.include?(@klass.new("11.0.0.0/8")) - ip = @klass.new("13.13.0.0/13") - assert_equal false, ip.include?(@klass.new("13.16.0.0/32")) - end - - def test_method_include_all? - ip = @klass.new("192.168.10.100/24") - addr1 = @klass.new("192.168.10.102/24") - addr2 = @klass.new("192.168.10.103/24") - assert_equal true, ip.include_all?(addr1,addr2) - assert_equal false, ip.include_all?(addr1, @klass.new("13.16.0.0/32")) - end - - def test_method_ipv4? - assert_equal true, @ip.ipv4? - end - - def test_method_ipv6? - assert_equal false, @ip.ipv6? - end - - def test_method_private? - assert_equal true, @klass.new("192.168.10.50/24").private? - assert_equal true, @klass.new("192.168.10.50/16").private? - assert_equal true, @klass.new("172.16.77.40/24").private? - assert_equal true, @klass.new("172.16.10.50/14").private? - assert_equal true, @klass.new("10.10.10.10/10").private? - assert_equal true, @klass.new("10.0.0.0/8").private? - assert_equal false, @klass.new("192.168.10.50/12").private? - assert_equal false, @klass.new("3.3.3.3").private? - assert_equal false, @klass.new("10.0.0.0/7").private? - assert_equal false, @klass.new("172.32.0.0/12").private? - assert_equal false, @klass.new("172.16.0.0/11").private? - assert_equal false, @klass.new("192.0.0.2/24").private? - end - - def test_method_octet - assert_equal 172, @ip[0] - assert_equal 16, @ip[1] - assert_equal 10, @ip[2] - assert_equal 1, @ip[3] - end - - def test_method_a? - assert_equal true, @class_a.a? - assert_equal false, @class_b.a? - assert_equal false, @class_c.a? - end - - def test_method_b? - assert_equal true, @class_b.b? - assert_equal false, @class_a.b? - assert_equal false, @class_c.b? - end - - def test_method_c? - assert_equal true, @class_c.c? - assert_equal false, @class_a.c? - assert_equal false, @class_b.c? - end - - def test_method_to_ipv6 - assert_equal "ac10:0a01", @ip.to_ipv6 - end - - def test_method_reverse - assert_equal "1.10.16.172.in-addr.arpa", @ip.reverse - end - - def test_method_comparabble - ip1 = @klass.new("10.1.1.1/8") - ip2 = @klass.new("10.1.1.1/16") - ip3 = @klass.new("172.16.1.1/14") - ip4 = @klass.new("10.1.1.1/8") - - # ip1 should be major than ip2 - assert_equal true, ip1 > ip2 - assert_equal false, ip1 < ip2 - assert_equal false, ip2 > ip1 - # ip2 should be minor than ip3 - assert_equal true, ip2 < ip3 - assert_equal false, ip2 > ip3 - # ip1 should be minor than ip3 - assert_equal true, ip1 < ip3 - assert_equal false, ip1 > ip3 - assert_equal false, ip3 < ip1 - # ip1 should be equal to itself - assert_equal true, ip1 == ip1 - # ip1 should be equal to ip4 - assert_equal true, ip1 == ip4 - # test sorting - arr = ["10.1.1.1/16","10.1.1.1/8","172.16.1.1/14"] - assert_equal arr, [ip1,ip2,ip3].sort.map{|s| s.to_string} - end - - def test_method_minus - ip1 = @klass.new("10.1.1.1/8") - ip2 = @klass.new("10.1.1.10/8") - assert_equal 9, ip2 - ip1 - assert_equal 9, ip1 - ip2 - end - - def test_method_plus - ip1 = @klass.new("172.16.10.1/24") - ip2 = @klass.new("172.16.11.2/24") - assert_equal ["172.16.10.0/23"], (ip1+ip2).map{|i| i.to_string} - - ip2 = @klass.new("172.16.12.2/24") - assert_equal [ip1.network.to_string, ip2.network.to_string], - (ip1 + ip2).map{|i| i.to_string} - - ip1 = @klass.new("10.0.0.0/23") - ip2 = @klass.new("10.0.2.0/24") - assert_equal ["10.0.0.0/23","10.0.2.0/24"], (ip1+ip2).map{|i| i.to_string} - - ip1 = @klass.new("10.0.0.0/23") - ip2 = @klass.new("10.0.2.0/24") - assert_equal ["10.0.0.0/23","10.0.2.0/24"], (ip2+ip1).map{|i| i.to_string} - - ip1 = @klass.new("10.0.0.0/16") - ip2 = @klass.new("10.0.2.0/24") - assert_equal ["10.0.0.0/16"], (ip1+ip2).map{|i| i.to_string} - - ip1 = @klass.new("10.0.0.0/23") - ip2 = @klass.new("10.1.0.0/24") - assert_equal ["10.0.0.0/23","10.1.0.0/24"], (ip1+ip2).map{|i| i.to_string} - - end - - def test_method_netmask_equal - ip = @klass.new("10.1.1.1/16") - assert_equal 16, ip.prefix.to_i - ip.netmask = "255.255.255.0" - assert_equal 24, ip.prefix.to_i - end - - def test_method_subnet - assert_raise(ArgumentError) {@ip.subnet(0)} - assert_raise(ArgumentError) {@ip.subnet(257)} - - arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", - "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27", - "172.16.10.192/27", "172.16.10.224/27"] - assert_equal arr, @network.subnet(8).map {|s| s.to_string} - arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", - "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27", - "172.16.10.192/26"] - assert_equal arr, @network.subnet(7).map {|s| s.to_string} - arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", - "172.16.10.96/27", "172.16.10.128/26", "172.16.10.192/26"] - assert_equal arr, @network.subnet(6).map {|s| s.to_string} - arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", - "172.16.10.96/27", "172.16.10.128/25"] - assert_equal arr, @network.subnet(5).map {|s| s.to_string} - arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26", - "172.16.10.192/26"] - assert_equal arr, @network.subnet(4).map {|s| s.to_string} - arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/25"] - assert_equal arr, @network.subnet(3).map {|s| s.to_string} - arr = ["172.16.10.0/25", "172.16.10.128/25"] - assert_equal arr, @network.subnet(2).map {|s| s.to_string} - arr = ["172.16.10.0/24"] - assert_equal arr, @network.subnet(1).map {|s| s.to_string} - end - - def test_method_supernet - assert_raise(ArgumentError) {@ip.supernet(0)} - assert_raise(ArgumentError) {@ip.supernet(24)} - assert_equal "172.16.10.0/23", @ip.supernet(23).to_string - assert_equal "172.16.8.0/22", @ip.supernet(22).to_string - end - - def test_classmethod_parse_u32 - @decimal_values.each do |addr,int| - ip = @klass.parse_u32(int) - ip.prefix = addr.split("/").last.to_i - assert_equal ip.to_string, addr - end - end - - def test_classhmethod_extract - str = "foobar172.16.10.1barbaz" - assert_equal "172.16.10.1", @klass.extract(str).to_s - end - - def test_classmethod_summarize - - # Should return self if only one network given - assert_equal [@ip.network], @klass.summarize(@ip) - - # Summarize homogeneous networks - ip1 = @klass.new("172.16.10.1/24") - ip2 = @klass.new("172.16.11.2/24") - assert_equal ["172.16.10.0/23"], @klass.summarize(ip1,ip2).map{|i| i.to_string} - - ip1 = @klass.new("10.0.0.1/24") - ip2 = @klass.new("10.0.1.1/24") - ip3 = @klass.new("10.0.2.1/24") - ip4 = @klass.new("10.0.3.1/24") - assert_equal ["10.0.0.0/22"], @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} - assert_equal ["10.0.0.0/22"], @klass.summarize(ip4,ip3,ip2,ip1).map{|i| i.to_string} - - # Summarize non homogeneous networks - ip1 = @klass.new("10.0.0.0/23") - ip2 = @klass.new("10.0.2.0/24") - assert_equal ["10.0.0.0/23","10.0.2.0/24"], @klass.summarize(ip1,ip2).map{|i| i.to_string} - - ip1 = @klass.new("10.0.0.0/16") - ip2 = @klass.new("10.0.2.0/24") - assert_equal ["10.0.0.0/16"], @klass.summarize(ip1,ip2).map{|i| i.to_string} - - ip1 = @klass.new("10.0.0.0/23") - ip2 = @klass.new("10.1.0.0/24") - assert_equal ["10.0.0.0/23","10.1.0.0/24"], @klass.summarize(ip1,ip2).map{|i| i.to_string} - - ip1 = @klass.new("10.0.0.0/23") - ip2 = @klass.new("10.0.2.0/23") - ip3 = @klass.new("10.0.4.0/24") - ip4 = @klass.new("10.0.6.0/24") - assert_equal ["10.0.0.0/22","10.0.4.0/24","10.0.6.0/24"], - @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} - - ip1 = @klass.new("10.0.1.1/24") - ip2 = @klass.new("10.0.2.1/24") - ip3 = @klass.new("10.0.3.1/24") - ip4 = @klass.new("10.0.4.1/24") - result = ["10.0.1.0/24","10.0.2.0/23","10.0.4.0/24"] - assert_equal result, @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} - assert_equal result, @klass.summarize(ip4,ip3,ip2,ip1).map{|i| i.to_string} - - ip1 = @klass.new("10.0.1.1/24") - ip2 = @klass.new("10.10.2.1/24") - ip3 = @klass.new("172.16.0.1/24") - ip4 = @klass.new("172.16.1.1/24") - result = ["10.0.1.0/24","10.10.2.0/24","172.16.0.0/23"] - assert_equal result, @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} - - ips = [@klass.new("10.0.0.12/30"), - @klass.new("10.0.100.0/24")] - result = ["10.0.0.12/30", "10.0.100.0/24"] - assert_equal result, @klass.summarize(*ips).map{|i| i.to_string} - - ips = [@klass.new("172.16.0.0/31"), - @klass.new("10.10.2.1/32")] - result = ["10.10.2.1/32", "172.16.0.0/31"] - assert_equal result, @klass.summarize(*ips).map{|i| i.to_string} - - ips = [@klass.new("172.16.0.0/32"), - @klass.new("10.10.2.1/32")] - result = ["10.10.2.1/32", "172.16.0.0/32"] - assert_equal result, @klass.summarize(*ips).map{|i| i.to_string} - - end - - def test_classmethod_parse_data - ip = @klass.parse_data "\254\020\n\001" - assert_instance_of @klass, ip - assert_equal "172.16.10.1", ip.address - assert_equal "172.16.10.1/32", ip.to_string - end - - def test_classmethod_parse_classful - @classful.each do |ip,prefix| - res = @klass.parse_classful(ip) - assert_equal prefix, res.prefix - assert_equal "#{ip}/#{prefix}", res.to_string - end - assert_raise(ArgumentError){ @klass.parse_classful("192.168.256.257") } - end - -end # class IPv4Test - - diff --git a/test/ipaddress/ipv6_test.rb b/test/ipaddress/ipv6_test.rb deleted file mode 100644 index 84be76c..0000000 --- a/test/ipaddress/ipv6_test.rb +++ /dev/null @@ -1,356 +0,0 @@ -require 'test_helper' - -class IPv6Test < Test::Unit::TestCase - - def setup - @klass = IPAddress::IPv6 - - @compress_addr = { - "2001:db8:0000:0000:0008:0800:200c:417a" => "2001:db8::8:800:200c:417a", - "2001:db8:0:0:8:800:200c:417a" => "2001:db8::8:800:200c:417a", - "ff01:0:0:0:0:0:0:101" => "ff01::101", - "0:0:0:0:0:0:0:1" => "::1", - "0:0:0:0:0:0:0:0" => "::"} - - @valid_ipv6 = { # Kindly taken from the python IPy library - "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210" => 338770000845734292534325025077361652240, - "1080:0000:0000:0000:0008:0800:200C:417A" => 21932261930451111902915077091070067066, - "1080:0:0:0:8:800:200C:417A" => 21932261930451111902915077091070067066, - "1080:0::8:800:200C:417A" => 21932261930451111902915077091070067066, - "1080::8:800:200C:417A" => 21932261930451111902915077091070067066, - "FF01:0:0:0:0:0:0:43" => 338958331222012082418099330867817087043, - "FF01:0:0::0:0:43" => 338958331222012082418099330867817087043, - "FF01::43" => 338958331222012082418099330867817087043, - "0:0:0:0:0:0:0:1" => 1, - "0:0:0::0:0:1" => 1, - "::1" => 1, - "0:0:0:0:0:0:0:0" => 0, - "0:0:0::0:0:0" => 0, - "::" => 0, - "1080:0:0:0:8:800:200C:417A" => 21932261930451111902915077091070067066, - "1080::8:800:200C:417A" => 21932261930451111902915077091070067066} - - @invalid_ipv6 = [":1:2:3:4:5:6:7", - ":1:2:3:4:5:6:7"] - - @ip = @klass.new "2001:db8::8:800:200c:417a/64" - @network = @klass.new "2001:db8:8:800::/64" - @arr = [8193,3512,0,0,8,2048,8204,16762] - @hex = "20010db80000000000080800200c417a" - end - - def test_attribute_address - addr = "2001:0db8:0000:0000:0008:0800:200c:417a" - assert_equal addr, @ip.address - end - - def test_initialize - assert_instance_of @klass, @ip - @valid_ipv6.keys.each do |ip| - assert_nothing_raised {@klass.new ip} - end - @invalid_ipv6.each do |ip| - assert_raise(ArgumentError) {@klass.new ip} - end - assert_equal 64, @ip.prefix - - assert_raise(ArgumentError) { - @klass.new "::10.1.1.1" - } - end - - def test_attribute_groups - assert_equal @arr, @ip.groups - end - - def test_method_hexs - arr = "2001:0db8:0000:0000:0008:0800:200c:417a".split(":") - assert_equal arr, @ip.hexs - end - - def test_method_to_i - @valid_ipv6.each do |ip,num| - assert_equal num, @klass.new(ip).to_i - end - end - - def test_method_bits - bits = "0010000000000001000011011011100000000000000000000" + - "000000000000000000000000000100000001000000000000010000" + - "0000011000100000101111010" - assert_equal bits, @ip.bits - end - - def test_method_prefix=() - ip = @klass.new "2001:db8::8:800:200c:417a" - assert_equal 128, ip.prefix - ip.prefix = 64 - assert_equal 64, ip.prefix - assert_equal "2001:db8::8:800:200c:417a/64", ip.to_string - end - - def test_method_mapped? - assert_equal false, @ip.mapped? - ip6 = @klass.new "::ffff:1234:5678" - assert_equal true, ip6.mapped? - end - - def test_method_literal - str = "2001-0db8-0000-0000-0008-0800-200c-417a.ipv6-literal.net" - assert_equal str, @ip.literal - end - - def test_method_group - @arr.each_with_index do |val,index| - assert_equal val, @ip[index] - end - end - - def test_method_ipv4? - assert_equal false, @ip.ipv4? - end - - def test_method_ipv6? - assert_equal true, @ip.ipv6? - end - - def test_method_network? - assert_equal true, @network.network? - assert_equal false, @ip.network? - end - - def test_method_network_u128 - assert_equal 42540766411282592856903984951653826560, @ip.network_u128 - end - - def test_method_include? - assert_equal true, @ip.include?(@ip) - # test prefix on same address - included = @klass.new "2001:db8::8:800:200c:417a/128" - not_included = @klass.new "2001:db8::8:800:200c:417a/46" - assert_equal true, @ip.include?(included) - assert_equal false, @ip.include?(not_included) - # test address on same prefix - included = @klass.new "2001:db8::8:800:200c:0/64" - not_included = @klass.new "2001:db8:1::8:800:200c:417a/64" - assert_equal true, @ip.include?(included) - assert_equal false, @ip.include?(not_included) - # general test - included = @klass.new "2001:db8::8:800:200c:1/128" - not_included = @klass.new "2001:db8:1::8:800:200c:417a/76" - assert_equal true, @ip.include?(included) - assert_equal false, @ip.include?(not_included) - end - - def test_method_to_hex - assert_equal @hex, @ip.to_hex - end - - def test_method_to_s - assert_equal "2001:db8::8:800:200c:417a", @ip.to_s - end - - def test_method_to_string - assert_equal "2001:db8::8:800:200c:417a/64", @ip.to_string - end - - def test_method_to_string_uncompressed - str = "2001:0db8:0000:0000:0008:0800:200c:417a/64" - assert_equal str, @ip.to_string_uncompressed - end - - def test_method_data - str = " \001\r\270\000\000\000\000\000\b\b\000 \fAz" - assert_equal str, @ip.data - end - - def test_method_reverse - str = "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa" - assert_equal str, @klass.new("3ffe:505:2::f").reverse - end - - def test_method_compressed - assert_equal "1:1:1::1", @klass.new("1:1:1:0:0:0:0:1").compressed - assert_equal "1:0:1::1", @klass.new("1:0:1:0:0:0:0:1").compressed - assert_equal "1:0:0:1::1", @klass.new("1:0:0:1:0:0:0:1").compressed - assert_equal "1::1:0:0:1", @klass.new("1:0:0:0:1:0:0:1").compressed - assert_equal "1::1", @klass.new("1:0:0:0:0:0:0:1").compressed - end - - def test_method_unspecified? - assert_equal true, @klass.new("::").unspecified? - assert_equal false, @ip.unspecified? - end - - def test_method_loopback? - assert_equal true, @klass.new("::1").loopback? - assert_equal false, @ip.loopback? - end - - def test_classmethod_expand - compressed = "2001:db8:0:cd30::" - expanded = "2001:0db8:0000:cd30:0000:0000:0000:0000" - assert_equal expanded, @klass.expand(compressed) - assert_not_equal expanded, @klass.expand("2001:0db8:0:cd3") - assert_not_equal expanded, @klass.expand("2001:0db8::cd30") - assert_not_equal expanded, @klass.expand("2001:0db8::cd3") - end - - def test_classmethod_compress - compressed = "2001:db8:0:cd30::" - expanded = "2001:0db8:0000:cd30:0000:0000:0000:0000" - assert_equal compressed, @klass.compress(expanded) - assert_not_equal compressed, @klass.compress("2001:0db8:0:cd3") - assert_not_equal compressed, @klass.compress("2001:0db8::cd30") - assert_not_equal compressed, @klass.compress("2001:0db8::cd3") - end - - def test_classmethod_parse_data - str = " \001\r\270\000\000\000\000\000\b\b\000 \fAz" - ip = @klass.parse_data str - assert_instance_of @klass, ip - assert_equal "2001:0db8:0000:0000:0008:0800:200c:417a", ip.address - assert_equal "2001:db8::8:800:200c:417a/128", ip.to_string - end - - def test_classhmethod_parse_u128 - @valid_ipv6.each do |ip,num| - assert_equal @klass.new(ip).to_s, @klass.parse_u128(num).to_s - end - end - - def test_classmethod_parse_hex - assert_equal @ip.to_s, @klass.parse_hex(@hex,64).to_s - end - -end # class IPv6Test - -class IPv6UnspecifiedTest < Test::Unit::TestCase - - def setup - @klass = IPAddress::IPv6::Unspecified - @ip = @klass.new - @s = "::" - @str = "::/128" - @string = "0000:0000:0000:0000:0000:0000:0000:0000/128" - @u128 = 0 - @address = "::" - end - - def test_initialize - assert_nothing_raised {@klass.new} - assert_instance_of @klass, @ip - end - - def test_attributes - assert_equal @address, @ip.compressed - assert_equal 128, @ip.prefix - assert_equal true, @ip.unspecified? - assert_equal @s, @ip.to_s - assert_equal @str, @ip.to_string - assert_equal @string, @ip.to_string_uncompressed - assert_equal @u128, @ip.to_u128 - end - - def test_method_ipv6? - assert_equal true, @ip.ipv6? - end - -end # class IPv6UnspecifiedTest - - -class IPv6LoopbackTest < Test::Unit::TestCase - - def setup - @klass = IPAddress::IPv6::Loopback - @ip = @klass.new - @s = "::1" - @str = "::1/128" - @string = "0000:0000:0000:0000:0000:0000:0000:0001/128" - @u128 = 1 - @address = "::1" - end - - def test_initialize - assert_nothing_raised {@klass.new} - assert_instance_of @klass, @ip - end - - def test_attributes - assert_equal @address, @ip.compressed - assert_equal 128, @ip.prefix - assert_equal true, @ip.loopback? - assert_equal @s, @ip.to_s - assert_equal @str, @ip.to_string - assert_equal @string, @ip.to_string_uncompressed - assert_equal @u128, @ip.to_u128 - end - - def test_method_ipv6? - assert_equal true, @ip.ipv6? - end - -end # class IPv6LoopbackTest - -class IPv6MappedTest < Test::Unit::TestCase - - def setup - @klass = IPAddress::IPv6::Mapped - @ip = @klass.new("::172.16.10.1") - @s = "::ffff:172.16.10.1" - @str = "::ffff:172.16.10.1/128" - @string = "0000:0000:0000:0000:0000:ffff:ac10:0a01/128" - @u128 = 281473568475649 - @address = "::ffff:ac10:a01" - - @valid_mapped = {'::13.1.68.3' => 281470899930115, - '0:0:0:0:0:ffff:129.144.52.38' => 281472855454758, - '::ffff:129.144.52.38' => 281472855454758} - - @valid_mapped_ipv6 = {'::0d01:4403' => 281470899930115, - '0:0:0:0:0:ffff:8190:3426' => 281472855454758, - '::ffff:8190:3426' => 281472855454758} - - @valid_mapped_ipv6_conversion = {'::0d01:4403' => "13.1.68.3", - '0:0:0:0:0:ffff:8190:3426' => "129.144.52.38", - '::ffff:8190:3426' => "129.144.52.38"} - - end - - def test_initialize - assert_nothing_raised {@klass.new("::172.16.10.1")} - assert_instance_of @klass, @ip - @valid_mapped.each do |ip, u128| - assert_nothing_raised {@klass.new ip} - assert_equal u128, @klass.new(ip).to_u128 - end - @valid_mapped_ipv6.each do |ip, u128| - assert_nothing_raised {@klass.new ip} - assert_equal u128, @klass.new(ip).to_u128 - end - end - - def test_mapped_from_ipv6_conversion - @valid_mapped_ipv6_conversion.each do |ip6,ip4| - assert_equal ip4, @klass.new(ip6).ipv4.to_s - end - end - - def test_attributes - assert_equal @address, @ip.compressed - assert_equal 128, @ip.prefix - assert_equal @s, @ip.to_s - assert_equal @str, @ip.to_string - assert_equal @string, @ip.to_string_uncompressed - assert_equal @u128, @ip.to_u128 - end - - def test_method_ipv6? - assert_equal true, @ip.ipv6? - end - - def test_mapped? - assert_equal true, @ip.mapped? - end - -end # class IPv6MappedTest diff --git a/test/ipaddress/prefix_test.rb b/test/ipaddress/prefix_test.rb deleted file mode 100644 index bff812e..0000000 --- a/test/ipaddress/prefix_test.rb +++ /dev/null @@ -1,159 +0,0 @@ -require 'test_helper' - -class Prefix32Test < Test::Unit::TestCase - - def setup - @netmask0 = "0.0.0.0" - @netmask8 = "255.0.0.0" - @netmask16 = "255.255.0.0" - @netmask24 = "255.255.255.0" - @netmask30 = "255.255.255.252" - @netmasks = [@netmask0,@netmask8,@netmask16,@netmask24,@netmask30] - - @prefix_hash = { - "0.0.0.0" => 0, - "255.0.0.0" => 8, - "255.255.0.0" => 16, - "255.255.255.0" => 24, - "255.255.255.252" => 30} - - @octets_hash = { - [0,0,0,0] => 0, - [255,0,0,0] => 8, - [255,255,0,0] => 16, - [255,255,255,0] => 24, - [255,255,255,252] => 30} - - @u32_hash = { - 0 => 0, - 8 => 4278190080, - 16 => 4294901760, - 24 => 4294967040, - 30 => 4294967292} - - @klass = IPAddress::Prefix32 - end - - def test_attributes - @prefix_hash.values.each do |num| - prefix = @klass.new(num) - assert_equal num, prefix.prefix - end - end - - def test_parse_netmask - @prefix_hash.each do |netmask, num| - prefix = @klass.parse_netmask(netmask) - assert_equal num, prefix.prefix - assert_instance_of @klass, prefix - end - end - - def test_method_to_ip - @prefix_hash.each do |netmask, num| - prefix = @klass.new(num) - assert_equal netmask, prefix.to_ip - end - end - - def test_method_to_s - prefix = @klass.new(8) - assert_equal "8", prefix.to_s - end - - def test_method_bits - prefix = @klass.new(16) - str = "1"*16 + "0"*16 - assert_equal str, prefix.bits - end - - def test_method_to_u32 - @u32_hash.each do |num,u32| - assert_equal u32, @klass.new(num).to_u32 - end - end - - def test_method_plus - p1 = @klass.new 8 - p2 = @klass.new 10 - assert_equal 18, p1+p2 - assert_equal 12, p1+4 - end - - def test_method_minus - p1 = @klass.new 8 - p2 = @klass.new 24 - assert_equal 16, p1-p2 - assert_equal 16, p2-p1 - assert_equal 20, p2-4 - end - - def test_initialize - assert_raise (ArgumentError) do - @klass.new 33 - end - assert_nothing_raised do - @klass.new 8 - end - assert_instance_of @klass, @klass.new(8) - end - - def test_method_octets - @octets_hash.each do |arr,pref| - prefix = @klass.new(pref) - assert_equal prefix.octets, arr - end - end - - def test_method_brackets - @octets_hash.each do |arr,pref| - prefix = @klass.new(pref) - arr.each_with_index do |oct,index| - assert_equal prefix[index], oct - end - end - end - - def test_method_hostmask - prefix = @klass.new(8) - assert_equal "0.255.255.255", prefix.hostmask - end - -end # class Prefix32Test - - -class Prefix128Test < Test::Unit::TestCase - - def setup - @u128_hash = { - 32 => 340282366841710300949110269838224261120, - 64 => 340282366920938463444927863358058659840, - 96 => 340282366920938463463374607427473244160, - 126 => 340282366920938463463374607431768211452} - - @klass = IPAddress::Prefix128 - end - - def test_initialize - assert_raise (ArgumentError) do - @klass.new 129 - end - assert_nothing_raised do - @klass.new 64 - end - assert_instance_of @klass, @klass.new(64) - end - - def test_method_bits - prefix = @klass.new(64) - str = "1"*64 + "0"*64 - assert_equal str, prefix.bits - end - - def test_method_to_u32 - @u128_hash.each do |num,u128| - assert_equal u128, @klass.new(num).to_u128 - end - end - -end # class Prefix128Test diff --git a/test/ipaddress_test.rb b/test/ipaddress_test.rb index ed72aed..8251fc5 100644 --- a/test/ipaddress_test.rb +++ b/test/ipaddress_test.rb @@ -51,5 +51,1029 @@ class IPAddressTest < Test::Unit::TestCase end end + +class Prefix32Test < Test::Unit::TestCase + + def setup + @netmask0 = "0.0.0.0" + @netmask8 = "255.0.0.0" + @netmask16 = "255.255.0.0" + @netmask24 = "255.255.255.0" + @netmask30 = "255.255.255.252" + @netmasks = [@netmask0,@netmask8,@netmask16,@netmask24,@netmask30] + + @prefix_hash = { + "0.0.0.0" => 0, + "255.0.0.0" => 8, + "255.255.0.0" => 16, + "255.255.255.0" => 24, + "255.255.255.252" => 30} + + @octets_hash = { + [0,0,0,0] => 0, + [255,0,0,0] => 8, + [255,255,0,0] => 16, + [255,255,255,0] => 24, + [255,255,255,252] => 30} + + @u32_hash = { + 0 => 0, + 8 => 4278190080, + 16 => 4294901760, + 24 => 4294967040, + 30 => 4294967292} + + @klass = IPAddress::Prefix32 + end + + def test_attributes + @prefix_hash.values.each do |num| + prefix = @klass.new(num) + assert_equal num, prefix.prefix + end + end + + def test_parse_netmask + @prefix_hash.each do |netmask, num| + prefix = @klass.parse_netmask(netmask) + assert_equal num, prefix.prefix + assert_instance_of @klass, prefix + end + end + + def test_method_to_ip + @prefix_hash.each do |netmask, num| + prefix = @klass.new(num) + assert_equal netmask, prefix.to_ip + end + end + + def test_method_to_s + prefix = @klass.new(8) + assert_equal "8", prefix.to_s + end + + def test_method_bits + prefix = @klass.new(16) + str = "1"*16 + "0"*16 + assert_equal str, prefix.bits + end + + def test_method_to_u32 + @u32_hash.each do |num,u32| + assert_equal u32, @klass.new(num).to_u32 + end + end + + def test_method_plus + p1 = @klass.new 8 + p2 = @klass.new 10 + assert_equal 18, p1+p2 + assert_equal 12, p1+4 + end + + def test_method_minus + p1 = @klass.new 8 + p2 = @klass.new 24 + assert_equal 16, p1-p2 + assert_equal 16, p2-p1 + assert_equal 20, p2-4 + end + + def test_initialize + assert_raise (ArgumentError) do + @klass.new 33 + end + assert_nothing_raised do + @klass.new 8 + end + assert_instance_of @klass, @klass.new(8) + end + + def test_method_octets + @octets_hash.each do |arr,pref| + prefix = @klass.new(pref) + assert_equal prefix.octets, arr + end + end + + def test_method_brackets + @octets_hash.each do |arr,pref| + prefix = @klass.new(pref) + arr.each_with_index do |oct,index| + assert_equal prefix[index], oct + end + end + end + + def test_method_hostmask + prefix = @klass.new(8) + assert_equal "0.255.255.255", prefix.hostmask + end + +end # class Prefix32Test + + +class Prefix128Test < Test::Unit::TestCase + + def setup + @u128_hash = { + 32 => 340282366841710300949110269838224261120, + 64 => 340282366920938463444927863358058659840, + 96 => 340282366920938463463374607427473244160, + 126 => 340282366920938463463374607431768211452} + + @klass = IPAddress::Prefix128 + end + + def test_initialize + assert_raise (ArgumentError) do + @klass.new 129 + end + assert_nothing_raised do + @klass.new 64 + end + assert_instance_of @klass, @klass.new(64) + end + + def test_method_bits + prefix = @klass.new(64) + str = "1"*64 + "0"*64 + assert_equal str, prefix.bits + end + + def test_method_to_u32 + @u128_hash.each do |num,u128| + assert_equal u128, @klass.new(num).to_u128 + end + end + +end # class Prefix128Test + + +class IPv4Test < Test::Unit::TestCase + + def setup + @klass = IPAddress::IPv4 + + @valid_ipv4 = { + "0.0.0.0/0" => ["0.0.0.0", 0], + "10.0.0.0" => ["10.0.0.0", 32], + "10.0.0.1" => ["10.0.0.1", 32], + "10.0.0.1/24" => ["10.0.0.1", 24], + "10.0.0.1/255.255.255.0" => ["10.0.0.1", 24]} + + @invalid_ipv4 = ["10.0.0.256", + "10.0.0.0.0", + "10.0.0", + "10.0"] + + @valid_ipv4_range = ["10.0.0.1-254", + "10.0.1-254.0", + "10.1-254.0.0"] + + @netmask_values = { + "0.0.0.0/0" => "0.0.0.0", + "10.0.0.0/8" => "255.0.0.0", + "172.16.0.0/16" => "255.255.0.0", + "192.168.0.0/24" => "255.255.255.0", + "192.168.100.4/30" => "255.255.255.252"} + + @decimal_values ={ + "0.0.0.0/0" => 0, + "10.0.0.0/8" => 167772160, + "172.16.0.0/16" => 2886729728, + "192.168.0.0/24" => 3232235520, + "192.168.100.4/30" => 3232261124} + + @ip = @klass.new("172.16.10.1/24") + @network = @klass.new("172.16.10.0/24") + + @broadcast = { + "10.0.0.0/8" => "10.255.255.255/8", + "172.16.0.0/16" => "172.16.255.255/16", + "192.168.0.0/24" => "192.168.0.255/24", + "192.168.100.4/30" => "192.168.100.7/30"} + + @networks = { + "10.5.4.3/8" => "10.0.0.0/8", + "172.16.5.4/16" => "172.16.0.0/16", + "192.168.4.3/24" => "192.168.4.0/24", + "192.168.100.5/30" => "192.168.100.4/30"} + + @class_a = @klass.new("10.0.0.1/8") + @class_b = @klass.new("172.16.0.1/16") + @class_c = @klass.new("192.168.0.1/24") + + @classful = { + "10.1.1.1" => 8, + "150.1.1.1" => 16, + "200.1.1.1" => 24 } + + end + + def test_initialize + @valid_ipv4.keys.each do |i| + ip = @klass.new(i) + assert_instance_of @klass, ip + end + assert_instance_of IPAddress::Prefix32, @ip.prefix + assert_raise (ArgumentError) do + @klass.new + end + assert_nothing_raised do + @klass.new "10.0.0.0/8" + end + end + + def test_initialize_format_error + @invalid_ipv4.each do |i| + assert_raise(ArgumentError) {@klass.new(i)} + end + assert_raise (ArgumentError) {@klass.new("10.0.0.0/asd")} + end + + def test_initialize_without_prefix + assert_nothing_raised do + @klass.new("10.10.0.0") + end + ip = @klass.new("10.10.0.0") + assert_instance_of IPAddress::Prefix32, ip.prefix + assert_equal 32, ip.prefix.to_i + end + + def test_attributes + @valid_ipv4.each do |arg,attr| + ip = @klass.new(arg) + assert_equal attr.first, ip.address + assert_equal attr.last, ip.prefix.to_i + end + end + + def test_octets + ip = @klass.new("10.1.2.3/8") + assert_equal ip.octets, [10,1,2,3] + end + + def test_initialize_should_require_ip + assert_raise(ArgumentError) { @klass.new } + end + + def test_method_data + assert_equal "\254\020\n\001", @ip.data + end + + def test_method_to_string + @valid_ipv4.each do |arg,attr| + ip = @klass.new(arg) + assert_equal attr.join("/"), ip.to_string + end + end + + def test_method_to_s + @valid_ipv4.each do |arg,attr| + ip = @klass.new(arg) + assert_equal attr.first, ip.to_s + end + end + + def test_netmask + @netmask_values.each do |addr,mask| + ip = @klass.new(addr) + assert_equal mask, ip.netmask + end + end + + def test_method_to_u32 + @decimal_values.each do |addr,int| + ip = @klass.new(addr) + assert_equal int, ip.to_u32 + end + end + + def test_method_network? + assert_equal true, @network.network? + assert_equal false, @ip.network? + end + + def test_method_broadcast + @broadcast.each do |addr,bcast| + ip = @klass.new(addr) + assert_instance_of @klass, ip.broadcast + assert_equal bcast, ip.broadcast.to_string + end + end + + def test_method_network + @networks.each do |addr,net| + ip = @klass.new addr + assert_instance_of @klass, ip.network + assert_equal net, ip.network.to_string + end + end + + def test_method_bits + ip = @klass.new("127.0.0.1") + assert_equal "01111111000000000000000000000001", ip.bits + end + + def test_method_first + ip = @klass.new("192.168.100.0/24") + assert_instance_of @klass, ip.first + assert_equal "192.168.100.1", ip.first.to_s + ip = @klass.new("192.168.100.50/24") + assert_instance_of @klass, ip.first + assert_equal "192.168.100.1", ip.first.to_s + end + + def test_method_last + ip = @klass.new("192.168.100.0/24") + assert_instance_of @klass, ip.last + assert_equal "192.168.100.254", ip.last.to_s + ip = @klass.new("192.168.100.50/24") + assert_instance_of @klass, ip.last + assert_equal "192.168.100.254", ip.last.to_s + end + + def test_method_each_host + ip = @klass.new("10.0.0.1/29") + arr = [] + ip.each_host {|i| arr << i.to_s} + expected = ["10.0.0.1","10.0.0.2","10.0.0.3", + "10.0.0.4","10.0.0.5","10.0.0.6"] + assert_equal expected, arr + end + + def test_method_each + ip = @klass.new("10.0.0.1/29") + arr = [] + ip.each {|i| arr << i.to_s} + expected = ["10.0.0.0","10.0.0.1","10.0.0.2", + "10.0.0.3","10.0.0.4","10.0.0.5", + "10.0.0.6","10.0.0.7"] + assert_equal expected, arr + end + + def test_method_size + ip = @klass.new("10.0.0.1/29") + assert_equal 8, ip.size + end + + def test_method_hosts + ip = @klass.new("10.0.0.1/29") + expected = ["10.0.0.1","10.0.0.2","10.0.0.3", + "10.0.0.4","10.0.0.5","10.0.0.6"] + assert_equal expected, ip.hosts.map {|i| i.to_s} + end + + def test_method_network_u32 + assert_equal 2886732288, @ip.network_u32 + end + + def test_method_broadcast_u32 + assert_equal 2886732543, @ip.broadcast_u32 + end + + def test_method_include? + ip = @klass.new("192.168.10.100/24") + addr = @klass.new("192.168.10.102/24") + assert_equal true, ip.include?(addr) + assert_equal false, ip.include?(@klass.new("172.16.0.48")) + ip = @klass.new("10.0.0.0/8") + assert_equal true, ip.include?(@klass.new("10.0.0.0/9")) + assert_equal true, ip.include?(@klass.new("10.1.1.1/32")) + assert_equal true, ip.include?(@klass.new("10.1.1.1/9")) + assert_equal false, ip.include?(@klass.new("172.16.0.0/16")) + assert_equal false, ip.include?(@klass.new("10.0.0.0/7")) + assert_equal false, ip.include?(@klass.new("5.5.5.5/32")) + assert_equal false, ip.include?(@klass.new("11.0.0.0/8")) + ip = @klass.new("13.13.0.0/13") + assert_equal false, ip.include?(@klass.new("13.16.0.0/32")) + end + + def test_method_include_all? + ip = @klass.new("192.168.10.100/24") + addr1 = @klass.new("192.168.10.102/24") + addr2 = @klass.new("192.168.10.103/24") + assert_equal true, ip.include_all?(addr1,addr2) + assert_equal false, ip.include_all?(addr1, @klass.new("13.16.0.0/32")) + end + + def test_method_ipv4? + assert_equal true, @ip.ipv4? + end + + def test_method_ipv6? + assert_equal false, @ip.ipv6? + end + + def test_method_private? + assert_equal true, @klass.new("192.168.10.50/24").private? + assert_equal true, @klass.new("192.168.10.50/16").private? + assert_equal true, @klass.new("172.16.77.40/24").private? + assert_equal true, @klass.new("172.16.10.50/14").private? + assert_equal true, @klass.new("10.10.10.10/10").private? + assert_equal true, @klass.new("10.0.0.0/8").private? + assert_equal false, @klass.new("192.168.10.50/12").private? + assert_equal false, @klass.new("3.3.3.3").private? + assert_equal false, @klass.new("10.0.0.0/7").private? + assert_equal false, @klass.new("172.32.0.0/12").private? + assert_equal false, @klass.new("172.16.0.0/11").private? + assert_equal false, @klass.new("192.0.0.2/24").private? + end + + def test_method_octet + assert_equal 172, @ip[0] + assert_equal 16, @ip[1] + assert_equal 10, @ip[2] + assert_equal 1, @ip[3] + end + + def test_method_a? + assert_equal true, @class_a.a? + assert_equal false, @class_b.a? + assert_equal false, @class_c.a? + end + + def test_method_b? + assert_equal true, @class_b.b? + assert_equal false, @class_a.b? + assert_equal false, @class_c.b? + end + + def test_method_c? + assert_equal true, @class_c.c? + assert_equal false, @class_a.c? + assert_equal false, @class_b.c? + end + + def test_method_to_ipv6 + assert_equal "ac10:0a01", @ip.to_ipv6 + end + + def test_method_reverse + assert_equal "1.10.16.172.in-addr.arpa", @ip.reverse + end + + def test_method_comparabble + ip1 = @klass.new("10.1.1.1/8") + ip2 = @klass.new("10.1.1.1/16") + ip3 = @klass.new("172.16.1.1/14") + ip4 = @klass.new("10.1.1.1/8") + + # ip1 should be major than ip2 + assert_equal true, ip1 > ip2 + assert_equal false, ip1 < ip2 + assert_equal false, ip2 > ip1 + # ip2 should be minor than ip3 + assert_equal true, ip2 < ip3 + assert_equal false, ip2 > ip3 + # ip1 should be minor than ip3 + assert_equal true, ip1 < ip3 + assert_equal false, ip1 > ip3 + assert_equal false, ip3 < ip1 + # ip1 should be equal to itself + assert_equal true, ip1 == ip1 + # ip1 should be equal to ip4 + assert_equal true, ip1 == ip4 + # test sorting + arr = ["10.1.1.1/16","10.1.1.1/8","172.16.1.1/14"] + assert_equal arr, [ip1,ip2,ip3].sort.map{|s| s.to_string} + end + + def test_method_minus + ip1 = @klass.new("10.1.1.1/8") + ip2 = @klass.new("10.1.1.10/8") + assert_equal 9, ip2 - ip1 + assert_equal 9, ip1 - ip2 + end + + def test_method_plus + ip1 = @klass.new("172.16.10.1/24") + ip2 = @klass.new("172.16.11.2/24") + assert_equal ["172.16.10.0/23"], (ip1+ip2).map{|i| i.to_string} + + ip2 = @klass.new("172.16.12.2/24") + assert_equal [ip1.network.to_string, ip2.network.to_string], + (ip1 + ip2).map{|i| i.to_string} + + ip1 = @klass.new("10.0.0.0/23") + ip2 = @klass.new("10.0.2.0/24") + assert_equal ["10.0.0.0/23","10.0.2.0/24"], (ip1+ip2).map{|i| i.to_string} + + ip1 = @klass.new("10.0.0.0/23") + ip2 = @klass.new("10.0.2.0/24") + assert_equal ["10.0.0.0/23","10.0.2.0/24"], (ip2+ip1).map{|i| i.to_string} + + ip1 = @klass.new("10.0.0.0/16") + ip2 = @klass.new("10.0.2.0/24") + assert_equal ["10.0.0.0/16"], (ip1+ip2).map{|i| i.to_string} + + ip1 = @klass.new("10.0.0.0/23") + ip2 = @klass.new("10.1.0.0/24") + assert_equal ["10.0.0.0/23","10.1.0.0/24"], (ip1+ip2).map{|i| i.to_string} + + end + + def test_method_netmask_equal + ip = @klass.new("10.1.1.1/16") + assert_equal 16, ip.prefix.to_i + ip.netmask = "255.255.255.0" + assert_equal 24, ip.prefix.to_i + end + + def test_method_subnet + assert_raise(ArgumentError) {@ip.subnet(0)} + assert_raise(ArgumentError) {@ip.subnet(257)} + + arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", + "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27", + "172.16.10.192/27", "172.16.10.224/27"] + assert_equal arr, @network.subnet(8).map {|s| s.to_string} + arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", + "172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27", + "172.16.10.192/26"] + assert_equal arr, @network.subnet(7).map {|s| s.to_string} + arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", + "172.16.10.96/27", "172.16.10.128/26", "172.16.10.192/26"] + assert_equal arr, @network.subnet(6).map {|s| s.to_string} + arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27", + "172.16.10.96/27", "172.16.10.128/25"] + assert_equal arr, @network.subnet(5).map {|s| s.to_string} + arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26", + "172.16.10.192/26"] + assert_equal arr, @network.subnet(4).map {|s| s.to_string} + arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/25"] + assert_equal arr, @network.subnet(3).map {|s| s.to_string} + arr = ["172.16.10.0/25", "172.16.10.128/25"] + assert_equal arr, @network.subnet(2).map {|s| s.to_string} + arr = ["172.16.10.0/24"] + assert_equal arr, @network.subnet(1).map {|s| s.to_string} + end + + def test_method_supernet + assert_raise(ArgumentError) {@ip.supernet(0)} + assert_raise(ArgumentError) {@ip.supernet(24)} + assert_equal "172.16.10.0/23", @ip.supernet(23).to_string + assert_equal "172.16.8.0/22", @ip.supernet(22).to_string + end + + def test_classmethod_parse_u32 + @decimal_values.each do |addr,int| + ip = @klass.parse_u32(int) + ip.prefix = addr.split("/").last.to_i + assert_equal ip.to_string, addr + end + end + + def test_classhmethod_extract + str = "foobar172.16.10.1barbaz" + assert_equal "172.16.10.1", @klass.extract(str).to_s + end + + def test_classmethod_summarize + + # Should return self if only one network given + assert_equal [@ip.network], @klass.summarize(@ip) + + # Summarize homogeneous networks + ip1 = @klass.new("172.16.10.1/24") + ip2 = @klass.new("172.16.11.2/24") + assert_equal ["172.16.10.0/23"], @klass.summarize(ip1,ip2).map{|i| i.to_string} + + ip1 = @klass.new("10.0.0.1/24") + ip2 = @klass.new("10.0.1.1/24") + ip3 = @klass.new("10.0.2.1/24") + ip4 = @klass.new("10.0.3.1/24") + assert_equal ["10.0.0.0/22"], @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} + assert_equal ["10.0.0.0/22"], @klass.summarize(ip4,ip3,ip2,ip1).map{|i| i.to_string} + + # Summarize non homogeneous networks + ip1 = @klass.new("10.0.0.0/23") + ip2 = @klass.new("10.0.2.0/24") + assert_equal ["10.0.0.0/23","10.0.2.0/24"], @klass.summarize(ip1,ip2).map{|i| i.to_string} + + ip1 = @klass.new("10.0.0.0/16") + ip2 = @klass.new("10.0.2.0/24") + assert_equal ["10.0.0.0/16"], @klass.summarize(ip1,ip2).map{|i| i.to_string} + + ip1 = @klass.new("10.0.0.0/23") + ip2 = @klass.new("10.1.0.0/24") + assert_equal ["10.0.0.0/23","10.1.0.0/24"], @klass.summarize(ip1,ip2).map{|i| i.to_string} + + ip1 = @klass.new("10.0.0.0/23") + ip2 = @klass.new("10.0.2.0/23") + ip3 = @klass.new("10.0.4.0/24") + ip4 = @klass.new("10.0.6.0/24") + assert_equal ["10.0.0.0/22","10.0.4.0/24","10.0.6.0/24"], + @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} + + ip1 = @klass.new("10.0.1.1/24") + ip2 = @klass.new("10.0.2.1/24") + ip3 = @klass.new("10.0.3.1/24") + ip4 = @klass.new("10.0.4.1/24") + result = ["10.0.1.0/24","10.0.2.0/23","10.0.4.0/24"] + assert_equal result, @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} + assert_equal result, @klass.summarize(ip4,ip3,ip2,ip1).map{|i| i.to_string} + + ip1 = @klass.new("10.0.1.1/24") + ip2 = @klass.new("10.10.2.1/24") + ip3 = @klass.new("172.16.0.1/24") + ip4 = @klass.new("172.16.1.1/24") + result = ["10.0.1.0/24","10.10.2.0/24","172.16.0.0/23"] + assert_equal result, @klass.summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} + + ips = [@klass.new("10.0.0.12/30"), + @klass.new("10.0.100.0/24")] + result = ["10.0.0.12/30", "10.0.100.0/24"] + assert_equal result, @klass.summarize(*ips).map{|i| i.to_string} + + ips = [@klass.new("172.16.0.0/31"), + @klass.new("10.10.2.1/32")] + result = ["10.10.2.1/32", "172.16.0.0/31"] + assert_equal result, @klass.summarize(*ips).map{|i| i.to_string} + + ips = [@klass.new("172.16.0.0/32"), + @klass.new("10.10.2.1/32")] + result = ["10.10.2.1/32", "172.16.0.0/32"] + assert_equal result, @klass.summarize(*ips).map{|i| i.to_string} + + end + + def test_classmethod_parse_data + ip = @klass.parse_data "\254\020\n\001" + assert_instance_of @klass, ip + assert_equal "172.16.10.1", ip.address + assert_equal "172.16.10.1/32", ip.to_string + end + + def test_classmethod_parse_classful + @classful.each do |ip,prefix| + res = @klass.parse_classful(ip) + assert_equal prefix, res.prefix + assert_equal "#{ip}/#{prefix}", res.to_string + end + assert_raise(ArgumentError){ @klass.parse_classful("192.168.256.257") } + end + +end # class IPv4Test + + + +class IPv6Test < Test::Unit::TestCase + + def setup + @klass = IPAddress::IPv6 + + @compress_addr = { + "2001:db8:0000:0000:0008:0800:200c:417a" => "2001:db8::8:800:200c:417a", + "2001:db8:0:0:8:800:200c:417a" => "2001:db8::8:800:200c:417a", + "ff01:0:0:0:0:0:0:101" => "ff01::101", + "0:0:0:0:0:0:0:1" => "::1", + "0:0:0:0:0:0:0:0" => "::"} + + @valid_ipv6 = { # Kindly taken from the python IPy library + "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210" => 338770000845734292534325025077361652240, + "1080:0000:0000:0000:0008:0800:200C:417A" => 21932261930451111902915077091070067066, + "1080:0:0:0:8:800:200C:417A" => 21932261930451111902915077091070067066, + "1080:0::8:800:200C:417A" => 21932261930451111902915077091070067066, + "1080::8:800:200C:417A" => 21932261930451111902915077091070067066, + "FF01:0:0:0:0:0:0:43" => 338958331222012082418099330867817087043, + "FF01:0:0::0:0:43" => 338958331222012082418099330867817087043, + "FF01::43" => 338958331222012082418099330867817087043, + "0:0:0:0:0:0:0:1" => 1, + "0:0:0::0:0:1" => 1, + "::1" => 1, + "0:0:0:0:0:0:0:0" => 0, + "0:0:0::0:0:0" => 0, + "::" => 0, + "1080:0:0:0:8:800:200C:417A" => 21932261930451111902915077091070067066, + "1080::8:800:200C:417A" => 21932261930451111902915077091070067066} + + @invalid_ipv6 = [":1:2:3:4:5:6:7", + ":1:2:3:4:5:6:7"] + + @ip = @klass.new "2001:db8::8:800:200c:417a/64" + @network = @klass.new "2001:db8:8:800::/64" + @arr = [8193,3512,0,0,8,2048,8204,16762] + @hex = "20010db80000000000080800200c417a" + end + + def test_attribute_address + addr = "2001:0db8:0000:0000:0008:0800:200c:417a" + assert_equal addr, @ip.address + end + + def test_initialize + assert_instance_of @klass, @ip + @valid_ipv6.keys.each do |ip| + assert_nothing_raised {@klass.new ip} + end + @invalid_ipv6.each do |ip| + assert_raise(ArgumentError) {@klass.new ip} + end + assert_equal 64, @ip.prefix + + assert_raise(ArgumentError) { + @klass.new "::10.1.1.1" + } + end + + def test_attribute_groups + assert_equal @arr, @ip.groups + end + + def test_method_hexs + arr = "2001:0db8:0000:0000:0008:0800:200c:417a".split(":") + assert_equal arr, @ip.hexs + end + + def test_method_to_i + @valid_ipv6.each do |ip,num| + assert_equal num, @klass.new(ip).to_i + end + end + + def test_method_bits + bits = "0010000000000001000011011011100000000000000000000" + + "000000000000000000000000000100000001000000000000010000" + + "0000011000100000101111010" + assert_equal bits, @ip.bits + end + + def test_method_prefix=() + ip = @klass.new "2001:db8::8:800:200c:417a" + assert_equal 128, ip.prefix + ip.prefix = 64 + assert_equal 64, ip.prefix + assert_equal "2001:db8::8:800:200c:417a/64", ip.to_string + end + + def test_method_mapped? + assert_equal false, @ip.mapped? + ip6 = @klass.new "::ffff:1234:5678" + assert_equal true, ip6.mapped? + end + + def test_method_literal + str = "2001-0db8-0000-0000-0008-0800-200c-417a.ipv6-literal.net" + assert_equal str, @ip.literal + end + + def test_method_group + @arr.each_with_index do |val,index| + assert_equal val, @ip[index] + end + end + + def test_method_ipv4? + assert_equal false, @ip.ipv4? + end + + def test_method_ipv6? + assert_equal true, @ip.ipv6? + end + + def test_method_network? + assert_equal true, @network.network? + assert_equal false, @ip.network? + end + + def test_method_network_u128 + assert_equal 42540766411282592856903984951653826560, @ip.network_u128 + end + + def test_method_include? + assert_equal true, @ip.include?(@ip) + # test prefix on same address + included = @klass.new "2001:db8::8:800:200c:417a/128" + not_included = @klass.new "2001:db8::8:800:200c:417a/46" + assert_equal true, @ip.include?(included) + assert_equal false, @ip.include?(not_included) + # test address on same prefix + included = @klass.new "2001:db8::8:800:200c:0/64" + not_included = @klass.new "2001:db8:1::8:800:200c:417a/64" + assert_equal true, @ip.include?(included) + assert_equal false, @ip.include?(not_included) + # general test + included = @klass.new "2001:db8::8:800:200c:1/128" + not_included = @klass.new "2001:db8:1::8:800:200c:417a/76" + assert_equal true, @ip.include?(included) + assert_equal false, @ip.include?(not_included) + end + + def test_method_to_hex + assert_equal @hex, @ip.to_hex + end + + def test_method_to_s + assert_equal "2001:db8::8:800:200c:417a", @ip.to_s + end + + def test_method_to_string + assert_equal "2001:db8::8:800:200c:417a/64", @ip.to_string + end + + def test_method_to_string_uncompressed + str = "2001:0db8:0000:0000:0008:0800:200c:417a/64" + assert_equal str, @ip.to_string_uncompressed + end + + def test_method_data + str = " \001\r\270\000\000\000\000\000\b\b\000 \fAz" + assert_equal str, @ip.data + end + + def test_method_reverse + str = "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa" + assert_equal str, @klass.new("3ffe:505:2::f").reverse + end + + def test_method_compressed + assert_equal "1:1:1::1", @klass.new("1:1:1:0:0:0:0:1").compressed + assert_equal "1:0:1::1", @klass.new("1:0:1:0:0:0:0:1").compressed + assert_equal "1:0:0:1::1", @klass.new("1:0:0:1:0:0:0:1").compressed + assert_equal "1::1:0:0:1", @klass.new("1:0:0:0:1:0:0:1").compressed + assert_equal "1::1", @klass.new("1:0:0:0:0:0:0:1").compressed + end + + def test_method_unspecified? + assert_equal true, @klass.new("::").unspecified? + assert_equal false, @ip.unspecified? + end + + def test_method_loopback? + assert_equal true, @klass.new("::1").loopback? + assert_equal false, @ip.loopback? + end + + def test_classmethod_expand + compressed = "2001:db8:0:cd30::" + expanded = "2001:0db8:0000:cd30:0000:0000:0000:0000" + assert_equal expanded, @klass.expand(compressed) + assert_not_equal expanded, @klass.expand("2001:0db8:0:cd3") + assert_not_equal expanded, @klass.expand("2001:0db8::cd30") + assert_not_equal expanded, @klass.expand("2001:0db8::cd3") + end + + def test_classmethod_compress + compressed = "2001:db8:0:cd30::" + expanded = "2001:0db8:0000:cd30:0000:0000:0000:0000" + assert_equal compressed, @klass.compress(expanded) + assert_not_equal compressed, @klass.compress("2001:0db8:0:cd3") + assert_not_equal compressed, @klass.compress("2001:0db8::cd30") + assert_not_equal compressed, @klass.compress("2001:0db8::cd3") + end + + def test_classmethod_parse_data + str = " \001\r\270\000\000\000\000\000\b\b\000 \fAz" + ip = @klass.parse_data str + assert_instance_of @klass, ip + assert_equal "2001:0db8:0000:0000:0008:0800:200c:417a", ip.address + assert_equal "2001:db8::8:800:200c:417a/128", ip.to_string + end + + def test_classhmethod_parse_u128 + @valid_ipv6.each do |ip,num| + assert_equal @klass.new(ip).to_s, @klass.parse_u128(num).to_s + end + end + + def test_classmethod_parse_hex + assert_equal @ip.to_s, @klass.parse_hex(@hex,64).to_s + end + +end # class IPv6Test + +class IPv6UnspecifiedTest < Test::Unit::TestCase + + def setup + @klass = IPAddress::IPv6::Unspecified + @ip = @klass.new + @s = "::" + @str = "::/128" + @string = "0000:0000:0000:0000:0000:0000:0000:0000/128" + @u128 = 0 + @address = "::" + end + + def test_initialize + assert_nothing_raised {@klass.new} + assert_instance_of @klass, @ip + end + + def test_attributes + assert_equal @address, @ip.compressed + assert_equal 128, @ip.prefix + assert_equal true, @ip.unspecified? + assert_equal @s, @ip.to_s + assert_equal @str, @ip.to_string + assert_equal @string, @ip.to_string_uncompressed + assert_equal @u128, @ip.to_u128 + end + + def test_method_ipv6? + assert_equal true, @ip.ipv6? + end + +end # class IPv6UnspecifiedTest + + +class IPv6LoopbackTest < Test::Unit::TestCase + + def setup + @klass = IPAddress::IPv6::Loopback + @ip = @klass.new + @s = "::1" + @str = "::1/128" + @string = "0000:0000:0000:0000:0000:0000:0000:0001/128" + @u128 = 1 + @address = "::1" + end + + def test_initialize + assert_nothing_raised {@klass.new} + assert_instance_of @klass, @ip + end + + def test_attributes + assert_equal @address, @ip.compressed + assert_equal 128, @ip.prefix + assert_equal true, @ip.loopback? + assert_equal @s, @ip.to_s + assert_equal @str, @ip.to_string + assert_equal @string, @ip.to_string_uncompressed + assert_equal @u128, @ip.to_u128 + end + + def test_method_ipv6? + assert_equal true, @ip.ipv6? + end + +end # class IPv6LoopbackTest + +class IPv6MappedTest < Test::Unit::TestCase + + def setup + @klass = IPAddress::IPv6::Mapped + @ip = @klass.new("::172.16.10.1") + @s = "::ffff:172.16.10.1" + @str = "::ffff:172.16.10.1/128" + @string = "0000:0000:0000:0000:0000:ffff:ac10:0a01/128" + @u128 = 281473568475649 + @address = "::ffff:ac10:a01" + + @valid_mapped = {'::13.1.68.3' => 281470899930115, + '0:0:0:0:0:ffff:129.144.52.38' => 281472855454758, + '::ffff:129.144.52.38' => 281472855454758} + + @valid_mapped_ipv6 = {'::0d01:4403' => 281470899930115, + '0:0:0:0:0:ffff:8190:3426' => 281472855454758, + '::ffff:8190:3426' => 281472855454758} + + @valid_mapped_ipv6_conversion = {'::0d01:4403' => "13.1.68.3", + '0:0:0:0:0:ffff:8190:3426' => "129.144.52.38", + '::ffff:8190:3426' => "129.144.52.38"} + + end + + def test_initialize + assert_nothing_raised {@klass.new("::172.16.10.1")} + assert_instance_of @klass, @ip + @valid_mapped.each do |ip, u128| + assert_nothing_raised {@klass.new ip} + assert_equal u128, @klass.new(ip).to_u128 + end + @valid_mapped_ipv6.each do |ip, u128| + assert_nothing_raised {@klass.new ip} + assert_equal u128, @klass.new(ip).to_u128 + end + end + + def test_mapped_from_ipv6_conversion + @valid_mapped_ipv6_conversion.each do |ip6,ip4| + assert_equal ip4, @klass.new(ip6).ipv4.to_s + end + end + + def test_attributes + assert_equal @address, @ip.compressed + assert_equal 128, @ip.prefix + assert_equal @s, @ip.to_s + assert_equal @str, @ip.to_string + assert_equal @string, @ip.to_string_uncompressed + assert_equal @u128, @ip.to_u128 + end + + def test_method_ipv6? + assert_equal true, @ip.ipv6? + end + + def test_mapped? + assert_equal true, @ip.mapped? + end + +end # class IPv6MappedTest + |