1 /*
2  * Copyright (c) 2019, 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 8231880 8231258
26  * @library /test/lib
27  * @summary Test DatagramChannel bound to specific address/ephemeral port after disconnect
28  * @run testng/othervm AfterDisconnect
29  * @run testng/othervm -Djava.net.preferIPv4Stack=true AfterDisconnect
30  * @run testng/othervm -Djava.net.preferIPv6Addresses=true AfterDisconnect
31  */
32 
33 import java.io.IOException;
34 import java.net.InetAddress;
35 import java.net.Inet6Address;
36 import java.net.InetSocketAddress;
37 import java.net.NetworkInterface;
38 import java.net.SocketAddress;
39 import java.net.SocketOption;
40 import java.net.StandardSocketOptions;
41 import java.net.StandardProtocolFamily;
42 import java.nio.ByteBuffer;
43 import java.nio.channels.DatagramChannel;
44 import java.nio.channels.MembershipKey;
45 import java.nio.channels.SelectionKey;
46 import java.nio.channels.Selector;
47 import java.util.HashMap;
48 import java.util.Map;
49 
50 import org.testng.annotations.Test;
51 import static org.testng.Assert.*;
52 
53 import jdk.test.lib.net.IPSupport;
54 
55 public class AfterDisconnect {
56 
57     @Test
execute()58     public void execute() throws IOException {
59         IPSupport.throwSkippedExceptionIfNonOperational();
60         boolean preferIPv6 = Boolean.getBoolean("java.net.preferIPv6Addresses");
61         InetAddress lb = InetAddress.getLoopbackAddress();
62 
63         // test with default protocol family
64         try (DatagramChannel dc = DatagramChannel.open()) {
65             System.out.println("Test with default");
66             dc.bind(new InetSocketAddress(lb, 0));
67             test(dc);
68             test(dc);
69         }
70 
71         // test with IPv6 socket
72         if (IPSupport.hasIPv6()) {
73             System.out.println("Test with IPv6 socket");
74             try (DatagramChannel dc = DatagramChannel.open(StandardProtocolFamily.INET6)) {
75                 dc.bind(new InetSocketAddress(lb, 0));
76                 test(dc);
77                 test(dc);
78             }
79         }
80 
81         // test with IPv4 socket
82         if (IPSupport.hasIPv4() && !preferIPv6) {
83             System.out.println("Test with IPv4 socket");
84             try (DatagramChannel dc = DatagramChannel.open(StandardProtocolFamily.INET)) {
85                 dc.bind(new InetSocketAddress(lb, 0));
86                 test(dc);
87                 test(dc);
88             }
89         }
90     }
91 
test(DatagramChannel dc)92     void test(DatagramChannel dc) throws IOException {
93         testLocalAddress(dc);
94         testSocketOptions(dc);
95         testSelectorRegistration(dc);
96         testMulticastGroups(dc);
97     }
98 
99     /**
100      * Test that disconnect restores local address
101      */
testLocalAddress(DatagramChannel dc)102     void testLocalAddress(DatagramChannel dc) throws IOException {
103         try (DatagramChannel server = DatagramChannel.open()) {
104             server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
105 
106             SocketAddress local = dc.getLocalAddress();
107             SocketAddress remote = server.getLocalAddress();
108 
109             dc.connect(remote);
110             assertTrue(dc.isConnected());
111             assertEquals(dc.getLocalAddress(), local);
112             assertEquals(dc.getRemoteAddress(), remote);
113 
114             dc.disconnect();
115             assertFalse(dc.isConnected());
116             assertEquals(dc.getLocalAddress(), local);
117             assertTrue(dc.getRemoteAddress() == null);
118         }
119     }
120 
121     /**
122      * Test that disconnect does not change socket options
123      */
testSocketOptions(DatagramChannel dc)124     void testSocketOptions(DatagramChannel dc) throws IOException {
125         // set a few socket options
126         dc.setOption(StandardSocketOptions.SO_SNDBUF, 32*1024);
127         dc.setOption(StandardSocketOptions.SO_RCVBUF, 64*1024);
128         InetAddress ia = dc.socket().getLocalAddress();
129         NetworkInterface ni = NetworkInterface.getByInetAddress(ia);
130         if (ni != null && ni.supportsMulticast())
131             dc.setOption(StandardSocketOptions.IP_MULTICAST_IF, ni);
132 
133         // capture values of socket options
134         Map<SocketOption<?>, Object> map = options(dc);
135 
136         dc.connect(dc.getLocalAddress());
137         dc.disconnect();
138 
139         // check socket options have not changed
140         assertEquals(map, options(dc));
141     }
142 
143     /**
144      * Returns a map of the given channel's socket options and values.
145      */
options(DatagramChannel dc)146     private Map<SocketOption<?>, Object> options(DatagramChannel dc) throws IOException {
147         Map<SocketOption<?>, Object> map = new HashMap<>();
148         for (SocketOption<?> option : dc.supportedOptions()) {
149             try {
150                 Object value = dc.getOption(option);
151                 if (value != null) {
152                     map.put(option, value);
153                 }
154             } catch (IOException ignore) { }
155         }
156         return map;
157     }
158 
159     /**
160      * Test that disconnect does not interfere with Selector registrations
161      */
testSelectorRegistration(DatagramChannel dc)162     void testSelectorRegistration(DatagramChannel dc) throws IOException {
163         try (Selector sel = Selector.open()) {
164             dc.configureBlocking(false);
165             SelectionKey key = dc.register(sel, SelectionKey.OP_READ);
166 
167             // ensure socket is registered
168             sel.selectNow();
169 
170             dc.connect(dc.getLocalAddress());
171             dc.disconnect();
172 
173             // selection key should still be valid
174             assertTrue(key.isValid());
175 
176             // check blocking mode with non-blocking receive
177             ByteBuffer bb = ByteBuffer.allocate(100);
178             SocketAddress sender = dc.receive(bb);
179             assertTrue(sender == null);
180 
181             // send datagram and ensure that channel is selected
182             dc.send(ByteBuffer.wrap("Hello".getBytes("UTF-8")), dc.getLocalAddress());
183             assertFalse(key.isReadable());
184             while (sel.select() == 0);
185             assertTrue(key.isReadable());
186             sender = dc.receive(bb);
187             assertEquals(sender, dc.getLocalAddress());
188 
189             // cancel key, flush from Selector, and restore blocking mode
190             key.cancel();
191             sel.selectNow();
192             dc.configureBlocking(true);
193         }
194     }
195 
196     /**
197      * Test that disconnect does not interfere with multicast group membership
198      */
testMulticastGroups(DatagramChannel dc)199     void testMulticastGroups(DatagramChannel dc) throws IOException {
200         InetAddress localAddress = dc.socket().getLocalAddress();
201         InetAddress group;
202         if (localAddress instanceof Inet6Address) {
203             group = InetAddress.getByName("ff02::a");
204         } else {
205             group = InetAddress.getByName("225.4.5.6");
206         }
207         NetworkInterface ni = NetworkInterface.getByInetAddress(localAddress);
208         if (ni != null && ni.supportsMulticast()) {
209             // join group
210             MembershipKey key = dc.join(group, ni);
211 
212             dc.connect(dc.getLocalAddress());
213             dc.disconnect();
214 
215             // membership key should still be valid
216             assertTrue(key.isValid());
217 
218             // send datagram to multicast group, should be received
219             dc.send(ByteBuffer.wrap("Hello".getBytes("UTF-8")), dc.getLocalAddress());
220             ByteBuffer bb = ByteBuffer.allocate(100);
221             SocketAddress sender = dc.receive(bb);
222             assertEquals(sender, dc.getLocalAddress());
223 
224             // drop membership
225             key.drop();
226         }
227     }
228 }
229