1 /*
   2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /* @test
  25  * @bug 8236925
  26  * @summary Test DatagramChannel socket adaptor as a MulticastSocket
  27  * @library /test/lib
  28  * @build jdk.test.lib.NetworkConfiguration
  29  *        jdk.test.lib.net.IPSupport
  30  * @run main AdaptorMulticasting
  31  * @run main/othervm -Djava.net.preferIPv4Stack=true AdaptorMulticasting
  32  */
  33 
  34 import java.io.IOException;
  35 import java.net.DatagramPacket;
  36 import java.net.InetAddress;
  37 import java.net.InetSocketAddress;
  38 import java.net.MulticastSocket;
  39 import java.net.NetworkInterface;
  40 import java.net.ProtocolFamily;
  41 import java.net.SocketAddress;
  42 import java.net.SocketException;
  43 import java.net.SocketTimeoutException;
  44 import java.net.SocketOption;
  45 import java.nio.channels.DatagramChannel;
  46 import java.util.List;
  47 import java.util.stream.Collectors;
  48 import static java.net.StandardSocketOptions.*;
  49 import static java.net.StandardProtocolFamily.*;
  50 
  51 import jdk.test.lib.NetworkConfiguration;
  52 import jdk.test.lib.net.IPSupport;
  53 
  54 public class AdaptorMulticasting {
  55     static final ProtocolFamily UNSPEC = () -> "UNSPEC";
  56 
  57     public static void main(String[] args) throws IOException {
  58         IPSupport.throwSkippedExceptionIfNonOperational();
  59 
  60         // IPv4 and IPv6 interfaces that support multicasting
  61         NetworkConfiguration config = NetworkConfiguration.probe();
  62         List<NetworkInterface> ip4MulticastInterfaces = config.ip4MulticastInterfaces()
  63                 .collect(Collectors.toList());
  64         List<NetworkInterface> ip6MulticastInterfaces = config.ip6MulticastInterfaces()
  65                 .collect(Collectors.toList());
  66 
  67         // multicast groups used for the test
  68         InetAddress ip4Group = InetAddress.getByName("225.4.5.6");
  69         InetAddress ip6Group = InetAddress.getByName("ff02::a");
  70 
  71         for (NetworkInterface ni : ip4MulticastInterfaces) {
  72             test(INET, ip4Group, ni);
  73             if (IPSupport.hasIPv6()) {
  74                 test(UNSPEC, ip4Group, ni);
  75                 test(INET6, ip4Group, ni);
  76             }
  77         }
  78         for (NetworkInterface ni : ip6MulticastInterfaces) {
  79             test(UNSPEC, ip6Group, ni);
  80             test(INET6, ip6Group, ni);
  81         }
  82     }
  83 
  84     static void test(ProtocolFamily family, InetAddress group, NetworkInterface ni)
  85         throws IOException
  86     {
  87         System.out.format("Test family=%s, multicast group=%s, interface=%s%n",
  88             family.name(), group, ni.getName());
  89 
  90         // test 1-arg joinGroup/leaveGroup
  91         try (MulticastSocket s = create(family)) {
  92             testJoinGroup1(family, s, group, ni);
  93         }
  94 
  95         // test 2-arg joinGroup/leaveGroup
  96         try (MulticastSocket s = create(family)) {
  97             testJoinGroup2(family, s, group, ni);
  98         }
  99 
 100         // test socket options
 101         try (MulticastSocket s = create(family)) {
 102             testNetworkInterface(s, ni);
 103             testTimeToLive(s);
 104             testLoopbackMode(s);
 105         }
 106     }
 107 
 108     /**
 109      * Creates a MulticastSocket. The SO_REUSEADDR socket option is set and it
 110      * is bound to the wildcard address.
 111      */
 112     static MulticastSocket create(ProtocolFamily family) throws IOException {
 113         DatagramChannel dc = (family == UNSPEC)
 114                 ? DatagramChannel.open()
 115                 : DatagramChannel.open(family);
 116         try {
 117             dc.setOption(SO_REUSEADDR, true).bind(new InetSocketAddress(0));
 118         } catch (IOException ioe) {
 119             dc.close();
 120             throw ioe;
 121         }
 122         return (MulticastSocket) dc.socket();
 123     }
 124 
 125     /**
 126      * Test 1-arg joinGroup/leaveGroup
 127      */
 128     static void testJoinGroup1(ProtocolFamily family,
 129                                MulticastSocket s,
 130                                InetAddress group,
 131                                NetworkInterface ni) throws IOException {
 132         // check network interface not set
 133         assertTrue(s.getOption(IP_MULTICAST_IF) == null);
 134 
 135         // join
 136         s.joinGroup(group);
 137 
 138         // join should not set the outgoing multicast interface
 139         assertTrue(s.getOption(IP_MULTICAST_IF) == null);
 140 
 141         // already a member (exception not specified)
 142         assertThrows(SocketException.class, () -> s.joinGroup(group));
 143 
 144         // leave
 145         s.leaveGroup(group);
 146 
 147         // not a member (exception not specified)
 148         assertThrows(SocketException.class, () -> s.leaveGroup(group));
 149 
 150         // join/leave with outgoing multicast interface set and check that
 151         // multicast datagrams can be sent and received
 152         s.setOption(IP_MULTICAST_IF, ni);
 153         s.joinGroup(group);
 154         testSendReceive(s, group);
 155         s.leaveGroup(group);
 156         testSendNoReceive(s, group);
 157 
 158         // not a multicast address
 159         var localHost = InetAddress.getLocalHost();
 160         assertThrows(SocketException.class, () -> s.joinGroup(localHost));
 161         assertThrows(SocketException.class, () -> s.leaveGroup(localHost));
 162 
 163         // IPv4 socket cannot join IPv6 group (exception not specified)
 164         if (family == INET) {
 165             InetAddress ip6Group = InetAddress.getByName("ff02::a");
 166             assertThrows(SocketException.class, () -> s.joinGroup(ip6Group));
 167             assertThrows(SocketException.class, () -> s.leaveGroup(ip6Group));
 168         }
 169 
 170         // null (exception not specified)
 171         assertThrows(NullPointerException.class, () -> s.joinGroup(null));
 172         assertThrows(NullPointerException.class, () -> s.leaveGroup(null));
 173     }
 174 
 175     /**
 176      * Test 2-arg joinGroup/leaveGroup
 177      */
 178     static void testJoinGroup2(ProtocolFamily family,
 179                                MulticastSocket s,
 180                                InetAddress group,
 181                                NetworkInterface ni) throws IOException {
 182         // check network interface not set
 183         assertTrue(s.getOption(IP_MULTICAST_IF) == null);
 184 
 185         // join on default interface
 186         s.joinGroup(new InetSocketAddress(group, 0), null);
 187 
 188         // join should not change the outgoing multicast interface
 189         assertTrue(s.getOption(IP_MULTICAST_IF) == null);
 190 
 191         // already a member (exception not specified)
 192         assertThrows(SocketException.class,
 193                      () -> s.joinGroup(new InetSocketAddress(group, 0), null));
 194 
 195         // leave
 196         s.leaveGroup(new InetSocketAddress(group, 0), null);
 197 
 198         // not a member (exception not specified)
 199         assertThrows(SocketException.class,
 200                      () -> s.leaveGroup(new InetSocketAddress(group, 0), null));
 201 
 202         // join on specified interface
 203         s.joinGroup(new InetSocketAddress(group, 0), ni);
 204 
 205         // join should not change the outgoing multicast interface
 206         assertTrue(s.getOption(IP_MULTICAST_IF) == null);
 207 
 208         // already a member (exception not specified)
 209         assertThrows(SocketException.class,
 210                      () -> s.joinGroup(new InetSocketAddress(group, 0), ni));
 211 
 212         // leave
 213         s.leaveGroup(new InetSocketAddress(group, 0), ni);
 214 
 215         // not a member (exception not specified)
 216         assertThrows(SocketException.class,
 217                      () -> s.leaveGroup(new InetSocketAddress(group, 0), ni));
 218 
 219         // join/leave with outgoing multicast interface set and check that
 220         // multicast datagrams can be sent and received
 221         s.setOption(IP_MULTICAST_IF, ni);
 222         s.joinGroup(new InetSocketAddress(group, 0), null);
 223         testSendReceive(s, group);
 224         s.leaveGroup(new InetSocketAddress(group, 0), null);
 225         testSendNoReceive(s, group);
 226         s.joinGroup(new InetSocketAddress(group, 0), ni);
 227         testSendReceive(s, group);
 228         s.leaveGroup(new InetSocketAddress(group, 0), ni);
 229         testSendNoReceive(s, group);
 230 
 231         // not a multicast address
 232         var localHost = InetAddress.getLocalHost();
 233         assertThrows(SocketException.class,
 234                      () -> s.joinGroup(new InetSocketAddress(localHost, 0), null));
 235         assertThrows(SocketException.class,
 236                      () -> s.leaveGroup(new InetSocketAddress(localHost, 0), null));
 237         assertThrows(SocketException.class,
 238                      () -> s.joinGroup(new InetSocketAddress(localHost, 0), ni));
 239         assertThrows(SocketException.class,
 240                      () -> s.leaveGroup(new InetSocketAddress(localHost, 0), ni));
 241 
 242         // not an InetSocketAddress
 243         var customSocketAddress = new SocketAddress() { };
 244         assertThrows(IllegalArgumentException.class,
 245                      () -> s.joinGroup(customSocketAddress, null));
 246         assertThrows(IllegalArgumentException.class,
 247                      () -> s.leaveGroup(customSocketAddress, null));
 248         assertThrows(IllegalArgumentException.class,
 249                      () -> s.joinGroup(customSocketAddress, ni));
 250         assertThrows(IllegalArgumentException.class,
 251                      () -> s.leaveGroup(customSocketAddress, ni));
 252 
 253         // IPv4 socket cannot join IPv6 group
 254         if (family == INET) {
 255             InetAddress ip6Group = InetAddress.getByName("ff02::a");
 256             assertThrows(IllegalArgumentException.class,
 257                          () -> s.joinGroup(new InetSocketAddress(ip6Group, 0), null));
 258             assertThrows(IllegalArgumentException.class,
 259                          () -> s.joinGroup(new InetSocketAddress(ip6Group, 0), ni));
 260 
 261             // not a member of IPv6 group (exception not specified)
 262             assertThrows(SocketException.class,
 263                          () -> s.leaveGroup(new InetSocketAddress(ip6Group, 0), null));
 264             assertThrows(SocketException.class,
 265                          () -> s.leaveGroup(new InetSocketAddress(ip6Group, 0), ni));
 266         }
 267 
 268         // null
 269         assertThrows(IllegalArgumentException.class, () -> s.joinGroup(null, null));
 270         assertThrows(IllegalArgumentException.class, () -> s.leaveGroup(null, null));
 271         assertThrows(IllegalArgumentException.class, () -> s.joinGroup(null, ni));
 272         assertThrows(IllegalArgumentException.class, () -> s.leaveGroup(null, ni));
 273     }
 274 
 275     /**
 276      * Test getNetworkInterface/setNetworkInterface/getInterface/setInterface
 277      * and IP_MULTICAST_IF socket option.
 278      */
 279     static void testNetworkInterface(MulticastSocket s,
 280                                      NetworkInterface ni) throws IOException {
 281         // default value
 282         NetworkInterface nif = s.getNetworkInterface();
 283         assertTrue(nif.getIndex() == 0);
 284         assertTrue(nif.inetAddresses().count() == 1);
 285         assertTrue(nif.inetAddresses().findAny().orElseThrow().isAnyLocalAddress());
 286         assertTrue(s.getOption(IP_MULTICAST_IF) == null);
 287         assertTrue(s.getInterface().isAnyLocalAddress());
 288 
 289         // setNetworkInterface
 290         s.setNetworkInterface(ni);
 291         assertTrue(s.getNetworkInterface().equals(ni));
 292         assertTrue(s.getOption(IP_MULTICAST_IF).equals(ni));
 293         InetAddress address = s.getInterface();
 294         assertTrue(ni.inetAddresses().filter(address::equals).findAny().isPresent());
 295 
 296         // setInterface
 297         s.setInterface(address);
 298         assertTrue(s.getInterface().equals(address));
 299         assertTrue(s.getNetworkInterface()
 300                 .inetAddresses()
 301                 .filter(address::equals)
 302                 .findAny()
 303                 .isPresent());
 304 
 305         // null (exception not specified)
 306         assertThrows(IllegalArgumentException.class, () -> s.setNetworkInterface(null));
 307         assertThrows(SocketException.class, () -> s.setInterface(null));
 308 
 309         // setOption(IP_MULTICAST_IF)
 310         s.setOption(IP_MULTICAST_IF, ni);
 311         assertTrue(s.getOption(IP_MULTICAST_IF).equals(ni));
 312         assertTrue(s.getNetworkInterface().equals(ni));
 313 
 314         // bad values for IP_MULTICAST_IF
 315         assertThrows(IllegalArgumentException.class,
 316                      () -> s.setOption(IP_MULTICAST_IF, null));
 317         assertThrows(IllegalArgumentException.class,
 318                      () -> s.setOption((SocketOption) IP_MULTICAST_IF, "badValue"));
 319     }
 320 
 321     /**
 322      * Test getTimeToLive/setTimeToLive/getTTL/getTTL and IP_MULTICAST_TTL socket
 323      * option.
 324      */
 325     static void testTimeToLive(MulticastSocket s) throws IOException {
 326         // should be 1 by default
 327         assertTrue(s.getTimeToLive() == 1);
 328         assertTrue(s.getTTL() == 1);
 329         assertTrue(s.getOption(IP_MULTICAST_TTL) == 1);
 330 
 331         // setTimeToLive
 332         for (int ttl = 0; ttl <= 2; ttl++) {
 333             s.setTimeToLive(ttl);
 334             assertTrue(s.getTimeToLive() == ttl);
 335             assertTrue(s.getTTL() == ttl);
 336             assertTrue(s.getOption(IP_MULTICAST_TTL) == ttl);
 337         }
 338         assertThrows(IllegalArgumentException.class, () -> s.setTimeToLive(-1));
 339 
 340         // setTTL
 341         for (byte ttl = (byte) -2; ttl <= 2; ttl++) {
 342             s.setTTL(ttl);
 343             assertTrue(s.getTTL() == ttl);
 344             int intValue = Byte.toUnsignedInt(ttl);
 345             assertTrue(s.getTimeToLive() == intValue);
 346             assertTrue(s.getOption(IP_MULTICAST_TTL) == intValue);
 347         }
 348 
 349         // setOption(IP_MULTICAST_TTL)
 350         for (int ttl = 0; ttl <= 2; ttl++) {
 351             s.setOption(IP_MULTICAST_TTL, ttl);
 352             assertTrue(s.getOption(IP_MULTICAST_TTL) == ttl);
 353             assertTrue(s.getTimeToLive() == ttl);
 354             assertTrue(s.getTTL() == ttl);
 355         }
 356 
 357         // bad values for IP_MULTICAST_TTL
 358         assertThrows(IllegalArgumentException.class,
 359                     () -> s.setOption(IP_MULTICAST_TTL, -1));
 360         assertThrows(IllegalArgumentException.class,
 361                     () -> s.setOption(IP_MULTICAST_TTL, null));
 362         assertThrows(IllegalArgumentException.class,
 363                     () -> s.setOption((SocketOption) IP_MULTICAST_TTL, "badValue"));
 364     }
 365 
 366     /**
 367      * Test getLoopbackMode/setLoopbackMode and IP_MULTICAST_LOOP socket option.
 368      */
 369     static void testLoopbackMode(MulticastSocket s) throws IOException {
 370         // should be enabled by default
 371         assertTrue(s.getLoopbackMode() == false);
 372         assertTrue(s.getOption(IP_MULTICAST_LOOP) == true);
 373 
 374         // setLoopbackMode
 375         s.setLoopbackMode(true);    // disable
 376         assertTrue(s.getLoopbackMode());
 377         assertTrue(s.getOption(IP_MULTICAST_LOOP) == false);
 378         s.setLoopbackMode(false);   // enable
 379         assertTrue(s.getLoopbackMode() == false);
 380         assertTrue(s.getOption(IP_MULTICAST_LOOP) == true);
 381 
 382         // setOption(IP_MULTICAST_LOOP)
 383         s.setOption(IP_MULTICAST_LOOP, false);   // disable
 384         assertTrue(s.getOption(IP_MULTICAST_LOOP) == false);
 385         assertTrue(s.getLoopbackMode() == true);
 386         s.setOption(IP_MULTICAST_LOOP, true);  // enable
 387         assertTrue(s.getOption(IP_MULTICAST_LOOP) == true);
 388         assertTrue(s.getLoopbackMode() == false);
 389 
 390         // bad values for IP_MULTICAST_LOOP
 391         assertThrows(IllegalArgumentException.class,
 392                      () -> s.setOption(IP_MULTICAST_LOOP, null));
 393         assertThrows(IllegalArgumentException.class,
 394                      () -> s.setOption((SocketOption) IP_MULTICAST_LOOP, "badValue"));
 395     }
 396 
 397     /**
 398      * Send a datagram to the given multicast group and check that it is received.
 399      */
 400     static void testSendReceive(MulticastSocket s, InetAddress group) throws IOException {
 401         // outgoing multicast interface needs to be set
 402         assertTrue(s.getOption(IP_MULTICAST_IF) != null);
 403 
 404         SocketAddress target = new InetSocketAddress(group, s.getLocalPort());
 405         byte[] message = "hello".getBytes("UTF-8");
 406 
 407         // send message to multicast group
 408         DatagramPacket p = new DatagramPacket(message, message.length);
 409         p.setSocketAddress(target);
 410         s.send(p, (byte) 1);
 411 
 412         // receive message
 413         s.setSoTimeout(0);
 414         p = new DatagramPacket(new byte[1024], 100);
 415         s.receive(p);
 416 
 417         assertTrue(p.getLength() == message.length);
 418         assertTrue(p.getPort() == s.getLocalPort());
 419     }
 420 
 421     /**
 422      * Send a datagram to the given multicast group and check that it is not
 423      * received.
 424      */
 425     static void testSendNoReceive(MulticastSocket s, InetAddress group) throws IOException {
 426         // outgoing multicast interface needs to be set
 427         assertTrue(s.getOption(IP_MULTICAST_IF) != null);
 428 
 429         SocketAddress target = new InetSocketAddress(group, s.getLocalPort());
 430         byte[] message = "hello".getBytes("UTF-8");
 431 
 432         // send datagram to multicast group
 433         DatagramPacket p = new DatagramPacket(message, message.length);
 434         p.setSocketAddress(target);
 435         s.send(p, (byte) 1);
 436 
 437         // datagram should not be received
 438         s.setSoTimeout(500);
 439         p = new DatagramPacket(new byte[1024], 100);
 440         try {
 441             s.receive(p);
 442             assertTrue(false);
 443         } catch (SocketTimeoutException expected) { }
 444     }
 445 
 446 
 447     static void assertTrue(boolean e) {
 448         if (!e) throw new RuntimeException();
 449     }
 450 
 451     interface ThrowableRunnable {
 452         void run() throws Exception;
 453     }
 454 
 455     static void assertThrows(Class<?> exceptionClass, ThrowableRunnable task) {
 456         try {
 457             task.run();
 458             throw new RuntimeException("Exception not thrown");
 459         } catch (Exception e) {
 460             if (!exceptionClass.isInstance(e)) {
 461                 throw new RuntimeException("expected: " + exceptionClass + ", actual: " + e);
 462             }
 463         }
 464     }
 465 }