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