diff --git a/common/src/main/java/io/netty/util/SocketAddresses.java b/common/src/main/java/io/netty/util/SocketAddresses.java index 93009adb74..07a7ccf97d 100644 --- a/common/src/main/java/io/netty/util/SocketAddresses.java +++ b/common/src/main/java/io/netty/util/SocketAddresses.java @@ -19,11 +19,14 @@ import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; import java.net.UnknownHostException; public final class SocketAddresses { public static final InetAddress LOCALHOST; + public static final NetworkInterface LOOPBACK_IF; private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketAddresses.class); @@ -48,6 +51,26 @@ public final class SocketAddresses { } LOCALHOST = localhost; + + NetworkInterface loopbackIf; + try { + loopbackIf = NetworkInterface.getByInetAddress(LOCALHOST); + } catch (SocketException e) { + loopbackIf = null; + } + + // check if the NetworkInterface is null, this is the case on my ubuntu dev machine but not on osx and windows. + // if so fail back the the first interface + if (loopbackIf == null) { + // use nextElement() as NetWorkInterface.getByIndex(0) returns null + try { + loopbackIf = NetworkInterface.getNetworkInterfaces().nextElement(); + } catch (SocketException e) { + logger.error("Failed to enumerate network interfaces", e); + } + } + + LOOPBACK_IF = loopbackIf; } private SocketAddresses() { diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java deleted file mode 100644 index ab71275350..0000000000 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractDatagramMulticastTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2011 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.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.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.testsuite.transport.socket; - -import static org.junit.Assert.assertTrue; -import io.netty.bootstrap.ConnectionlessBootstrap; -import io.netty.buffer.ChannelBuffer; -import io.netty.buffer.ChannelBuffers; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.MessageEvent; -import io.netty.channel.SimpleChannelUpstreamHandler; -import io.netty.channel.socket.DatagramChannel; -import io.netty.channel.socket.DatagramChannelFactory; -import io.netty.testsuite.util.TestUtils; -import io.netty.util.internal.ExecutorUtil; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.NetworkInterface; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -public abstract class AbstractDatagramMulticastTest { - - - private static ExecutorService executor; - - - @BeforeClass - public static void init() { - executor = Executors.newCachedThreadPool(); - } - - @AfterClass - public static void destroy() { - ExecutorUtil.terminate(executor); - } - - protected abstract DatagramChannelFactory newServerSocketChannelFactory(Executor executor); - protected abstract DatagramChannelFactory newClientSocketChannelFactory(Executor executor); - - @Test - public void testMulticast() throws Throwable { - - ConnectionlessBootstrap sb = new ConnectionlessBootstrap(newServerSocketChannelFactory(executor)); - ConnectionlessBootstrap cb = new ConnectionlessBootstrap(newClientSocketChannelFactory(executor)); - MulticastTestHandler mhandler = new MulticastTestHandler(); - - cb.pipeline().addFirst("handler", mhandler); - sb.pipeline().addFirst("handler", new SimpleChannelUpstreamHandler()); - - int port = TestUtils.getFreePort(); - - NetworkInterface iface = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()); - - // check if the NetworkInterface is null, this is the case on my ubuntu dev machine but not on osx and windows. - // if so fail back the the first interface - if (iface == null) { - // use nextElement() as NetWorkInterface.getByIndex(0) returns null - iface = NetworkInterface.getNetworkInterfaces().nextElement(); - } - sb.setOption("networkInterface", iface); - sb.setOption("reuseAddress", true); - - Channel sc = sb.bind(new InetSocketAddress(port)); - - - String group = "230.0.0.1"; - InetSocketAddress groupAddress = new InetSocketAddress(group, port); - - cb.setOption("networkInterface", iface); - cb.setOption("reuseAddress", true); - - DatagramChannel cc = (DatagramChannel) cb.bind(new InetSocketAddress(port)); - - - assertTrue(cc.joinGroup(groupAddress, iface).awaitUninterruptibly().isSuccess()); - - assertTrue(sc.write(ChannelBuffers.copyInt(1), groupAddress).awaitUninterruptibly().isSuccess()); - - - assertTrue(mhandler.await()); - - assertTrue(sc.write(ChannelBuffers.copyInt(1), groupAddress).awaitUninterruptibly().isSuccess()); - - - // leave the group - assertTrue(cc.leaveGroup(groupAddress, iface).awaitUninterruptibly().isSuccess()); - - // sleep a second to make sure we left the group - Thread.sleep(1000); - - // we should not receive a message anymore as we left the group before - assertTrue(sc.write(ChannelBuffers.copyInt(1), groupAddress).awaitUninterruptibly().isSuccess()); - - sc.close().awaitUninterruptibly(); - cc.close().awaitUninterruptibly(); - - } - - private final class MulticastTestHandler extends SimpleChannelUpstreamHandler { - private final CountDownLatch latch = new CountDownLatch(1); - - private boolean done = false; - private volatile boolean fail = false; - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { - super.messageReceived(ctx, e); - if (done) { - fail = true; - } - - Assert.assertEquals(1,((ChannelBuffer)e.getMessage()).readInt()); - - latch.countDown(); - - // mark the handler as done as we only are supposed to receive one message - done = true; - } - - public boolean await() throws Exception { - boolean success = latch.await(10, TimeUnit.SECONDS); - if (fail) { - // fail if we receive an message after we are done - Assert.fail(); - } - return success; - } - - } -} diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java new file mode 100644 index 0000000000..9e9239d9ae --- /dev/null +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java @@ -0,0 +1,119 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.testsuite.transport.socket; + +import static org.junit.Assert.*; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ChannelBuffers; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInboundHandlerContext; +import io.netty.channel.ChannelInboundMessageHandlerAdapter; +import io.netty.channel.ChannelOption; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.DatagramPacket; +import io.netty.util.SocketAddresses; + +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Test; + +public class DatagramMulticastTest extends AbstractDatagramTest { + + @Test + public void testMulticast() throws Throwable { + run(); + } + + public void testMulticast(Bootstrap sb, Bootstrap cb) throws Throwable { + MulticastTestHandler mhandler = new MulticastTestHandler(); + + sb.handler(mhandler); + cb.handler(new ChannelInboundMessageHandlerAdapter() { + @Override + public void messageReceived( + ChannelInboundHandlerContext ctx, + DatagramPacket msg) throws Exception { + // Nothing will be sent. + } + }); + + sb.option(ChannelOption.IP_MULTICAST_IF, SocketAddresses.LOOPBACK_IF); + sb.option(ChannelOption.SO_REUSEADDR, true); + cb.option(ChannelOption.IP_MULTICAST_IF, SocketAddresses.LOOPBACK_IF); + cb.option(ChannelOption.SO_REUSEADDR, true); + cb.localAddress(addr); + + Channel sc = sb.bind().sync().channel(); + DatagramChannel cc = (DatagramChannel) cb.bind().sync().channel(); + + String group = "230.0.0.1"; + InetSocketAddress groupAddress = new InetSocketAddress(group, addr.getPort()); + + cc.joinGroup(groupAddress, SocketAddresses.LOOPBACK_IF).sync(); + + sc.write(new DatagramPacket(ChannelBuffers.copyInt(1), groupAddress)).sync(); + assertTrue(mhandler.await()); + sc.write(new DatagramPacket(ChannelBuffers.copyInt(1), groupAddress)).sync(); + + // leave the group + cc.leaveGroup(groupAddress, SocketAddresses.LOOPBACK_IF).sync(); + + // sleep a second to make sure we left the group + Thread.sleep(1000); + + // we should not receive a message anymore as we left the group before + sc.write(new DatagramPacket(ChannelBuffers.copyInt(1), groupAddress)).sync(); + mhandler.await(); + + sc.close().awaitUninterruptibly(); + cc.close().awaitUninterruptibly(); + + } + + private final class MulticastTestHandler extends ChannelInboundMessageHandlerAdapter { + private final CountDownLatch latch = new CountDownLatch(1); + + private boolean done = false; + private volatile boolean fail = false; + + @Override + public void messageReceived( + ChannelInboundHandlerContext ctx, + DatagramPacket msg) throws Exception { + if (done) { + fail = true; + } + + Assert.assertEquals(1, msg.data().readInt()); + latch.countDown(); + + // mark the handler as done as we only are supposed to receive one message + done = true; + } + + public boolean await() throws Exception { + boolean success = latch.await(10, TimeUnit.SECONDS); + if (fail) { + // fail if we receive an message after we are done + Assert.fail(); + } + return success; + } + } +} diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannelConfig.java index 112100da74..118ce92b08 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioDatagramChannelConfig.java @@ -24,6 +24,7 @@ import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.nio.channels.DatagramChannel; +import java.nio.channels.NetworkChannel; import java.util.Enumeration; /** @@ -53,7 +54,7 @@ class NioDatagramChannelConfig extends DefaultDatagramChannelConfig { Method setOption = null; if (socketOptionType != null) { try { - ipMulticastIf = Class.forName("java.net.StandardSocketOptions", true, classLoader).getDeclaredField("IP_MULTICAST_TTL").get(null); + ipMulticastTtl = Class.forName("java.net.StandardSocketOptions", true, classLoader).getDeclaredField("IP_MULTICAST_TTL").get(null); } catch (Exception e) { throw new Error("cannot locate the IP_MULTICAST_TTL field", e); } @@ -65,19 +66,19 @@ class NioDatagramChannelConfig extends DefaultDatagramChannelConfig { } try { - ipMulticastIf = Class.forName("java.net.StandardSocketOptions", true, classLoader).getDeclaredField("IP_MULTICAST_LOOP").get(null); + ipMulticastLoop = Class.forName("java.net.StandardSocketOptions", true, classLoader).getDeclaredField("IP_MULTICAST_LOOP").get(null); } catch (Exception e) { throw new Error("cannot locate the IP_MULTICAST_LOOP field", e); } try { - getOption = DatagramChannel.class.getDeclaredMethod("getOption", socketOptionType); + getOption = NetworkChannel.class.getDeclaredMethod("getOption", socketOptionType); } catch (Exception e) { throw new Error("cannot locate the getOption() method", e); } try { - setOption = DatagramChannel.class.getDeclaredMethod("setOption", socketOptionType, Object.class); + setOption = NetworkChannel.class.getDeclaredMethod("setOption", socketOptionType, Object.class); } catch (Exception e) { throw new Error("cannot locate the setOption() method", e); }