1 /* 2 * Copyright (c) 2021, 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 8237352 26 * @summary Test DatagramSocket for sending and receiving multicast datagrams 27 * @library /test/lib 28 * @build jdk.test.lib.NetworkConfiguration 29 * jdk.test.lib.net.IPSupport 30 * @run main/othervm DatagramSocketMulticasting 31 * @run main/othervm -Djava.net.preferIPv4Stack=true DatagramSocketMulticasting 32 * @run main/othervm -Djdk.usePlainDatagramSocketImpl=true DatagramSocketMulticasting 33 * @run main/othervm -Djdk.usePlainDatagramSocketImpl=true -Djava.net.preferIPv4Stack=true DatagramSocketMulticasting 34 */ 35 36 import java.io.IOException; 37 import java.net.DatagramPacket; 38 import java.net.DatagramSocket; 39 import java.net.InetAddress; 40 import java.net.InetSocketAddress; 41 import java.net.NetworkInterface; 42 import java.net.ProtocolFamily; 43 import java.net.SocketAddress; 44 import java.net.SocketException; 45 import java.net.SocketOption; 46 import java.net.SocketTimeoutException; 47 import java.util.Arrays; 48 import java.util.List; 49 import java.util.stream.Collectors; 50 51 import jdk.test.lib.NetworkConfiguration; 52 import jdk.test.lib.net.IPSupport; 53 54 import static java.net.StandardProtocolFamily.INET; 55 import static java.net.StandardProtocolFamily.INET6; 56 import static java.net.StandardSocketOptions.IP_MULTICAST_IF; 57 import static java.net.StandardSocketOptions.IP_MULTICAST_LOOP; 58 import static java.net.StandardSocketOptions.IP_MULTICAST_TTL; 59 import static java.net.StandardSocketOptions.SO_REUSEADDR; 60 import static jdk.test.lib.NetworkConfiguration.isSameInterface; 61 62 public class DatagramSocketMulticasting { 63 static final ProtocolFamily UNSPEC = () -> "UNSPEC"; 64 main(String[] args)65 public static void main(String[] args) throws IOException { 66 IPSupport.throwSkippedExceptionIfNonOperational(); 67 68 // IPv4 and IPv6 interfaces that support multicasting 69 NetworkConfiguration config = NetworkConfiguration.probe(); 70 List<NetworkInterface> ip4MulticastInterfaces = config.ip4MulticastInterfaces() 71 .collect(Collectors.toList()); 72 List<NetworkInterface> ip6MulticastInterfaces = config.ip6MulticastInterfaces() 73 .collect(Collectors.toList()); 74 75 // multicast groups used for the test 76 InetAddress ip4Group = InetAddress.getByName("225.4.5.6"); 77 InetAddress ip6Group = InetAddress.getByName("ff02::a"); 78 79 for (NetworkInterface ni : ip4MulticastInterfaces) { 80 test(INET, ip4Group, ni); 81 if (IPSupport.hasIPv6()) { 82 test(UNSPEC, ip4Group, ni); 83 test(INET6, ip4Group, ni); 84 } 85 } 86 for (NetworkInterface ni : ip6MulticastInterfaces) { 87 test(UNSPEC, ip6Group, ni); 88 test(INET6, ip6Group, ni); 89 } 90 } 91 test(ProtocolFamily family, InetAddress group, NetworkInterface ni)92 static void test(ProtocolFamily family, InetAddress group, NetworkInterface ni) 93 throws IOException 94 { 95 System.out.format("Test family=%s, multicast group=%s, interface=%s%n", 96 family.name(), group, ni.getName()); 97 98 // test 2-arg joinGroup/leaveGroup 99 try (DatagramSocket s = create()) { 100 testJoinGroup2(family, s, group, ni); 101 } 102 103 // test socket options 104 try (DatagramSocket s = create()) { 105 testNetworkInterface(s, ni); 106 testTimeToLive(s); 107 testLoopbackMode(s); 108 } 109 } 110 111 /** 112 * Creates a MulticastSocket. The SO_REUSEADDR socket option is set and it 113 * is bound to the wildcard address. 114 */ create()115 static DatagramSocket create() throws IOException { 116 DatagramSocket ds = new DatagramSocket(null); 117 try { 118 ds.setOption(SO_REUSEADDR, true).bind(new InetSocketAddress(0)); 119 } catch (IOException ioe) { 120 ds.close(); 121 throw ioe; 122 } 123 return ds; 124 } 125 126 /** 127 * Test 2-arg joinGroup/leaveGroup 128 */ testJoinGroup2(ProtocolFamily family, DatagramSocket s, InetAddress group, NetworkInterface ni)129 static void testJoinGroup2(ProtocolFamily family, 130 DatagramSocket s, 131 InetAddress group, 132 NetworkInterface ni) throws IOException { 133 134 System.out.format("testJoinGroup2: local socket address: %s%n", s.getLocalSocketAddress()); 135 136 // check network interface not set 137 assertTrue(s.getOption(IP_MULTICAST_IF) == null); 138 139 // join on default interface 140 s.joinGroup(new InetSocketAddress(group, 0), null); 141 142 // join should not change the outgoing multicast interface 143 assertTrue(s.getOption(IP_MULTICAST_IF) == null); 144 145 // already a member (exception not specified) 146 assertThrows(SocketException.class, 147 () -> s.joinGroup(new InetSocketAddress(group, 0), null)); 148 149 // leave 150 s.leaveGroup(new InetSocketAddress(group, 0), null); 151 152 // not a member (exception not specified) 153 assertThrows(SocketException.class, 154 () -> s.leaveGroup(new InetSocketAddress(group, 0), null)); 155 156 // join on specified interface 157 s.joinGroup(new InetSocketAddress(group, 0), ni); 158 159 // join should not change the outgoing multicast interface 160 assertTrue(s.getOption(IP_MULTICAST_IF) == null); 161 162 // already a member (exception not specified) 163 assertThrows(SocketException.class, 164 () -> s.joinGroup(new InetSocketAddress(group, 0), ni)); 165 166 // leave 167 s.leaveGroup(new InetSocketAddress(group, 0), ni); 168 169 // not a member (exception not specified) 170 assertThrows(SocketException.class, 171 () -> s.leaveGroup(new InetSocketAddress(group, 0), ni)); 172 173 // join/leave with outgoing multicast interface set and check that 174 // multicast datagrams can be sent and received 175 s.setOption(IP_MULTICAST_IF, ni); 176 s.joinGroup(new InetSocketAddress(group, 0), null); 177 testSendReceive(s, group); 178 s.leaveGroup(new InetSocketAddress(group, 0), null); 179 testSendNoReceive(s, group); 180 s.joinGroup(new InetSocketAddress(group, 0), ni); 181 testSendReceive(s, group); 182 s.leaveGroup(new InetSocketAddress(group, 0), ni); 183 testSendNoReceive(s, group); 184 185 // not a multicast address 186 var localHost = InetAddress.getLocalHost(); 187 assertThrows(SocketException.class, 188 () -> s.joinGroup(new InetSocketAddress(localHost, 0), null)); 189 assertThrows(SocketException.class, 190 () -> s.leaveGroup(new InetSocketAddress(localHost, 0), null)); 191 assertThrows(SocketException.class, 192 () -> s.joinGroup(new InetSocketAddress(localHost, 0), ni)); 193 assertThrows(SocketException.class, 194 () -> s.leaveGroup(new InetSocketAddress(localHost, 0), ni)); 195 196 // not an InetSocketAddress 197 var customSocketAddress = new SocketAddress() { }; 198 assertThrows(IllegalArgumentException.class, 199 () -> s.joinGroup(customSocketAddress, null)); 200 assertThrows(IllegalArgumentException.class, 201 () -> s.leaveGroup(customSocketAddress, null)); 202 assertThrows(IllegalArgumentException.class, 203 () -> s.joinGroup(customSocketAddress, ni)); 204 assertThrows(IllegalArgumentException.class, 205 () -> s.leaveGroup(customSocketAddress, ni)); 206 207 // IPv4 socket cannot join IPv6 group 208 if (family == INET && !IPSupport.hasIPv6()) { 209 System.out.println("Test IPv4 can't join IPv6"); 210 InetAddress ip6Group = InetAddress.getByName("ff02::a"); 211 assertThrows(IllegalArgumentException.class, 212 () -> s.joinGroup(new InetSocketAddress(ip6Group, 0), null)); 213 assertThrows(IllegalArgumentException.class, 214 () -> s.joinGroup(new InetSocketAddress(ip6Group, 0), ni)); 215 216 // not a member of IPv6 group (exception not specified) 217 assertThrows(SocketException.class, 218 () -> s.leaveGroup(new InetSocketAddress(ip6Group, 0), null)); 219 assertThrows(SocketException.class, 220 () -> s.leaveGroup(new InetSocketAddress(ip6Group, 0), ni)); 221 } 222 223 // null 224 assertThrows(IllegalArgumentException.class, () -> s.joinGroup(null, null)); 225 assertThrows(IllegalArgumentException.class, () -> s.leaveGroup(null, null)); 226 assertThrows(IllegalArgumentException.class, () -> s.joinGroup(null, ni)); 227 assertThrows(IllegalArgumentException.class, () -> s.leaveGroup(null, ni)); 228 } 229 230 /** 231 * Test getNetworkInterface/setNetworkInterface/getInterface/setInterface 232 * and IP_MULTICAST_IF socket option. 233 */ testNetworkInterface(DatagramSocket s, NetworkInterface ni)234 static void testNetworkInterface(DatagramSocket s, 235 NetworkInterface ni) throws IOException { 236 // default value 237 assertTrue(s.getOption(IP_MULTICAST_IF) == null); 238 239 // setOption(IP_MULTICAST_IF) 240 s.setOption(IP_MULTICAST_IF, ni); 241 assertTrue(isSameInterface(s.getOption(IP_MULTICAST_IF), ni)); 242 243 // bad values for IP_MULTICAST_IF 244 assertThrows(IllegalArgumentException.class, 245 () -> s.setOption(IP_MULTICAST_IF, null)); 246 assertThrows(IllegalArgumentException.class, 247 () -> s.setOption((SocketOption) IP_MULTICAST_IF, "badValue")); 248 } 249 250 /** 251 * Test getTimeToLive/setTimeToLive/getTTL/getTTL and IP_MULTICAST_TTL socket 252 * option. 253 */ testTimeToLive(DatagramSocket s)254 static void testTimeToLive(DatagramSocket s) throws IOException { 255 // should be 1 by default 256 assertTrue(s.getOption(IP_MULTICAST_TTL) == 1); 257 258 // setOption(IP_MULTICAST_TTL) 259 for (int ttl = 0; ttl <= 2; ttl++) { 260 s.setOption(IP_MULTICAST_TTL, ttl); 261 assertTrue(s.getOption(IP_MULTICAST_TTL) == ttl); 262 } 263 264 // bad values for IP_MULTICAST_TTL 265 assertThrows(IllegalArgumentException.class, 266 () -> s.setOption(IP_MULTICAST_TTL, -1)); 267 assertThrows(IllegalArgumentException.class, 268 () -> s.setOption(IP_MULTICAST_TTL, null)); 269 assertThrows(IllegalArgumentException.class, 270 () -> s.setOption((SocketOption) IP_MULTICAST_TTL, "badValue")); 271 } 272 273 /** 274 * Test getLoopbackMode/setLoopbackMode and IP_MULTICAST_LOOP socket option. 275 */ testLoopbackMode(DatagramSocket s)276 static void testLoopbackMode(DatagramSocket s) throws IOException { 277 // should be enabled by default 278 assertTrue(s.getOption(IP_MULTICAST_LOOP) == true); 279 280 // setLoopbackMode 281 282 // setOption(IP_MULTICAST_LOOP) 283 s.setOption(IP_MULTICAST_LOOP, false); // disable 284 assertTrue(s.getOption(IP_MULTICAST_LOOP) == false); 285 s.setOption(IP_MULTICAST_LOOP, true); // enable 286 assertTrue(s.getOption(IP_MULTICAST_LOOP) == true); 287 288 // bad values for IP_MULTICAST_LOOP 289 assertThrows(IllegalArgumentException.class, 290 () -> s.setOption(IP_MULTICAST_LOOP, null)); 291 assertThrows(IllegalArgumentException.class, 292 () -> s.setOption((SocketOption) IP_MULTICAST_LOOP, "badValue")); 293 } 294 295 /** 296 * Send a datagram to the given multicast group and check that it is received. 297 */ testSendReceive(DatagramSocket s, InetAddress group)298 static void testSendReceive(DatagramSocket s, InetAddress group) throws IOException { 299 300 System.out.println("testSendReceive"); 301 302 // outgoing multicast interface needs to be set 303 assertTrue(s.getOption(IP_MULTICAST_IF) != null); 304 305 SocketAddress target = new InetSocketAddress(group, s.getLocalPort()); 306 byte[] message = "hello".getBytes("UTF-8"); 307 308 // send message to multicast group 309 DatagramPacket p = new DatagramPacket(message, message.length); 310 p.setSocketAddress(target); 311 s.send(p); 312 313 // receive message 314 s.setSoTimeout(0); 315 p = new DatagramPacket(new byte[1024], 100); 316 s.receive(p); 317 318 assertTrue(p.getLength() == message.length); 319 assertTrue(p.getPort() == s.getLocalPort()); 320 } 321 322 /** 323 * Send a datagram to the given multicast group and check that it is not 324 * received. 325 */ testSendNoReceive(DatagramSocket s, InetAddress group)326 static void testSendNoReceive(DatagramSocket s, InetAddress group) throws IOException { 327 328 System.out.println("testSendNoReceive"); 329 330 // outgoing multicast interface needs to be set 331 assertTrue(s.getOption(IP_MULTICAST_IF) != null); 332 333 SocketAddress target = new InetSocketAddress(group, s.getLocalPort()); 334 long nano = System.nanoTime(); 335 String text = nano + ": hello"; 336 byte[] message = text.getBytes("UTF-8"); 337 338 // send datagram to multicast group 339 DatagramPacket p = new DatagramPacket(message, message.length); 340 p.setSocketAddress(target); 341 s.send(p); 342 343 // datagram should not be received 344 s.setSoTimeout(500); 345 p = new DatagramPacket(new byte[1024], 100); 346 while (true) { 347 try { 348 s.receive(p); 349 if (Arrays.equals(p.getData(), p.getOffset(), p.getLength(), message, 0, message.length)) { 350 throw new RuntimeException("message shouldn't have been received"); 351 } else { 352 System.out.format("Received unexpected message from %s%n", p.getSocketAddress()); 353 } 354 } catch (SocketTimeoutException expected) { 355 break; 356 } 357 } 358 } 359 360 assertTrue(boolean e)361 static void assertTrue(boolean e) { 362 if (!e) throw new RuntimeException(); 363 } 364 365 interface ThrowableRunnable { run()366 void run() throws Exception; 367 } 368 assertThrows(Class<?> exceptionClass, ThrowableRunnable task)369 static void assertThrows(Class<?> exceptionClass, ThrowableRunnable task) { 370 try { 371 task.run(); 372 throw new RuntimeException("Exception not thrown"); 373 } catch (Exception e) { 374 if (!exceptionClass.isInstance(e)) { 375 throw new RuntimeException("expected: " + exceptionClass + ", actual: " + e); 376 } 377 } 378 } 379 } 380