diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 70b804f..486a6ad 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -328,7 +328,7 @@ def private? end end - # Returns true if the ipaddr is a link-local address. IPv4 + # Returns true if the ipaddr is a link-local unicast address. IPv4 # addresses in 169.254.0.0/16 reserved by RFC 3927 and link-local # IPv6 Unicast Addresses in fe80::/10 reserved by RFC 4291 are # considered link-local. Link-local IPv4 addresses in the @@ -347,6 +347,66 @@ def link_local? end end + alias link_local_unicast? link_local? + + # Returns true if the ipaddr is a multicast address. IPv4 + # addresses in 224.0.0.0/4 and link-local IPv6 unicast addresses + # in ff00::/8 are considered multicast. Multicast IPv4 addresses in the + # IPv4-mapped IPv6 address range are also considered multicast. + def multicast? + case @family + when Socket::AF_INET + @addr & 0xf0000000 == 0xe0000000 # 224.0.0.0/4 + when Socket::AF_INET6 + @addr & 0xff00_0000_0000_0000_0000_0000_0000_0000 == 0xff00_0000_0000_0000_0000_0000_0000_0000 || # ff00::/8 + (@addr >> 32 == 0xffff && + @addr & 0xf0000000 == 0xe0000000 + ) + else + raise AddressFamilyError, "unsupported address family" + end + end + + # Returns true if the ipaddr is a link-local multicast address. IPv4 + # addresses in 224.0.0.0/4 and link-local IPv6 Multicast Addresses in ff00::/16 + # are considered link-local multicast. Link-local multicast IPv4 addresses in the + # IPv4-mapped IPv6 address range are also considered link-local multicast. + def link_local_multicast? + case @family + when Socket::AF_INET + @addr & 0xffffff00 == 0xe0000000 # 224.0.0.0/24 same as multicast? + when Socket::AF_INET6 + @addr & 0xff0f_0000_0000_0000_0000_0000_0000_0000 == 0xff02_0000_0000_0000_0000_0000_0000_0000 || # ff00::/16 + (@addr >> 32 == 0xffff && ( + @addr & 0xffff0000 == 0xe0000000 # 224.0.0.0/24 + )) + else + raise AddressFamilyError, "unsupported address family" + end + end + + # Returns true if the ipaddr is a global unicast address. This is an address which is + # not a broadcast address, not an unspecified address (i.e. gateway), not a loopback + # address, not a multicast address, and not a link local unicast address. + def global_unicast? + broadcast_or_unspecified = case @family + when Socket::AF_INET + bits = @addr & IN4MASK + bits == IN4MASK || bits == 0x00000000 + when Socket::AF_INET6 + @addr & IN6MASK == IN6MASK + else + raise AddressFamilyError, "unsupported address family" + end + + !( + broadcast_or_unspecified || + loopback? || + multicast? || + link_local_unicast? + ) + end + # Returns true if the ipaddr is an IPv4-mapped IPv6 address. def ipv4_mapped? return ipv6? && (@addr >> 32) == 0xffff diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index 9725ab3..22a7050 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -623,6 +623,64 @@ def test_link_local? assert_equal(false, IPAddr.new('2001:db8:1:1:0:ffff:a9fe:101').link_local?) end + def test_multicast? + assert_equal(false, IPAddr.new('192.168.0.0').multicast?) + assert_equal(false, IPAddr.new('169.254.1.1').multicast?) + assert_equal(false, IPAddr.new('169.254.254.255').multicast?) + + # notable ipv4 multicast addresses + assert_equal(true, IPAddr.new('224.0.0.0').multicast?) + assert_equal(true, IPAddr.new('224.0.0.1').multicast?) + assert_equal(true, IPAddr.new('224.0.0.6').multicast?) + assert_equal(true, IPAddr.new('224.0.1.41').multicast?) + assert_equal(true, IPAddr.new('224.0.1.129').multicast?) + assert_equal(true, IPAddr.new('224.0.23.12').multicast?) + assert_equal(true, IPAddr.new('239.255.255.250').multicast?) + assert_equal(true, IPAddr.new('239.255.255.253').multicast?) + + assert_equal(false, IPAddr.new('::1').multicast?) + assert_equal(false, IPAddr.new('::').multicast?) + assert_equal(false, IPAddr.new('fb84:8bf7:e905::1').multicast?) + + # notable ipv6 multicast addresses + assert_equal(true, IPAddr.new('ff02::1').multicast?) + assert_equal(true, IPAddr.new('ff02::2').multicast?) + assert_equal(true, IPAddr.new('ff02::5').multicast?) + assert_equal(true, IPAddr.new('ff02::6').multicast?) + assert_equal(true, IPAddr.new('ff02::8').multicast?) + assert_equal(true, IPAddr.new('ff02::1:2').multicast?) + assert_equal(true, IPAddr.new('ff02::1:3').multicast?) + assert_equal(true, IPAddr.new('ff05::101').multicast?) + end + + def test_link_local_multicast? + assert_equal(false, IPAddr.new('192.168.0.0').link_local_multicast?) + assert_equal(false, IPAddr.new('169.254.1.1').link_local_multicast?) + assert_equal(false, IPAddr.new('169.254.254.255').link_local_multicast?) + assert_equal(false, IPAddr.new('239.0.0.0').link_local_multicast?) + + assert_equal(false, IPAddr.new('::1').link_local_multicast?) + assert_equal(false, IPAddr.new('::').link_local_multicast?) + + assert_equal(true, IPAddr.new('224.0.0.0').link_local_multicast?) + + assert_equal(false, IPAddr.new('::1').link_local_multicast?) + assert_equal(false, IPAddr.new('::').link_local_multicast?) + assert_equal(false, IPAddr.new('ff05::1').link_local_multicast?) + assert_equal(true, IPAddr.new('ff02::1').link_local_multicast?) + assert_equal(true, IPAddr.new('ff02::2').link_local_multicast?) + end + + def test_global_unicast? + assert_equal(true, IPAddr.new('240.0.0.0').global_unicast?) + assert_equal(false, IPAddr.new('232.0.0.0').global_unicast?) + assert_equal(false, IPAddr.new('169.254.0.0').global_unicast?) + assert_equal(false, IPAddr.new('255.255.255.255').global_unicast?) + assert_equal(true, IPAddr.new('2001::1').global_unicast?) + assert_equal(false, IPAddr.new('fe80::').global_unicast?) + assert_equal(false, IPAddr.new('ff05::').global_unicast?) + end + def test_hash a1 = IPAddr.new('192.168.2.0') a2 = IPAddr.new('192.168.2.0')