├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── ipaddr.gemspec ├── lib └── ipaddr.rb └── test ├── lib └── helper.rb └── test_ipaddr.rb /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @knu 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.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-13, ruby: '2.4' } 23 | - { os: macos-13, ruby: '2.5' } 24 | 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - uses: actions/checkout@v4 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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", "ipaddr.gemspec", "lib/ipaddr.rb"] 33 | spec.require_paths = ["lib"] 34 | 35 | spec.required_ruby_version = ">= 2.4" 36 | end 37 | -------------------------------------------------------------------------------- /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 | VERSION = "1.2.7" 44 | 45 | # 32 bit mask for IPv4 46 | IN4MASK = 0xffffffff 47 | # 128 bit mask for IPv6 48 | IN6MASK = 0xffffffffffffffffffffffffffffffff 49 | # Format string for IPv6 50 | IN6FORMAT = (["%.4x"] * 8).join(':').freeze 51 | 52 | # Regexp _internally_ used for parsing IPv4 address. 53 | RE_IPV4ADDRLIKE = %r{ 54 | \A 55 | \d+ \. \d+ \. \d+ \. \d+ 56 | \z 57 | }x 58 | 59 | # Regexp _internally_ used for parsing IPv6 address. 60 | RE_IPV6ADDRLIKE_FULL = %r{ 61 | \A 62 | (?: 63 | (?: [\da-f]{1,4} : ){7} [\da-f]{1,4} 64 | | 65 | ( (?: [\da-f]{1,4} : ){6} ) 66 | (\d+) \. (\d+) \. (\d+) \. (\d+) 67 | ) 68 | \z 69 | }xi 70 | 71 | # Regexp _internally_ used for parsing IPv6 address. 72 | RE_IPV6ADDRLIKE_COMPRESSED = %r{ 73 | \A 74 | ( (?: (?: [\da-f]{1,4} : )* [\da-f]{1,4} )? ) 75 | :: 76 | ( (?: 77 | ( (?: [\da-f]{1,4} : )* ) 78 | (?: 79 | [\da-f]{1,4} 80 | | 81 | (\d+) \. (\d+) \. (\d+) \. (\d+) 82 | ) 83 | )? ) 84 | \z 85 | }xi 86 | 87 | # Generic IPAddr related error. Exceptions raised in this class should 88 | # inherit from Error. 89 | class Error < ArgumentError; end 90 | 91 | # Raised when the provided IP address is an invalid address. 92 | class InvalidAddressError < Error; end 93 | 94 | # Raised when the address family is invalid such as an address with an 95 | # unsupported family, an address with an inconsistent family, or an address 96 | # who's family cannot be determined. 97 | class AddressFamilyError < Error; end 98 | 99 | # Raised when the address is an invalid length. 100 | class InvalidPrefixError < InvalidAddressError; end 101 | 102 | # Returns the address family of this IP address. 103 | attr_reader :family 104 | 105 | # Creates a new ipaddr containing the given network byte ordered 106 | # string form of an IP address. 107 | def self.new_ntoh(addr) 108 | return new(ntop(addr)) 109 | end 110 | 111 | # Convert a network byte ordered string form of an IP address into 112 | # human readable form. 113 | # It expects the string to be encoded in Encoding::ASCII_8BIT (BINARY). 114 | def self.ntop(addr) 115 | if addr.is_a?(String) && addr.encoding != Encoding::BINARY 116 | raise InvalidAddressError, "invalid encoding (given #{addr.encoding}, expected BINARY)" 117 | end 118 | 119 | case addr.bytesize 120 | when 4 121 | addr.unpack('C4').join('.') 122 | when 16 123 | IN6FORMAT % addr.unpack('n8') 124 | else 125 | raise AddressFamilyError, "unsupported address family" 126 | end 127 | end 128 | 129 | # Returns a new ipaddr built by bitwise AND. 130 | def &(other) 131 | return self.clone.set(@addr & coerce_other(other).to_i) 132 | end 133 | 134 | # Returns a new ipaddr built by bitwise OR. 135 | def |(other) 136 | return self.clone.set(@addr | coerce_other(other).to_i) 137 | end 138 | 139 | # Returns a new ipaddr built by bitwise right-shift. 140 | def >>(num) 141 | return self.clone.set(@addr >> num) 142 | end 143 | 144 | # Returns a new ipaddr built by bitwise left shift. 145 | def <<(num) 146 | return self.clone.set(addr_mask(@addr << num)) 147 | end 148 | 149 | # Returns a new ipaddr built by bitwise negation. 150 | def ~ 151 | return self.clone.set(addr_mask(~@addr)) 152 | end 153 | 154 | # Returns a new ipaddr greater than the original address by offset 155 | def +(offset) 156 | self.clone.set(@addr + offset, @family) 157 | end 158 | 159 | # Returns a new ipaddr less than the original address by offset 160 | def -(offset) 161 | self.clone.set(@addr - offset, @family) 162 | end 163 | 164 | # Returns true if two ipaddrs are equal. 165 | def ==(other) 166 | other = coerce_other(other) 167 | rescue 168 | false 169 | else 170 | @family == other.family && @addr == other.to_i 171 | end 172 | 173 | # Returns a new ipaddr built by masking IP address with the given 174 | # prefixlen/netmask. (e.g. 8, 64, "255.255.255.0", etc.) 175 | def mask(prefixlen) 176 | return self.clone.mask!(prefixlen) 177 | end 178 | 179 | # Returns true if the given ipaddr is in the range. 180 | # 181 | # e.g.: 182 | # require 'ipaddr' 183 | # net1 = IPAddr.new("192.168.2.0/24") 184 | # net2 = IPAddr.new("192.168.2.100") 185 | # net3 = IPAddr.new("192.168.3.0") 186 | # net4 = IPAddr.new("192.168.2.0/16") 187 | # p net1.include?(net2) #=> true 188 | # p net1.include?(net3) #=> false 189 | # p net1.include?(net4) #=> false 190 | # p net4.include?(net1) #=> true 191 | def include?(other) 192 | other = coerce_other(other) 193 | return false unless other.family == family 194 | begin_addr <= other.begin_addr && end_addr >= other.end_addr 195 | end 196 | alias === include? 197 | 198 | # Returns the integer representation of the ipaddr. 199 | def to_i 200 | return @addr 201 | end 202 | 203 | # Returns a string containing the IP address representation. 204 | def to_s 205 | str = to_string 206 | return str if ipv4? 207 | 208 | str.gsub!(/\b0{1,3}([\da-f]+)\b/i, '\1') 209 | loop do 210 | break if str.sub!(/\A0:0:0:0:0:0:0:0\z/, '::') 211 | break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':') 212 | break if str.sub!(/\b0:0:0:0:0:0\b/, ':') 213 | break if str.sub!(/\b0:0:0:0:0\b/, ':') 214 | break if str.sub!(/\b0:0:0:0\b/, ':') 215 | break if str.sub!(/\b0:0:0\b/, ':') 216 | break if str.sub!(/\b0:0\b/, ':') 217 | break 218 | end 219 | str.sub!(/:{3,}/, '::') 220 | 221 | if /\A::(ffff:)?([\da-f]{1,4}):([\da-f]{1,4})\z/i =~ str 222 | str = sprintf('::%s%d.%d.%d.%d', $1, $2.hex / 256, $2.hex % 256, $3.hex / 256, $3.hex % 256) 223 | end 224 | 225 | str 226 | end 227 | 228 | # Returns a string containing the IP address representation in 229 | # canonical form. 230 | def to_string 231 | str = _to_string(@addr) 232 | 233 | if @family == Socket::AF_INET6 234 | str << zone_id.to_s 235 | end 236 | 237 | return str 238 | end 239 | 240 | # Returns a string containing the IP address representation with prefix. 241 | def as_json(*) 242 | if ipv4? && prefix == 32 243 | to_s 244 | elsif ipv6? && prefix == 128 245 | to_s 246 | else 247 | cidr 248 | end 249 | end 250 | 251 | # Returns a json string containing the IP address representation. 252 | def to_json(*a) 253 | %Q{"#{as_json(*a)}"} 254 | end 255 | 256 | # Returns a string containing the IP address representation in 257 | # cidr notation 258 | def cidr 259 | "#{to_s}/#{prefix}" 260 | end 261 | 262 | # Returns a network byte ordered string form of the IP address. 263 | def hton 264 | case @family 265 | when Socket::AF_INET 266 | return [@addr].pack('N') 267 | when Socket::AF_INET6 268 | return (0..7).map { |i| 269 | (@addr >> (112 - 16 * i)) & 0xffff 270 | }.pack('n8') 271 | else 272 | raise AddressFamilyError, "unsupported address family" 273 | end 274 | end 275 | 276 | # Returns true if the ipaddr is an IPv4 address. 277 | def ipv4? 278 | return @family == Socket::AF_INET 279 | end 280 | 281 | # Returns true if the ipaddr is an IPv6 address. 282 | def ipv6? 283 | return @family == Socket::AF_INET6 284 | end 285 | 286 | # Returns true if the ipaddr is a loopback address. 287 | # Loopback IPv4 addresses in the IPv4-mapped IPv6 288 | # address range are also considered as loopback addresses. 289 | def loopback? 290 | case @family 291 | when Socket::AF_INET 292 | @addr & 0xff000000 == 0x7f000000 # 127.0.0.1/8 293 | when Socket::AF_INET6 294 | @addr == 1 || # ::1 295 | (@addr & 0xffff_0000_0000 == 0xffff_0000_0000 && ( 296 | @addr & 0xff000000 == 0x7f000000 # ::ffff:127.0.0.1/8 297 | )) 298 | else 299 | raise AddressFamilyError, "unsupported address family" 300 | end 301 | end 302 | 303 | # Returns true if the ipaddr is a private address. IPv4 addresses 304 | # in 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16 as defined in RFC 305 | # 1918 and IPv6 Unique Local Addresses in fc00::/7 as defined in RFC 306 | # 4193 are considered private. Private IPv4 addresses in the 307 | # IPv4-mapped IPv6 address range are also considered private. 308 | def private? 309 | case @family 310 | when Socket::AF_INET 311 | @addr & 0xff000000 == 0x0a000000 || # 10.0.0.0/8 312 | @addr & 0xfff00000 == 0xac100000 || # 172.16.0.0/12 313 | @addr & 0xffff0000 == 0xc0a80000 # 192.168.0.0/16 314 | when Socket::AF_INET6 315 | @addr & 0xfe00_0000_0000_0000_0000_0000_0000_0000 == 0xfc00_0000_0000_0000_0000_0000_0000_0000 || 316 | (@addr & 0xffff_0000_0000 == 0xffff_0000_0000 && ( 317 | @addr & 0xff000000 == 0x0a000000 || # ::ffff:10.0.0.0/8 318 | @addr & 0xfff00000 == 0xac100000 || # ::ffff::172.16.0.0/12 319 | @addr & 0xffff0000 == 0xc0a80000 # ::ffff::192.168.0.0/16 320 | )) 321 | else 322 | raise AddressFamilyError, "unsupported address family" 323 | end 324 | end 325 | 326 | # Returns true if the ipaddr is a link-local address. IPv4 327 | # addresses in 169.254.0.0/16 reserved by RFC 3927 and link-local 328 | # IPv6 Unicast Addresses in fe80::/10 reserved by RFC 4291 are 329 | # considered link-local. Link-local IPv4 addresses in the 330 | # IPv4-mapped IPv6 address range are also considered link-local. 331 | def link_local? 332 | case @family 333 | when Socket::AF_INET 334 | @addr & 0xffff0000 == 0xa9fe0000 # 169.254.0.0/16 335 | when Socket::AF_INET6 336 | @addr & 0xffc0_0000_0000_0000_0000_0000_0000_0000 == 0xfe80_0000_0000_0000_0000_0000_0000_0000 || # fe80::/10 337 | (@addr & 0xffff_0000_0000 == 0xffff_0000_0000 && ( 338 | @addr & 0xffff0000 == 0xa9fe0000 # ::ffff:169.254.0.0/16 339 | )) 340 | else 341 | raise AddressFamilyError, "unsupported address family" 342 | end 343 | end 344 | 345 | # Returns true if the ipaddr is an IPv4-mapped IPv6 address. 346 | def ipv4_mapped? 347 | return ipv6? && (@addr >> 32) == 0xffff 348 | end 349 | 350 | # Returns true if the ipaddr is an IPv4-compatible IPv6 address. 351 | def ipv4_compat? 352 | warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE 353 | _ipv4_compat? 354 | end 355 | 356 | def _ipv4_compat? 357 | if !ipv6? || (@addr >> 32) != 0 358 | return false 359 | end 360 | a = (@addr & IN4MASK) 361 | return a != 0 && a != 1 362 | end 363 | 364 | private :_ipv4_compat? 365 | 366 | # Returns a new ipaddr built by converting the native IPv4 address 367 | # into an IPv4-mapped IPv6 address. 368 | def ipv4_mapped 369 | if !ipv4? 370 | raise InvalidAddressError, "not an IPv4 address: #{@addr}" 371 | end 372 | clone = self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6) 373 | clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) 374 | clone 375 | end 376 | 377 | # Returns a new ipaddr built by converting the native IPv4 address 378 | # into an IPv4-compatible IPv6 address. 379 | def ipv4_compat 380 | warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE 381 | if !ipv4? 382 | raise InvalidAddressError, "not an IPv4 address: #{@addr}" 383 | end 384 | return self.clone.set(@addr, Socket::AF_INET6) 385 | end 386 | 387 | # Returns a new ipaddr built by converting the IPv6 address into a 388 | # native IPv4 address. If the IP address is not an IPv4-mapped or 389 | # IPv4-compatible IPv6 address, returns self. 390 | def native 391 | if !ipv4_mapped? && !_ipv4_compat? 392 | return self 393 | end 394 | return self.clone.set(@addr & IN4MASK, Socket::AF_INET) 395 | end 396 | 397 | # Returns a string for DNS reverse lookup. It returns a string in 398 | # RFC3172 form for an IPv6 address. 399 | def reverse 400 | case @family 401 | when Socket::AF_INET 402 | return _reverse + ".in-addr.arpa" 403 | when Socket::AF_INET6 404 | return ip6_arpa 405 | else 406 | raise AddressFamilyError, "unsupported address family" 407 | end 408 | end 409 | 410 | # Returns a string for DNS reverse lookup compatible with RFC3172. 411 | def ip6_arpa 412 | if !ipv6? 413 | raise InvalidAddressError, "not an IPv6 address: #{@addr}" 414 | end 415 | return _reverse + ".ip6.arpa" 416 | end 417 | 418 | # Returns a string for DNS reverse lookup compatible with RFC1886. 419 | def ip6_int 420 | if !ipv6? 421 | raise InvalidAddressError, "not an IPv6 address: #{@addr}" 422 | end 423 | return _reverse + ".ip6.int" 424 | end 425 | 426 | # Returns the successor to the ipaddr. 427 | def succ 428 | return self.clone.set(@addr + 1, @family) 429 | end 430 | 431 | # Compares the ipaddr with another. 432 | def <=>(other) 433 | other = coerce_other(other) 434 | rescue 435 | nil 436 | else 437 | @addr <=> other.to_i if other.family == @family 438 | end 439 | include Comparable 440 | 441 | # Checks equality used by Hash. 442 | def eql?(other) 443 | return self.class == other.class && self.hash == other.hash && self == other 444 | end 445 | 446 | # Returns a hash value used by Hash, Set, and Array classes 447 | def hash 448 | return ([@addr, @mask_addr, @zone_id].hash << 1) | (ipv4? ? 0 : 1) 449 | end 450 | 451 | # Creates a Range object for the network address. 452 | def to_range 453 | self.class.new(begin_addr, @family)..self.class.new(end_addr, @family) 454 | end 455 | 456 | # Returns the prefix length in bits for the ipaddr. 457 | def prefix 458 | case @family 459 | when Socket::AF_INET 460 | n = IN4MASK ^ @mask_addr 461 | i = 32 462 | when Socket::AF_INET6 463 | n = IN6MASK ^ @mask_addr 464 | i = 128 465 | else 466 | raise AddressFamilyError, "unsupported address family" 467 | end 468 | while n.positive? 469 | n >>= 1 470 | i -= 1 471 | end 472 | i 473 | end 474 | 475 | # Sets the prefix length in bits 476 | def prefix=(prefix) 477 | case prefix 478 | when Integer 479 | mask!(prefix) 480 | else 481 | raise InvalidPrefixError, "prefix must be an integer" 482 | end 483 | end 484 | 485 | # Returns a string containing a human-readable representation of the 486 | # ipaddr. ("#") 487 | def inspect 488 | case @family 489 | when Socket::AF_INET 490 | af = "IPv4" 491 | when Socket::AF_INET6 492 | af = "IPv6" 493 | zone_id = @zone_id.to_s 494 | else 495 | raise AddressFamilyError, "unsupported address family" 496 | end 497 | return sprintf("#<%s: %s:%s%s/%s>", self.class.name, 498 | af, _to_string(@addr), zone_id, _to_string(@mask_addr)) 499 | end 500 | 501 | # Returns the netmask in string format e.g. 255.255.0.0 502 | def netmask 503 | _to_string(@mask_addr) 504 | end 505 | 506 | # Returns the wildcard mask in string format e.g. 0.0.255.255 507 | def wildcard_mask 508 | case @family 509 | when Socket::AF_INET 510 | mask = IN4MASK ^ @mask_addr 511 | when Socket::AF_INET6 512 | mask = IN6MASK ^ @mask_addr 513 | else 514 | raise AddressFamilyError, "unsupported address family" 515 | end 516 | 517 | _to_string(mask) 518 | end 519 | 520 | # Returns the IPv6 zone identifier, if present. 521 | # Raises InvalidAddressError if not an IPv6 address. 522 | def zone_id 523 | if @family == Socket::AF_INET6 524 | @zone_id 525 | else 526 | raise InvalidAddressError, "not an IPv6 address" 527 | end 528 | end 529 | 530 | # Returns the IPv6 zone identifier, if present. 531 | # Raises InvalidAddressError if not an IPv6 address. 532 | def zone_id=(zid) 533 | if @family == Socket::AF_INET6 534 | case zid 535 | when nil, /\A%(\w+)\z/ 536 | @zone_id = zid 537 | else 538 | raise InvalidAddressError, "invalid zone identifier for address" 539 | end 540 | else 541 | raise InvalidAddressError, "not an IPv6 address" 542 | end 543 | end 544 | 545 | protected 546 | 547 | def begin_addr 548 | @addr & @mask_addr 549 | end 550 | 551 | def end_addr 552 | case @family 553 | when Socket::AF_INET 554 | @addr | (IN4MASK ^ @mask_addr) 555 | when Socket::AF_INET6 556 | @addr | (IN6MASK ^ @mask_addr) 557 | else 558 | raise AddressFamilyError, "unsupported address family" 559 | end 560 | end 561 | 562 | # Set +@addr+, the internal stored ip address, to given +addr+. The 563 | # parameter +addr+ is validated using the first +family+ member, 564 | # which is +Socket::AF_INET+ or +Socket::AF_INET6+. 565 | def set(addr, *family) 566 | case family[0] ? family[0] : @family 567 | when Socket::AF_INET 568 | if addr < 0 || addr > IN4MASK 569 | raise InvalidAddressError, "invalid address: #{addr}" 570 | end 571 | when Socket::AF_INET6 572 | if addr < 0 || addr > IN6MASK 573 | raise InvalidAddressError, "invalid address: #{addr}" 574 | end 575 | else 576 | raise AddressFamilyError, "unsupported address family" 577 | end 578 | @addr = addr 579 | if family[0] 580 | @family = family[0] 581 | if @family == Socket::AF_INET 582 | @mask_addr &= IN4MASK 583 | end 584 | end 585 | return self 586 | end 587 | 588 | # Set current netmask to given mask. 589 | def mask!(mask) 590 | case mask 591 | when String 592 | case mask 593 | when /\A(0|[1-9]+\d*)\z/ 594 | prefixlen = mask.to_i 595 | when /\A\d+\z/ 596 | raise InvalidPrefixError, "leading zeros in prefix" 597 | else 598 | m = IPAddr.new(mask) 599 | if m.family != @family 600 | raise InvalidPrefixError, "address family is not same" 601 | end 602 | @mask_addr = m.to_i 603 | n = @mask_addr ^ m.instance_variable_get(:@mask_addr) 604 | unless ((n + 1) & n).zero? 605 | raise InvalidPrefixError, "invalid mask #{mask}" 606 | end 607 | @addr &= @mask_addr 608 | return self 609 | end 610 | else 611 | prefixlen = mask 612 | end 613 | case @family 614 | when Socket::AF_INET 615 | if prefixlen < 0 || prefixlen > 32 616 | raise InvalidPrefixError, "invalid length" 617 | end 618 | masklen = 32 - prefixlen 619 | @mask_addr = ((IN4MASK >> masklen) << masklen) 620 | when Socket::AF_INET6 621 | if prefixlen < 0 || prefixlen > 128 622 | raise InvalidPrefixError, "invalid length" 623 | end 624 | masklen = 128 - prefixlen 625 | @mask_addr = ((IN6MASK >> masklen) << masklen) 626 | else 627 | raise AddressFamilyError, "unsupported address family" 628 | end 629 | @addr = ((@addr >> masklen) << masklen) 630 | return self 631 | end 632 | 633 | private 634 | 635 | # Creates a new ipaddr object either from a human readable IP 636 | # address representation in string, or from a packed in_addr value 637 | # followed by an address family. 638 | # 639 | # In the former case, the following are the valid formats that will 640 | # be recognized: "address", "address/prefixlen" and "address/mask", 641 | # where IPv6 address may be enclosed in square brackets (`[' and 642 | # `]'). If a prefixlen or a mask is specified, it returns a masked 643 | # IP address. Although the address family is determined 644 | # automatically from a specified string, you can specify one 645 | # explicitly by the optional second argument. 646 | # 647 | # Otherwise an IP address is generated from a packed in_addr value 648 | # and an address family. 649 | # 650 | # The IPAddr class defines many methods and operators, and some of 651 | # those, such as &, |, include? and ==, accept a string, or a packed 652 | # in_addr value instead of an IPAddr object. 653 | def initialize(addr = '::', family = Socket::AF_UNSPEC) 654 | @mask_addr = nil 655 | if !addr.kind_of?(String) 656 | case family 657 | when Socket::AF_INET, Socket::AF_INET6 658 | set(addr.to_i, family) 659 | @mask_addr = (family == Socket::AF_INET) ? IN4MASK : IN6MASK 660 | return 661 | when Socket::AF_UNSPEC 662 | raise AddressFamilyError, "address family must be specified" 663 | else 664 | raise AddressFamilyError, "unsupported address family: #{family}" 665 | end 666 | end 667 | prefix, prefixlen = addr.split('/', 2) 668 | if prefix =~ /\A\[(.*)\]\z/i 669 | prefix = $1 670 | family = Socket::AF_INET6 671 | end 672 | if prefix =~ /\A(.*)(%\w+)\z/ 673 | prefix = $1 674 | zone_id = $2 675 | family = Socket::AF_INET6 676 | end 677 | # It seems AI_NUMERICHOST doesn't do the job. 678 | #Socket.getaddrinfo(left, nil, Socket::AF_INET6, Socket::SOCK_STREAM, nil, 679 | # Socket::AI_NUMERICHOST) 680 | @addr = @family = nil 681 | if family == Socket::AF_UNSPEC || family == Socket::AF_INET 682 | @addr = in_addr(prefix) 683 | if @addr 684 | @family = Socket::AF_INET 685 | end 686 | end 687 | if !@addr && (family == Socket::AF_UNSPEC || family == Socket::AF_INET6) 688 | @addr = in6_addr(prefix) 689 | @family = Socket::AF_INET6 690 | end 691 | @zone_id = zone_id 692 | if family != Socket::AF_UNSPEC && @family != family 693 | raise AddressFamilyError, "address family mismatch" 694 | end 695 | if prefixlen 696 | mask!(prefixlen) 697 | else 698 | @mask_addr = (@family == Socket::AF_INET) ? IN4MASK : IN6MASK 699 | end 700 | end 701 | 702 | def coerce_other(other) 703 | case other 704 | when IPAddr 705 | other 706 | when String 707 | self.class.new(other) 708 | else 709 | self.class.new(other, @family) 710 | end 711 | end 712 | 713 | def in_addr(addr) 714 | case addr 715 | when Array 716 | octets = addr 717 | else 718 | RE_IPV4ADDRLIKE.match?(addr) or return nil 719 | octets = addr.split('.') 720 | end 721 | octets.inject(0) { |i, s| 722 | (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{@addr}" 723 | (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{@addr}" 724 | i << 8 | n 725 | } 726 | end 727 | 728 | def in6_addr(left) 729 | case left 730 | when RE_IPV6ADDRLIKE_FULL 731 | if $2 732 | addr = in_addr($~[2,4]) 733 | left = $1 + ':' 734 | else 735 | addr = 0 736 | end 737 | right = '' 738 | when RE_IPV6ADDRLIKE_COMPRESSED 739 | if $4 740 | left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{@addr}" 741 | addr = in_addr($~[4,4]) 742 | left = $1 743 | right = $3 + '0:0' 744 | else 745 | left.count(':') <= ($1.empty? || $2.empty? ? 8 : 7) or 746 | raise InvalidAddressError, "invalid address: #{@addr}" 747 | left = $1 748 | right = $2 749 | addr = 0 750 | end 751 | else 752 | raise InvalidAddressError, "invalid address: #{@addr}" 753 | end 754 | l = left.split(':') 755 | r = right.split(':') 756 | rest = 8 - l.size - r.size 757 | if rest < 0 758 | return nil 759 | end 760 | (l + Array.new(rest, '0') + r).inject(0) { |i, s| 761 | i << 16 | s.hex 762 | } | addr 763 | end 764 | 765 | def addr_mask(addr) 766 | case @family 767 | when Socket::AF_INET 768 | return addr & IN4MASK 769 | when Socket::AF_INET6 770 | return addr & IN6MASK 771 | else 772 | raise AddressFamilyError, "unsupported address family" 773 | end 774 | end 775 | 776 | def _reverse 777 | case @family 778 | when Socket::AF_INET 779 | return (0..3).map { |i| 780 | (@addr >> (8 * i)) & 0xff 781 | }.join('.') 782 | when Socket::AF_INET6 783 | return ("%.32x" % @addr).reverse!.gsub!(/.(?!$)/, '\&.') 784 | else 785 | raise AddressFamilyError, "unsupported address family" 786 | end 787 | end 788 | 789 | def _to_string(addr) 790 | case @family 791 | when Socket::AF_INET 792 | return (0..3).map { |i| 793 | (addr >> (24 - 8 * i)) & 0xff 794 | }.join('.') 795 | when Socket::AF_INET6 796 | return (("%.32x" % addr).gsub!(/.{4}(?!$)/, '\&:')) 797 | else 798 | raise AddressFamilyError, "unsupported address family" 799 | end 800 | end 801 | 802 | end 803 | 804 | unless Socket.const_defined? :AF_INET6 805 | class Socket < BasicSocket 806 | # IPv6 protocol family 807 | AF_INET6 = Object.new.freeze 808 | end 809 | 810 | class << IPSocket 811 | private 812 | 813 | def valid_v6?(addr) 814 | case addr 815 | when IPAddr::RE_IPV6ADDRLIKE_FULL 816 | if $2 817 | $~[2,4].all? {|i| i.to_i < 256 } 818 | else 819 | true 820 | end 821 | when IPAddr::RE_IPV6ADDRLIKE_COMPRESSED 822 | if $4 823 | addr.count(':') <= 6 && $~[4,4].all? {|i| i.to_i < 256} 824 | else 825 | addr.count(':') <= 7 826 | end 827 | else 828 | false 829 | end 830 | end 831 | 832 | alias getaddress_orig getaddress 833 | 834 | public 835 | 836 | # Returns a +String+ based representation of a valid DNS hostname, 837 | # IPv4 or IPv6 address. 838 | # 839 | # IPSocket.getaddress 'localhost' #=> "::1" 840 | # IPSocket.getaddress 'broadcasthost' #=> "255.255.255.255" 841 | # IPSocket.getaddress 'www.ruby-lang.org' #=> "221.186.184.68" 842 | # IPSocket.getaddress 'www.ccc.de' #=> "2a00:1328:e102:ccc0::122" 843 | def getaddress(s) 844 | if valid_v6?(s) 845 | s 846 | else 847 | getaddress_orig(s) 848 | end 849 | end 850 | end 851 | end 852 | -------------------------------------------------------------------------------- /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "core_assertions" 3 | 4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions 5 | -------------------------------------------------------------------------------- /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 | end 200 | 201 | def test_ipv4_mapped 202 | a = IPAddr.new("::ffff:192.168.1.2") 203 | assert_equal("::ffff:192.168.1.2", a.to_s) 204 | assert_equal("0000:0000:0000:0000:0000:ffff:c0a8:0102", a.to_string) 205 | assert_equal(Socket::AF_INET6, a.family) 206 | assert_equal(true, a.ipv4_mapped?) 207 | b = a.native 208 | assert_equal("192.168.1.2", b.to_s) 209 | assert_equal(Socket::AF_INET, b.family) 210 | assert_equal(false, b.ipv4_mapped?) 211 | 212 | a = IPAddr.new("192.168.1.2") 213 | b = a.ipv4_mapped 214 | assert_equal("::ffff:192.168.1.2", b.to_s) 215 | assert_equal(Socket::AF_INET6, b.family) 216 | end 217 | 218 | def test_reverse 219 | 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) 220 | assert_equal("1.2.168.192.in-addr.arpa", IPAddr.new("192.168.2.1").reverse) 221 | end 222 | 223 | def test_ip6_arpa 224 | 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) 225 | assert_raise(IPAddr::InvalidAddressError) { 226 | IPAddr.new("192.168.2.1").ip6_arpa 227 | } 228 | end 229 | 230 | def test_ip6_int 231 | 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) 232 | assert_raise(IPAddr::InvalidAddressError) { 233 | IPAddr.new("192.168.2.1").ip6_int 234 | } 235 | end 236 | 237 | def test_prefix_writer 238 | a = IPAddr.new("192.168.1.2") 239 | ["1", "255.255.255.0", "ffff:ffff:ffff:ffff::", nil, 1.0, -1, 33].each { |x| 240 | assert_raise(IPAddr::InvalidPrefixError) { a.prefix = x } 241 | } 242 | a = IPAddr.new("1:2:3:4:5:6:7:8") 243 | ["1", "255.255.255.0", "ffff:ffff:ffff:ffff::", nil, 1.0, -1, 129].each { |x| 244 | assert_raise(IPAddr::InvalidPrefixError) { a.prefix = x } 245 | } 246 | 247 | a = IPAddr.new("192.168.1.2") 248 | a.prefix = 26 249 | assert_equal(26, a.prefix) 250 | assert_equal("192.168.1.0", a.to_s) 251 | 252 | a = IPAddr.new("1:2:3:4:5:6:7:8") 253 | a.prefix = 52 254 | assert_equal(52, a.prefix) 255 | assert_equal("1:2:3::", a.to_s) 256 | end 257 | 258 | def test_to_s 259 | assert_equal("3ffe:0505:0002:0000:0000:0000:0000:0001", IPAddr.new("3ffe:505:2::1").to_string) 260 | assert_equal("3ffe:505:2::1", IPAddr.new("3ffe:505:2::1").to_s) 261 | end 262 | 263 | def test_as_json 264 | assert_equal("192.168.1.2", IPAddr.new("192.168.1.2").as_json) 265 | assert_equal("192.168.1.0/24", IPAddr.new("192.168.1.2/24").as_json) 266 | assert_equal("2001:200:300::1", IPAddr.new("2001:200:300::1").as_json) 267 | assert_equal("2001:200:300::/48", IPAddr.new("2001:200:300::/48").as_json) 268 | end 269 | 270 | def test_to_json 271 | assert_equal("\"192.168.1.2\"", IPAddr.new("192.168.1.2").to_json) 272 | assert_equal("\"192.168.1.0/24\"", IPAddr.new("192.168.1.2/24").to_json) 273 | assert_equal("\"2001:200:300::1\"", IPAddr.new("2001:200:300::1").to_json) 274 | assert_equal("\"2001:200:300::/48\"", IPAddr.new("2001:200:300::/48").to_json) 275 | end 276 | 277 | def test_netmask 278 | a = IPAddr.new("192.168.1.2/8") 279 | assert_equal(a.netmask, "255.0.0.0") 280 | 281 | a = IPAddr.new("192.168.1.2/16") 282 | assert_equal(a.netmask, "255.255.0.0") 283 | 284 | a = IPAddr.new("192.168.1.2/24") 285 | assert_equal(a.netmask, "255.255.255.0") 286 | end 287 | 288 | def test_wildcard_mask 289 | a = IPAddr.new("192.168.1.2/1") 290 | assert_equal(a.wildcard_mask, "127.255.255.255") 291 | 292 | a = IPAddr.new("192.168.1.2/8") 293 | assert_equal(a.wildcard_mask, "0.255.255.255") 294 | 295 | a = IPAddr.new("192.168.1.2/16") 296 | assert_equal(a.wildcard_mask, "0.0.255.255") 297 | 298 | a = IPAddr.new("192.168.1.2/24") 299 | assert_equal(a.wildcard_mask, "0.0.0.255") 300 | 301 | a = IPAddr.new("192.168.1.2/32") 302 | assert_equal(a.wildcard_mask, "0.0.0.0") 303 | 304 | a = IPAddr.new("3ffe:505:2::/48") 305 | assert_equal(a.wildcard_mask, "0000:0000:0000:ffff:ffff:ffff:ffff:ffff") 306 | 307 | a = IPAddr.new("3ffe:505:2::/128") 308 | assert_equal(a.wildcard_mask, "0000:0000:0000:0000:0000:0000:0000:0000") 309 | end 310 | 311 | def test_zone_id 312 | a = IPAddr.new("192.168.1.2") 313 | assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%ab0' } 314 | assert_raise(IPAddr::InvalidAddressError) { a.zone_id } 315 | 316 | a = IPAddr.new("1:2:3:4:5:6:7:8") 317 | a.zone_id = '%ab0' 318 | assert_equal('%ab0', a.zone_id) 319 | assert_equal("1:2:3:4:5:6:7:8%ab0", a.to_s) 320 | assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%' } 321 | end 322 | 323 | def test_to_range 324 | a1 = IPAddr.new("127.0.0.1") 325 | range = a1..a1 326 | assert_equal(range, a1.to_range) 327 | assert_equal(range, a1.freeze.to_range) 328 | 329 | a2 = IPAddr.new("192.168.0.1/16") 330 | range = IPAddr.new("192.168.0.0")..IPAddr.new("192.168.255.255") 331 | assert_equal(range, a2.to_range) 332 | assert_equal(range, a2.freeze.to_range) 333 | 334 | a3 = IPAddr.new("3ffe:505:2::1") 335 | range = a3..a3 336 | assert_equal(range, a3.to_range) 337 | assert_equal(range, a3.freeze.to_range) 338 | 339 | a4 = IPAddr.new("::ffff/127") 340 | range = IPAddr.new("::fffe")..IPAddr.new("::ffff") 341 | assert_equal(range, a4.to_range) 342 | assert_equal(range, a4.freeze.to_range) 343 | end 344 | end 345 | 346 | class TC_Operator < Test::Unit::TestCase 347 | 348 | IN6MASK32 = "ffff:ffff::" 349 | IN6MASK128 = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" 350 | 351 | def setup 352 | @in6_addr_any = IPAddr.new() 353 | @a = IPAddr.new("3ffe:505:2::/48") 354 | @b = IPAddr.new("0:0:0:1::") 355 | @c = IPAddr.new(IN6MASK32) 356 | @inconvertible_range = 1..5 357 | @inconvertible_string = "sometext" 358 | end 359 | alias set_up setup 360 | 361 | def test_or 362 | assert_equal("3ffe:505:2:1::", (@a | @b).to_s) 363 | a = @a 364 | a |= @b 365 | assert_equal("3ffe:505:2:1::", a.to_s) 366 | assert_equal("3ffe:505:2::", @a.to_s) 367 | assert_equal("3ffe:505:2:1::", 368 | (@a | 0x00000000000000010000000000000000).to_s) 369 | end 370 | 371 | def test_and 372 | assert_equal("3ffe:505::", (@a & @c).to_s) 373 | a = @a 374 | a &= @c 375 | assert_equal("3ffe:505::", a.to_s) 376 | assert_equal("3ffe:505:2::", @a.to_s) 377 | assert_equal("3ffe:505::", (@a & 0xffffffff000000000000000000000000).to_s) 378 | end 379 | 380 | def test_shift_right 381 | assert_equal("0:3ffe:505:2::", (@a >> 16).to_s) 382 | a = @a 383 | a >>= 16 384 | assert_equal("0:3ffe:505:2::", a.to_s) 385 | assert_equal("3ffe:505:2::", @a.to_s) 386 | end 387 | 388 | def test_shift_left 389 | assert_equal("505:2::", (@a << 16).to_s) 390 | a = @a 391 | a <<= 16 392 | assert_equal("505:2::", a.to_s) 393 | assert_equal("3ffe:505:2::", @a.to_s) 394 | end 395 | 396 | def test_carrot 397 | a = ~@in6_addr_any 398 | assert_equal(IN6MASK128, a.to_s) 399 | assert_equal("::", @in6_addr_any.to_s) 400 | end 401 | 402 | def test_plus 403 | a = IPAddr.new("192.168.1.10") 404 | assert_equal("192.168.1.20", (a + 10).to_s) 405 | 406 | a = IPAddr.new("0.0.0.0") 407 | assert_equal("0.0.0.10", (a + 10).to_s) 408 | 409 | a = IPAddr.new("255.255.255.255") 410 | assert_raise(IPAddr::InvalidAddressError) { a + 10 } 411 | 412 | a = IPAddr.new("3ffe:505:2::a") 413 | assert_equal("3ffe:505:2::14", (a + 10).to_s) 414 | 415 | a = IPAddr.new("::") 416 | assert_equal("::a", (a + 10).to_s) 417 | 418 | a = IPAddr.new("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") 419 | assert_raise(IPAddr::InvalidAddressError) { a + 10 } 420 | end 421 | 422 | def test_minus 423 | a = IPAddr.new("192.168.1.10") 424 | assert_equal("192.168.1.0", (a - 10).to_s) 425 | 426 | a = IPAddr.new("0.0.0.0") 427 | assert_raise(IPAddr::InvalidAddressError) { a - 10 } 428 | 429 | a = IPAddr.new("255.255.255.255") 430 | assert_equal("255.255.255.245", (a - 10).to_s) 431 | 432 | a = IPAddr.new("3ffe:505:2::a") 433 | assert_equal("3ffe:505:2::", (a - 10).to_s) 434 | 435 | a = IPAddr.new("::") 436 | assert_raise(IPAddr::InvalidAddressError) { a - 10 } 437 | 438 | a = IPAddr.new("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") 439 | assert_equal("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff5", (a - 10).to_s) 440 | end 441 | 442 | def test_equal 443 | assert_equal(true, @a == IPAddr.new("3FFE:505:2::")) 444 | assert_equal(true, @a == IPAddr.new("3ffe:0505:0002::")) 445 | assert_equal(true, @a == IPAddr.new("3ffe:0505:0002:0:0:0:0:0")) 446 | assert_equal(false, @a == IPAddr.new("3ffe:505:3::")) 447 | assert_equal(true, @a != IPAddr.new("3ffe:505:3::")) 448 | assert_equal(false, @a != IPAddr.new("3ffe:505:2::")) 449 | assert_equal(false, @a == @inconvertible_range) 450 | assert_equal(false, @a == @inconvertible_string) 451 | end 452 | 453 | def test_compare 454 | assert_equal(nil, @a <=> @inconvertible_range) 455 | assert_equal(nil, @a <=> @inconvertible_string) 456 | end 457 | 458 | def test_mask 459 | a = @a.mask(32) 460 | assert_equal("3ffe:505::", a.to_s) 461 | assert_equal("3ffe:505::", @a.mask("ffff:ffff::").to_s) 462 | assert_equal("3ffe:505:2::", @a.to_s) 463 | a = IPAddr.new("192.168.2.0/24") 464 | assert_equal("192.168.0.0", a.mask(16).to_s) 465 | assert_equal("192.168.0.0", a.mask("255.255.0.0").to_s) 466 | assert_equal("192.168.2.0", a.to_s) 467 | assert_raise(IPAddr::InvalidPrefixError) {a.mask("255.255.0.255")} 468 | assert_raise(IPAddr::InvalidPrefixError) {@a.mask("ffff:1::")} 469 | end 470 | 471 | def test_include? 472 | assert_equal(true, @a.include?(IPAddr.new("3ffe:505:2::"))) 473 | assert_equal(true, @a.include?(IPAddr.new("3ffe:505:2::1"))) 474 | assert_equal(false, @a.include?(IPAddr.new("3ffe:505:3::"))) 475 | net1 = IPAddr.new("192.168.2.0/24") 476 | assert_equal(true, net1.include?(IPAddr.new("192.168.2.0"))) 477 | assert_equal(true, net1.include?(IPAddr.new("192.168.2.255"))) 478 | assert_equal(false, net1.include?(IPAddr.new("192.168.3.0"))) 479 | assert_equal(true, net1.include?(IPAddr.new("192.168.2.0/28"))) 480 | assert_equal(false, net1.include?(IPAddr.new("192.168.2.0/16"))) 481 | # test with integer parameter 482 | int = (192 << 24) + (168 << 16) + (2 << 8) + 13 483 | 484 | assert_equal(true, net1.include?(int)) 485 | assert_equal(false, net1.include?(int+255)) 486 | 487 | end 488 | 489 | def test_native_coerce_mask_addr 490 | assert_equal(IPAddr.new("0.0.0.2/255.255.255.255"), IPAddr.new("::2").native) 491 | assert_equal(IPAddr.new("0.0.0.2/255.255.255.255").to_range, IPAddr.new("::2").native.to_range) 492 | end 493 | 494 | def test_loopback? 495 | assert_equal(true, IPAddr.new('127.0.0.1').loopback?) 496 | assert_equal(true, IPAddr.new('127.127.1.1').loopback?) 497 | assert_equal(false, IPAddr.new('0.0.0.0').loopback?) 498 | assert_equal(false, IPAddr.new('192.168.2.0').loopback?) 499 | assert_equal(false, IPAddr.new('255.0.0.0').loopback?) 500 | assert_equal(true, IPAddr.new('::1').loopback?) 501 | assert_equal(false, IPAddr.new('::').loopback?) 502 | assert_equal(false, IPAddr.new('3ffe:505:2::1').loopback?) 503 | 504 | assert_equal(true, IPAddr.new('::ffff:127.0.0.1').loopback?) 505 | assert_equal(true, IPAddr.new('::ffff:127.127.1.1').loopback?) 506 | assert_equal(false, IPAddr.new('::ffff:0.0.0.0').loopback?) 507 | assert_equal(false, IPAddr.new('::ffff:192.168.2.0').loopback?) 508 | assert_equal(false, IPAddr.new('::ffff:255.0.0.0').loopback?) 509 | end 510 | 511 | def test_private? 512 | assert_equal(false, IPAddr.new('0.0.0.0').private?) 513 | assert_equal(false, IPAddr.new('127.0.0.1').private?) 514 | 515 | assert_equal(false, IPAddr.new('8.8.8.8').private?) 516 | assert_equal(true, IPAddr.new('10.0.0.0').private?) 517 | assert_equal(true, IPAddr.new('10.255.255.255').private?) 518 | assert_equal(false, IPAddr.new('11.255.1.1').private?) 519 | 520 | assert_equal(false, IPAddr.new('172.15.255.255').private?) 521 | assert_equal(true, IPAddr.new('172.16.0.0').private?) 522 | assert_equal(true, IPAddr.new('172.31.255.255').private?) 523 | assert_equal(false, IPAddr.new('172.32.0.0').private?) 524 | 525 | assert_equal(false, IPAddr.new('190.168.0.0').private?) 526 | assert_equal(true, IPAddr.new('192.168.0.0').private?) 527 | assert_equal(true, IPAddr.new('192.168.255.255').private?) 528 | assert_equal(false, IPAddr.new('192.169.0.0').private?) 529 | 530 | assert_equal(false, IPAddr.new('169.254.0.1').private?) 531 | 532 | assert_equal(false, IPAddr.new('::1').private?) 533 | assert_equal(false, IPAddr.new('::').private?) 534 | 535 | assert_equal(false, IPAddr.new('fb84:8bf7:e905::1').private?) 536 | assert_equal(true, IPAddr.new('fc84:8bf7:e905::1').private?) 537 | assert_equal(true, IPAddr.new('fd84:8bf7:e905::1').private?) 538 | assert_equal(false, IPAddr.new('fe84:8bf7:e905::1').private?) 539 | 540 | assert_equal(false, IPAddr.new('::ffff:0.0.0.0').private?) 541 | assert_equal(false, IPAddr.new('::ffff:127.0.0.1').private?) 542 | 543 | assert_equal(false, IPAddr.new('::ffff:8.8.8.8').private?) 544 | assert_equal(true, IPAddr.new('::ffff:10.0.0.0').private?) 545 | assert_equal(true, IPAddr.new('::ffff:10.255.255.255').private?) 546 | assert_equal(false, IPAddr.new('::ffff:11.255.1.1').private?) 547 | 548 | assert_equal(false, IPAddr.new('::ffff:172.15.255.255').private?) 549 | assert_equal(true, IPAddr.new('::ffff:172.16.0.0').private?) 550 | assert_equal(true, IPAddr.new('::ffff:172.31.255.255').private?) 551 | assert_equal(false, IPAddr.new('::ffff:172.32.0.0').private?) 552 | 553 | assert_equal(false, IPAddr.new('::ffff:190.168.0.0').private?) 554 | assert_equal(true, IPAddr.new('::ffff:192.168.0.0').private?) 555 | assert_equal(true, IPAddr.new('::ffff:192.168.255.255').private?) 556 | assert_equal(false, IPAddr.new('::ffff:192.169.0.0').private?) 557 | 558 | assert_equal(false, IPAddr.new('::ffff:169.254.0.1').private?) 559 | end 560 | 561 | def test_link_local? 562 | assert_equal(false, IPAddr.new('0.0.0.0').link_local?) 563 | assert_equal(false, IPAddr.new('127.0.0.1').link_local?) 564 | assert_equal(false, IPAddr.new('10.0.0.0').link_local?) 565 | assert_equal(false, IPAddr.new('172.16.0.0').link_local?) 566 | assert_equal(false, IPAddr.new('192.168.0.0').link_local?) 567 | 568 | assert_equal(true, IPAddr.new('169.254.1.1').link_local?) 569 | assert_equal(true, IPAddr.new('169.254.254.255').link_local?) 570 | 571 | assert_equal(false, IPAddr.new('::1').link_local?) 572 | assert_equal(false, IPAddr.new('::').link_local?) 573 | assert_equal(false, IPAddr.new('fb84:8bf7:e905::1').link_local?) 574 | 575 | assert_equal(true, IPAddr.new('fe80::dead:beef:cafe:1234').link_local?) 576 | 577 | assert_equal(false, IPAddr.new('::ffff:0.0.0.0').link_local?) 578 | assert_equal(false, IPAddr.new('::ffff:127.0.0.1').link_local?) 579 | assert_equal(false, IPAddr.new('::ffff:10.0.0.0').link_local?) 580 | assert_equal(false, IPAddr.new('::ffff:172.16.0.0').link_local?) 581 | assert_equal(false, IPAddr.new('::ffff:192.168.0.0').link_local?) 582 | 583 | assert_equal(true, IPAddr.new('::ffff:169.254.1.1').link_local?) 584 | assert_equal(true, IPAddr.new('::ffff:169.254.254.255').link_local?) 585 | end 586 | 587 | def test_hash 588 | a1 = IPAddr.new('192.168.2.0') 589 | a2 = IPAddr.new('192.168.2.0') 590 | a3 = IPAddr.new('3ffe:505:2::1') 591 | a4 = IPAddr.new('3ffe:505:2::1') 592 | a5 = IPAddr.new('127.0.0.1') 593 | a6 = IPAddr.new('::1') 594 | a7 = IPAddr.new('192.168.2.0/25') 595 | a8 = IPAddr.new('192.168.2.0/25') 596 | 597 | h = { a1 => 'ipv4', a2 => 'ipv4', a3 => 'ipv6', a4 => 'ipv6', a5 => 'ipv4', a6 => 'ipv6', a7 => 'ipv4', a8 => 'ipv4'} 598 | assert_equal(5, h.size) 599 | assert_equal('ipv4', h[a1]) 600 | assert_equal('ipv4', h[a2]) 601 | assert_equal('ipv6', h[a3]) 602 | assert_equal('ipv6', h[a4]) 603 | 604 | require 'set' 605 | s = Set[a1, a2, a3, a4, a5, a6, a7, a8] 606 | assert_equal(5, s.size) 607 | assert_equal(true, s.include?(a1)) 608 | assert_equal(true, s.include?(a2)) 609 | assert_equal(true, s.include?(a3)) 610 | assert_equal(true, s.include?(a4)) 611 | assert_equal(true, s.include?(a5)) 612 | assert_equal(true, s.include?(a6)) 613 | end 614 | end 615 | --------------------------------------------------------------------------------