1/*
2 * Copyright (c) 2007, 2012, 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 4527345 7026376 6633549
26 * @summary Unit test for DatagramChannel's multicast support
27 * @library /test/lib
28 * @build jdk.test.lib.NetworkConfiguration
29 *        jdk.test.lib.Platform
30 *        MulticastSendReceiveTests
31 * @run main MulticastSendReceiveTests
32 * @run main/othervm -Djava.net.preferIPv4Stack=true MulticastSendReceiveTests
33 * @key randomness
34 */
35
36import java.nio.ByteBuffer;
37import java.nio.channels.*;
38import java.net.*;
39import static java.net.StandardProtocolFamily.*;
40import java.util.*;
41import java.io.IOException;
42import java.util.stream.Collectors;
43
44import jdk.test.lib.NetworkConfiguration;
45
46public class MulticastSendReceiveTests {
47
48    static final Random rand = new Random();
49
50    static final ProtocolFamily UNSPEC = new ProtocolFamily() {
51        public String name() {
52            return "UNSPEC";
53        }
54    };
55
56    /**
57     * Send datagram from given local address to given multicast
58     * group.
59     */
60    static int sendDatagram(InetAddress local,
61                            NetworkInterface nif,
62                            InetAddress group,
63                            int port)
64        throws IOException
65    {
66        ProtocolFamily family = (group instanceof Inet6Address) ?
67            StandardProtocolFamily.INET6 : StandardProtocolFamily.INET;
68        DatagramChannel dc = DatagramChannel.open(family)
69            .bind(new InetSocketAddress(local, 0))
70            .setOption(StandardSocketOptions.IP_MULTICAST_IF, nif);
71        int id = rand.nextInt();
72        byte[] msg = Integer.toString(id).getBytes("UTF-8");
73        ByteBuffer buf = ByteBuffer.wrap(msg);
74        System.out.format("Send message from %s -> group %s (id=0x%x)\n",
75            local.getHostAddress(), group.getHostAddress(), id);
76        dc.send(buf, new InetSocketAddress(group, port));
77        dc.close();
78        return id;
79    }
80
81    /**
82     * Wait (with timeout) for datagram.
83     *
84     * @param   expectedSender - expected sender address, or
85     *                           null if no datagram expected
86     * @param   id - expected id of datagram
87     */
88    static void receiveDatagram(DatagramChannel dc,
89                                InetAddress expectedSender,
90                                int id)
91        throws IOException
92    {
93        Selector sel = Selector.open();
94        dc.configureBlocking(false);
95        dc.register(sel, SelectionKey.OP_READ);
96        ByteBuffer buf = ByteBuffer.allocateDirect(100);
97
98        try {
99            for (;;) {
100                System.out.println("Waiting to receive message");
101                sel.select(5*1000);
102                SocketAddress sa = dc.receive(buf);
103
104                // no datagram received
105                if (sa == null) {
106                    if (expectedSender != null) {
107                        throw new RuntimeException("Expected message not received");
108                    }
109                    System.out.println("No message received (correct)");
110                    return;
111                }
112
113                // datagram received
114
115                InetAddress sender = ((InetSocketAddress)sa).getAddress();
116                buf.flip();
117                byte[] bytes = new byte[buf.remaining()];
118                buf.get(bytes);
119                String s = new String(bytes, "UTF-8");
120                int receivedId = -1;
121                try {
122                    receivedId = Integer.parseInt(s);
123                    System.out.format("Received message from %s (id=0x%x)\n",
124                            sender, receivedId);
125                } catch (NumberFormatException x) {
126                    System.out.format("Received message from %s (msg=%s)\n", sender, s);
127                }
128
129                if (expectedSender == null) {
130                    if (receivedId == id)
131                        throw new RuntimeException("Message not expected");
132                    System.out.println("Message ignored (has wrong id)");
133                } else {
134                    if (sender.equals(expectedSender)) {
135                        System.out.println("Message expected");
136                        return;
137                    }
138                    System.out.println("Message ignored (wrong sender)");
139                }
140
141                sel.selectedKeys().clear();
142                buf.rewind();
143            }
144        } finally {
145            sel.close();
146        }
147    }
148
149
150    /**
151     * Exercise multicast send/receive on given group/interface
152     */
153    static void test(ProtocolFamily family,
154                     NetworkInterface nif,
155                     InetAddress group,
156                     InetAddress source)
157        throws IOException
158    {
159        System.out.format("\nTest DatagramChannel to %s socket\n", family.name());
160        try (DatagramChannel dc = (family == UNSPEC) ?
161                DatagramChannel.open() : DatagramChannel.open(family)) {
162            dc.setOption(StandardSocketOptions.SO_REUSEADDR, true)
163              .bind(new InetSocketAddress(0));
164
165            // join group
166            System.out.format("join %s @ %s\n", group.getHostAddress(),
167                nif.getName());
168            MembershipKey key;
169            try {
170                key = dc.join(group, nif);
171            } catch (IllegalArgumentException iae) {
172                if (family == UNSPEC) {
173                    System.out.println("Not supported");
174                    return;
175                }
176                throw iae;
177            }
178
179            // send message to group
180            int port = ((InetSocketAddress)dc.getLocalAddress()).getPort();
181            int id = sendDatagram(source, nif, group, port);
182
183            // receive message and check id matches
184            receiveDatagram(dc, source, id);
185
186            // exclude-mode filtering
187
188            try {
189                System.out.format("block %s\n", source.getHostAddress());
190
191                // may throw UOE
192                key.block(source);
193                id = sendDatagram(source, nif, group, port);
194                receiveDatagram(dc, null, id);
195
196                // unblock source, send message, message should be received
197                System.out.format("unblock %s\n", source.getHostAddress());
198                key.unblock(source);
199                id = sendDatagram(source, nif, group, port);
200                receiveDatagram(dc, source, id);
201            } catch (UnsupportedOperationException x) {
202                String os = System.getProperty("os.name");
203                // Exclude-mode filtering supported on these platforms so UOE should never be thrown
204                if (os.equals("SunOS") || os.equals("Linux"))
205                    throw x;
206                System.out.println("Exclude-mode filtering not supported!");
207            }
208
209            key.drop();
210
211            // include-mode filtering
212
213            InetAddress bogus = (group instanceof Inet6Address) ?
214                InetAddress.getByName("fe80::1234") :
215                InetAddress.getByName("1.2.3.4");
216            System.out.format("join %s @ %s only-source %s\n", group.getHostAddress(),
217                nif.getName(), bogus.getHostAddress());
218            try {
219                // may throw UOE
220                key = dc.join(group, nif, bogus);
221
222                id = sendDatagram(source, nif, group, port);
223                receiveDatagram(dc, null, id);
224
225                System.out.format("join %s @ %s only-source %s\n", group.getHostAddress(),
226                    nif.getName(), source.getHostAddress());
227                key = dc.join(group, nif, source);
228
229                id = sendDatagram(source, nif, group, port);
230                receiveDatagram(dc, source, id);
231            } catch (UnsupportedOperationException x) {
232                String os = System.getProperty("os.name");
233                // Include-mode filtering supported on these platforms so UOE should never be thrown
234                if (os.equals("SunOS") || os.equals("Linux"))
235                    throw x;
236                System.out.println("Include-mode filtering not supported!");
237            }
238        }
239    }
240
241    public static void main(String[] args) throws IOException {
242        NetworkConfiguration config = NetworkConfiguration.probe();
243
244        // multicast groups used for the test
245        InetAddress ip4Group = InetAddress.getByName("225.4.5.6");
246        InetAddress ip6Group = InetAddress.getByName("ff02::a");
247        for (NetworkInterface nif: config.ip4MulticastInterfaces()
248                                         .collect(Collectors.toList())) {
249            InetAddress source = config.ip4Addresses(nif).iterator().next();
250            test(INET,   nif, ip4Group, source);
251            test(UNSPEC, nif, ip4Group, source);
252        }
253
254        for (NetworkInterface nif: config.ip6MulticastInterfaces()
255                                         .collect(Collectors.toList())) {
256            InetAddress source = config.ip6Addresses(nif).iterator().next();
257            test(INET6,  nif, ip6Group, source);
258            test(UNSPEC, nif, ip6Group, source);
259        }
260    }
261}
262