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