diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .travis.yml | 19 | ||||
-rw-r--r-- | CONTRIBUTING.md | 39 | ||||
-rw-r--r-- | Gemfile | 11 | ||||
-rw-r--r-- | README.rdoc | 17 | ||||
-rw-r--r-- | Rakefile | 3 | ||||
-rw-r--r-- | VERSION | 1 | ||||
-rw-r--r-- | ipaddress.gemspec | 101 | ||||
-rw-r--r-- | lib/ipaddress.rb | 132 | ||||
-rw-r--r-- | lib/ipaddress/ipv4.rb | 57 | ||||
-rw-r--r-- | lib/ipaddress/ipv6.rb | 68 | ||||
-rw-r--r-- | lib/ipaddress/prefix.rb | 10 | ||||
-rw-r--r-- | tasks/jeweler.rake | 15 | ||||
-rw-r--r-- | test/ipaddress/ipv4_test.rb | 58 | ||||
-rw-r--r-- | test/ipaddress/ipv6_test.rb | 90 | ||||
-rw-r--r-- | test/ipaddress/mongoid_test.rb | 6 | ||||
-rw-r--r-- | test/ipaddress/prefix_test.rb | 2 | ||||
-rw-r--r-- | test/ipaddress_test.rb | 64 | ||||
-rw-r--r-- | test/test_helper.rb | 2 |
19 files changed, 595 insertions, 102 deletions
@@ -16,3 +16,5 @@ server.rb *.sw? /tmp/ /_yardoc/ +.idea/ +**/*.gem diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..30293f6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: ruby +rvm: +- 2.3 +- 2.4 +- 2.5 +- 2.6 +- 2.7 +install: +- gem install bundler +- gem install rake +- bundle install +script: +- uname -s +- rake test +- codeclimate-test-reporter +addons: + code_climate: + repo_token: + secure: Na2Ghl3W0IpUWAWx66V9skLie5MqiJWfn7muCSRkAYIVL/j9fR6jhhawOKlX0R6bg4byouOyLieDu9HWsv2EY5L7JweVspodRuuaJndxDQ1E5rOp0mU6vDl7kIqBeboX3AsivXDgcc8C8qvE+WD++vr3oEMg22c2/RIbt6ecJGs= diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..94f60b5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing to the IPAddress Gem + +[![Build Status](https://travis-ci.org/ipaddress-gem/ipaddress.svg?branch=master)](https://travis-ci.org/ipaddress-gem/ipaddress) [![Code Climate](https://codeclimate.com/github/ipaddress-gem/ipaddress/badges/gpa.svg)](https://codeclimate.com/github/ipaddress-gem/ipaddress) [![Dependency Status](https://www.versioneye.com/user/projects/57001305fcd19a0051853bde/badge.svg?style=flat)](https://www.versioneye.com/user/projects/57001305fcd19a0051853bde) + +This gem is run by people who have jobs. So please understand if we can't always prioritize PRs and issues. + +You can help by making your code submissions. We can't promise a specific turnaround time, or that your code will be incorporated but all submissions are appreciated. + +## Steps to Submit a Pull Request + +* [Fork](https://help.github.com/articles/fork-a-repo) the project on GitHub. +* Make your feature addition or bug fix [in a feature branch](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches). (Include a description of your changes in the PR) +* Push your feature branch to GitHub. +* Send a [Pull Request](https://help.github.com/articles/using-pull-requests) + +## Style Guide + +We will require that you adhere to the [ruby-style-guide](https://github.com/bbatsov/ruby-style-guide) for your code submissions. + +## Test Coverage + +All submissions of code must include test coverage which describes intent and expected behavior. The test suite used by this gem is [Minitest](https://github.com/seattlerb/minitest) + +Unit tests are expected to execute quickly. We will ask you to revise any long-running tests. + +We intend to add [Travis CI](https://travis-ci.org/) for automatic execution of branch tests. + +## Versioning: Jeweler and Semantic Versioning + +This repo uses [semantic versioning](http://semver.org/) implemented by the [Jeweler Gem](https://github.com/technicalpickles/jeweler). Please do not update the version by editting files, instead, you may increment or modify the version using the Rake tasks added by Jeweler. + +``` +$ rake -T | grep version +rake version # Displays the current version +rake version:bump:major # Bump the major version by 1 +rake version:bump:minor # Bump the a minor version by 1 +rake version:bump:patch # Bump the patch version by 1 +rake version:write # Writes out an explicit version +```
\ No newline at end of file @@ -1,3 +1,12 @@ source "https://rubygems.org" -gemspec +group :development do + gem 'bundler', '>= 1.0' + gem 'rake', '10.5.0' + gem 'minitest', '~> 5.8', '>= 5.8.4' + gem 'pry', '>= 0.10.1' + gem 'travis', '>= 1.8.2' + gem 'jeweler', '>=2.0.1' + gem 'codeclimate-test-reporter' + gem 'simplecov' +end diff --git a/README.rdoc b/README.rdoc index 05698a0..7d9600a 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,7 +1,3 @@ -== <b>IPAddress 1.0 is currently under development and will be released soon! Stay tuned!</b> - ---- - = IPAddress IPAddress is a Ruby library designed to make the use of IPv4 and IPv6 @@ -18,10 +14,13 @@ examples of typical usage. == Requirements -* Ruby >= 1.8.7 (not tested with previous versions) -* Ruby 1.9.2 or later is strongly recommended +* Ruby 1.9.3 or later + +Please refer to {Travis CI}[https://travis-ci.org/ipaddress-gem/ipaddress] for Build Tests on specific versions of Ruby. + +{<img src="https://travis-ci.org/ipaddress-gem/ipaddress.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/ipaddress-gem/ipaddress] {<img src="https://codeclimate.com/github/ipaddress-gem/ipaddress/badges/gpa.svg" />}[https://codeclimate.com/github/ipaddress-gem/ipaddress] {<img src="https://www.versioneye.com/user/projects/57001305fcd19a0051853bde/badge.svg?style=flat" alt="Dependency Status" />}[https://www.versioneye.com/user/projects/57001305fcd19a0051853bde] -IPAddress 0.8.2 has been tested on: +IPAddress 0.8.2 was manually tested on: * ruby-1.8.7-p334 [ i386 ] * ree-1.8.7-2011.03 [ i386 ] @@ -32,9 +31,7 @@ IPAddress 0.8.2 has been tested on: * 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]. +If you want to contribute, please refer to {Contributing.md}[https://github.com/ipaddress-gem/ipaddress/blob/master/CONTRIBUTING.md]. == Installation @@ -9,6 +9,7 @@ Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' test.pattern = 'test/**/*_test.rb' test.verbose = true + test.warning = true end begin @@ -64,3 +65,5 @@ task :todo do end egrep /(FIXME|TODO|TBD)/ end + +Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |raketask| load raketask } @@ -0,0 +1 @@ +0.8.3 diff --git a/ipaddress.gemspec b/ipaddress.gemspec index 2f66ccf..599c9bd 100644 --- a/ipaddress.gemspec +++ b/ipaddress.gemspec @@ -1,26 +1,83 @@ -# coding: utf-8 -lib = File.expand_path('../lib', __FILE__) -$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'ipaddress/version' +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' +# -*- encoding: utf-8 -*- +# stub: ipaddress 0.8.3 ruby lib -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" +Gem::Specification.new do |s| + s.name = "ipaddress" + s.version = "0.8.3" - 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"] + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.require_paths = ["lib"] + s.authors = ["bluemonk", "mikemackintosh"] + s.date = "2016-03-23" + s.description = "IPAddress is a Ruby library designed to make manipulation\n of IPv4 and IPv6 addresses both powerful and simple. It maintains\n a layer of compatibility with Ruby's own IPAddr, while\n addressing many of its issues." + s.email = "ceresa@gmail.com" + s.extra_rdoc_files = [ + "CHANGELOG.rdoc", + "LICENSE.txt", + "README.rdoc" + ] + s.files = [ + ".document", + ".rock.yml", + ".travis.yml", + "CHANGELOG.rdoc", + "CONTRIBUTING.md", + "Gemfile", + "LICENSE.txt", + "README.rdoc", + "Rakefile", + "VERSION", + "ipaddress.gemspec", + "lib/ipaddress.rb", + "lib/ipaddress/ipv4.rb", + "lib/ipaddress/ipv6.rb", + "lib/ipaddress/mongoid.rb", + "lib/ipaddress/prefix.rb", + "lib/ipaddress/version.rb", + "tasks/jeweler.rake", + "test/ipaddress/ipv4_test.rb", + "test/ipaddress/ipv6_test.rb", + "test/ipaddress/mongoid_test.rb", + "test/ipaddress/prefix_test.rb", + "test/ipaddress_test.rb", + "test/test_helper.rb" + ] + s.homepage = "https://github.com/bluemonk/ipaddress" + s.licenses = ["MIT"] + s.rubygems_version = "2.4.6" + s.summary = "IPv4/IPv6 address manipulation library" - spec.add_development_dependency "bundler" - spec.add_development_dependency "rake" + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q<bundler>, [">= 1.0"]) + s.add_development_dependency(%q<rake>, [">= 0"]) + s.add_development_dependency(%q<minitest>, [">= 5.8.4", "~> 5.8"]) + s.add_development_dependency(%q<pry>, [">= 0.10.1"]) + s.add_development_dependency(%q<travis>, [">= 1.8.2"]) + s.add_development_dependency(%q<jeweler>, [">= 2.0.1"]) + s.add_development_dependency(%q<codeclimate-test-reporter>, [">= 0"]) + else + s.add_dependency(%q<bundler>, [">= 1.0"]) + s.add_dependency(%q<rake>, [">= 0"]) + s.add_dependency(%q<minitest>, [">= 5.8.4", "~> 5.8"]) + s.add_dependency(%q<pry>, [">= 0.10.1"]) + s.add_dependency(%q<travis>, [">= 1.8.2"]) + s.add_dependency(%q<jeweler>, [">= 2.0.1"]) + s.add_dependency(%q<codeclimate-test-reporter>, [">= 0"]) + end + else + s.add_dependency(%q<bundler>, [">= 1.0"]) + s.add_dependency(%q<rake>, [">= 0"]) + s.add_dependency(%q<minitest>, [">= 5.8.4", "~> 5.8"]) + s.add_dependency(%q<pry>, [">= 0.10.1"]) + s.add_dependency(%q<travis>, [">= 1.8.2"]) + s.add_dependency(%q<jeweler>, [">= 2.0.1"]) + s.add_dependency(%q<codeclimate-test-reporter>, [">= 0"]) + end end + diff --git a/lib/ipaddress.rb b/lib/ipaddress.rb index 19b526f..96ac9f5 100644 --- a/lib/ipaddress.rb +++ b/lib/ipaddress.rb @@ -26,12 +26,12 @@ module IPAddress # 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 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 + # All the object created will be instances of the # correct class: # # ip.class @@ -42,17 +42,17 @@ module IPAddress # #=> IPAddress::IPv6::Mapped # def IPAddress::parse(str) - + # Check if an int was passed if str.kind_of? Integer - return IPAddress::IPv4.new(ntoa(str)) + return IPAddress::IPv4.new(ntoa(str)) end case str when /:.+\./ IPAddress::IPv6::Mapped.new(str) when /\./ - IPAddress::IPv4.new(str) + IPAddress::IPv4.new(str) when /:/ IPAddress::IPv6.new(str) else @@ -68,14 +68,14 @@ module IPAddress # 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('.') + raise(::ArgumentError, "not a long integer: #{uint.inspect}") + end + ret = [] + 4.times do + ret.unshift(uint & 0xff) + uint >>= 8 + end + ret.join('.') end # @@ -89,7 +89,7 @@ module IPAddress def ipv4? self.kind_of? IPAddress::IPv4 end - + # # True if the object is an IPv6 address # @@ -102,22 +102,88 @@ module IPAddress self.kind_of? IPAddress::IPv6 end - # - # Checks if the given string is a valid IP address, - # either IPv4 or IPv6 + + # + # Checks if the given string is either a valid IP, either a valid IPv4 subnet # # Example: # + # IPAddress::valid? "10.0.0.0/24" + # #=> true + # # IPAddress::valid? "2002::1" # #=> true # - # IPAddress::valid? "10.0.0.256" + # IPAddress::valid? "10.0.0.256" + # #=> false + # + # IPAddress::valid? "10.0.0.0/999" # #=> false # def self.valid?(addr) + valid_ip?(addr) || valid_ipv4_subnet?(addr) || valid_ipv6_subnet?(addr) + end + + # + # Checks if the given string is a valid IP address, + # either IPv4 or IPv6 + # + # Example: + # + # IPAddress::valid_ip? "2002::1" + # #=> true + # + # IPAddress::valid_ip? "10.0.0.256" + # #=> false + # + def self.valid_ip?(addr) valid_ipv4?(addr) || valid_ipv6?(addr) end - + + # + # Checks if the given string is a valid IPv4 subnet + # + # Example: + # + # IPAddress::valid_ipv4_subnet? "10.0.0.0/24" + # #=> true + # + # IPAddress::valid_ipv4_subnet? "10.0.0.0/255.255.255.0" + # #=> true + # + # IPAddress::valid_ipv4_subnet? "10.0.0.0/64" + # #=> false + # + def self.valid_ipv4_subnet?(addr) + ip, netmask = addr.split("/") + + valid_ipv4?(ip) && (!(netmask =~ /\A([12]?\d|3[0-2])\z/).nil? || valid_ipv4_netmask?(netmask)) + end + + # + # Checks if the given string is a valid IPv6 subnet + # + # Example: + # + # IPAddress::valid_ipv6_subnet? "::/0" + # #=> true + # + # IPAddress::valid_ipv6_subnet? "dead:beef:cafe:babe::/64" + # #=> true + # + # IPAddress::valid_ipv6_subnet? "2001::1/129" + # #=> false + # + def self.valid_ipv6_subnet?(addr) + ip, netmask = addr.split("/") + + netmask = Integer(netmask, 10) + + valid_ipv6?(ip) && netmask >= 0 && netmask <= 128 + rescue ArgumentError + false + end + # # Checks if the given string is a valid IPv4 address # @@ -130,12 +196,12 @@ module IPAddress # #=> true # def self.valid_ipv4?(addr) - if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ addr + if /^(0|[1-9]{1}\d{0,2})\.(0|[1-9]{1}\d{0,2})\.(0|[1-9]{1}\d{0,2})\.(0|[1-9]{1}\d{0,2})$/ =~ 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. @@ -149,7 +215,7 @@ module IPAddress rescue return false end - + # # Checks if the given string is a valid IPv6 address # @@ -161,35 +227,35 @@ module IPAddress # IPAddress::valid_ipv6? "2002::DEAD::BEEF" # #=> false # - def self.valid_ipv6?(addr) + 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 +# 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 +# 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" +# ip_mapped = IPAddress::IPv6::Mapped "::ffff:172.16.10.1/128" # # you can just use the IPAddress wrapper: # @@ -197,7 +263,7 @@ end # module IPAddress # 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 +# All the object created will be instances of the # correct class: # # ip.class @@ -219,8 +285,8 @@ if RUBY_VERSION =~ /^1\.8/ alias :key :index end module Math # :nodoc: - def Math.log2(n) - log(n) / log(2) + def Math.log2(n) + log(n) / log(2) end end end diff --git a/lib/ipaddress/ipv4.rb b/lib/ipaddress/ipv4.rb index 3ad2c15..82546d6 100644 --- a/lib/ipaddress/ipv4.rb +++ b/lib/ipaddress/ipv4.rb @@ -61,6 +61,7 @@ module IPAddress; # IPAddress::IPv4.new "10.0.0.1/255.0.0.0" # def initialize(str) + raise ArgumentError, "Nil IP" unless str ip, netmask = str.split("/") # Check the ip and remove white space @@ -89,6 +90,7 @@ module IPAddress; # 32 bits interger containing the address @u32 = (@octets[0]<< 24) + (@octets[1]<< 16) + (@octets[2]<< 8) + (@octets[3]) + @allocator = 0 end # def initialize # @@ -509,6 +511,7 @@ module IPAddress; # #=> ["10.100.100.1/8","10.100.100.1/16","172.16.0.1/16"] # def <=>(oth) + return nil unless oth.is_a?(self.class) return prefix <=> oth.prefix if to_u32 == oth.to_u32 to_u32 <=> oth.to_u32 end @@ -637,30 +640,30 @@ module IPAddress; # # Checks if an IPv4 address objects belongs - # to a multicast network RFC3171 + # to a loopback network RFC1122 # # Example: # - # ip = IPAddress "224.0.0.0/4" - # ip.multicast? + # ip = IPAddress "127.0.0.1" + # ip.loopback? # #=> true # - def multicast? - [self.class.new("224.0.0.0/4")].any? {|i| i.include? self} + def loopback? + [self.class.new("127.0.0.0/8")].any? {|i| i.include? self} end # # Checks if an IPv4 address objects belongs - # to a loopback network RFC1122 + # to a link-local network RFC3927 # # Example: # - # ip = IPAddress "127.0.0.1" - # ip.loopback? + # ip = IPAddress "169.254.0.1" + # ip.link_local? # #=> true - # - def loopback? - [self.class.new("127.0.0.0/8")].any? {|i| i.include? self} + # + def link_local? + [self.class.new("169.254.0.0/16")].any? {|i| i.include? self} end # @@ -786,7 +789,7 @@ module IPAddress; # # we can calculate the subnets with a /26 prefix # - # ip.subnets(26).map{&:to_string) + # ip.subnet(26).map{&:to_string) # #=> ["172.16.10.0/26", "172.16.10.64/26", # "172.16.10.128/26", "172.16.10.192/26"] # @@ -1079,6 +1082,36 @@ module IPAddress; end # + # Allocates a new ip from the current subnet. Optional skip parameter + # can be used to skip addresses. + # + # Will raise StopIteration exception when all addresses have been allocated + # + # Example: + # + # ip = IPAddress("10.0.0.0/24") + # ip.allocate + # #=> "10.0.0.1/24" + # ip.allocate + # #=> "10.0.0.2/24" + # ip.allocate(2) + # #=> "10.0.0.5/24" + # + # + # Uses an internal @allocator which tracks the state of allocated + # addresses. + # + def allocate(skip=0) + @allocator += 1 + skip + + next_ip = network_u32+@allocator + if next_ip > broadcast_u32+1 + raise StopIteration + end + self.class.parse_u32(network_u32+@allocator, @prefix) + end + + # # private methods # private diff --git a/lib/ipaddress/ipv6.rb b/lib/ipaddress/ipv6.rb index 2dda4d2..1601a01 100644 --- a/lib/ipaddress/ipv6.rb +++ b/lib/ipaddress/ipv6.rb @@ -87,6 +87,7 @@ module IPAddress; # ip6 = IPAddress "2001:db8::8:800:200c:417a/64" # def initialize(str) + raise ArgumentError, "Nil IP" unless str ip, netmask = str.split("/") if str =~ /:.+\./ @@ -102,6 +103,7 @@ module IPAddress; end @prefix = Prefix128.new(netmask ? netmask : 128) + @allocator = 0 end # def initialize @@ -397,6 +399,13 @@ module IPAddress; @compressed end + # + # Returns true if the address is a link local address + # + def link_local? + @groups[0] == 0xfe80 + end + # # Returns true if the address is an unspecified address # @@ -415,6 +424,34 @@ module IPAddress; @prefix == 128 and @compressed == "::1" end + # + # Checks if an IPv6 address objects belongs + # to a link-local network RFC4291 + # + # Example: + # + # ip = IPAddress "fe80::1" + # ip.link_local? + # #=> true + # + def link_local? + [self.class.new("fe80::/64")].any? {|i| i.include? self} + end + + # + # Checks if an IPv6 address objects belongs + # to a unique-local network RFC4193 + # + # Example: + # + # ip = IPAddress "fc00::1" + # ip.unique_local? + # #=> true + # + def unique_local? + [self.class.new("fc00::/7")].any? {|i| i.include? self} + end + # # Returns true if the address is a mapped address # @@ -487,6 +524,7 @@ module IPAddress; # #=> ["2001:db8:1::1/64","2001:db8:1::1/65","2001:db8:2::1/64"] # def <=>(oth) + return nil unless oth.is_a?(self.class) return prefix <=> oth.prefix if to_u128 == oth.to_u128 to_u128 <=> oth.to_u128 end @@ -628,6 +666,36 @@ module IPAddress; def self.parse_hex(hex, prefix=128) self.parse_u128(hex.hex, prefix) end + + # + # Allocates a new ip from the current subnet. Optional skip parameter + # can be used to skip addresses. + # + # Will raise StopIteration exception when all addresses have been allocated + # + # Example: + # + # ip = IPAddress("10.0.0.0/24") + # ip.allocate + # #=> "10.0.0.1/24" + # ip.allocate + # #=> "10.0.0.2/24" + # ip.allocate(2) + # #=> "10.0.0.5/24" + # + # + # Uses an internal @allocator which tracks the state of allocated + # addresses. + # + def allocate(skip=0) + @allocator += 1 + skip + + next_ip = network_u128+@allocator + if next_ip > broadcast_u128 + raise StopIteration + end + self.class.parse_u128(next_ip, @prefix) + end private diff --git a/lib/ipaddress/prefix.rb b/lib/ipaddress/prefix.rb index a0e3bb0..e819800 100644 --- a/lib/ipaddress/prefix.rb +++ b/lib/ipaddress/prefix.rb @@ -55,10 +55,10 @@ module IPAddress # # Sums two prefixes or a prefix to a - # number, returns a Fixnum + # number, returns a Integer # def +(oth) - if oth.is_a? Fixnum + if oth.is_a? Integer self.prefix + oth else self.prefix + oth.prefix @@ -68,17 +68,17 @@ module IPAddress # # Returns the difference between two # prefixes, or a prefix and a number, - # as a Fixnum + # as a Integer # def -(oth) - if oth.is_a? Fixnum + if oth.is_a? Integer self.prefix - oth else (self.prefix - oth.prefix).abs end end - end # class Prefix + end # class Prefix class Prefix32 < Prefix diff --git a/tasks/jeweler.rake b/tasks/jeweler.rake new file mode 100644 index 0000000..eda8319 --- /dev/null +++ b/tasks/jeweler.rake @@ -0,0 +1,15 @@ +require 'jeweler' +Jeweler::Tasks.new do |gem| + # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options + gem.name = "ipaddress" + gem.summary = %q{IPv4/IPv6 address manipulation library} + gem.description = %q{IPAddress is a Ruby library designed to make manipulation + of IPv4 and IPv6 addresses both powerful and simple. It maintains + a layer of compatibility with Ruby's own IPAddr, while + addressing many of its issues.} + gem.email = "ceresa@gmail.com" + gem.homepage = "https://github.com/bluemonk/ipaddress" + gem.authors = ["bluemonk", "mikemackintosh"] + gem.license = "MIT" +end +Jeweler::RubygemsDotOrgTasks.new diff --git a/test/ipaddress/ipv4_test.rb b/test/ipaddress/ipv4_test.rb index 3fc808b..5e8d5a9 100644 --- a/test/ipaddress/ipv4_test.rb +++ b/test/ipaddress/ipv4_test.rb @@ -74,6 +74,22 @@ class IPv4Test < Minitest::Test "10.32.0.1" => ["10.32.0.253", 253], "192.0.0.0" => ["192.1.255.255", 131072] } + + @link_local = [ + "169.254.0.0", + "169.254.255.255", + "169.254.12.34", + "169.254.0.0/16", + "169.254.0.0/17"] + + @not_link_local = [ + "127.0.0.1", + "127.0.1.1", + "192.168.0.100", + "169.255.0.0", + "169.254.0.0/15", + "0.0.0.0", + "255.255.255.255"] end @@ -83,7 +99,7 @@ class IPv4Test < Minitest::Test assert_instance_of @klass, ip end assert_instance_of IPAddress::Prefix32, @ip.prefix - assert_raises (ArgumentError) do + assert_raises(ArgumentError) do @klass.new end end @@ -92,6 +108,7 @@ class IPv4Test < Minitest::Test @invalid_ipv4.each do |i| assert_raises(ArgumentError) {@klass.new(i)} end + assert_raises (ArgumentError) {@klass.new(nil)} assert_raises (ArgumentError) {@klass.new("10.0.0.0/asd")} end @@ -309,6 +326,15 @@ class IPv4Test < Minitest::Test assert_equal false, @klass.new("192.0.0.2/24").private? end + def test_method_link_local? + @link_local.each do |addr| + assert_equal true, @klass.new(addr).link_local? + end + @not_link_local.each do |addr| + assert_equal false, @klass.new(addr).link_local? + end + end + def test_method_octet assert_equal 172, @ip[0] assert_equal 16, @ip[1] @@ -374,6 +400,12 @@ class IPv4Test < Minitest::Test 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} + # compare with alien thing + ip1 = @klass.new('127.0.0.1') + ip2 = IPAddress::IPv6.new('::1') + not_ip = String + assert_equal nil, ip1 <=> ip2 + assert_equal nil, ip1 <=> not_ip end def test_method_minus @@ -597,6 +629,30 @@ class IPv4Test < Minitest::Test assert_equal "192.168.200.0/24", ip.to_string end + def test_allocate_addresses + ip = @klass.new("10.0.0.0/24") + ip1 = ip.allocate + ip2 = ip.allocate + ip3 = ip.allocate + assert_equal "10.0.0.1/24", ip1.to_string + assert_equal "10.0.0.2/24", ip2.to_string + assert_equal "10.0.0.3/24", ip3.to_string + end + + def test_allocate_can_skip_addresses + ip = @klass.new("10.0.0.0/24") + ip1 = ip.allocate(2) + assert_equal "10.0.0.3/24", ip1.to_string + end + + def test_allocate_will_raise_stopiteration + ip = @klass.new("10.0.0.0/30") + ip.allocate(3) + assert_raises (StopIteration) do + ip.allocate + end + end + end # class IPv4Test diff --git a/test/ipaddress/ipv6_test.rb b/test/ipaddress/ipv6_test.rb index a1cd072..294d5fa 100644 --- a/test/ipaddress/ipv6_test.rb +++ b/test/ipaddress/ipv6_test.rb @@ -26,9 +26,7 @@ class IPv6Test < Minitest::Test "::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} + "::" => 0} @invalid_ipv6 = [":1:2:3:4:5:6:7", ":1:2:3:4:5:6:7", @@ -44,6 +42,37 @@ class IPv6Test < Minitest::Test @network = @klass.new "2001:db8:8:800::/64" @arr = [8193,3512,0,0,8,2048,8204,16762] @hex = "20010db80000000000080800200c417a" + + @link_local = [ + "fe80::", + "fe80::1", + "fe80::208:74ff:feda:625c", + "fe80::/64", + "fe80::/65"] + + @not_link_local = [ + "::", + "::1", + "ff80:03:02:01::", + "2001:db8::8:800:200c:417a", + "fe80::/63"] + + @unique_local = [ + "fc00::/7", + "fc00::/8", + "fd00::/8", + "fd12:3456:789a:1::1", + "fd12:3456:789a:1::/64", + "fc00::1"] + + @not_unique_local = [ + "fc00::/6", + "::", + "::1", + "fe80::", + "fe80::1", + "fe80::/64"] + end def test_attribute_address @@ -58,6 +87,7 @@ class IPv6Test < Minitest::Test end assert_equal 64, @ip.prefix + assert_raises(ArgumentError) {@klass.new nil } assert_raises(ArgumentError) { @klass.new "::10.1.1.1" } @@ -200,6 +230,12 @@ class IPv6Test < Minitest::Test assert_equal "1::1", @klass.new("1:0:0:0:0:0:0:1").compressed end + def test_method_link_local? + assert_equal true, @klass.new("fe80::1").link_local? + assert_equal true, @klass.new("fe80:ffff::1").link_local? + assert_equal false, @klass.new("fe81::1").link_local? + end + def test_method_unspecified? assert_equal true, @klass.new("::").unspecified? assert_equal false, @ip.unspecified? @@ -210,6 +246,24 @@ class IPv6Test < Minitest::Test assert_equal false, @ip.loopback? end + def test_method_link_local? + @link_local.each do |addr| + assert_equal true, @klass.new(addr).link_local? + end + @not_link_local.each do |addr| + assert_equal false, @klass.new(addr).link_local? + end + end + + def test_method_unique_local? + @unique_local.each do |addr| + assert_equal true, @klass.new(addr).unique_local? + end + @not_unique_local.each do |addr| + assert_equal false, @klass.new(addr).unique_local? + end + end + def test_method_network @networks.each do |addr,net| ip = @klass.new addr @@ -228,6 +282,30 @@ class IPv6Test < Minitest::Test assert_equal expected, arr end + def test_allocate_addresses + ip = @klass.new("2001:db8::4/125") + ip1 = ip.allocate + ip2 = ip.allocate + ip3 = ip.allocate + assert_equal "2001:db8::1", ip1.compressed + assert_equal "2001:db8::2", ip2.compressed + assert_equal "2001:db8::3", ip3.compressed + end + + def test_allocate_can_skip_addresses + ip = @klass.new("2001:db8::4/125") + ip1 = ip.allocate(2) + assert_equal "2001:db8::3", ip1.compressed + end + + def test_allocate_will_raise_stopiteration + ip = @klass.new("2001:db8::4/125") + ip.allocate(6) + assert_raises (StopIteration) do + ip.allocate + end + end + def test_method_compare ip1 = @klass.new("2001:db8:1::1/64") ip2 = @klass.new("2001:db8:2::1/64") @@ -259,6 +337,12 @@ class IPv6Test < Minitest::Test 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} + # compare with alien thing + ip1 = @klass.new('::1') + ip2 = IPAddress::IPv4.new('127.0.0.1') + not_ip = String + assert_equal nil, ip1 <=> ip2 + assert_equal nil, ip1 <=> not_ip end def test_classmethod_expand diff --git a/test/ipaddress/mongoid_test.rb b/test/ipaddress/mongoid_test.rb index b463e84..4f32093 100644 --- a/test/ipaddress/mongoid_test.rb +++ b/test/ipaddress/mongoid_test.rb @@ -38,7 +38,7 @@ class MongoidTest < Minitest::Test @invalid_values.each do |invalid_value| # Invalid addresses should serialize to nil - assert_equal nil, IPAddress.mongoize(invalid_value) + assert_nil IPAddress.mongoize(invalid_value) end end @@ -57,7 +57,7 @@ class MongoidTest < Minitest::Test @invalid_values.each do |invalid_value| # Invalid stored value should be loaded as nil - assert_equal nil, IPAddress.demongoize(invalid_value) + assert_nil IPAddress.demongoize(invalid_value) end end @@ -67,4 +67,4 @@ class MongoidTest < Minitest::Test assert_equal IPAddress.mongoize(@valid_network4), IPAddress.evolve(@valid_network4) end -end
\ No newline at end of file +end diff --git a/test/ipaddress/prefix_test.rb b/test/ipaddress/prefix_test.rb index 1a0e277..f0a3d8c 100644 --- a/test/ipaddress/prefix_test.rb +++ b/test/ipaddress/prefix_test.rb @@ -132,7 +132,7 @@ class Prefix128Test < Minitest::Test end def test_initialize - assert_raises (ArgumentError) do + assert_raises(ArgumentError) do @klass.new 129 end assert_instance_of @klass, @klass.new(64) diff --git a/test/ipaddress_test.rb b/test/ipaddress_test.rb index 862c889..777352e 100644 --- a/test/ipaddress_test.rb +++ b/test/ipaddress_test.rb @@ -18,17 +18,19 @@ class IPAddressTest < Minitest::Test @invalid_ipv4_uint32 = [4294967296, # 256.0.0.0 "A294967295", # Invalid uINT - -1] # Invalid + -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"] + "10.0", + "0127.010.010.010", + "055.055.055.055"] @valid_ipv4_range = ["10.0.0.1-254", "10.0.1-254.0", @@ -39,18 +41,18 @@ class IPAddressTest < Minitest::Test def test_method_IPAddress - assert_instance_of @ipv4class, @method.call(@valid_ipv4) - assert_instance_of @ipv6class, @method.call(@valid_ipv6) + 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_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])} @@ -59,6 +61,30 @@ class IPAddressTest < Minitest::Test end def test_module_method_valid? + assert_equal true, IPAddress::valid?("0.0.0.5/20") + assert_equal true, IPAddress::valid?("0.0.0.0/8") + assert_equal false, IPAddress::valid?("800.754.1.1/13") + assert_equal false, IPAddress::valid?("0xff/4") + assert_equal false, IPAddress::valid?("0xff.0xff.0xff.0xfe/20") + assert_equal false, IPAddress::valid?("037.05.05.01/8") + assert_equal false, IPAddress::valid?("0127.0.0.01/16") + assert_equal false, IPAddress::valid?("055.027.043.09/16") + # four digits fails the three digits check + assert_equal false, IPAddress::valid?("0255.0255.0255.01/20") + assert_equal false, IPAddress::valid?("013.055.0255.0216/29") + assert_equal false, IPAddress::valid?("013.055.025.021/29") + assert_equal false, IPAddress::valid?("052.015.024.020/29") + assert_equal true, IPAddress::valid?("10.0.0.0/24") + assert_equal true, IPAddress::valid?("10.0.0.0/255.255.255.0") + assert_equal false, IPAddress::valid?("10.0.0.0/64") + assert_equal false, IPAddress::valid?("10.0.0.0/255.255.255.256") + assert_equal true, IPAddress::valid?("::/0") + assert_equal true, IPAddress::valid?("2002::1/128") + assert_equal true, IPAddress::valid?("dead:beef:cafe:babe::/64") + assert_equal false, IPAddress::valid?("2002::1/129") + end + + def test_module_method_valid_ip? 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") @@ -69,14 +95,30 @@ class IPAddressTest < Minitest::Test 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? + def test_module_method_valid_ipv4_netmask? assert_equal true, IPAddress::valid_ipv4_netmask?("255.255.255.0") assert_equal false, IPAddress::valid_ipv4_netmask?("10.0.0.1") end + def test_module_method_valid_ipv4_subnet? + assert_equal true, IPAddress::valid_ipv4_subnet?("10.0.0.0/255.255.255.0") + assert_equal true, IPAddress::valid_ipv4_subnet?("10.0.0.0/0") + assert_equal true, IPAddress::valid_ipv4_subnet?("10.0.0.0/32") + assert_equal false, IPAddress::valid_ipv4_subnet?("10.0.0.0/ABC") + assert_equal false, IPAddress::valid_ipv4_subnet?("10.0.0.1") + assert_equal false, IPAddress::valid_ipv4_subnet?("10.0.0.0/33") + assert_equal false, IPAddress::valid_ipv4_subnet?("10.0.0.256/24") + assert_equal false, IPAddress::valid_ipv4_subnet?("10.0.0.0/255.255.255.256") + end + + def test_module_method_valid_ipv6_subnet? + assert_equal true, IPAddress::valid_ipv6_subnet?("::/0") + assert_equal true, IPAddress::valid_ipv6_subnet?("2002::1/128") + assert_equal true, IPAddress::valid_ipv6_subnet?("dead:beef:cafe:babe::/64") + assert_equal false, IPAddress::valid_ipv6_subnet?("2002::1/129") + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 249ea52..4a80d3e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,7 @@ require 'rubygems' require 'minitest/autorun' +require 'simplecov' +SimpleCov.start $LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) |