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 }