From 93ed9f08ad9e9aa1aa44d83e6dc850721e1fd112 Mon Sep 17 00:00:00 2001 From: shirosaki Date: Thu, 25 Apr 2013 15:43:22 +0000 Subject: [PATCH] ring.rb: specify multicast interface * lib/rinda/ring.rb (Rinda::RingServer#initialize): accept array arguments of address to specify multicast interface. * lib/rinda/ring.rb (Rinda::RingServer#make_socket): add optional arguments for multicast interface. * test/rinda/test_rinda.rb (TestRingFinger#test_ring_server_ipv4_multicast, TestRingFinger#test_ring_server_ipv6_multicast): add tests for above change. * test/rinda/test_rinda.rb (TestRingServer#test_make_socket_ipv4_multicast, TestRingServer#test_make_socket_ipv6_multicast): change bound interface address because multicast address is not allowed on Linux or Windows. [ruby-core:53692] [Bug #8159] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@40472 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 20 ++++++++++++++ lib/rinda/ring.rb | 58 +++++++++++++++++++++++++++++++++++----- test/rinda/test_rinda.rb | 42 ++++++++++++++++++++++++++--- 3 files changed, 111 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2275785525..60a6558996 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +Fri Apr 26 00:07:52 2013 Hiroshi Shirosaki + + * lib/rinda/ring.rb (Rinda::RingServer#initialize): accept array + arguments of address to specify multicast interface. + + * lib/rinda/ring.rb (Rinda::RingServer#make_socket): add optional + arguments for multicast interface. + + * test/rinda/test_rinda.rb + (TestRingFinger#test_ring_server_ipv4_multicast, + TestRingFinger#test_ring_server_ipv6_multicast): add tests for + above change. + + * test/rinda/test_rinda.rb + (TestRingServer#test_make_socket_ipv4_multicast, + TestRingServer#test_make_socket_ipv6_multicast): change bound + interface address because multicast address is not allowed on Linux + or Windows. + [ruby-core:53692] [Bug #8159] + Thu Apr 25 23:45:02 2013 Hiroshi Shirosaki * lib/rinda/ring.rb (Rinda::RingServer#initialize): add a socket diff --git a/lib/rinda/ring.rb b/lib/rinda/ring.rb index f6566c906a..29ea5f0ff6 100644 --- a/lib/rinda/ring.rb +++ b/lib/rinda/ring.rb @@ -67,6 +67,29 @@ module Rinda # +addresses+ can contain multiple addresses. If a multicast address is # given in +addresses+ then the RingServer will listen for multicast # queries. + # + # If you use IPv4 multicast you may need to set an address of the inbound + # interface which joins a multicast group. + # + # ts = Rinda::TupleSpace.new + # rs = Rinda::RingServer.new(ts, [['239.0.0.1', '9.5.1.1']]) + # + # You can set addresses as an Array Object. The first element of the + # Array is a multicast address and the second is an inbound interface + # address. If the second is omitted then '0.0.0.0' is used. + # + # If you use IPv6 multicast you may need to set both the local interface + # address and the inbound interface index: + # + # rs = Rinda::RingServer.new(ts, [['ff02::1', '::1', 1]]) + # + # The first element is a multicast address and the second is an inbound + # interface address. The third is an inbound interface index. + # + # At this time there is no easy way to get an interface index by name. + # + # If the second is omitted then '::1' is used. + # If the third is omitted then 0 (default interface) is used. def initialize(ts, addresses=[Socket::INADDR_ANY], port=Ring_PORT) @port = port @@ -80,7 +103,11 @@ module Rinda @ts = ts @sockets = [] addresses.each do |address| - make_socket(address) + if Array === address + make_socket(*address) + else + make_socket(address) + end end @w_services = write_services @@ -89,8 +116,20 @@ module Rinda ## # Creates a socket at +address+ + # + # If +address+ is multicast address then +interface_address+ and + # +multicast_interface+ can be set as optional. + # + # A created socket is bound to +interface_address+. If you use IPv4 + # multicast then the interface of +interface_address+ is used as the + # inbound interface. If +interface_address+ is omitted or nil then + # '0.0.0.0' or '::1' is used. + # + # If you use IPv6 multicast then +multicast_interface+ is used as the + # inbound interface. +multicast_interface+ is a network interface index. + # If +multicast_interface+ is omitted then 0 (default interface) is used. - def make_socket(address) + def make_socket(address, interface_address=nil, multicast_interface=0) addrinfo = Addrinfo.udp(address, @port) socket = Socket.new(addrinfo.pfamily, addrinfo.socktype, @@ -105,19 +144,26 @@ module Rinda end if addrinfo.ipv4_multicast? then + interface_address = '0.0.0.0' if interface_address.nil? + socket.bind(Addrinfo.udp(interface_address, @port)) + mreq = IPAddr.new(addrinfo.ip_address).hton + - IPAddr.new('0.0.0.0').hton + IPAddr.new(interface_address).hton socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, mreq) else - mreq = IPAddr.new(addrinfo.ip_address).hton + [0].pack('I') + interface_address = '::1' if interface_address.nil? + socket.bind(Addrinfo.udp(interface_address, @port)) + + mreq = IPAddr.new(addrinfo.ip_address).hton + + [multicast_interface].pack('I') socket.setsockopt(:IPPROTO_IPV6, :IPV6_JOIN_GROUP, mreq) end + else + socket.bind(addrinfo) end - socket.bind(addrinfo) - socket end diff --git a/test/rinda/test_rinda.rb b/test/rinda/test_rinda.rb index 577eb1a7e5..263dbfb50d 100644 --- a/test/rinda/test_rinda.rb +++ b/test/rinda/test_rinda.rb @@ -575,8 +575,8 @@ class TestRingServer < Test::Unit::TestCase assert(v4mc.getsockopt(:SOCKET, :SO_REUSEADDR).bool) end - assert_equal('239.0.0.1', v4mc.local_address.ip_address) - assert_equal(@port, v4mc.local_address.ip_port) + assert_equal('0.0.0.0', v4mc.local_address.ip_address) + assert_equal(@port, v4mc.local_address.ip_port) end def test_make_socket_ipv6_multicast @@ -595,7 +595,43 @@ class TestRingServer < Test::Unit::TestCase assert v6mc.getsockopt(:SOCKET, :SO_REUSEADDR).bool end - assert_equal('ff02::1', v6mc.local_address.ip_address) + assert_equal('::1', v6mc.local_address.ip_address) + assert_equal(@port, v6mc.local_address.ip_port) + end + + def test_ring_server_ipv4_multicast + @rs = Rinda::RingServer.new(@ts, [['239.0.0.1', '0.0.0.0']], @port) + v4mc = @rs.instance_variable_get('@sockets').first + + if Socket.const_defined?(:SO_REUSEPORT) then + assert(v4mc.getsockopt(:SOCKET, :SO_REUSEPORT).bool) + else + assert(v4mc.getsockopt(:SOCKET, :SO_REUSEADDR).bool) + end + + assert_equal('0.0.0.0', v4mc.local_address.ip_address) + assert_equal(@port, v4mc.local_address.ip_port) + end + + def test_ring_server_ipv6_multicast + skip 'IPv6 not available' unless + Socket.ip_address_list.any? { |addrinfo| addrinfo.ipv6? } + + begin + @rs = Rinda::RingServer.new(@ts, [['ff02::1', '::1', 0]], @port) + rescue Errno::EADDRNOTAVAIL + return # IPv6 address for multicast not available + end + + v6mc = @rs.instance_variable_get('@sockets').first + + if Socket.const_defined?(:SO_REUSEPORT) then + assert v6mc.getsockopt(:SOCKET, :SO_REUSEPORT).bool + else + assert v6mc.getsockopt(:SOCKET, :SO_REUSEADDR).bool + end + + assert_equal('::1', v6mc.local_address.ip_address) assert_equal(@port, v6mc.local_address.ip_port) end