├── .github ├── CODEOWNERS ├── release.yml ├── dependabot.yml └── workflows │ ├── test.yml │ └── sync-ruby.yml ├── .document ├── test ├── lib │ └── helper.rb └── test_ipaddr.rb ├── .gitignore ├── Gemfile ├── Rakefile ├── ipaddr.gemspec ├── LICENSE.txt ├── README.md ├── CHANGELOG.md └── lib └── ipaddr.rb /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @knu 2 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | LICENSE.txt 3 | README.md 4 | lib/ 5 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - dependencies # Added by Dependabot 5 | -------------------------------------------------------------------------------- /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "core_assertions" 3 | 4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "bundler" 6 | gem "rake" 7 | gem "test-unit" 8 | gem "test-unit-ruby-core" 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test/lib" 6 | t.ruby_opts << "-rhelper" 7 | t.test_files = FileList["test/**/test_*.rb"] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 8 | with: 9 | min_version: 2.4 10 | 11 | build: 12 | needs: ruby-versions 13 | name: build (${{ matrix.ruby }} / ${{ matrix.os }}) 14 | strategy: 15 | matrix: 16 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 17 | os: [ ubuntu-latest, macos-latest ] 18 | exclude: 19 | - { os: macos-latest, ruby: '2.4' } 20 | - { os: macos-latest, ruby: '2.5' } 21 | include: 22 | - { os: macos-15-intel, ruby: '2.4' } 23 | - { os: macos-15-intel, ruby: '2.5' } 24 | 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - uses: actions/checkout@v6 28 | - name: Set up Ruby 29 | uses: ruby/setup-ruby@v1 30 | with: 31 | ruby-version: ${{ matrix.ruby }} 32 | bundler-cache: true # 'bundle install' and cache 33 | - name: Run test 34 | run: bundle exec rake test 35 | -------------------------------------------------------------------------------- /.github/workflows/sync-ruby.yml: -------------------------------------------------------------------------------- 1 | name: Sync ruby 2 | on: 3 | push: 4 | branches: [master] 5 | jobs: 6 | sync: 7 | name: Sync ruby 8 | runs-on: ubuntu-latest 9 | if: ${{ github.repository_owner == 'ruby' }} 10 | steps: 11 | - uses: actions/checkout@v6 12 | 13 | - name: Create GitHub App token 14 | id: app-token 15 | uses: actions/create-github-app-token@v2 16 | with: 17 | app-id: 2060836 18 | private-key: ${{ secrets.RUBY_SYNC_DEFAULT_GEMS_PRIVATE_KEY }} 19 | owner: ruby 20 | repositories: ruby 21 | 22 | - name: Sync to ruby/ruby 23 | uses: convictional/trigger-workflow-and-wait@v1.6.5 24 | with: 25 | owner: ruby 26 | repo: ruby 27 | workflow_file_name: sync_default_gems.yml 28 | github_token: ${{ steps.app-token.outputs.token }} 29 | ref: master 30 | client_payload: | 31 | {"gem":"${{ github.event.repository.name }}","before":"${{ github.event.before }}","after":"${{ github.event.after }}"} 32 | propagate_failure: true 33 | wait_interval: 10 34 | -------------------------------------------------------------------------------- /ipaddr.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # coding: utf-8 3 | 4 | if File.exist?(File.expand_path("ipaddr.gemspec")) 5 | lib = File.expand_path("../lib", __FILE__) 6 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 7 | 8 | file = File.expand_path("ipaddr.rb", lib) 9 | else 10 | # for ruby-core 11 | file = File.expand_path("../ipaddr.rb", __FILE__) 12 | end 13 | 14 | version = File.foreach(file).find do |line| 15 | /^\s*VERSION\s*=\s*["'](.*)["']/ =~ line and break $1 16 | end 17 | 18 | Gem::Specification.new do |spec| 19 | spec.name = "ipaddr" 20 | spec.version = version 21 | spec.authors = ["Akinori MUSHA", "Hajimu UMEMOTO"] 22 | spec.email = ["knu@idaemons.org", "ume@mahoroba.org"] 23 | 24 | spec.summary = %q{A class to manipulate an IP address in ruby} 25 | spec.description = <<-'DESCRIPTION' 26 | IPAddr provides a set of methods to manipulate an IP address. 27 | Both IPv4 and IPv6 are supported. 28 | DESCRIPTION 29 | spec.homepage = "https://github.com/ruby/ipaddr" 30 | spec.licenses = ["Ruby", "BSD-2-Clause"] 31 | 32 | spec.files = ["LICENSE.txt", "README.md", "lib/ipaddr.rb"] 33 | spec.require_paths = ["lib"] 34 | 35 | spec.required_ruby_version = ">= 2.4" 36 | end 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2002 Hajimu UMEMOTO 2 | Copyright (c) 2007-2017 Akinori MUSHA 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IPAddr 2 | 3 | IPAddr provides a set of methods to manipulate an IP address. Both 4 | IPv4 and IPv6 are supported. 5 | 6 | [![build](https://github.com/ruby/ipaddr/actions/workflows/test.yml/badge.svg)](https://github.com/ruby/ipaddr/actions/workflows/test.yml) 7 | 8 | ## Installation 9 | 10 | This library is part of the standard ruby distribution as default gem 11 | and synchronized periodically, but you can explicitly depend on this 12 | gem with version constraints as necessary, like when you need a newer 13 | version than comes with older versions of ruby. 14 | 15 | For example, you can add this line to your application's Gemfile: 16 | 17 | ```ruby 18 | gem 'ipaddr', '~> 1.2' 19 | ``` 20 | 21 | And then execute: 22 | 23 | $ bundle 24 | 25 | Or install it yourself as: 26 | 27 | $ gem install ipaddr 28 | 29 | ## Usage 30 | 31 | ```ruby 32 | require 'ipaddr' 33 | 34 | ipaddr1 = IPAddr.new "3ffe:505:2::1" 35 | 36 | p ipaddr1 #=> # 37 | 38 | p ipaddr1.to_s #=> "3ffe:505:2::1" 39 | 40 | ipaddr2 = ipaddr1.mask(48) #=> # 41 | 42 | p ipaddr2.to_s #=> "3ffe:505:2::" 43 | 44 | ipaddr3 = IPAddr.new "192.168.2.0/24" 45 | 46 | p ipaddr3 #=> # 47 | ``` 48 | 49 | ## Alternative 50 | 51 | The [ipaddress](https://rubygems.org/gems/ipaddress) gem is a popular, 52 | extensive library for manipulating IP addresses with a layer of 53 | compatibility with `ipaddr`. If you need a richer set of features 54 | than `ipaddr` has, try this library instead. 55 | 56 | ## Development 57 | 58 | After checking out the repo, run `bundle install` to install dependencies. Then, run `rake test` to run the tests. 59 | 60 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 61 | 62 | ## Contributing 63 | 64 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/ipaddr. 65 | 66 | ## License 67 | 68 | The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). 69 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 1.2.7 2 | ----- 3 | 4 | - Add `IPAddr#to_json/as_json` [\#77](https://github.com/ruby/ipaddr/pull/77) ([taketo1113](https://github.com/taketo1113)) 5 | - Consider IPv4-mapped IPv6 addresses link local/loopback if IPV4 address is private [\#65](https://github.com/ruby/ipaddr/pull/65) ([Earlopain](https://github.com/Earlopain)) 6 | - Fix broken exception messages [\#64](https://github.com/ruby/ipaddr/pull/64) ([lrandall-godaddy](https://github.com/lrandall-godaddy)) 7 | - ntop: Measure address size in bytes [\#61](https://github.com/ruby/ipaddr/pull/61) ([hanazuki](https://github.com/hanazuki)) 8 | - String\#split seems to be faster than capturing digits with Regexp [\#50](https://github.com/ruby/ipaddr/pull/50) ([amatsuda](https://github.com/amatsuda)) 9 | - Prefer String\#start\_with? over Regexp.match [\#49](https://github.com/ruby/ipaddr/pull/49) ([amatsuda](https://github.com/amatsuda)) 10 | - Add IPAddr\#wildcard\_mask [\#44](https://github.com/ruby/ipaddr/pull/44) ([taketo1113](https://github.com/taketo1113)) 11 | - Add IPAddr.cidr to return ip address in cidr notation [\#23](https://github.com/ruby/ipaddr/pull/23) ([beanieboi](https://github.com/beanieboi)) 12 | 13 | 1.2.6 14 | ----- 15 | 16 | - Consider IPv4-mapped IPv6 addresses private if IPv4 address is private [\#57](https://github.com/ruby/ipaddr/pull/57) ([jeremyevans](https://github.com/jeremyevans)) 17 | - Improve performance of include? by 5-10x [\#47](https://github.com/ruby/ipaddr/pull/47) ([skipkayhil](https://github.com/skipkayhil)) 18 | 19 | 1.2.5 20 | ----- 21 | 22 | - No user-visible changes for gem users 23 | 24 | 1.2.4 25 | ----- 26 | 27 | - Fix exception calling `to_range' after `freeze' - fix \#35 [\#36](https://github.com/ruby/ipaddr/pull/36) ([esparta](https://github.com/esparta)) 28 | - IPAddr\#native must also coerce `@mask_addr` [\#34](https://github.com/ruby/ipaddr/pull/34) ([casperisfine](https://github.com/casperisfine)) 29 | - Expose IPAddr::VERSION [\#33](https://github.com/ruby/ipaddr/pull/33) ([casperisfine](https://github.com/casperisfine)) 30 | 31 | 1.2.3 32 | ----- 33 | 34 | - Ruby 2.3 is the minimum supported version 35 | - Follow the license change in Ruby and add BSD-2-Clause 36 | - Make the parser a bit stricter [Bug #15832](https://bugs.ruby-lang.org/issues/15832) 37 | - Disallow leading zeros and extra slashes in mask 38 | - Make IPAddr#include? consider range of argument [Bug #14119](https://bugs.ruby-lang.org/issues/14119) 39 | - Support zone identifiers in IPv6 addresses [Feature #10911](https://bugs.ruby-lang.org/issues/10911) 40 | - Fix include? and ipv4_mapped (https://github.com/ruby/ipaddr/pull/31) 41 | 42 | 1.2.2 43 | ----- 44 | 45 | - Include the input in the message when raising InvalidAddressError [Feature #5987] (https://bugs.ruby-lang.org/issues/5987) 46 | 47 | 1.2.1 48 | ----- 49 | 50 | - Enable frozen_string_literal and do a bit of code cleanup 51 | 52 | 1.2.0 53 | ----- 54 | 55 | - Add `IPAddr#link_local?` [Feature #10912](https://bugs.ruby-lang.org/issues/10912) 56 | - Add `IPAddr#private?` [Feature #11666](https://bugs.ruby-lang.org/issues/11666) 57 | - Add `IPAddr#loopback?` 58 | - IPAddr.new now rejects invalid address mask. [Bug #13399] 59 | - Warn that `IPAddr#ipv4_compat` and `#ipv4_compat?` are deprecated [Bug #13769] 60 | 61 | 1.1.0 62 | ----- 63 | 64 | - Add IPAddr#prefix 65 | 66 | 1.0.0 67 | ----- 68 | 69 | - Initial release as gem extracted from ruby 2.4.1. 70 | -------------------------------------------------------------------------------- /test/test_ipaddr.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'test/unit' 3 | require 'ipaddr' 4 | 5 | class TC_IPAddr < Test::Unit::TestCase 6 | def test_s_new 7 | [ 8 | ["3FFE:505:ffff::/48"], 9 | ["0:0:0:1::"], 10 | ["2001:200:300::/48"], 11 | ["2001:200:300::192.168.1.2/48"], 12 | ["1:2:3:4:5:6:7::"], 13 | ["::2:3:4:5:6:7:8"], 14 | ].each { |args| 15 | assert_nothing_raised { 16 | IPAddr.new(*args) 17 | } 18 | } 19 | 20 | a = IPAddr.new 21 | assert_equal("::", a.to_s) 22 | assert_equal("0000:0000:0000:0000:0000:0000:0000:0000", a.to_string) 23 | assert_equal("::/128", a.cidr) 24 | assert_equal(Socket::AF_INET6, a.family) 25 | assert_equal(128, a.prefix) 26 | 27 | a = IPAddr.new("0123:4567:89ab:cdef:0ABC:DEF0:1234:5678") 28 | assert_equal("123:4567:89ab:cdef:abc:def0:1234:5678", a.to_s) 29 | assert_equal("0123:4567:89ab:cdef:0abc:def0:1234:5678", a.to_string) 30 | assert_equal("123:4567:89ab:cdef:abc:def0:1234:5678/128", a.cidr) 31 | assert_equal(Socket::AF_INET6, a.family) 32 | assert_equal(128, a.prefix) 33 | 34 | a = IPAddr.new("3ffe:505:2::/48") 35 | assert_equal("3ffe:505:2::", a.to_s) 36 | assert_equal("3ffe:0505:0002:0000:0000:0000:0000:0000", a.to_string) 37 | assert_equal("3ffe:505:2::/48", a.cidr) 38 | assert_equal(Socket::AF_INET6, a.family) 39 | assert_equal(false, a.ipv4?) 40 | assert_equal(true, a.ipv6?) 41 | assert_equal("#", a.inspect) 42 | assert_equal(48, a.prefix) 43 | 44 | a = IPAddr.new("3ffe:505:2::/ffff:ffff:ffff::") 45 | assert_equal("3ffe:505:2::", a.to_s) 46 | assert_equal("3ffe:0505:0002:0000:0000:0000:0000:0000", a.to_string) 47 | assert_equal("3ffe:505:2::/48", a.cidr) 48 | assert_equal(Socket::AF_INET6, a.family) 49 | assert_equal(48, a.prefix) 50 | assert_nil(a.zone_id) 51 | 52 | a = IPAddr.new("fe80::1%ab0") 53 | assert_equal("fe80::1%ab0", a.to_s) 54 | assert_equal("fe80:0000:0000:0000:0000:0000:0000:0001%ab0", a.to_string) 55 | assert_equal(Socket::AF_INET6, a.family) 56 | assert_equal(false, a.ipv4?) 57 | assert_equal(true, a.ipv6?) 58 | assert_equal("#", a.inspect) 59 | assert_equal(128, a.prefix) 60 | assert_equal('%ab0', a.zone_id) 61 | 62 | a = IPAddr.new("0.0.0.0") 63 | assert_equal("0.0.0.0", a.to_s) 64 | assert_equal("0.0.0.0", a.to_string) 65 | assert_equal("0.0.0.0/32", a.cidr) 66 | assert_equal(Socket::AF_INET, a.family) 67 | assert_equal(32, a.prefix) 68 | 69 | a = IPAddr.new("192.168.1.2") 70 | assert_equal("192.168.1.2", a.to_s) 71 | assert_equal("192.168.1.2", a.to_string) 72 | assert_equal("192.168.1.2/32", a.cidr) 73 | assert_equal(Socket::AF_INET, a.family) 74 | assert_equal(true, a.ipv4?) 75 | assert_equal(false, a.ipv6?) 76 | assert_equal(32, a.prefix) 77 | 78 | a = IPAddr.new("192.168.1.2/26") 79 | assert_equal("192.168.1.0", a.to_s) 80 | assert_equal("192.168.1.0", a.to_string) 81 | assert_equal("192.168.1.0/26", a.cidr) 82 | assert_equal(Socket::AF_INET, a.family) 83 | assert_equal("#", a.inspect) 84 | assert_equal(26, a.prefix) 85 | 86 | a = IPAddr.new("192.168.1.2/255.255.255.0") 87 | assert_equal("192.168.1.0", a.to_s) 88 | assert_equal("192.168.1.0", a.to_string) 89 | assert_equal("192.168.1.0/24", a.cidr) 90 | assert_equal(Socket::AF_INET, a.family) 91 | assert_equal(24, a.prefix) 92 | 93 | (0..32).each do |prefix| 94 | assert_equal(prefix, IPAddr.new("10.20.30.40/#{prefix}").prefix) 95 | end 96 | (0..128).each do |prefix| 97 | assert_equal(prefix, IPAddr.new("1:2:3:4:5:6:7:8/#{prefix}").prefix) 98 | end 99 | 100 | assert_equal("0:0:0:1::", IPAddr.new("0:0:0:1::").to_s) 101 | assert_equal("2001:200:300::", IPAddr.new("2001:200:300::/48").to_s) 102 | 103 | assert_equal("2001:200:300::", IPAddr.new("[2001:200:300::]/48").to_s) 104 | assert_equal("1:2:3:4:5:6:7:0", IPAddr.new("1:2:3:4:5:6:7::").to_s) 105 | assert_equal("0:2:3:4:5:6:7:8", IPAddr.new("::2:3:4:5:6:7:8").to_s) 106 | 107 | assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.256") } 108 | assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.011") } 109 | assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("fe80::1%") } 110 | assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("fe80::1%]") } 111 | assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[192.168.1.2]/120") } 112 | assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("[2001:200:300::]\nINVALID") } 113 | assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.1/32\nINVALID") } 114 | assert_raise(IPAddr::InvalidAddressError) { IPAddr.new("192.168.0.1/32/20") } 115 | assert_raise(IPAddr::InvalidPrefixError) { IPAddr.new("192.168.0.1/032") } 116 | assert_raise(IPAddr::InvalidPrefixError) { IPAddr.new("::1/0128") } 117 | assert_raise(IPAddr::InvalidPrefixError) { IPAddr.new("::1/255.255.255.0") } 118 | assert_raise(IPAddr::InvalidPrefixError) { IPAddr.new("::1/129") } 119 | assert_raise(IPAddr::InvalidPrefixError) { IPAddr.new("192.168.0.1/33") } 120 | assert_raise(IPAddr::InvalidPrefixError) { IPAddr.new("192.168.0.1/255.255.255.1") } 121 | assert_raise(IPAddr::AddressFamilyError) { IPAddr.new(1) } 122 | assert_raise(IPAddr::AddressFamilyError) { IPAddr.new("::ffff:192.168.1.2/120", Socket::AF_INET) } 123 | end 124 | 125 | def test_s_new_ntoh 126 | addr = '' 127 | IPAddr.new("1234:5678:9abc:def0:1234:5678:9abc:def0").hton.each_byte { |c| 128 | addr += sprintf("%02x", c) 129 | } 130 | assert_equal("123456789abcdef0123456789abcdef0", addr) 131 | addr = '' 132 | IPAddr.new("123.45.67.89").hton.each_byte { |c| 133 | addr += sprintf("%02x", c) 134 | } 135 | assert_equal(sprintf("%02x%02x%02x%02x", 123, 45, 67, 89), addr) 136 | a = IPAddr.new("3ffe:505:2::") 137 | assert_equal("3ffe:505:2::", IPAddr.new_ntoh(a.hton).to_s) 138 | a = IPAddr.new("192.168.2.1") 139 | assert_equal("192.168.2.1", IPAddr.new_ntoh(a.hton).to_s) 140 | end 141 | 142 | def test_ntop 143 | # IPv4 144 | assert_equal("192.168.1.1", IPAddr.ntop("\xC0\xA8\x01\x01".b)) 145 | assert_equal("10.231.140.171", IPAddr.ntop("\x0A\xE7\x8C\xAB".b)) 146 | # IPv6 147 | assert_equal("0000:0000:0000:0000:0000:0000:0000:0001", 148 | IPAddr.ntop("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".b)) 149 | assert_equal("fe80:0000:0000:0000:f09f:9985:f09f:9986", 150 | IPAddr.ntop("\xFE\x80\x00\x00\x00\x00\x00\x00\xF0\x9F\x99\x85\xF0\x9F\x99\x86".b)) 151 | 152 | # Invalid parameters 153 | ## wrong length 154 | assert_raise(IPAddr::AddressFamilyError) { 155 | IPAddr.ntop("192.168.1.1".b) 156 | } 157 | assert_raise(IPAddr::AddressFamilyError) { 158 | IPAddr.ntop("\xC0\xA8\x01\xFF1".b) 159 | } 160 | ## UTF-8 161 | assert_raise(IPAddr::InvalidAddressError) { 162 | IPAddr.ntop("192.168.1.1") 163 | } 164 | assert_raise(IPAddr::InvalidAddressError) { 165 | IPAddr.ntop("\x0A\x0A\x0A\x0A") 166 | } 167 | assert_raise(IPAddr::InvalidAddressError) { 168 | IPAddr.ntop("\x0A\xE7\x8C\xAB") 169 | } 170 | assert_raise(IPAddr::InvalidAddressError) { 171 | IPAddr.ntop("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01") 172 | } 173 | assert_raise(IPAddr::InvalidAddressError) { 174 | IPAddr.ntop("\xFE\x80\x00\x00\x00\x00\x00\x00\xF0\x9F\x99\x85\xF0\x9F\x99\x86") 175 | } 176 | end 177 | 178 | def test_ipv4_compat 179 | a = IPAddr.new("::192.168.1.2") 180 | assert_equal("::192.168.1.2", a.to_s) 181 | assert_equal("0000:0000:0000:0000:0000:0000:c0a8:0102", a.to_string) 182 | assert_equal(Socket::AF_INET6, a.family) 183 | assert_warning(/obsolete/) { 184 | assert_predicate(a, :ipv4_compat?) 185 | } 186 | b = a.native 187 | assert_equal("192.168.1.2", b.to_s) 188 | assert_equal(Socket::AF_INET, b.family) 189 | assert_warning(/obsolete/) { 190 | assert_not_predicate(b, :ipv4_compat?) 191 | } 192 | 193 | a = IPAddr.new("192.168.1.2") 194 | assert_warning(/obsolete/) { 195 | b = a.ipv4_compat 196 | } 197 | assert_equal("::192.168.1.2", b.to_s) 198 | assert_equal(Socket::AF_INET6, b.family) 199 | assert_equal(128, b.prefix) 200 | 201 | a = IPAddr.new("192.168.0.0/16") 202 | assert_warning(/obsolete/) { 203 | b = a.ipv4_compat 204 | } 205 | assert_equal("::192.168.0.0", b.to_s) 206 | assert_equal(Socket::AF_INET6, b.family) 207 | assert_equal(112, b.prefix) 208 | end 209 | 210 | def test_ipv4_mapped 211 | a = IPAddr.new("::ffff:192.168.1.2") 212 | assert_equal("::ffff:192.168.1.2", a.to_s) 213 | assert_equal("0000:0000:0000:0000:0000:ffff:c0a8:0102", a.to_string) 214 | assert_equal(Socket::AF_INET6, a.family) 215 | assert_equal(true, a.ipv4_mapped?) 216 | b = a.native 217 | assert_equal("192.168.1.2", b.to_s) 218 | assert_equal(Socket::AF_INET, b.family) 219 | assert_equal(false, b.ipv4_mapped?) 220 | 221 | a = IPAddr.new("192.168.1.2") 222 | b = a.ipv4_mapped 223 | assert_equal("::ffff:192.168.1.2", b.to_s) 224 | assert_equal(Socket::AF_INET6, b.family) 225 | end 226 | 227 | def test_reverse 228 | assert_equal("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", IPAddr.new("3ffe:505:2::f").reverse) 229 | assert_equal("1.2.168.192.in-addr.arpa", IPAddr.new("192.168.2.1").reverse) 230 | end 231 | 232 | def test_ip6_arpa 233 | assert_equal("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", IPAddr.new("3ffe:505:2::f").ip6_arpa) 234 | assert_raise(IPAddr::InvalidAddressError) { 235 | IPAddr.new("192.168.2.1").ip6_arpa 236 | } 237 | end 238 | 239 | def test_ip6_int 240 | assert_equal("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.int", IPAddr.new("3ffe:505:2::f").ip6_int) 241 | assert_raise(IPAddr::InvalidAddressError) { 242 | IPAddr.new("192.168.2.1").ip6_int 243 | } 244 | end 245 | 246 | def test_prefix_writer 247 | a = IPAddr.new("192.168.1.2") 248 | ["1", "255.255.255.0", "ffff:ffff:ffff:ffff::", nil, 1.0, -1, 33].each { |x| 249 | assert_raise(IPAddr::InvalidPrefixError) { a.prefix = x } 250 | } 251 | a = IPAddr.new("1:2:3:4:5:6:7:8") 252 | ["1", "255.255.255.0", "ffff:ffff:ffff:ffff::", nil, 1.0, -1, 129].each { |x| 253 | assert_raise(IPAddr::InvalidPrefixError) { a.prefix = x } 254 | } 255 | 256 | a = IPAddr.new("192.168.1.2") 257 | a.prefix = 26 258 | assert_equal(26, a.prefix) 259 | assert_equal("192.168.1.0", a.to_s) 260 | 261 | a = IPAddr.new("1:2:3:4:5:6:7:8") 262 | a.prefix = 52 263 | assert_equal(52, a.prefix) 264 | assert_equal("1:2:3::", a.to_s) 265 | end 266 | 267 | def test_to_s 268 | assert_equal("3ffe:0505:0002:0000:0000:0000:0000:0001", IPAddr.new("3ffe:505:2::1").to_string) 269 | assert_equal("3ffe:505:2::1", IPAddr.new("3ffe:505:2::1").to_s) 270 | end 271 | 272 | def test_as_json 273 | assert_equal("192.168.1.2", IPAddr.new("192.168.1.2").as_json) 274 | assert_equal("192.168.1.0/24", IPAddr.new("192.168.1.2/24").as_json) 275 | assert_equal("2001:200:300::1", IPAddr.new("2001:200:300::1").as_json) 276 | assert_equal("2001:200:300::/48", IPAddr.new("2001:200:300::/48").as_json) 277 | end 278 | 279 | def test_to_json 280 | assert_equal("\"192.168.1.2\"", IPAddr.new("192.168.1.2").to_json) 281 | assert_equal("\"192.168.1.0/24\"", IPAddr.new("192.168.1.2/24").to_json) 282 | assert_equal("\"2001:200:300::1\"", IPAddr.new("2001:200:300::1").to_json) 283 | assert_equal("\"2001:200:300::/48\"", IPAddr.new("2001:200:300::/48").to_json) 284 | end 285 | 286 | def test_netmask 287 | a = IPAddr.new("192.168.1.2/8") 288 | assert_equal(a.netmask, "255.0.0.0") 289 | 290 | a = IPAddr.new("192.168.1.2/16") 291 | assert_equal(a.netmask, "255.255.0.0") 292 | 293 | a = IPAddr.new("192.168.1.2/24") 294 | assert_equal(a.netmask, "255.255.255.0") 295 | end 296 | 297 | def test_wildcard_mask 298 | a = IPAddr.new("192.168.1.2/1") 299 | assert_equal(a.wildcard_mask, "127.255.255.255") 300 | 301 | a = IPAddr.new("192.168.1.2/8") 302 | assert_equal(a.wildcard_mask, "0.255.255.255") 303 | 304 | a = IPAddr.new("192.168.1.2/16") 305 | assert_equal(a.wildcard_mask, "0.0.255.255") 306 | 307 | a = IPAddr.new("192.168.1.2/24") 308 | assert_equal(a.wildcard_mask, "0.0.0.255") 309 | 310 | a = IPAddr.new("192.168.1.2/32") 311 | assert_equal(a.wildcard_mask, "0.0.0.0") 312 | 313 | a = IPAddr.new("3ffe:505:2::/48") 314 | assert_equal(a.wildcard_mask, "0000:0000:0000:ffff:ffff:ffff:ffff:ffff") 315 | 316 | a = IPAddr.new("3ffe:505:2::/128") 317 | assert_equal(a.wildcard_mask, "0000:0000:0000:0000:0000:0000:0000:0000") 318 | end 319 | 320 | def test_zone_id 321 | a = IPAddr.new("192.168.1.2") 322 | assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%ab0' } 323 | assert_raise(IPAddr::InvalidAddressError) { a.zone_id } 324 | 325 | a = IPAddr.new("1:2:3:4:5:6:7:8") 326 | a.zone_id = '%ab0' 327 | assert_equal('%ab0', a.zone_id) 328 | assert_equal("1:2:3:4:5:6:7:8%ab0", a.to_s) 329 | assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%' } 330 | end 331 | 332 | def test_to_range 333 | a1 = IPAddr.new("127.0.0.1") 334 | range = a1..a1 335 | assert_equal(range, a1.to_range) 336 | assert_equal(range, a1.freeze.to_range) 337 | 338 | a2 = IPAddr.new("192.168.0.1/16") 339 | range = IPAddr.new("192.168.0.0")..IPAddr.new("192.168.255.255") 340 | assert_equal(range, a2.to_range) 341 | assert_equal(range, a2.freeze.to_range) 342 | 343 | a3 = IPAddr.new("3ffe:505:2::1") 344 | range = a3..a3 345 | assert_equal(range, a3.to_range) 346 | assert_equal(range, a3.freeze.to_range) 347 | 348 | a4 = IPAddr.new("::ffff/127") 349 | range = IPAddr.new("::fffe")..IPAddr.new("::ffff") 350 | assert_equal(range, a4.to_range) 351 | assert_equal(range, a4.freeze.to_range) 352 | end 353 | end 354 | 355 | class TC_Operator < Test::Unit::TestCase 356 | 357 | IN6MASK32 = "ffff:ffff::" 358 | IN6MASK128 = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" 359 | 360 | def setup 361 | @in6_addr_any = IPAddr.new() 362 | @a = IPAddr.new("3ffe:505:2::/48") 363 | @b = IPAddr.new("0:0:0:1::") 364 | @c = IPAddr.new(IN6MASK32) 365 | @inconvertible_range = 1..5 366 | @inconvertible_string = "sometext" 367 | end 368 | alias set_up setup 369 | 370 | def test_or 371 | assert_equal("3ffe:505:2:1::", (@a | @b).to_s) 372 | a = @a 373 | a |= @b 374 | assert_equal("3ffe:505:2:1::", a.to_s) 375 | assert_equal("3ffe:505:2::", @a.to_s) 376 | assert_equal("3ffe:505:2:1::", 377 | (@a | 0x00000000000000010000000000000000).to_s) 378 | end 379 | 380 | def test_and 381 | assert_equal("3ffe:505::", (@a & @c).to_s) 382 | a = @a 383 | a &= @c 384 | assert_equal("3ffe:505::", a.to_s) 385 | assert_equal("3ffe:505:2::", @a.to_s) 386 | assert_equal("3ffe:505::", (@a & 0xffffffff000000000000000000000000).to_s) 387 | end 388 | 389 | def test_shift_right 390 | assert_equal("0:3ffe:505:2::", (@a >> 16).to_s) 391 | a = @a 392 | a >>= 16 393 | assert_equal("0:3ffe:505:2::", a.to_s) 394 | assert_equal("3ffe:505:2::", @a.to_s) 395 | end 396 | 397 | def test_shift_left 398 | assert_equal("505:2::", (@a << 16).to_s) 399 | a = @a 400 | a <<= 16 401 | assert_equal("505:2::", a.to_s) 402 | assert_equal("3ffe:505:2::", @a.to_s) 403 | end 404 | 405 | def test_carrot 406 | a = ~@in6_addr_any 407 | assert_equal(IN6MASK128, a.to_s) 408 | assert_equal("::", @in6_addr_any.to_s) 409 | end 410 | 411 | def test_plus 412 | a = IPAddr.new("192.168.1.10") 413 | assert_equal("192.168.1.20", (a + 10).to_s) 414 | 415 | a = IPAddr.new("0.0.0.0") 416 | assert_equal("0.0.0.10", (a + 10).to_s) 417 | 418 | a = IPAddr.new("255.255.255.255") 419 | assert_raise(IPAddr::InvalidAddressError) { a + 10 } 420 | 421 | a = IPAddr.new("3ffe:505:2::a") 422 | assert_equal("3ffe:505:2::14", (a + 10).to_s) 423 | 424 | a = IPAddr.new("::") 425 | assert_equal("::a", (a + 10).to_s) 426 | 427 | a = IPAddr.new("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") 428 | assert_raise(IPAddr::InvalidAddressError) { a + 10 } 429 | end 430 | 431 | def test_minus 432 | a = IPAddr.new("192.168.1.10") 433 | assert_equal("192.168.1.0", (a - 10).to_s) 434 | 435 | a = IPAddr.new("0.0.0.0") 436 | assert_raise(IPAddr::InvalidAddressError) { a - 10 } 437 | 438 | a = IPAddr.new("255.255.255.255") 439 | assert_equal("255.255.255.245", (a - 10).to_s) 440 | 441 | a = IPAddr.new("3ffe:505:2::a") 442 | assert_equal("3ffe:505:2::", (a - 10).to_s) 443 | 444 | a = IPAddr.new("::") 445 | assert_raise(IPAddr::InvalidAddressError) { a - 10 } 446 | 447 | a = IPAddr.new("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") 448 | assert_equal("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff5", (a - 10).to_s) 449 | end 450 | 451 | def test_equal 452 | assert_equal(true, @a == IPAddr.new("3FFE:505:2::")) 453 | assert_equal(true, @a == IPAddr.new("3ffe:0505:0002::")) 454 | assert_equal(true, @a == IPAddr.new("3ffe:0505:0002:0:0:0:0:0")) 455 | assert_equal(false, @a == IPAddr.new("3ffe:505:3::")) 456 | assert_equal(true, @a != IPAddr.new("3ffe:505:3::")) 457 | assert_equal(false, @a != IPAddr.new("3ffe:505:2::")) 458 | assert_equal(false, @a == @inconvertible_range) 459 | assert_equal(false, @a == @inconvertible_string) 460 | end 461 | 462 | def test_compare 463 | assert_equal(nil, @a <=> @inconvertible_range) 464 | assert_equal(nil, @a <=> @inconvertible_string) 465 | end 466 | 467 | def test_mask 468 | a = @a.mask(32) 469 | assert_equal("3ffe:505::", a.to_s) 470 | assert_equal("3ffe:505::", @a.mask("ffff:ffff::").to_s) 471 | assert_equal("3ffe:505:2::", @a.to_s) 472 | a = IPAddr.new("192.168.2.0/24") 473 | assert_equal("192.168.0.0", a.mask(16).to_s) 474 | assert_equal("192.168.0.0", a.mask("255.255.0.0").to_s) 475 | assert_equal("192.168.2.0", a.to_s) 476 | assert_raise(IPAddr::InvalidPrefixError) {a.mask("255.255.0.255")} 477 | assert_raise(IPAddr::InvalidPrefixError) {@a.mask("ffff:1::")} 478 | end 479 | 480 | def test_include? 481 | assert_equal(true, @a.include?(IPAddr.new("3ffe:505:2::"))) 482 | assert_equal(true, @a.include?(IPAddr.new("3ffe:505:2::1"))) 483 | assert_equal(false, @a.include?(IPAddr.new("3ffe:505:3::"))) 484 | net1 = IPAddr.new("192.168.2.0/24") 485 | assert_equal(true, net1.include?(IPAddr.new("192.168.2.0"))) 486 | assert_equal(true, net1.include?(IPAddr.new("192.168.2.255"))) 487 | assert_equal(false, net1.include?(IPAddr.new("192.168.3.0"))) 488 | assert_equal(true, net1.include?(IPAddr.new("192.168.2.0/28"))) 489 | assert_equal(false, net1.include?(IPAddr.new("192.168.2.0/16"))) 490 | # test with integer parameter 491 | int = (192 << 24) + (168 << 16) + (2 << 8) + 13 492 | 493 | assert_equal(true, net1.include?(int)) 494 | assert_equal(false, net1.include?(int+255)) 495 | 496 | end 497 | 498 | def test_native_coerce_mask_addr 499 | assert_equal(IPAddr.new("0.0.0.2/255.255.255.255"), IPAddr.new("::2").native) 500 | assert_equal(IPAddr.new("0.0.0.2/255.255.255.255").to_range, IPAddr.new("::2").native.to_range) 501 | end 502 | 503 | def test_loopback? 504 | assert_equal(true, IPAddr.new('127.0.0.1').loopback?) 505 | assert_equal(true, IPAddr.new('127.127.1.1').loopback?) 506 | assert_equal(false, IPAddr.new('0.0.0.0').loopback?) 507 | assert_equal(false, IPAddr.new('192.168.2.0').loopback?) 508 | assert_equal(false, IPAddr.new('255.0.0.0').loopback?) 509 | assert_equal(true, IPAddr.new('::1').loopback?) 510 | assert_equal(false, IPAddr.new('::').loopback?) 511 | assert_equal(false, IPAddr.new('3ffe:505:2::1').loopback?) 512 | 513 | assert_equal(true, IPAddr.new('::ffff:127.0.0.1').loopback?) 514 | assert_equal(true, IPAddr.new('::ffff:127.127.1.1').loopback?) 515 | assert_equal(false, IPAddr.new('::ffff:0.0.0.0').loopback?) 516 | assert_equal(false, IPAddr.new('::ffff:192.168.2.0').loopback?) 517 | assert_equal(false, IPAddr.new('::ffff:255.0.0.0').loopback?) 518 | end 519 | 520 | def test_private? 521 | assert_equal(false, IPAddr.new('0.0.0.0').private?) 522 | assert_equal(false, IPAddr.new('127.0.0.1').private?) 523 | 524 | assert_equal(false, IPAddr.new('8.8.8.8').private?) 525 | assert_equal(true, IPAddr.new('10.0.0.0').private?) 526 | assert_equal(true, IPAddr.new('10.255.255.255').private?) 527 | assert_equal(false, IPAddr.new('11.255.1.1').private?) 528 | 529 | assert_equal(false, IPAddr.new('172.15.255.255').private?) 530 | assert_equal(true, IPAddr.new('172.16.0.0').private?) 531 | assert_equal(true, IPAddr.new('172.31.255.255').private?) 532 | assert_equal(false, IPAddr.new('172.32.0.0').private?) 533 | 534 | assert_equal(false, IPAddr.new('190.168.0.0').private?) 535 | assert_equal(true, IPAddr.new('192.168.0.0').private?) 536 | assert_equal(true, IPAddr.new('192.168.255.255').private?) 537 | assert_equal(false, IPAddr.new('192.169.0.0').private?) 538 | 539 | assert_equal(false, IPAddr.new('169.254.0.1').private?) 540 | 541 | assert_equal(false, IPAddr.new('::1').private?) 542 | assert_equal(false, IPAddr.new('::').private?) 543 | 544 | assert_equal(false, IPAddr.new('fb84:8bf7:e905::1').private?) 545 | assert_equal(true, IPAddr.new('fc84:8bf7:e905::1').private?) 546 | assert_equal(true, IPAddr.new('fd84:8bf7:e905::1').private?) 547 | assert_equal(false, IPAddr.new('fe84:8bf7:e905::1').private?) 548 | 549 | assert_equal(false, IPAddr.new('::ffff:0.0.0.0').private?) 550 | assert_equal(false, IPAddr.new('::ffff:127.0.0.1').private?) 551 | 552 | assert_equal(false, IPAddr.new('::ffff:8.8.8.8').private?) 553 | assert_equal(true, IPAddr.new('::ffff:10.0.0.0').private?) 554 | assert_equal(true, IPAddr.new('::ffff:10.255.255.255').private?) 555 | assert_equal(false, IPAddr.new('::ffff:11.255.1.1').private?) 556 | 557 | assert_equal(false, IPAddr.new('::ffff:172.15.255.255').private?) 558 | assert_equal(true, IPAddr.new('::ffff:172.16.0.0').private?) 559 | assert_equal(true, IPAddr.new('::ffff:172.31.255.255').private?) 560 | assert_equal(false, IPAddr.new('::ffff:172.32.0.0').private?) 561 | 562 | assert_equal(false, IPAddr.new('::ffff:190.168.0.0').private?) 563 | assert_equal(true, IPAddr.new('::ffff:192.168.0.0').private?) 564 | assert_equal(true, IPAddr.new('::ffff:192.168.255.255').private?) 565 | assert_equal(false, IPAddr.new('::ffff:192.169.0.0').private?) 566 | 567 | assert_equal(false, IPAddr.new('::ffff:169.254.0.1').private?) 568 | end 569 | 570 | def test_link_local? 571 | assert_equal(false, IPAddr.new('0.0.0.0').link_local?) 572 | assert_equal(false, IPAddr.new('127.0.0.1').link_local?) 573 | assert_equal(false, IPAddr.new('10.0.0.0').link_local?) 574 | assert_equal(false, IPAddr.new('172.16.0.0').link_local?) 575 | assert_equal(false, IPAddr.new('192.168.0.0').link_local?) 576 | 577 | assert_equal(true, IPAddr.new('169.254.1.1').link_local?) 578 | assert_equal(true, IPAddr.new('169.254.254.255').link_local?) 579 | 580 | assert_equal(false, IPAddr.new('::1').link_local?) 581 | assert_equal(false, IPAddr.new('::').link_local?) 582 | assert_equal(false, IPAddr.new('fb84:8bf7:e905::1').link_local?) 583 | 584 | assert_equal(true, IPAddr.new('fe80::dead:beef:cafe:1234').link_local?) 585 | 586 | assert_equal(false, IPAddr.new('::ffff:0.0.0.0').link_local?) 587 | assert_equal(false, IPAddr.new('::ffff:127.0.0.1').link_local?) 588 | assert_equal(false, IPAddr.new('::ffff:10.0.0.0').link_local?) 589 | assert_equal(false, IPAddr.new('::ffff:172.16.0.0').link_local?) 590 | assert_equal(false, IPAddr.new('::ffff:192.168.0.0').link_local?) 591 | 592 | assert_equal(true, IPAddr.new('::ffff:169.254.1.1').link_local?) 593 | assert_equal(true, IPAddr.new('::ffff:169.254.254.255').link_local?) 594 | end 595 | 596 | def test_hash 597 | a1 = IPAddr.new('192.168.2.0') 598 | a2 = IPAddr.new('192.168.2.0') 599 | a3 = IPAddr.new('3ffe:505:2::1') 600 | a4 = IPAddr.new('3ffe:505:2::1') 601 | a5 = IPAddr.new('127.0.0.1') 602 | a6 = IPAddr.new('::1') 603 | a7 = IPAddr.new('192.168.2.0/25') 604 | a8 = IPAddr.new('192.168.2.0/25') 605 | 606 | h = { a1 => 'ipv4', a2 => 'ipv4', a3 => 'ipv6', a4 => 'ipv6', a5 => 'ipv4', a6 => 'ipv6', a7 => 'ipv4', a8 => 'ipv4'} 607 | assert_equal(5, h.size) 608 | assert_equal('ipv4', h[a1]) 609 | assert_equal('ipv4', h[a2]) 610 | assert_equal('ipv6', h[a3]) 611 | assert_equal('ipv6', h[a4]) 612 | 613 | require 'set' 614 | s = Set[a1, a2, a3, a4, a5, a6, a7, a8] 615 | assert_equal(5, s.size) 616 | assert_equal(true, s.include?(a1)) 617 | assert_equal(true, s.include?(a2)) 618 | assert_equal(true, s.include?(a3)) 619 | assert_equal(true, s.include?(a4)) 620 | assert_equal(true, s.include?(a5)) 621 | assert_equal(true, s.include?(a6)) 622 | end 623 | 624 | def test_raises_invalid_address_error_with_error_message 625 | e = assert_raise(IPAddr::InvalidAddressError) do 626 | IPAddr.new('192.168.0.1000') 627 | end 628 | assert_equal('invalid address: 192.168.0.1000', e.message) 629 | 630 | e = assert_raise(IPAddr::InvalidAddressError) do 631 | IPAddr.new('192.168.01.100') 632 | end 633 | assert_equal('zero-filled number in IPv4 address is ambiguous: 192.168.01.100', e.message) 634 | end 635 | end 636 | -------------------------------------------------------------------------------- /lib/ipaddr.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ipaddr.rb - A class to manipulate an IP address 4 | # 5 | # Copyright (c) 2002 Hajimu UMEMOTO . 6 | # Copyright (c) 2007, 2009, 2012 Akinori MUSHA . 7 | # All rights reserved. 8 | # 9 | # You can redistribute and/or modify it under the same terms as Ruby. 10 | # 11 | # $Id$ 12 | # 13 | # Contact: 14 | # - Akinori MUSHA (current maintainer) 15 | # 16 | # TODO: 17 | # - scope_id support 18 | # 19 | require 'socket' 20 | 21 | # IPAddr provides a set of methods to manipulate an IP address. Both IPv4 and 22 | # IPv6 are supported. 23 | # 24 | # == Example 25 | # 26 | # require 'ipaddr' 27 | # 28 | # ipaddr1 = IPAddr.new "3ffe:505:2::1" 29 | # 30 | # p ipaddr1 #=> # 31 | # 32 | # p ipaddr1.to_s #=> "3ffe:505:2::1" 33 | # 34 | # ipaddr2 = ipaddr1.mask(48) #=> # 35 | # 36 | # p ipaddr2.to_s #=> "3ffe:505:2::" 37 | # 38 | # ipaddr3 = IPAddr.new "192.168.2.0/24" 39 | # 40 | # p ipaddr3 #=> # 41 | 42 | class IPAddr 43 | # The version string 44 | VERSION = "1.2.8" 45 | 46 | # 32 bit mask for IPv4 47 | IN4MASK = 0xffffffff 48 | # 128 bit mask for IPv6 49 | IN6MASK = 0xffffffffffffffffffffffffffffffff 50 | # Format string for IPv6 51 | IN6FORMAT = (["%.4x"] * 8).join(':').freeze 52 | 53 | # Regexp _internally_ used for parsing IPv4 address. 54 | RE_IPV4ADDRLIKE = %r{ 55 | \A 56 | \d+ \. \d+ \. \d+ \. \d+ 57 | \z 58 | }x 59 | 60 | # Regexp _internally_ used for parsing IPv6 address. 61 | RE_IPV6ADDRLIKE_FULL = %r{ 62 | \A 63 | (?: 64 | (?: [\da-f]{1,4} : ){7} [\da-f]{1,4} 65 | | 66 | ( (?: [\da-f]{1,4} : ){6} ) 67 | (\d+) \. (\d+) \. (\d+) \. (\d+) 68 | ) 69 | \z 70 | }xi 71 | 72 | # Regexp _internally_ used for parsing IPv6 address. 73 | RE_IPV6ADDRLIKE_COMPRESSED = %r{ 74 | \A 75 | ( (?: (?: [\da-f]{1,4} : )* [\da-f]{1,4} )? ) 76 | :: 77 | ( (?: 78 | ( (?: [\da-f]{1,4} : )* ) 79 | (?: 80 | [\da-f]{1,4} 81 | | 82 | (\d+) \. (\d+) \. (\d+) \. (\d+) 83 | ) 84 | )? ) 85 | \z 86 | }xi 87 | 88 | # Generic IPAddr related error. Exceptions raised in this class should 89 | # inherit from Error. 90 | class Error < ArgumentError; end 91 | 92 | # Raised when the provided IP address is an invalid address. 93 | class InvalidAddressError < Error; end 94 | 95 | # Raised when the address family is invalid such as an address with an 96 | # unsupported family, an address with an inconsistent family, or an address 97 | # who's family cannot be determined. 98 | class AddressFamilyError < Error; end 99 | 100 | # Raised when the address is an invalid length. 101 | class InvalidPrefixError < InvalidAddressError; end 102 | 103 | # Returns the address family of this IP address. 104 | attr_reader :family 105 | 106 | # Creates a new ipaddr containing the given network byte ordered 107 | # string form of an IP address. 108 | def self.new_ntoh(addr) 109 | return new(ntop(addr)) 110 | end 111 | 112 | # Convert a network byte ordered string form of an IP address into 113 | # human readable form. 114 | # It expects the string to be encoded in Encoding::ASCII_8BIT (BINARY). 115 | def self.ntop(addr) 116 | if addr.is_a?(String) && addr.encoding != Encoding::BINARY 117 | raise InvalidAddressError, "invalid encoding (given #{addr.encoding}, expected BINARY)" 118 | end 119 | 120 | case addr.bytesize 121 | when 4 122 | addr.unpack('C4').join('.') 123 | when 16 124 | IN6FORMAT % addr.unpack('n8') 125 | else 126 | raise AddressFamilyError, "unsupported address family" 127 | end 128 | end 129 | 130 | # Returns a new ipaddr built by bitwise AND. 131 | def &(other) 132 | return self.clone.set(@addr & coerce_other(other).to_i) 133 | end 134 | 135 | # Returns a new ipaddr built by bitwise OR. 136 | def |(other) 137 | return self.clone.set(@addr | coerce_other(other).to_i) 138 | end 139 | 140 | # Returns a new ipaddr built by bitwise right-shift. 141 | def >>(num) 142 | return self.clone.set(@addr >> num) 143 | end 144 | 145 | # Returns a new ipaddr built by bitwise left shift. 146 | def <<(num) 147 | return self.clone.set(addr_mask(@addr << num)) 148 | end 149 | 150 | # Returns a new ipaddr built by bitwise negation. 151 | def ~ 152 | return self.clone.set(addr_mask(~@addr)) 153 | end 154 | 155 | # Returns a new ipaddr greater than the original address by offset 156 | def +(offset) 157 | self.clone.set(@addr + offset, @family) 158 | end 159 | 160 | # Returns a new ipaddr less than the original address by offset 161 | def -(offset) 162 | self.clone.set(@addr - offset, @family) 163 | end 164 | 165 | # Returns true if two ipaddrs are equal. 166 | def ==(other) 167 | other = coerce_other(other) 168 | rescue 169 | false 170 | else 171 | @family == other.family && @addr == other.to_i 172 | end 173 | 174 | # Returns a new ipaddr built by masking IP address with the given 175 | # prefixlen/netmask. (e.g. 8, 64, "255.255.255.0", etc.) 176 | def mask(prefixlen) 177 | return self.clone.mask!(prefixlen) 178 | end 179 | 180 | # Returns true if the given ipaddr is in the range. 181 | # 182 | # e.g.: 183 | # require 'ipaddr' 184 | # net1 = IPAddr.new("192.168.2.0/24") 185 | # net2 = IPAddr.new("192.168.2.100") 186 | # net3 = IPAddr.new("192.168.3.0") 187 | # net4 = IPAddr.new("192.168.2.0/16") 188 | # p net1.include?(net2) #=> true 189 | # p net1.include?(net3) #=> false 190 | # p net1.include?(net4) #=> false 191 | # p net4.include?(net1) #=> true 192 | def include?(other) 193 | other = coerce_other(other) 194 | return false unless other.family == family 195 | begin_addr <= other.begin_addr && end_addr >= other.end_addr 196 | end 197 | alias === include? 198 | 199 | # Returns the integer representation of the ipaddr. 200 | def to_i 201 | return @addr 202 | end 203 | 204 | # Returns a string containing the IP address representation. 205 | def to_s 206 | str = to_string 207 | return str if ipv4? 208 | 209 | str.gsub!(/\b0{1,3}([\da-f]+)\b/i, '\1') 210 | loop do 211 | break if str.sub!(/\A0:0:0:0:0:0:0:0\z/, '::') 212 | break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':') 213 | break if str.sub!(/\b0:0:0:0:0:0\b/, ':') 214 | break if str.sub!(/\b0:0:0:0:0\b/, ':') 215 | break if str.sub!(/\b0:0:0:0\b/, ':') 216 | break if str.sub!(/\b0:0:0\b/, ':') 217 | break if str.sub!(/\b0:0\b/, ':') 218 | break 219 | end 220 | str.sub!(/:{3,}/, '::') 221 | 222 | if /\A::(ffff:)?([\da-f]{1,4}):([\da-f]{1,4})\z/i =~ str 223 | str = sprintf('::%s%d.%d.%d.%d', $1, $2.hex / 256, $2.hex % 256, $3.hex / 256, $3.hex % 256) 224 | end 225 | 226 | str 227 | end 228 | 229 | # Returns a string containing the IP address representation in 230 | # canonical form. 231 | def to_string 232 | str = _to_string(@addr) 233 | 234 | if @family == Socket::AF_INET6 235 | str << zone_id.to_s 236 | end 237 | 238 | return str 239 | end 240 | 241 | # Returns a string containing the IP address representation with prefix. 242 | def as_json(*) 243 | if ipv4? && prefix == 32 244 | to_s 245 | elsif ipv6? && prefix == 128 246 | to_s 247 | else 248 | cidr 249 | end 250 | end 251 | 252 | # Returns a json string containing the IP address representation. 253 | def to_json(*a) 254 | %Q{"#{as_json(*a)}"} 255 | end 256 | 257 | # Returns a string containing the IP address representation in 258 | # cidr notation 259 | def cidr 260 | "#{to_s}/#{prefix}" 261 | end 262 | 263 | # Returns a network byte ordered string form of the IP address. 264 | def hton 265 | case @family 266 | when Socket::AF_INET 267 | return [@addr].pack('N') 268 | when Socket::AF_INET6 269 | return (0..7).map { |i| 270 | (@addr >> (112 - 16 * i)) & 0xffff 271 | }.pack('n8') 272 | else 273 | raise AddressFamilyError, "unsupported address family" 274 | end 275 | end 276 | 277 | # Returns true if the ipaddr is an IPv4 address. 278 | def ipv4? 279 | return @family == Socket::AF_INET 280 | end 281 | 282 | # Returns true if the ipaddr is an IPv6 address. 283 | def ipv6? 284 | return @family == Socket::AF_INET6 285 | end 286 | 287 | # Returns true if the ipaddr is a loopback address. 288 | # Loopback IPv4 addresses in the IPv4-mapped IPv6 289 | # address range are also considered as loopback addresses. 290 | def loopback? 291 | case @family 292 | when Socket::AF_INET 293 | @addr & 0xff000000 == 0x7f000000 # 127.0.0.1/8 294 | when Socket::AF_INET6 295 | @addr == 1 || # ::1 296 | (@addr & 0xffff_0000_0000 == 0xffff_0000_0000 && ( 297 | @addr & 0xff000000 == 0x7f000000 # ::ffff:127.0.0.1/8 298 | )) 299 | else 300 | raise AddressFamilyError, "unsupported address family" 301 | end 302 | end 303 | 304 | # Returns true if the ipaddr is a private address. IPv4 addresses 305 | # in 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16 as defined in RFC 306 | # 1918 and IPv6 Unique Local Addresses in fc00::/7 as defined in RFC 307 | # 4193 are considered private. Private IPv4 addresses in the 308 | # IPv4-mapped IPv6 address range are also considered private. 309 | def private? 310 | case @family 311 | when Socket::AF_INET 312 | @addr & 0xff000000 == 0x0a000000 || # 10.0.0.0/8 313 | @addr & 0xfff00000 == 0xac100000 || # 172.16.0.0/12 314 | @addr & 0xffff0000 == 0xc0a80000 # 192.168.0.0/16 315 | when Socket::AF_INET6 316 | @addr & 0xfe00_0000_0000_0000_0000_0000_0000_0000 == 0xfc00_0000_0000_0000_0000_0000_0000_0000 || 317 | (@addr & 0xffff_0000_0000 == 0xffff_0000_0000 && ( 318 | @addr & 0xff000000 == 0x0a000000 || # ::ffff:10.0.0.0/8 319 | @addr & 0xfff00000 == 0xac100000 || # ::ffff::172.16.0.0/12 320 | @addr & 0xffff0000 == 0xc0a80000 # ::ffff::192.168.0.0/16 321 | )) 322 | else 323 | raise AddressFamilyError, "unsupported address family" 324 | end 325 | end 326 | 327 | # Returns true if the ipaddr is a link-local address. IPv4 328 | # addresses in 169.254.0.0/16 reserved by RFC 3927 and link-local 329 | # IPv6 Unicast Addresses in fe80::/10 reserved by RFC 4291 are 330 | # considered link-local. Link-local IPv4 addresses in the 331 | # IPv4-mapped IPv6 address range are also considered link-local. 332 | def link_local? 333 | case @family 334 | when Socket::AF_INET 335 | @addr & 0xffff0000 == 0xa9fe0000 # 169.254.0.0/16 336 | when Socket::AF_INET6 337 | @addr & 0xffc0_0000_0000_0000_0000_0000_0000_0000 == 0xfe80_0000_0000_0000_0000_0000_0000_0000 || # fe80::/10 338 | (@addr & 0xffff_0000_0000 == 0xffff_0000_0000 && ( 339 | @addr & 0xffff0000 == 0xa9fe0000 # ::ffff:169.254.0.0/16 340 | )) 341 | else 342 | raise AddressFamilyError, "unsupported address family" 343 | end 344 | end 345 | 346 | # Returns true if the ipaddr is an IPv4-mapped IPv6 address. 347 | def ipv4_mapped? 348 | return ipv6? && (@addr >> 32) == 0xffff 349 | end 350 | 351 | # Returns true if the ipaddr is an IPv4-compatible IPv6 address. 352 | def ipv4_compat? 353 | warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE 354 | _ipv4_compat? 355 | end 356 | 357 | def _ipv4_compat? # :nodoc: 358 | if !ipv6? || (@addr >> 32) != 0 359 | return false 360 | end 361 | a = (@addr & IN4MASK) 362 | return a != 0 && a != 1 363 | end 364 | 365 | private :_ipv4_compat? 366 | 367 | # Returns a new ipaddr built by converting the native IPv4 address 368 | # into an IPv4-mapped IPv6 address. 369 | def ipv4_mapped 370 | if !ipv4? 371 | raise InvalidAddressError, "not an IPv4 address: #{@addr}" 372 | end 373 | clone = self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6) 374 | clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) 375 | clone 376 | end 377 | 378 | # Returns a new ipaddr built by converting the native IPv4 address 379 | # into an IPv4-compatible IPv6 address. 380 | def ipv4_compat 381 | warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE 382 | if !ipv4? 383 | raise InvalidAddressError, "not an IPv4 address: #{@addr}" 384 | end 385 | clone = self.clone.set(@addr, Socket::AF_INET6) 386 | clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) 387 | clone 388 | end 389 | 390 | # Returns a new ipaddr built by converting the IPv6 address into a 391 | # native IPv4 address. If the IP address is not an IPv4-mapped or 392 | # IPv4-compatible IPv6 address, returns self. 393 | def native 394 | if !ipv4_mapped? && !_ipv4_compat? 395 | return self 396 | end 397 | return self.clone.set(@addr & IN4MASK, Socket::AF_INET) 398 | end 399 | 400 | # Returns a string for DNS reverse lookup. It returns a string in 401 | # RFC3172 form for an IPv6 address. 402 | def reverse 403 | case @family 404 | when Socket::AF_INET 405 | return _reverse + ".in-addr.arpa" 406 | when Socket::AF_INET6 407 | return ip6_arpa 408 | else 409 | raise AddressFamilyError, "unsupported address family" 410 | end 411 | end 412 | 413 | # Returns a string for DNS reverse lookup compatible with RFC3172. 414 | def ip6_arpa 415 | if !ipv6? 416 | raise InvalidAddressError, "not an IPv6 address: #{@addr}" 417 | end 418 | return _reverse + ".ip6.arpa" 419 | end 420 | 421 | # Returns a string for DNS reverse lookup compatible with RFC1886. 422 | def ip6_int 423 | if !ipv6? 424 | raise InvalidAddressError, "not an IPv6 address: #{@addr}" 425 | end 426 | return _reverse + ".ip6.int" 427 | end 428 | 429 | # Returns the successor to the ipaddr. 430 | def succ 431 | return self.clone.set(@addr + 1, @family) 432 | end 433 | 434 | # Compares the ipaddr with another. 435 | def <=>(other) 436 | other = coerce_other(other) 437 | rescue 438 | nil 439 | else 440 | @addr <=> other.to_i if other.family == @family 441 | end 442 | include Comparable 443 | 444 | # Checks equality used by Hash. 445 | def eql?(other) 446 | return self.class == other.class && self.hash == other.hash && self == other 447 | end 448 | 449 | # Returns a hash value used by Hash, Set, and Array classes 450 | def hash 451 | return ([@addr, @mask_addr, @zone_id].hash << 1) | (ipv4? ? 0 : 1) 452 | end 453 | 454 | # Creates a Range object for the network address. 455 | def to_range 456 | self.class.new(begin_addr, @family)..self.class.new(end_addr, @family) 457 | end 458 | 459 | # Returns the prefix length in bits for the ipaddr. 460 | def prefix 461 | case @family 462 | when Socket::AF_INET 463 | n = IN4MASK ^ @mask_addr 464 | i = 32 465 | when Socket::AF_INET6 466 | n = IN6MASK ^ @mask_addr 467 | i = 128 468 | else 469 | raise AddressFamilyError, "unsupported address family" 470 | end 471 | while n.positive? 472 | n >>= 1 473 | i -= 1 474 | end 475 | i 476 | end 477 | 478 | # Sets the prefix length in bits 479 | def prefix=(prefix) 480 | case prefix 481 | when Integer 482 | mask!(prefix) 483 | else 484 | raise InvalidPrefixError, "prefix must be an integer" 485 | end 486 | end 487 | 488 | # Returns a string containing a human-readable representation of the 489 | # ipaddr. ("#") 490 | def inspect 491 | case @family 492 | when Socket::AF_INET 493 | af = "IPv4" 494 | when Socket::AF_INET6 495 | af = "IPv6" 496 | zone_id = @zone_id.to_s 497 | else 498 | raise AddressFamilyError, "unsupported address family" 499 | end 500 | return sprintf("#<%s: %s:%s%s/%s>", self.class.name, 501 | af, _to_string(@addr), zone_id, _to_string(@mask_addr)) 502 | end 503 | 504 | # Returns the netmask in string format e.g. 255.255.0.0 505 | def netmask 506 | _to_string(@mask_addr) 507 | end 508 | 509 | # Returns the wildcard mask in string format e.g. 0.0.255.255 510 | def wildcard_mask 511 | case @family 512 | when Socket::AF_INET 513 | mask = IN4MASK ^ @mask_addr 514 | when Socket::AF_INET6 515 | mask = IN6MASK ^ @mask_addr 516 | else 517 | raise AddressFamilyError, "unsupported address family" 518 | end 519 | 520 | _to_string(mask) 521 | end 522 | 523 | # Returns the IPv6 zone identifier, if present. 524 | # Raises InvalidAddressError if not an IPv6 address. 525 | def zone_id 526 | if @family == Socket::AF_INET6 527 | @zone_id 528 | else 529 | raise InvalidAddressError, "not an IPv6 address" 530 | end 531 | end 532 | 533 | # Returns the IPv6 zone identifier, if present. 534 | # Raises InvalidAddressError if not an IPv6 address. 535 | def zone_id=(zid) 536 | if @family == Socket::AF_INET6 537 | case zid 538 | when nil, /\A%(\w+)\z/ 539 | @zone_id = zid 540 | else 541 | raise InvalidAddressError, "invalid zone identifier for address" 542 | end 543 | else 544 | raise InvalidAddressError, "not an IPv6 address" 545 | end 546 | end 547 | 548 | protected 549 | # :stopdoc: 550 | 551 | def begin_addr 552 | @addr & @mask_addr 553 | end 554 | 555 | def end_addr 556 | case @family 557 | when Socket::AF_INET 558 | @addr | (IN4MASK ^ @mask_addr) 559 | when Socket::AF_INET6 560 | @addr | (IN6MASK ^ @mask_addr) 561 | else 562 | raise AddressFamilyError, "unsupported address family" 563 | end 564 | end 565 | #:startdoc: 566 | 567 | # Set +@addr+, the internal stored ip address, to given +addr+. The 568 | # parameter +addr+ is validated using the first +family+ member, 569 | # which is +Socket::AF_INET+ or +Socket::AF_INET6+. 570 | def set(addr, *family) 571 | case family[0] ? family[0] : @family 572 | when Socket::AF_INET 573 | if addr < 0 || addr > IN4MASK 574 | raise InvalidAddressError, "invalid address: #{addr}" 575 | end 576 | when Socket::AF_INET6 577 | if addr < 0 || addr > IN6MASK 578 | raise InvalidAddressError, "invalid address: #{addr}" 579 | end 580 | else 581 | raise AddressFamilyError, "unsupported address family" 582 | end 583 | @addr = addr 584 | if family[0] 585 | @family = family[0] 586 | if @family == Socket::AF_INET 587 | @mask_addr &= IN4MASK 588 | end 589 | end 590 | return self 591 | end 592 | 593 | # Set current netmask to given mask. 594 | def mask!(mask) 595 | case mask 596 | when String 597 | case mask 598 | when /\A(0|[1-9]+\d*)\z/ 599 | prefixlen = mask.to_i 600 | when /\A\d+\z/ 601 | raise InvalidPrefixError, "leading zeros in prefix" 602 | else 603 | m = IPAddr.new(mask) 604 | if m.family != @family 605 | raise InvalidPrefixError, "address family is not same" 606 | end 607 | @mask_addr = m.to_i 608 | n = @mask_addr ^ m.instance_variable_get(:@mask_addr) 609 | unless ((n + 1) & n).zero? 610 | raise InvalidPrefixError, "invalid mask #{mask}" 611 | end 612 | @addr &= @mask_addr 613 | return self 614 | end 615 | else 616 | prefixlen = mask 617 | end 618 | case @family 619 | when Socket::AF_INET 620 | if prefixlen < 0 || prefixlen > 32 621 | raise InvalidPrefixError, "invalid length" 622 | end 623 | masklen = 32 - prefixlen 624 | @mask_addr = ((IN4MASK >> masklen) << masklen) 625 | when Socket::AF_INET6 626 | if prefixlen < 0 || prefixlen > 128 627 | raise InvalidPrefixError, "invalid length" 628 | end 629 | masklen = 128 - prefixlen 630 | @mask_addr = ((IN6MASK >> masklen) << masklen) 631 | else 632 | raise AddressFamilyError, "unsupported address family" 633 | end 634 | @addr = ((@addr >> masklen) << masklen) 635 | return self 636 | end 637 | 638 | private 639 | 640 | # Creates a new ipaddr object either from a human readable IP 641 | # address representation in string, or from a packed in_addr value 642 | # followed by an address family. 643 | # 644 | # In the former case, the following are the valid formats that will 645 | # be recognized: "address", "address/prefixlen" and "address/mask", 646 | # where IPv6 address may be enclosed in square brackets (`[' and 647 | # `]'). If a prefixlen or a mask is specified, it returns a masked 648 | # IP address. Although the address family is determined 649 | # automatically from a specified string, you can specify one 650 | # explicitly by the optional second argument. 651 | # 652 | # Otherwise an IP address is generated from a packed in_addr value 653 | # and an address family. 654 | # 655 | # The IPAddr class defines many methods and operators, and some of 656 | # those, such as &, |, include? and ==, accept a string, or a packed 657 | # in_addr value instead of an IPAddr object. 658 | def initialize(addr = '::', family = Socket::AF_UNSPEC) 659 | @mask_addr = nil 660 | if !addr.kind_of?(String) 661 | case family 662 | when Socket::AF_INET, Socket::AF_INET6 663 | set(addr.to_i, family) 664 | @mask_addr = (family == Socket::AF_INET) ? IN4MASK : IN6MASK 665 | return 666 | when Socket::AF_UNSPEC 667 | raise AddressFamilyError, "address family must be specified" 668 | else 669 | raise AddressFamilyError, "unsupported address family: #{family}" 670 | end 671 | end 672 | prefix, prefixlen = addr.split('/', 2) 673 | if prefix =~ /\A\[(.*)\]\z/i 674 | prefix = $1 675 | family = Socket::AF_INET6 676 | end 677 | if prefix =~ /\A(.*)(%\w+)\z/ 678 | prefix = $1 679 | zone_id = $2 680 | family = Socket::AF_INET6 681 | end 682 | # It seems AI_NUMERICHOST doesn't do the job. 683 | #Socket.getaddrinfo(left, nil, Socket::AF_INET6, Socket::SOCK_STREAM, nil, 684 | # Socket::AI_NUMERICHOST) 685 | @addr = @family = nil 686 | if family == Socket::AF_UNSPEC || family == Socket::AF_INET 687 | @addr = in_addr(prefix) 688 | if @addr 689 | @family = Socket::AF_INET 690 | end 691 | end 692 | if !@addr && (family == Socket::AF_UNSPEC || family == Socket::AF_INET6) 693 | @addr = in6_addr(prefix) 694 | @family = Socket::AF_INET6 695 | end 696 | @zone_id = zone_id 697 | if family != Socket::AF_UNSPEC && @family != family 698 | raise AddressFamilyError, "address family mismatch" 699 | end 700 | if prefixlen 701 | mask!(prefixlen) 702 | else 703 | @mask_addr = (@family == Socket::AF_INET) ? IN4MASK : IN6MASK 704 | end 705 | end 706 | 707 | # :stopdoc: 708 | def coerce_other(other) 709 | case other 710 | when IPAddr 711 | other 712 | when String 713 | self.class.new(other) 714 | else 715 | self.class.new(other, @family) 716 | end 717 | end 718 | 719 | def in_addr(addr) 720 | case addr 721 | when Array 722 | octets = addr 723 | else 724 | RE_IPV4ADDRLIKE.match?(addr) or return nil 725 | octets = addr.split('.') 726 | end 727 | octets.inject(0) { |i, s| 728 | (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{addr}" 729 | (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{addr}" 730 | i << 8 | n 731 | } 732 | end 733 | 734 | def in6_addr(left) 735 | case left 736 | when RE_IPV6ADDRLIKE_FULL 737 | if $2 738 | addr = in_addr($~[2,4]) 739 | left = $1 + ':' 740 | else 741 | addr = 0 742 | end 743 | right = '' 744 | when RE_IPV6ADDRLIKE_COMPRESSED 745 | if $4 746 | left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{@addr}" 747 | addr = in_addr($~[4,4]) 748 | left = $1 749 | right = $3 + '0:0' 750 | else 751 | left.count(':') <= ($1.empty? || $2.empty? ? 8 : 7) or 752 | raise InvalidAddressError, "invalid address: #{@addr}" 753 | left = $1 754 | right = $2 755 | addr = 0 756 | end 757 | else 758 | raise InvalidAddressError, "invalid address: #{@addr}" 759 | end 760 | l = left.split(':') 761 | r = right.split(':') 762 | rest = 8 - l.size - r.size 763 | if rest < 0 764 | return nil 765 | end 766 | (l + Array.new(rest, '0') + r).inject(0) { |i, s| 767 | i << 16 | s.hex 768 | } | addr 769 | end 770 | 771 | def addr_mask(addr) 772 | case @family 773 | when Socket::AF_INET 774 | return addr & IN4MASK 775 | when Socket::AF_INET6 776 | return addr & IN6MASK 777 | else 778 | raise AddressFamilyError, "unsupported address family" 779 | end 780 | end 781 | 782 | def _reverse 783 | case @family 784 | when Socket::AF_INET 785 | return (0..3).map { |i| 786 | (@addr >> (8 * i)) & 0xff 787 | }.join('.') 788 | when Socket::AF_INET6 789 | return ("%.32x" % @addr).reverse!.gsub!(/.(?!$)/, '\&.') 790 | else 791 | raise AddressFamilyError, "unsupported address family" 792 | end 793 | end 794 | 795 | def _to_string(addr) 796 | case @family 797 | when Socket::AF_INET 798 | return (0..3).map { |i| 799 | (addr >> (24 - 8 * i)) & 0xff 800 | }.join('.') 801 | when Socket::AF_INET6 802 | return (("%.32x" % addr).gsub!(/.{4}(?!$)/, '\&:')) 803 | else 804 | raise AddressFamilyError, "unsupported address family" 805 | end 806 | end 807 | 808 | end 809 | 810 | unless Socket.const_defined? :AF_INET6 811 | class Socket < BasicSocket 812 | # IPv6 protocol family 813 | AF_INET6 = Object.new.freeze 814 | end 815 | 816 | class << IPSocket 817 | private 818 | 819 | def valid_v6?(addr) # :nodoc: 820 | case addr 821 | when IPAddr::RE_IPV6ADDRLIKE_FULL 822 | if $2 823 | $~[2,4].all? {|i| i.to_i < 256 } 824 | else 825 | true 826 | end 827 | when IPAddr::RE_IPV6ADDRLIKE_COMPRESSED 828 | if $4 829 | addr.count(':') <= 6 && $~[4,4].all? {|i| i.to_i < 256} 830 | else 831 | addr.count(':') <= 7 832 | end 833 | else 834 | false 835 | end 836 | end 837 | 838 | alias getaddress_orig getaddress 839 | 840 | public 841 | 842 | # Returns a +String+ based representation of a valid DNS hostname, 843 | # IPv4 or IPv6 address. 844 | # 845 | # IPSocket.getaddress 'localhost' #=> "::1" 846 | # IPSocket.getaddress 'broadcasthost' #=> "255.255.255.255" 847 | # IPSocket.getaddress 'www.ruby-lang.org' #=> "221.186.184.68" 848 | # IPSocket.getaddress 'www.ccc.de' #=> "2a00:1328:e102:ccc0::122" 849 | def getaddress(s) 850 | if valid_v6?(s) 851 | s 852 | else 853 | getaddress_orig(s) 854 | end 855 | end 856 | end 857 | end 858 | --------------------------------------------------------------------------------