ipaddress-0.8.3/0000755000004100000410000000000012666647272013552 5ustar www-datawww-dataipaddress-0.8.3/Rakefile0000644000004100000410000000261012666647272015216 0ustar www-datawww-datarequire 'rubygems' require 'rake' require 'rake/clean' require "bundler/gem_tasks" require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' test.pattern = 'test/**/*_test.rb' test.verbose = true end begin require 'rcov/rcovtask' Rcov::RcovTask.new do |test| test.libs << 'test' test.pattern = 'test/**/*_test.rb' test.verbose = true end rescue LoadError task :rcov do abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov" end end task :default => :test require 'rdoc/task' Rake::RDocTask.new do |rdoc| if File.exist?('VERSION.yml') config = YAML.load(File.read('VERSION.yml')) version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}" else version = "" end rdoc.rdoc_dir = 'rdoc' rdoc.title = "ipaddress #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end desc "Open an irb session preloaded with this library" task :console do sh "irb -rubygems -I lib -r ipaddress.rb" end desc "Look for TODO and FIXME tags in the code" task :todo do def egrep(pattern) Dir['**/*.rb'].each do |fn| count = 0 open(fn) do |f| while line = f.gets count += 1 if line =~ pattern puts "#{fn}:#{count}:#{line}" end end end end end egrep /(FIXME|TODO|TBD)/ end ipaddress-0.8.3/Gemfile0000644000004100000410000000004712666647272015046 0ustar www-datawww-datasource "https://rubygems.org" gemspec ipaddress-0.8.3/CHANGELOG.rdoc0000644000004100000410000000767512666647272015731 0ustar www-datawww-data== ipaddress 0.8.2 CHANGED:: merged bundler branch to cleanup gemspec and Rakefiles FIXED:: IPAddress::IPv4.split handling (Issue #40) NEW:: Added #[]= method to IPv4/6 classes to add octet writing support. (Issue #24) NEW:: IPV4#multicast? NEW:: IPV4#loopback? NEW:: IPV4#to() == ipaddress 0.8.1 CHANGED:: ipaddress now uses minitest for testing, all tests passing == ipaddress 0.8.0 CHANGED:: ipaddress now uses this[https://gist.github.com/cpetschnig/294476] regexp to validate IPv6 addresses. Thanks to Christoph Petschnig for his regexp and to Bronislav Robenek for fixing this. CHANGED:: Removed extension methods and extension directory to facilitate integration with the stdlib CHANGED:: Reworked IPv4#<=>, now intuitively sorts objects based on the prefix CHANGED:: IPv4#supernet now returns "0.0.0.0/0" if supernetting with a prefix less than 1 CHANGED:: IPv4#subnet now accept a new prefix instead of number of subnets (as per RFC3531) NEW:: IPv6#network NEW:: Prefix128#host_prefix NEW:: IPv6#broadcast_u128 NEW:: IPv6#each NEW:: IPv6#<=> NEW:: IPv4#split NEW:: IP#ntoa == 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 ipaddress-0.8.3/LICENSE.txt0000644000004100000410000000204512666647272015376 0ustar www-datawww-dataCopyright (c) 2009-2015 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. ipaddress-0.8.3/README.rdoc0000644000004100000410000006756312666647272015401 0ustar www-datawww-data== IPAddress 1.0 is currently under development and will be released soon! Stay tuned! --- = 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.7 (not tested with previous versions) * Ruby 1.9.2 or later is strongly recommended IPAddress 0.8.2 has been tested on: * ruby-1.8.7-p334 [ i386 ] * ree-1.8.7-2011.03 [ i386 ] * rbx-head [ ] * jruby-1.6.1 [ linux-i386-java ] * ruby-1.9.1-p431 [ i386 ] * ruby-1.9.2-p180 [ i386 ] * ruby-2.0.0-p353 [ x86_64-darwin14.0.0 ] * ruby-2.1.3-p242 [ x86_64-darwin14.0.0 ] 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]. == 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.8.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 (uint32, 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" puts host.to_string #=> "10.1.1.1/32" The new created object has prefix `/32`, which is the same as we created the following: host = IPAddress::IPv4.new "10.1.1.1/32" You can also pass a `uint32` to obtain an `IPAddress::IPv4` object: # Create host object ip = IPAddress 167837953 puts ip.to_string #=> "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 #=> # net.to_string #=> "172.16.10.0/24" Method IPv4#network 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 #=> # 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" Checking if an address is loopback is easy with the `IPv4#loopback?` method: ip = IPAddress "127.0.0.1" ip.loopback? #=> true Checking if an address is in the multicast range can be done using the `IPv4#multicast?` method: ip = IPAddress "224.0.0.1/32" ip.multicast? #=> true The ability to generate a range also exists by using the `IPv4#to()` method. This allows you to create a subnet agnostic range based off a fixed amount. ip = IPAddress "172.16.10.100/24" ip.to('172.16.10.110') #=> ["172.16.10.100", ..., "172.16.10.110"] === 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" Also, you can transform an IPv4 address into a format which is suitable to use in IPv4-IPv6 mapped addresses: ip.to_ipv6 #=> "ac10:0a01" Finally, much like `IPv4#to_ipv6` you can use to `IPv4#to_h` method to return a non-semicolon delineated string (useful with pcap/byte level usage): ip.to_h #=> "ac100a01" === 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 divided 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. You actually have two options: * IPv4#subnet: specify a new prefix * IPv4#split: tell IPAddress how many subnets you want to create. Let's examine `IPv4#subnet` first. Say you have network "172.16.10.0/24" and you want to subnet it into /26 networks. With IPAddress it's very easy: network = IPAddress "172.16.10.0/24" subnets = network.subnet(26) 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"] As you can see, an Array has been created, containing 4 new IPv4 objects representing the new subnets. Another way to create subnets is to tell IPAddress how many subnets you'd like to have, and letting the library calculate the new prefix for you. Let's see how it works, using `IPv4#split` method. Say you want 4 new subnets: network = IPAddress("172.16.10.0/24") subnets = network.split(4) 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"] Hey, that's the same result as before! This actually makes sense, as the two operations are complementary. When you use `IPv4#subnet` with the new prefix, IPAddress will always create a number of subnets that is a power of two. This is equivalent to use IPv4#split with a power of 2. Where `IPv4#split` really shines is with the so called "uneven subnetting". You are not limited to split a network into a power-of-two numbers of subnets: IPAddress lets you create any number of subnets, and it will try to organize the new created network in the best possible way, making an efficient allocation of the space. An example here is worth a thousand words. Let's use the same network as the previous examples: network = IPAddress("172.16.10.0/24") How do we split this network into 3 subnets? Very easy: subnets = network.split(3) subnets.map{|i| i.to_string} #=> ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/25"] As you can see, IPAddress tried to perform a good allocation by filling up all the address space from the original network. There is no point in splitting a network into 3 subnets like `172.16.10.0/26`, `172.16.10.64/26` and `172.16.10.128/26`, as you would end up having `172.16.10.192/26` wasted (plus, I suppose I wouldn't need a Ruby library to perform un-efficient IP allocation, as I do that myself very well ;) ). We can go even further and split into 11 subnets: network.split(11) #=> ["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).map(&: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: 2001:0db8: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 2001:db8::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: 2001:db8::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 "2001:0db8:0000:0000:0008:0800:200C:417A" ip6 = IPAddress "2001:db8:0:0:8:800:200C:417A" ip6 = IPAddress "2001:db8: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 = 42540766411282592856906245548098208122 ip6 = IPAddress::IPv6::parse_u128 u128 ip6.prefix = 64 ip6.to_string #=>"2001:db8::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. == 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. == 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, Leif Gensert, Erik Ahlström, Peter Vandenberk and Steve Rawlinson for their support, feedback and bug reports. == Copyright Copyright (c) 2009-2015 Marco Ceresa and Mike Mackintosh. See LICENSE for details. ipaddress-0.8.3/.rock.yml0000644000004100000410000000010612666647272015306 0ustar www-datawww-dataruntime: ruby21 doc: exec rake rdoc build: | rock clean {{parent}}ipaddress-0.8.3/lib/0000755000004100000410000000000012666647272014320 5ustar www-datawww-dataipaddress-0.8.3/lib/ipaddress.rb0000644000004100000410000001350612666647272016630 0ustar www-datawww-data# # = IPAddress # # A ruby library to manipulate IPv4 and IPv6 addresses # # # Package:: IPAddress # Author:: Marco Ceresa # License:: Ruby License # #-- # #++ require 'ipaddress/ipv4' require 'ipaddress/ipv6' require 'ipaddress/mongoid' if defined?(Mongoid) module IPAddress NAME = "IPAddress" GEM = "ipaddress" AUTHORS = ["Marco Ceresa "] # # Parse the argument string to create a new # IPv4, IPv6 or Mapped IP object # # ip = IPAddress.parse 167837953 # 10.1.1.1 # ip = IPAddress.parse "172.16.10.1/24" # ip6 = IPAddress.parse "2001:db8::8:800:200c:417a/64" # ip_mapped = IPAddress.parse "::ffff:172.16.10.1/128" # # All the object created will be instances of the # correct class: # # ip.class # #=> IPAddress::IPv4 # ip6.class # #=> IPAddress::IPv6 # ip_mapped.class # #=> IPAddress::IPv6::Mapped # def IPAddress::parse(str) # Check if an int was passed if str.kind_of? Integer return IPAddress::IPv4.new(ntoa(str)) end case str when /:.+\./ IPAddress::IPv6::Mapped.new(str) when /\./ IPAddress::IPv4.new(str) when /:/ IPAddress::IPv6.new(str) else raise ArgumentError, "Unknown IP Address #{str}" end end # # Converts a unit32 to IPv4 # # IPAddress::ntoa(167837953) # #-> "10.1.1.1" # def self.ntoa(uint) unless(uint.is_a? Numeric and uint <= 0xffffffff and uint >= 0) raise(::ArgumentError, "not a long integer: #{uint.inspect}") end ret = [] 4.times do ret.unshift(uint & 0xff) uint >>= 8 end ret.join('.') end # # True if the object is an IPv4 address # # ip = IPAddress("192.168.10.100/24") # # ip.ipv4? # #-> true # def ipv4? self.kind_of? IPAddress::IPv4 end # # True if the object is an IPv6 address # # ip = IPAddress("192.168.10.100/24") # # ip.ipv6? # #-> false # def ipv6? self.kind_of? IPAddress::IPv6 end # # Checks if the given string is a valid IP address, # either IPv4 or IPv6 # # Example: # # IPAddress::valid? "2002::1" # #=> true # # IPAddress::valid? "10.0.0.256" # #=> false # def self.valid?(addr) valid_ipv4?(addr) || valid_ipv6?(addr) end # # Checks if the given string is a valid IPv4 address # # Example: # # IPAddress::valid_ipv4? "2002::1" # #=> false # # IPAddress::valid_ipv4? "172.16.10.1" # #=> true # def self.valid_ipv4?(addr) if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ addr return $~.captures.all? {|i| i.to_i < 256} end false end # # Checks if the argument is a valid IPv4 netmask # expressed in dotted decimal format. # # IPAddress.valid_ipv4_netmask? "255.255.0.0" # #=> true # def self.valid_ipv4_netmask?(addr) arr = addr.split(".").map{|i| i.to_i}.pack("CCCC").unpack("B*").first.scan(/01/) arr.empty? && valid_ipv4?(addr) rescue return false end # # Checks if the given string is a valid IPv6 address # # Example: # # IPAddress::valid_ipv6? "2002::1" # #=> true # # IPAddress::valid_ipv6? "2002::DEAD::BEEF" # #=> false # def self.valid_ipv6?(addr) # https://gist.github.com/cpetschnig/294476 # http://forums.intermapper.com/viewtopic.php?t=452 return true if /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/ =~ addr false end # # Deprecate method # def self.deprecate(message = nil) # :nodoc: message ||= "You are using deprecated behavior which will be removed from the next major or minor release." warn("DEPRECATION WARNING: #{message}") end end # module IPAddress # # IPAddress is a wrapper method built around # IPAddress's library classes. Its purpouse is to # make you indipendent from the type of IP address # you're going to use. # # For example, instead of creating the three types # of IP addresses using their own contructors # # ip = IPAddress::IPv4.new "172.16.10.1/24" # ip6 = IPAddress::IPv6.new "2001:db8::8:800:200c:417a/64" # ip_mapped = IPAddress::IPv6::Mapped "::ffff:172.16.10.1/128" # # you can just use the IPAddress wrapper: # # ip = IPAddress "172.16.10.1/24" # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" # ip_mapped = IPAddress "::ffff:172.16.10.1/128" # # All the object created will be instances of the # correct class: # # ip.class # #=> IPAddress::IPv4 # ip6.class # #=> IPAddress::IPv6 # ip_mapped.class # #=> IPAddress::IPv6::Mapped # def IPAddress(str) IPAddress::parse str end # # Compatibility with Ruby 1.8 # if RUBY_VERSION =~ /^1\.8/ class Hash # :nodoc: alias :key :index end module Math # :nodoc: def Math.log2(n) log(n) / log(2) end end end ipaddress-0.8.3/lib/ipaddress/0000755000004100000410000000000012666647272016276 5ustar www-datawww-dataipaddress-0.8.3/lib/ipaddress/prefix.rb0000644000004100000410000001221012666647272020114 0ustar www-datawww-datamodule 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 (0..128).include? num.to_i raise ArgumentError, "Prefix must be in range 0..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 # # Returns the length of the host portion # of a netmask. # # prefix = Prefix128.new 96 # # prefix.host_prefix # #=> 32 # def host_prefix 128 - @prefix end end # class Prefix123 < Prefix end # module IPAddress ipaddress-0.8.3/lib/ipaddress/version.rb0000644000004100000410000000005112666647272020304 0ustar www-datawww-datamodule Ipaddress VERSION = "0.8.3" end ipaddress-0.8.3/lib/ipaddress/mongoid.rb0000644000004100000410000000317612666647272020266 0ustar www-datawww-datamodule IPAddress # # Mongoid field serialization # # IPAddress objects are converted to String # # IPAddress.mongoize IPAddress.parse("172.16.10.1") # #=> "172.16.10.1" # # Prefix will be removed from host adresses # # IPAddress.mongoize "172.16.10.1/32" # #=> "172.16.10.1" # # Prefix will be kept for network addresses # # IPAddress.mongoize "172.16.10.1/24" # #=> "172.16.10.1/24" # # IPv6 addresses will be stored uncompressed to ease DB search and sorting # # IPAddress.mongoize "2001:db8::8:800:200c:417a" # #=> "2001:0db8:0000:0000:0008:0800:200c:417a" # IPAddress.mongoize "2001:db8::8:800:200c:417a/64" # #=> "2001:0db8:0000:0000:0008:0800:200c:417a/64" # # Invalid addresses will be serialized as nil # # IPAddress.mongoize "invalid" # #=> nil # IPAddress.mongoize "" # #=> nil # IPAddress.mongoize 1 # #=> nil # IPAddress.mongoize nil # #=> nil # def self.mongoize(ipaddress) ipaddress = self.parse(ipaddress) unless ipaddress.is_a?(IPAddress) if ipaddress.bits.length == ipaddress.prefix ipaddress.address elsif ipaddress.is_a?(IPAddress::IPv6) ipaddress.to_string_uncompressed else ipaddress.to_string end rescue ArgumentError nil end # # Mongoid field deserialization # def self.demongoize(string) parse(string) rescue ArgumentError nil end # # Delegates to IPAddress.mongoize # def self.evolve(ipaddress) mongoize(ipaddress) end # # Sends self object to IPAddress#mongoize # def mongoize IPAddress.mongoize(self) end endipaddress-0.8.3/lib/ipaddress/ipv4.rb0000644000004100000410000006656412666647272017526 0ustar www-datawww-datarequire '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 in # hex # # ip = IPAddress("10.0.0.0") # # ip.to_h # #=> 0a000000 # def hex(space=true) "%.4x%.4x" % [to_u32].pack("N").unpack("nn") end alias_method :to_h, :hex alias_method :to_hex, :hex # # 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, :[] # # Updated the octet specified at index # # ip = IPAddress("172.16.100.50/24") # ip[2] = 200 # # #=> # @prefix=32, @octets=[172, 16, 200, 1], @u32=2886780929> # def []=(index, value) @octets[index] = value.to_i initialize("#{@octets.join('.')}/#{prefix}") 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 case when prefix <= 30 self.class.parse_u32(broadcast_u32, @prefix) when prefix == 31 self.class.parse_u32(-1, @prefix) when prefix == 32 return self end 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? (@prefix < 32) && (@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 case when prefix <= 30 self.class.parse_u32(network_u32+1, @prefix) when prefix == 31 self.class.parse_u32(network_u32, @prefix) when prefix == 32 return self end 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 case when prefix <= 30 self.class.parse_u32(broadcast_u32-1, @prefix) when prefix == 31 self.class.parse_u32(broadcast_u32, @prefix) when prefix == 32 return self end 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 IPv4 objects # # Comparing IPv4 addresses is useful to ordinate # them into lists that match our intuitive # perception of ordered IP addresses. # # The first comparison criteria is the u32 value. # For example, 10.100.100.1 will be considered # to be less than 172.16.0.1, because, in a ordered list, # we expect 10.100.100.1 to come before 172.16.0.1. # # The second criteria, in case two IPv4 objects # have identical addresses, is the prefix. An higher # prefix will be considered greater than a lower # prefix. This is because we expect to see # 10.100.100.0/24 come before 10.100.100.0/25. # # 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 # # [ip1,ip2,ip3].sort.map{|i| i.to_string} # #=> ["10.100.100.1/8","10.100.100.1/16","172.16.0.1/16"] # def <=>(oth) return prefix <=> oth.prefix if to_u32 == oth.to_u32 to_u32 <=> oth.to_u32 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 # # 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 # # Checks if an IPv4 address objects belongs # to a multicast network RFC3171 # # Example: # # ip = IPAddress "224.0.0.0/4" # ip.multicast? # #=> true # def multicast? [self.class.new("224.0.0.0/4")].any? {|i| i.include? self} end # # Checks if an IPv4 address objects belongs # to a multicast network RFC3171 # # Example: # # ip = IPAddress "224.0.0.0/4" # ip.multicast? # #=> true # def multicast? [self.class.new("224.0.0.0/4")].any? {|i| i.include? self} end # # Checks if an IPv4 address objects belongs # to a loopback network RFC1122 # # Example: # # ip = IPAddress "127.0.0.1" # ip.loopback? # #=> true # def loopback? [self.class.new("127.0.0.0/8")].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 # # Return a list of IP's between @address # and the supplied IP # # ip = IPAddress("172.16.100.51/32") # # ip.to("172.16.100.100") # #=> ["172.16.100.51", # #=> "172.16.100.52", # #=> ... # #=> "172.16.100.99", # #=> "172.16.100.100"] # def to(e) unless e.is_a? IPAddress::IPv4 e = IPv4.new(e) end Range.new(@u32, e.to_u32).map{|i| IPAddress.ntoa(i) } end # # Splits a network into different subnets # # If the IP Address is a network, it can be divided into # multiple networks. If +self+ is not a network, this # 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 IPv4 objects # def split(subnets=2) unless (1..(2**@prefix.host_prefix)).include? subnets raise ArgumentError, "Value #{subnets} out of range" end networks = subnet(newprefix(subnets)) until networks.size == subnets networks = sum_first_found(networks) end return networks end alias_method :/, :split # # 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" # # If +new_prefix+ is less than 1, returns 0.0.0.0/0 # def supernet(new_prefix) raise ArgumentError, "New prefix must be smaller than existing prefix" if new_prefix >= @prefix.to_i return self.class.new("0.0.0.0/0") if new_prefix < 1 return self.class.new(@address+"/#{new_prefix}").network end # # This method implements the subnetting function # similar to the one described in RFC3531. # # By specifying a new prefix, the method calculates # the network number for the given IPv4 object # and calculates the subnets associated to the new # prefix. # # For example, given the following network: # # ip = IPAddress "172.16.10.0/24" # # we can calculate the subnets with a /26 prefix # # ip.subnets(26).map{&:to_string) # #=> ["172.16.10.0/26", "172.16.10.64/26", # "172.16.10.128/26", "172.16.10.192/26"] # # The resulting number of subnets will of course always be # a power of two. # def subnet(subprefix) unless ((@prefix.to_i)..32).include? subprefix raise ArgumentError, "New prefix must be between #@prefix and 32" end Array.new(2**(subprefix-@prefix.to_i)) do |i| self.class.parse_u32(network_u32+(i*(2**(32-subprefix))), subprefix) end 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 # # Example: # # ip = IPAddress::IPv4.parse_classful "10.0.0.1" # # ip.netmask # #=> "255.0.0.0" # ip.a? # #=> true # # Note that classes C, D and E will all have a default # prefix of /24 or 255.255.255.0 # 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 # Tweaked to remove the #upto(32) def newprefix(num) return @prefix + (Math::log2(num).ceil ) end def sum_first_found(arr) dup = arr.dup.reverse dup.each_with_index do |obj,i| a = [self.class.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 ipaddress-0.8.3/lib/ipaddress/ipv6.rb0000644000004100000410000005462412666647272017522 0ustar www-datawww-datarequire '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: # # 2001:0db8: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 # # 2001:db8::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: # # 2001:db8::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: # # * "2001:0db8:0000:0000:0008:0800:200C:417A": IPv6 address with no compression # * "2001:db8:0:0:8:800:200C:417A": IPv6 address with leading zeros compression # * "2001:db8::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, :[] # # Updated the octet specified at index # def []=(index, value) @groups[index] = value initialize("#{IN6FORMAT % @groups}/#{prefix}") 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 # # Returns the broadcast address in Unsigned 128bits format # # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" # # ip6.broadcast_u128 # #=> 42540766411282592875350729025363378175 # # Please note that there is no Broadcast concept in IPv6 # addresses as in IPv4 addresses, and this method is just # an helper to other functions. # def broadcast_u128 network_u128 + size - 1 end # # Returns the number of IP addresses included # in the network. It also counts the network # address and the broadcast address. # # ip6 = IPAddress("2001:db8::8:800:200c:417a/64") # # ip6.size # #=> 18446744073709551616 # def size 2 ** @prefix.host_prefix 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 # # Iterates over all the IP addresses for the given # network (or IP address). # # The object yielded is a new IPv6 object created # from the iteration. # # ip6 = IPAddress("2001:db8::4/125") # # ip6.each do |i| # p i.compressed # end # #=> "2001:db8::" # #=> "2001:db8::1" # #=> "2001:db8::2" # #=> "2001:db8::3" # #=> "2001:db8::4" # #=> "2001:db8::5" # #=> "2001:db8::6" # #=> "2001:db8::7" # # WARNING: if the host portion is very large, this method # can be very slow and possibly hang your system! # def each (network_u128..broadcast_u128).each do |i| yield self.class.parse_u128(i, @prefix) end end # # Spaceship operator to compare IPv6 objects # # Comparing IPv6 addresses is useful to ordinate # them into lists that match our intuitive # perception of ordered IP addresses. # # The first comparison criteria is the u128 value. # For example, 2001:db8:1::1 will be considered # to be less than 2001:db8:2::1, because, in a ordered list, # we expect 2001:db8:1::1 to come before 2001:db8:2::1. # # The second criteria, in case two IPv6 objects # have identical addresses, is the prefix. An higher # prefix will be considered greater than a lower # prefix. This is because we expect to see # 2001:db8:1::1/64 come before 2001:db8:1::1/65 # # Example: # # ip1 = IPAddress "2001:db8:1::1/64" # ip2 = IPAddress "2001:db8:2::1/64" # ip3 = IPAddress "2001:db8:1::1/65" # # ip1 < ip2 # #=> true # ip1 < ip3 # #=> false # # [ip1,ip2,ip3].sort.map{|i| i.to_string} # #=> ["2001:db8:1::1/64","2001:db8:1::1/65","2001:db8:2::1/64"] # def <=>(oth) return prefix <=> oth.prefix if to_u128 == oth.to_u128 to_u128 <=> oth.to_u128 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 # # Returns a new IPv6 object with the network number # for the given IP. # # ip = IPAddress "2001:db8:1:1:1:1:1:1/32" # # ip.network.to_string # #=> "2001:db8::/32" # def network self.class.parse_u128(network_u128, @prefix) 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(42540766411282592856906245548098208122) # ip6.prefix = 64 # # ip6.to_string # #=> "2001:db8::8:800:200c:417a/64" # # The +prefix+ parameter is optional: # # ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122, 64) # # ip6.to_string # #=> "2001:db8::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_string # #=> "2001:db8::8:800:200c:417a/64" # # The +prefix+ parameter is optional: # # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a", 64) # # ip6.to_string # #=> "2001:db8::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_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. # class IPAddress::IPv6::Loopback < IPAddress::IPv6 # # Creates a new IPv6 unspecified address # # ip = IPAddress::IPv6::Loopback.new # # ip.to_string # #=> "::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_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. # 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_string # #=> "::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 ipaddress-0.8.3/test/0000755000004100000410000000000012666647272014531 5ustar www-datawww-dataipaddress-0.8.3/test/ipaddress_test.rb0000644000004100000410000000535012666647272020076 0ustar www-datawww-datarequire 'test_helper' class IPAddressTest < Minitest::Test def setup @valid_ipv4 = "172.16.10.1/24" @valid_ipv6 = "2001:db8::8:800:200c:417a/64" @valid_mapped = "::13.1.68.3" @invalid_ipv4 = "10.0.0.256" @invalid_ipv6 = ":1:2:3:4:5:6:7" @invalid_mapped = "::1:2.3.4" @valid_ipv4_uint32 = [4294967295, # 255.255.255.255 167772160, # 10.0.0.0 3232235520, # 192.168.0.0 0] @invalid_ipv4_uint32 = [4294967296, # 256.0.0.0 "A294967295", # Invalid uINT -1] # Invalid @ipv4class = IPAddress::IPv4 @ipv6class = IPAddress::IPv6 @mappedclass = IPAddress::IPv6::Mapped @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"] @method = Module.method("IPAddress") end def test_method_IPAddress assert_instance_of @ipv4class, @method.call(@valid_ipv4) assert_instance_of @ipv6class, @method.call(@valid_ipv6) assert_instance_of @mappedclass, @method.call(@valid_mapped) assert_raises(ArgumentError) {@method.call(@invalid_ipv4)} assert_raises(ArgumentError) {@method.call(@invalid_ipv6)} assert_raises(ArgumentError) {@method.call(@invalid_mapped)} assert_instance_of @ipv4class, @method.call(@valid_ipv4_uint32[0]) assert_instance_of @ipv4class, @method.call(@valid_ipv4_uint32[1]) assert_instance_of @ipv4class, @method.call(@valid_ipv4_uint32[2]) assert_instance_of @ipv4class, @method.call(@valid_ipv4_uint32[3]) assert_raises(ArgumentError) {@method.call(@invalid_ipv4_uint32[0])} assert_raises(ArgumentError) {@method.call(@invalid_ipv4_uint32[1])} assert_raises(ArgumentError) {@method.call(@invalid_ipv4_uint32[2])} end def test_module_method_valid? assert_equal true, IPAddress::valid?("10.0.0.1") assert_equal true, IPAddress::valid?("10.0.0.0") assert_equal true, IPAddress::valid?("2002::1") assert_equal true, IPAddress::valid?("dead:beef:cafe:babe::f0ad") assert_equal false, IPAddress::valid?("10.0.0.256") assert_equal false, IPAddress::valid?("10.0.0.0.0") assert_equal false, IPAddress::valid?("10.0.0") assert_equal false, IPAddress::valid?("10.0") assert_equal false, IPAddress::valid?("2002:::1") assert_equal false, IPAddress::valid?("2002:516:2:200") end def test_module_method_valid_ipv4_netmark? assert_equal true, IPAddress::valid_ipv4_netmask?("255.255.255.0") assert_equal false, IPAddress::valid_ipv4_netmask?("10.0.0.1") end end ipaddress-0.8.3/test/test_helper.rb0000644000004100000410000000147012666647272017376 0ustar www-datawww-datarequire 'rubygems' require 'minitest/autorun' $LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'ipaddress' if Minitest.const_defined?('Test') # We're on Minitest 5+. Nothing to do here. else # Minitest 4 doesn't have Minitest::Test yet. Minitest::Test = MiniTest::Unit::TestCase end module Minitest class Test def self.must(name, &block) test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym defined = instance_method(test_name) rescue false raise "#{test_name} is already defined in #{self}" if defined if block_given? define_method(test_name, &block) else define_method(test_name) do flunk "No implementation provided for #{name}" end end end end end ipaddress-0.8.3/test/ipaddress/0000755000004100000410000000000012666647272016507 5ustar www-datawww-dataipaddress-0.8.3/test/ipaddress/ipv4_test.rb0000644000004100000410000004455712666647272020774 0ustar www-datawww-datarequire 'test_helper' class IPv4Test < Minitest::Test 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", "192.168.12.4/32" => "255.255.255.255"} @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} @hex_values = { "10.0.0.0" => "0a000000", "172.16.5.4" => "ac100504", "192.168.100.4" => "c0a86404", } @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", "192.168.12.3/31" => "255.255.255.255/31", "10.0.0.1/32" => "10.0.0.1/32"} @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", "192.168.1.3/31" => "192.168.1.2/31", "192.168.2.5/32" => "192.168.2.5/32"} @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 } @in_range = { "10.32.0.1" => ["10.32.0.253", 253], "192.0.0.0" => ["192.1.255.255", 131072] } 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_raises (ArgumentError) do @klass.new end end def test_initialize_format_error @invalid_ipv4.each do |i| assert_raises(ArgumentError) {@klass.new(i)} end assert_raises (ArgumentError) {@klass.new("10.0.0.0/asd")} end def test_initialize_without_prefix 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_raises(ArgumentError) { @klass.new } end def test_method_data if RUBY_VERSION < "2.0" assert_equal "\254\020\n\001", @ip.data else assert_equal "\xAC\x10\n\x01".b, @ip.data end 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_to_hex @hex_values.each do |addr,hex| ip = @klass.new(addr) assert_equal hex, ip.to_hex end end def test_method_network? assert_equal true, @network.network? assert_equal false, @ip.network? end def test_one_address_network network = @klass.new("172.16.10.1/32") assert_equal false, network.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 ip = @klass.new("192.168.100.50/32") assert_instance_of @klass, ip.first assert_equal "192.168.100.50", ip.first.to_s ip = @klass.new("192.168.100.50/31") assert_instance_of @klass, ip.first assert_equal "192.168.100.50", 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 ip = @klass.new("192.168.100.50/32") assert_instance_of @klass, ip.last assert_equal "192.168.100.50", ip.last.to_s ip = @klass.new("192.168.100.50/31") assert_instance_of @klass, ip.last assert_equal "192.168.100.51", 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_compare 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") # ip2 should be greater than ip1 assert_equal true, ip1 < ip2 assert_equal false, ip1 > ip2 assert_equal false, ip2 < ip1 # ip2 should be less than ip3 assert_equal true, ip2 < ip3 assert_equal false, ip2 > ip3 # ip1 should be less 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/8","10.1.1.1/16","172.16.1.1/14"] assert_equal arr, [ip1,ip2,ip3].sort.map{|s| s.to_string} # test same prefix ip1 = @klass.new("10.0.0.0/24") ip2 = @klass.new("10.0.0.0/16") ip3 = @klass.new("10.0.0.0/8") arr = ["10.0.0.0/8","10.0.0.0/16","10.0.0.0/24"] 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_split assert_raises(ArgumentError) {@ip.split(0)} assert_raises(ArgumentError) {@ip.split(257)} assert_equal @ip.network, @ip.split(1).first 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.split(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.split(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.split(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.split(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.split(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.split(3).map {|s| s.to_string} arr = ["172.16.10.0/25", "172.16.10.128/25"] assert_equal arr, @network.split(2).map {|s| s.to_string} arr = ["172.16.10.0/24"] assert_equal arr, @network.split(1).map {|s| s.to_string} end def test_method_subnet assert_raises(ArgumentError) {@network.subnet(23)} assert_raises(ArgumentError) {@network.subnet(33)} 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(26).map {|s| s.to_string} arr = ["172.16.10.0/25", "172.16.10.128/25"] assert_equal arr, @network.subnet(25).map {|s| s.to_string} arr = ["172.16.10.0/24"] assert_equal arr, @network.subnet(24).map {|s| s.to_string} end def test_method_supernet assert_raises(ArgumentError) {@ip.supernet(24)} assert_equal "0.0.0.0/0", @ip.supernet(0).to_string assert_equal "0.0.0.0/0", @ip.supernet(-2).to_string 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_raises(ArgumentError){ @klass.parse_classful("192.168.256.257") } end def test_network_split @classful.each do |ip,net| x = @klass.new("#{ip}/#{net}") assert_equal x.split(1).length, 1 assert_equal x.split(2).length, 2 assert_equal x.split(32).length, 32 assert_equal x.split(256).length, 256 end end def test_in_range @in_range.each do |s,d| ip = @klass.new(s) assert_equal ip.to(d[0]).length, d[1] end end def test_octect_updates ip = @klass.new("10.0.1.15/32") ip[1] = 15 assert_equal "10.15.1.15/32", ip.to_string ip = @klass.new("172.16.100.1") ip[3] = 200 assert_equal "172.16.100.200/32", ip.to_string ip = @klass.new("192.168.199.0/24") ip[2] = 200 assert_equal "192.168.200.0/24", ip.to_string end end # class IPv4Test ipaddress-0.8.3/test/ipaddress/mongoid_test.rb0000644000004100000410000000571712666647272021541 0ustar www-datawww-datarequire 'test_helper' require 'ipaddress/mongoid' class MongoidTest < Minitest::Test def setup @valid_host4 = "172.16.10.1" @valid_host6 = "2001:0db8:0000:0000:0008:0800:200c:417a" @valid_host6_compressed = IPAddress::IPv6.compress(@valid_host6) @valid_network4 = "#{@valid_host4}/24" @valid_network6 = "#{@valid_host6}/96" @valid_network6_compressed = "#{@valid_host6_compressed}/96" @host4 = IPAddress.parse(@valid_host4) @host6 = IPAddress.parse(@valid_host6) @network4 = IPAddress.parse(@valid_network4) @network6 = IPAddress.parse(@valid_network6) @invalid_values = [nil, "", "invalid"] end def test_mongoize # Instance method should be delegated to class method assert_equal @host4.mongoize, IPAddress.mongoize(@host4) assert_equal @network4.mongoize, IPAddress.mongoize(@network4) # Hosts addresses should be stored without prefix assert_equal @valid_host4, IPAddress.mongoize(@host4) assert_equal @valid_host6, IPAddress.mongoize(@host6) assert_equal @valid_host4, IPAddress.mongoize("#{@host4}/32") assert_equal @valid_host6, IPAddress.mongoize("#{@host6}/128") # Network addresses should be stored with their prefix assert_equal @valid_network4, IPAddress.mongoize(@network4) assert_equal @valid_network6, IPAddress.mongoize(@network6) # IPv6 addresses should always be stored uncompressed assert_equal @valid_host6, IPAddress.mongoize(@valid_host6_compressed) assert_equal @valid_network6, IPAddress.mongoize(@valid_network6_compressed) @invalid_values.each do |invalid_value| # Invalid addresses should serialize to nil assert_equal nil, IPAddress.mongoize(invalid_value) end end def test_demongoize # Valid stored values should be loaded with expected IPAddress type assert_instance_of IPAddress::IPv4, IPAddress.demongoize(@valid_host4) assert_instance_of IPAddress::IPv6, IPAddress.demongoize(@valid_host6) assert_instance_of IPAddress::IPv4, IPAddress.demongoize(@valid_network4) assert_instance_of IPAddress::IPv6, IPAddress.demongoize(@valid_network6) # Valid stored values should be loaded as the original IPAddress object assert_equal @host4, IPAddress.demongoize(@valid_host4) assert_equal @host6, IPAddress.demongoize(@valid_host6) assert_equal @network4, IPAddress.demongoize(@valid_network4) assert_equal @network6, IPAddress.demongoize(@valid_network6) @invalid_values.each do |invalid_value| # Invalid stored value should be loaded as nil assert_equal nil, IPAddress.demongoize(invalid_value) end end def test_evolve # evolve should delegate to mongoize assert_equal IPAddress.mongoize(@valid_host4), IPAddress.evolve(@valid_host4) assert_equal IPAddress.mongoize(@valid_network4), IPAddress.evolve(@valid_network4) end endipaddress-0.8.3/test/ipaddress/prefix_test.rb0000644000004100000410000000636012666647272021375 0ustar www-datawww-datarequire 'test_helper' class Prefix32Test < Minitest::Test 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_raises (ArgumentError) do @klass.new 33 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 < Minitest::Test def setup @u128_hash = { 32 => 340282366841710300949110269838224261120, 64 => 340282366920938463444927863358058659840, 96 => 340282366920938463463374607427473244160, 126 => 340282366920938463463374607431768211452} @klass = IPAddress::Prefix128 end def test_initialize assert_raises (ArgumentError) do @klass.new 129 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 ipaddress-0.8.3/test/ipaddress/ipv6_test.rb0000644000004100000410000003036712666647272020770 0ustar www-datawww-datarequire 'test_helper' class IPv6Test < Minitest::Test 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", "2002:516:2:200", "dd"] @networks = { "2001:db8:1:1:1:1:1:1/32" => "2001:db8::/32", "2001:db8:1:1:1:1:1::/32" => "2001:db8::/32", "2001:db8::1/64" => "2001:db8::/64"} @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 @invalid_ipv6.each do |ip| assert_raises(ArgumentError) {@klass.new ip} end assert_equal 64, @ip.prefix assert_raises(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_broadcast_u128 assert_equal 42540766411282592875350729025363378175, @ip.broadcast_u128 end def test_method_size ip = @klass.new("2001:db8::8:800:200c:417a/64") assert_equal 2**64, ip.size ip = @klass.new("2001:db8::8:800:200c:417a/32") assert_equal 2**96, ip.size ip = @klass.new("2001:db8::8:800:200c:417a/120") assert_equal 2**8, ip.size ip = @klass.new("2001:db8::8:800:200c:417a/124") assert_equal 2**4, ip.size 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 if RUBY_VERSION < "2.0" str = " \001\r\270\000\000\000\000\000\b\b\000 \fAz" else str = " \x01\r\xB8\x00\x00\x00\x00\x00\b\b\x00 \fAz".b end 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_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_each ip = @klass.new("2001:db8::4/125") arr = [] ip.each {|i| arr << i.compressed} expected = ["2001:db8::","2001:db8::1","2001:db8::2", "2001:db8::3","2001:db8::4","2001:db8::5", "2001:db8::6","2001:db8::7"] assert_equal expected, arr end def test_method_compare ip1 = @klass.new("2001:db8:1::1/64") ip2 = @klass.new("2001:db8:2::1/64") ip3 = @klass.new("2001:db8:1::2/64") ip4 = @klass.new("2001:db8:1::1/65") # ip2 should be greater than ip1 assert_equal true, ip2 > ip1 assert_equal false, ip1 > ip2 assert_equal false, ip2 < ip1 # ip3 should be less than ip2 assert_equal true, ip2 > ip3 assert_equal false, ip2 < ip3 # ip1 should be less 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 # ip4 should be greater than ip1 assert_equal true, ip1 < ip4 assert_equal false, ip1 > ip4 # test sorting arr = ["2001:db8:1::1/64","2001:db8:1::1/65", "2001:db8:1::2/64","2001:db8:2::1/64"] assert_equal arr, [ip1,ip2,ip3,ip4].sort.map{|s| s.to_string} 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) refute_equal expanded, @klass.expand("2001:0db8:0::cd3") refute_equal expanded, @klass.expand("2001:0db8::cd30") refute_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) refute_equal compressed, @klass.compress("2001:0db8:0::cd3") refute_equal compressed, @klass.compress("2001:0db8::cd30") refute_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 def test_group_updates ip = @klass.new("2001:db8::8:800:200c:417a/64") ip[2] = '1234' assert_equal "2001:db8:4d2:0:8:800:200c:417a/64", ip.to_string end end # class IPv6Test class IPv6UnspecifiedTest < Minitest::Test 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_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 < Minitest::Test 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_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 < Minitest::Test 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_instance_of @klass, @ip @valid_mapped.each do |ip, u128| assert_equal u128, @klass.new(ip).to_u128 end @valid_mapped_ipv6.each do |ip, u128| 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 ipaddress-0.8.3/.gitignore0000644000004100000410000000023012666647272015535 0ustar www-datawww-data*.a /.bundle/ *.bundle /coverage/ /doc/ .DS_Store /Gemfile.lock ipaddr.html mkmf.log *.o /pkg/ rdoc server.rb *.so /spec/reports/ *.sw? /tmp/ /_yardoc/ ipaddress-0.8.3/ipaddress.gemspec0000644000004100000410000000207412666647272017100 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'ipaddress/version' Gem::Specification.new do |spec| spec.name = "ipaddress" spec.version = Ipaddress::VERSION spec.authors = ["bluemonk", "mikemackintosh"] spec.email = ["ceresa@gmail.com"] spec.summary = %q{IPv4/IPv6 address manipulation library} spec.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.} spec.homepage = "https://github.com/bluemonk/ipaddress" spec.license = "MIT" spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_development_dependency "bundler" spec.add_development_dependency "rake" end ipaddress-0.8.3/.document0000644000004100000410000000007412666647272015372 0ustar www-datawww-dataREADME.rdoc lib/**/*.rb bin/* features/**/*.feature LICENSE