Browse files

Fix for JRuby 3227: Socket::IPPROTO_IP not available.

Signed-off-by: Hiro Asari <asari.ruby@gmail.com>
  • Loading branch information...
1 parent 62e7d57 commit cc8d3052cf91de60381c0d187e9ea0b39050f6a7 @joshuago joshuago committed with BanzaiMan Sep 9, 2010
View
90 src/org/jruby/ext/socket/MulticastStateManager.java
@@ -0,0 +1,90 @@
+/***** BEGIN LICENSE BLOCK *****
+ * Version: CPL 1.0/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Common Public
+ * License Version 1.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * Copyright (C) 2010 Joshua Go <joshuago@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the CPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the CPL, the GPL or the LGPL.
+ ***** END LICENSE BLOCK *****/
+package org.jruby.ext.socket;
+
+import java.io.IOException;
+import java.net.MulticastSocket;
+import java.net.InetAddress;
+
+import java.util.ArrayList;
+
+
+/**
+ * @author <a href="mailto:joshuago@gmail.com">Joshua Go</a>
+ */
+public class MulticastStateManager {
+ private MulticastSocket multicastSocket;
+ private ArrayList membershipGroups;
+ public static final int IP_ADD_MEMBERSHIP = 12;
+
+ public MulticastStateManager() {
+ membershipGroups = new ArrayList();
+ }
+
+ public void addMembership(byte [] ipaddr_buf) throws IOException {
+ String ipString = "";
+ if (ipaddr_buf.length >= 4)
+ {
+ ipString += String.valueOf((int) ipaddr_buf[0] & 0xff);
+ ipString += ".";
+ ipString += String.valueOf((int) ipaddr_buf[1] & 0xff);
+ ipString += ".";
+ ipString += String.valueOf((int) ipaddr_buf[2] & 0xff);
+ ipString += ".";
+ ipString += String.valueOf((int) ipaddr_buf[3] & 0xff);
+ }
+
+ membershipGroups.add(ipString);
+ updateMemberships();
+ }
+
+ public void rebindToPort(int port) throws IOException {
+ if (multicastSocket != null) {
+ multicastSocket.close();
+ }
+
+ multicastSocket = new MulticastSocket(port);
+ updateMemberships();
+ }
+
+ public MulticastSocket getMulticastSocket() {
+ return multicastSocket;
+ }
+
+ private void updateMemberships() throws IOException {
+ if (multicastSocket == null)
+ return;
+
+ for (int i = 0; i < membershipGroups.size(); i++) {
+ String ipString = (String) membershipGroups.get(i);
+ InetAddress group = InetAddress.getByName(ipString);
+ multicastSocket.joinGroup(group);
+ }
+ }
+
+}
View
31 src/org/jruby/ext/socket/RubyBasicSocket.java
@@ -28,12 +28,15 @@
package org.jruby.ext.socket;
import static com.kenai.constantine.platform.IPProto.IPPROTO_TCP;
+import static com.kenai.constantine.platform.IPProto.IPPROTO_IP;
import static com.kenai.constantine.platform.Sock.SOCK_DGRAM;
import static com.kenai.constantine.platform.Sock.SOCK_STREAM;
import static com.kenai.constantine.platform.TCP.TCP_NODELAY;
import java.io.EOFException;
import java.io.IOException;
+import java.net.MulticastSocket;
+import java.net.InetAddress;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
@@ -69,12 +72,14 @@
import com.kenai.constantine.platform.SocketLevel;
import com.kenai.constantine.platform.SocketOption;
+
/**
* @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
*/
@JRubyClass(name="BasicSocket", parent="IO")
public class RubyBasicSocket extends RubyIO {
private static final ByteList FORMAT_SMALL_I = new ByteList(ByteList.plain("i"));
+ protected MulticastStateManager multicastStateManager = null;
private static ObjectAllocator BASICSOCKET_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
@@ -264,6 +269,22 @@ private void setTcpNoDelay(IRubyObject val) throws IOException {
}
}
+ private void joinMulticastGroup(IRubyObject val) throws IOException {
+ Channel socketChannel = openFile.getMainStream().getDescriptor().getChannel();
+
+ if(socketChannel instanceof DatagramChannel) {
+ if (multicastStateManager == null) {
+ multicastStateManager = new MulticastStateManager();
+ }
+
+ if (val instanceof RubyString)
+ {
+ byte [] ipaddr_buf = val.convertToString().getBytes();
+ multicastStateManager.addMembership(ipaddr_buf);
+ }
+ }
+ }
+
private void setReuseAddr(IRubyObject val) throws IOException {
Channel socketChannel = openFile.getMainStream().getDescriptor().getChannel();
if (socketChannel instanceof ServerSocketChannel) {
@@ -570,6 +591,11 @@ public IRubyObject setsockopt(ThreadContext context, IRubyObject lev, IRubyObjec
default:
if (IPPROTO_TCP.value() == level && TCP_NODELAY.value() == opt) {
setTcpNoDelay(val);
+ }
+ else if (IPPROTO_IP.value() == level) {
+ if (MulticastStateManager.IP_ADD_MEMBERSHIP == opt) {
+ joinMulticastGroup(val);
+ }
} else {
throw context.getRuntime().newErrnoENOPROTOOPTError();
}
@@ -578,6 +604,11 @@ public IRubyObject setsockopt(ThreadContext context, IRubyObject lev, IRubyObjec
default:
if (IPPROTO_TCP.value() == level && TCP_NODELAY.value() == opt) {
setTcpNoDelay(val);
+ }
+ else if (IPPROTO_IP.value() == level) {
+ if (MulticastStateManager.IP_ADD_MEMBERSHIP == opt) {
+ joinMulticastGroup(val);
+ }
} else {
throw context.getRuntime().newErrnoENOPROTOOPTError();
}
View
59 src/org/jruby/ext/socket/RubyUDPSocket.java
@@ -34,7 +34,9 @@
import java.net.InetSocketAddress;
import java.net.PortUnreachableException;
import java.net.SocketException;
+import java.net.MulticastSocket;
import java.net.UnknownHostException;
+import java.net.DatagramPacket;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
@@ -127,14 +129,24 @@ public IRubyObject bind(ThreadContext context, IRubyObject host, IRubyObject por
addr = new InetSocketAddress(InetAddress.getByName("0.0.0.0"), RubyNumeric.fix2int(port));
}
} else {
+ // passing in something like INADDR_ANY
addr = new InetSocketAddress(InetAddress.getByName(host.convertToString().toString()), RubyNumeric.fix2int(port));
}
- ((DatagramChannel) this.getChannel()).socket().bind(addr);
+
+ if (this.multicastStateManager == null)
+ ((DatagramChannel) this.getChannel()).socket().bind(addr);
+ else
+ {
+ this.multicastStateManager.rebindToPort(RubyNumeric.fix2int(port));
+ }
+
return RubyFixnum.zero(context.getRuntime());
} catch (UnknownHostException e) {
throw sockerr(context.getRuntime(), "bind: name or service not known");
} catch (SocketException e) {
throw sockerr(context.getRuntime(), "bind: name or service not known");
+ } catch (IOException e) {
+ throw sockerr(context.getRuntime(), "bind: name or service not known");
} catch (Error e) {
// Workaround for a bug in Sun's JDK 1.5.x, see
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6303753
@@ -171,11 +183,24 @@ public IRubyObject recvfrom(IRubyObject[] args) {
@JRubyMethod(required = 1, rest = true)
public IRubyObject recvfrom(ThreadContext context, IRubyObject[] args) {
try {
+ InetSocketAddress sender = null;
int length = RubyNumeric.fix2int(args[0]);
ByteBuffer buf = ByteBuffer.allocate(length);
- ((DatagramChannel) this.getChannel()).configureBlocking(false);
- context.getThread().select(this, SelectionKey.OP_READ);
- InetSocketAddress sender = (InetSocketAddress) ((DatagramChannel) this.getChannel()).receive(buf);
+ byte[] buf2 = new byte[length];
+ DatagramPacket recv = new DatagramPacket(buf2, buf2.length);
+
+ if (this.multicastStateManager == null)
+ {
+ ((DatagramChannel) this.getChannel()).configureBlocking(false);
+ context.getThread().select(this, SelectionKey.OP_READ);
+ sender = (InetSocketAddress) ((DatagramChannel) this.getChannel()).receive(buf);
+ }
+ else
+ {
+ MulticastSocket ms = this.multicastStateManager.getMulticastSocket();
+ ms.receive(recv);
+ sender = (InetSocketAddress) recv.getSocketAddress();
+ }
// see JRUBY-4678
if (sender == null) {
@@ -188,7 +213,16 @@ public IRubyObject recvfrom(ThreadContext context, IRubyObject[] args) {
context.getRuntime().newString(sender.getHostName()),
context.getRuntime().newString(sender.getAddress().getHostAddress())
});
- IRubyObject result = context.getRuntime().newString(new ByteList(buf.array(), 0, buf.position()));
+
+ IRubyObject result = null;
+
+ if (this.multicastStateManager == null)
+ result = context.getRuntime().newString(new ByteList(buf.array(), 0, buf.position()));
+ else
+ {
+ result = context.getRuntime().newString(new ByteList(recv.getData(), 0, recv.getLength()));
+ }
+
return context.getRuntime().newArray(new IRubyObject[]{result, addressArray});
} catch (UnknownHostException e) {
throw sockerr(context.getRuntime(), "recvfrom: name or service not known");
@@ -232,6 +266,10 @@ public IRubyObject send(ThreadContext context, IRubyObject[] args) {
RubyString nameStr = args[2].convertToString();
RubyString data = args[0].convertToString();
ByteBuffer buf = ByteBuffer.wrap(data.getBytes());
+
+ byte [] buf2 = data.getBytes();
+ DatagramPacket sendDP = null;
+
int port;
if (args[3] instanceof RubyString) {
jnr.netdb.Service service = jnr.netdb.Service.getServiceByName(args[3].asJavaString(), "udp");
@@ -247,7 +285,16 @@ public IRubyObject send(ThreadContext context, IRubyObject[] args) {
InetAddress address = RubySocket.getRubyInetAddress(nameStr.getByteList());
InetSocketAddress addr =
new InetSocketAddress(address, port);
- written = ((DatagramChannel) this.getChannel()).send(buf, addr);
+
+ if (this.multicastStateManager == null) {
+ written = ((DatagramChannel) this.getChannel()).send(buf, addr);
+ }
+ else {
+ sendDP = new DatagramPacket(buf2, buf2.length, address, port);
+ MulticastSocket ms = this.multicastStateManager.getMulticastSocket();
+ ms.send(sendDP);
+ written = sendDP.getLength();
+ }
} else {
RubyString data = args[0].convertToString();
ByteBuffer buf = ByteBuffer.wrap(data.getBytes());
View
20 test/test_socket.rb
@@ -2,10 +2,30 @@
require 'socket'
require 'thread'
require 'test/test_helper'
+require 'ipaddr'
class SocketTest < Test::Unit::TestCase
include TestHelper
+ def test_multicast_send_and_receive
+ multicast_addr = "225.4.5.6"
+ port = 6789
+ multicast_msg = "Hello from automated JRuby test suite"
+
+ assert_nothing_raised do
+ socket = UDPSocket.new
+ ip = IPAddr.new(multicast_addr).hton + IPAddr.new("0.0.0.0").hton
+ socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, ip)
+ socket.bind(Socket::INADDR_ANY, port)
+ socket.send(multicast_msg, 0, multicast_addr, port)
+ msg, info = socket.recvfrom(1024)
+ assert_equal(multicast_msg, msg)
+ assert_equal(multicast_msg.size, msg.size)
+ assert_equal(port, info[1])
+ socket.close
+ end
+ end
+
def test_tcp_socket_allows_nil_for_hostname
assert_nothing_raised do
server = TCPServer.new(nil, 7789)

0 comments on commit cc8d305

Please sign in to comment.