View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.testsuite.transport.socket;
17  
18  import io.netty.bootstrap.Bootstrap;
19  import io.netty.buffer.Unpooled;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.ChannelOption;
22  import io.netty.channel.SimpleChannelInboundHandler;
23  import io.netty.channel.socket.DatagramChannel;
24  import io.netty.channel.socket.DatagramPacket;
25  import io.netty.channel.socket.InternetProtocolFamily;
26  import io.netty.channel.socket.oio.OioDatagramChannel;
27  import io.netty.testsuite.transport.TestsuitePermutation;
28  import io.netty.util.internal.SocketUtils;
29  import org.junit.jupiter.api.Test;
30  import org.junit.jupiter.api.TestInfo;
31  
32  import java.io.IOException;
33  import java.net.InetAddress;
34  import java.net.InetSocketAddress;
35  import java.net.MulticastSocket;
36  import java.net.NetworkInterface;
37  import java.net.UnknownHostException;
38  import java.util.Enumeration;
39  import java.util.List;
40  import java.util.concurrent.CountDownLatch;
41  import java.util.concurrent.TimeUnit;
42  
43  import static org.junit.jupiter.api.Assertions.assertEquals;
44  import static org.junit.jupiter.api.Assertions.assertFalse;
45  import static org.junit.jupiter.api.Assertions.assertTrue;
46  import static org.junit.jupiter.api.Assertions.fail;
47  import static org.junit.jupiter.api.Assumptions.assumeTrue;
48  
49  public class DatagramMulticastTest extends AbstractDatagramTest {
50  
51      @Test
52      public void testMulticast(TestInfo testInfo) throws Throwable {
53          run(testInfo, new Runner<Bootstrap, Bootstrap>() {
54              @Override
55              public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable {
56                  testMulticast(bootstrap, bootstrap2);
57              }
58          });
59      }
60  
61      public void testMulticast(Bootstrap sb, Bootstrap cb) throws Throwable {
62          NetworkInterface iface = multicastNetworkInterface();
63          assumeTrue(iface != null, "No NetworkInterface found that supports multicast and " +
64                               socketInternetProtocalFamily());
65  
66          MulticastTestHandler mhandler = new MulticastTestHandler();
67  
68          sb.handler(new SimpleChannelInboundHandler<Object>() {
69              @Override
70              public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
71                  // Nothing will be sent.
72              }
73          });
74  
75          cb.handler(mhandler);
76  
77          sb.option(ChannelOption.IP_MULTICAST_IF, iface);
78          sb.option(ChannelOption.SO_REUSEADDR, true);
79  
80          cb.option(ChannelOption.IP_MULTICAST_IF, iface);
81          cb.option(ChannelOption.SO_REUSEADDR, true);
82  
83          DatagramChannel sc = (DatagramChannel) sb.bind(newSocketAddress(iface)).sync().channel();
84          assertEquals(iface, sc.config().getNetworkInterface());
85          assertInterfaceAddress(iface, sc.config().getInterface());
86  
87          InetSocketAddress addr = sc.localAddress();
88          cb.localAddress(addr.getPort());
89  
90          if (sc instanceof OioDatagramChannel) {
91              // skip the test for OIO, as it fails because of
92              // No route to host which makes no sense.
93              // Maybe a JDK bug ?
94              sc.close().awaitUninterruptibly();
95              return;
96          }
97          DatagramChannel cc = (DatagramChannel) cb.bind().sync().channel();
98          assertEquals(iface, cc.config().getNetworkInterface());
99          assertInterfaceAddress(iface, cc.config().getInterface());
100 
101         InetSocketAddress groupAddress = SocketUtils.socketAddress(groupAddress(), addr.getPort());
102 
103         cc.joinGroup(groupAddress, iface).sync();
104 
105         sc.writeAndFlush(new DatagramPacket(Unpooled.copyInt(1), groupAddress)).sync();
106         assertTrue(mhandler.await());
107 
108         // leave the group
109         cc.leaveGroup(groupAddress, iface).sync();
110 
111         // sleep a second to make sure we left the group
112         Thread.sleep(1000);
113 
114         // we should not receive a message anymore as we left the group before
115         sc.writeAndFlush(new DatagramPacket(Unpooled.copyInt(1), groupAddress)).sync();
116         mhandler.await();
117 
118         cc.config().setLoopbackModeDisabled(false);
119         sc.config().setLoopbackModeDisabled(false);
120 
121         assertFalse(cc.config().isLoopbackModeDisabled());
122         assertFalse(sc.config().isLoopbackModeDisabled());
123 
124         cc.config().setLoopbackModeDisabled(true);
125         sc.config().setLoopbackModeDisabled(true);
126 
127         assertTrue(cc.config().isLoopbackModeDisabled());
128         assertTrue(sc.config().isLoopbackModeDisabled());
129 
130         sc.close().awaitUninterruptibly();
131         cc.close().awaitUninterruptibly();
132     }
133 
134     private static void assertInterfaceAddress(NetworkInterface networkInterface, InetAddress expected) {
135         Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
136         while (addresses.hasMoreElements()) {
137             if (expected.equals(addresses.nextElement())) {
138                 return;
139             }
140         }
141         fail();
142     }
143 
144     private static final class MulticastTestHandler extends SimpleChannelInboundHandler<DatagramPacket> {
145         private final CountDownLatch latch = new CountDownLatch(1);
146 
147         private boolean done;
148         private volatile boolean fail;
149 
150         @Override
151         protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
152             if (done) {
153                 fail = true;
154             }
155 
156             assertEquals(1, msg.content().readInt());
157 
158             latch.countDown();
159 
160             // mark the handler as done as we only are supposed to receive one message
161             done = true;
162         }
163 
164         public boolean await() throws Exception {
165             boolean success = latch.await(10, TimeUnit.SECONDS);
166             if (fail) {
167                 // fail if we receive an message after we are done
168                 fail();
169             }
170             return success;
171         }
172     }
173 
174     @Override
175     protected List<TestsuitePermutation.BootstrapComboFactory<Bootstrap, Bootstrap>> newFactories() {
176         return SocketTestPermutation.INSTANCE.datagram(socketInternetProtocalFamily());
177     }
178 
179     private InetSocketAddress newAnySocketAddress() throws UnknownHostException {
180         switch (socketInternetProtocalFamily()) {
181             case IPv4:
182                 return new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0);
183             case IPv6:
184                 return new InetSocketAddress(InetAddress.getByName("::"), 0);
185             default:
186                 throw new AssertionError();
187         }
188     }
189 
190     private InetSocketAddress newSocketAddress(NetworkInterface iface) {
191         Enumeration<InetAddress> addresses = iface.getInetAddresses();
192         while (addresses.hasMoreElements()) {
193             InetAddress address = addresses.nextElement();
194             if (socketInternetProtocalFamily().addressType().isAssignableFrom(address.getClass())) {
195                 return new InetSocketAddress(address, 0);
196             }
197         }
198         throw new AssertionError();
199     }
200 
201     private NetworkInterface multicastNetworkInterface() throws IOException {
202         Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
203         while (interfaces.hasMoreElements()) {
204             NetworkInterface iface = interfaces.nextElement();
205             if (iface.isUp() && iface.supportsMulticast()) {
206                 Enumeration<InetAddress> addresses = iface.getInetAddresses();
207                 while (addresses.hasMoreElements()) {
208                     InetAddress address = addresses.nextElement();
209                     if (socketInternetProtocalFamily().addressType().isAssignableFrom(address.getClass())) {
210                         MulticastSocket socket = new MulticastSocket(newAnySocketAddress());
211                         socket.setReuseAddress(true);
212                         socket.setNetworkInterface(iface);
213                         try {
214                             socket.send(new java.net.DatagramPacket(new byte[] { 1, 2, 3, 4 }, 4,
215                                                                     new InetSocketAddress(groupAddress(), 12345)));
216                             return iface;
217                         } catch (IOException ignore) {
218                             // Try the next interface
219                         } finally {
220                             socket.close();
221                         }
222                     }
223                 }
224             }
225         }
226         return null;
227     }
228 
229     private String groupAddress() {
230         return groupInternetProtocalFamily() == InternetProtocolFamily.IPv4?
231                 "230.0.0.1" : "FF01:0:0:0:0:0:0:101";
232     }
233 }