| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package libcore.java.net; |
| |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.ConnectException; |
| import java.net.Inet4Address; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.net.Proxy; |
| import java.net.ProxySelector; |
| import java.net.ServerSocket; |
| import java.net.Socket; |
| import java.net.SocketAddress; |
| import java.net.SocketException; |
| import java.net.SocketImpl; |
| import java.net.URI; |
| import java.net.UnknownHostException; |
| import java.nio.channels.ServerSocketChannel; |
| import java.nio.channels.SocketChannel; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.TimeUnit; |
| import libcore.junit.junit3.TestCaseWithRules; |
| import libcore.junit.util.ResourceLeakageDetector; |
| import org.junit.Rule; |
| import org.junit.rules.TestRule; |
| |
| |
| public class SocketTest extends TestCaseWithRules { |
| @Rule |
| public TestRule resourceLeakageDetectorRule = ResourceLeakageDetector.getRule(); |
| |
| // This hostname is required to resolve to 127.0.0.1 and ::1 for all tests to pass. |
| private static final String ALL_LOOPBACK_HOSTNAME = "loopback46.unittest.grpc.io"; |
| |
| // From net/inet_ecn.h |
| private static final int INET_ECN_MASK = 0x3; |
| |
| // See http://b/2980559. |
| public void test_close() throws Exception { |
| Socket s = new Socket(); |
| s.close(); |
| // Closing a closed socket does nothing. |
| s.close(); |
| } |
| |
| /** |
| * Our getLocalAddress and getLocalPort currently use getsockname(3). |
| * This means they give incorrect results on closed sockets (as well |
| * as requiring an unnecessary call into native code). |
| */ |
| public void test_getLocalAddress_after_close() throws Exception { |
| Socket s = new Socket(); |
| try { |
| // Bind to an ephemeral local port. |
| s.bind(new InetSocketAddress("localhost", 0)); |
| assertTrue(s.getLocalAddress().toString(), s.getLocalAddress().isLoopbackAddress()); |
| // What local port did we get? |
| int localPort = s.getLocalPort(); |
| assertTrue(localPort > 0); |
| // Now close the socket... |
| s.close(); |
| // The RI returns the ANY address but the original local port after close. |
| assertTrue(s.getLocalAddress().isAnyLocalAddress()); |
| assertEquals(localPort, s.getLocalPort()); |
| } finally { |
| s.close(); |
| } |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=7935 |
| public void test_newSocket_connection_refused() throws Exception { |
| try { |
| new Socket("localhost", 80); |
| fail("connection should have been refused"); |
| } catch (ConnectException expected) { |
| } |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=3123 |
| // http://code.google.com/p/android/issues/detail?id=1933 |
| public void test_socketLocalAndRemoteAddresses() throws Exception { |
| checkSocketLocalAndRemoteAddresses(false); |
| checkSocketLocalAndRemoteAddresses(true); |
| } |
| |
| public void checkSocketLocalAndRemoteAddresses(boolean setOptions) throws Exception { |
| InetAddress host = InetAddress.getLocalHost(); |
| |
| // Open a local server port. |
| ServerSocketChannel ssc = ServerSocketChannel.open(); |
| InetSocketAddress listenAddr = new InetSocketAddress(host, 0); |
| ssc.socket().bind(listenAddr, 0); |
| ServerSocket ss = ssc.socket(); |
| |
| // Open a socket to the local port. |
| SocketChannel out = SocketChannel.open(); |
| out.configureBlocking(false); |
| if (setOptions) { |
| out.socket().setTcpNoDelay(false); |
| } |
| InetSocketAddress addr = new InetSocketAddress(host, ssc.socket().getLocalPort()); |
| out.connect(addr); |
| while (!out.finishConnect()) { |
| Thread.sleep(1); |
| } |
| |
| SocketChannel in = ssc.accept(); |
| if (setOptions) { |
| in.socket().setTcpNoDelay(false); |
| } |
| |
| InetSocketAddress listenAddress = (InetSocketAddress) in.socket().getLocalSocketAddress(); |
| InetSocketAddress outRemoteAddress = (InetSocketAddress) out.socket().getRemoteSocketAddress(); |
| InetSocketAddress outLocalAddress = (InetSocketAddress) out.socket().getLocalSocketAddress(); |
| InetSocketAddress inLocalAddress = (InetSocketAddress) in.socket().getLocalSocketAddress(); |
| InetSocketAddress inRemoteAddress = (InetSocketAddress) in.socket().getRemoteSocketAddress(); |
| System.err.println("listenAddress: " + listenAddr); |
| System.err.println("inLocalAddress: " + inLocalAddress); |
| System.err.println("inRemoteAddress: " + inRemoteAddress); |
| System.err.println("outLocalAddress: " + outLocalAddress); |
| System.err.println("outRemoteAddress: " + outRemoteAddress); |
| |
| assertEquals(outRemoteAddress.getPort(), ss.getLocalPort()); |
| assertEquals(inLocalAddress.getPort(), ss.getLocalPort()); |
| assertEquals(inRemoteAddress.getPort(), outLocalAddress.getPort()); |
| |
| assertEquals(inLocalAddress.getAddress(), ss.getInetAddress()); |
| assertEquals(inRemoteAddress.getAddress(), ss.getInetAddress()); |
| assertEquals(outLocalAddress.getAddress(), ss.getInetAddress()); |
| assertEquals(outRemoteAddress.getAddress(), ss.getInetAddress()); |
| |
| assertFalse(ssc.socket().isClosed()); |
| assertTrue(ssc.socket().isBound()); |
| assertTrue(in.isConnected()); |
| assertTrue(in.socket().isConnected()); |
| assertTrue(out.socket().isConnected()); |
| assertTrue(out.isConnected()); |
| |
| in.close(); |
| out.close(); |
| ssc.close(); |
| |
| assertTrue(ssc.socket().isClosed()); |
| assertTrue(ssc.socket().isBound()); |
| assertFalse(in.isConnected()); |
| assertFalse(in.socket().isConnected()); |
| assertFalse(out.socket().isConnected()); |
| assertFalse(out.isConnected()); |
| |
| assertNull(in.socket().getRemoteSocketAddress()); |
| assertNull(out.socket().getRemoteSocketAddress()); |
| |
| // As per docs and RI - server socket local address methods continue to return the bind() |
| // addresses even after close(). |
| assertEquals(listenAddress, ssc.socket().getLocalSocketAddress()); |
| |
| // As per docs and RI - socket local address methods return the wildcard address before |
| // bind() and after close(), but the port will be the same as it was before close(). |
| InetSocketAddress inLocalAddressAfterClose = |
| (InetSocketAddress) in.socket().getLocalSocketAddress(); |
| assertTrue(inLocalAddressAfterClose.getAddress().isAnyLocalAddress()); |
| assertEquals(inLocalAddress.getPort(), inLocalAddressAfterClose.getPort()); |
| |
| InetSocketAddress outLocalAddressAfterClose = |
| (InetSocketAddress) out.socket().getLocalSocketAddress(); |
| assertTrue(outLocalAddressAfterClose.getAddress().isAnyLocalAddress()); |
| assertEquals(outLocalAddress.getPort(), outLocalAddressAfterClose.getPort()); |
| } |
| |
| // SocketOptions.setOption has weird behavior for setSoLinger/SO_LINGER. |
| // This test ensures we do what the RI does. |
| public void test_SocketOptions_setOption() throws Exception { |
| class MySocketImpl extends SocketImpl { |
| public int option; |
| public Object value; |
| |
| public boolean createCalled; |
| public boolean createStream; |
| |
| public MySocketImpl() { super(); } |
| @Override protected void accept(SocketImpl arg0) throws IOException { } |
| @Override protected int available() throws IOException { return 0; } |
| @Override protected void bind(InetAddress arg0, int arg1) throws IOException { } |
| @Override protected void close() throws IOException { } |
| @Override protected void connect(String arg0, int arg1) throws IOException { } |
| @Override protected void connect(InetAddress arg0, int arg1) throws IOException { } |
| @Override protected void connect(SocketAddress arg0, int arg1) throws IOException { } |
| @Override protected InputStream getInputStream() throws IOException { return null; } |
| @Override protected OutputStream getOutputStream() throws IOException { return null; } |
| @Override protected void listen(int arg0) throws IOException { } |
| @Override protected void sendUrgentData(int arg0) throws IOException { } |
| public Object getOption(int arg0) throws SocketException { return null; } |
| |
| @Override protected void create(boolean isStream) throws IOException { |
| this.createCalled = true; |
| this.createStream = isStream; |
| } |
| |
| public void setOption(int option, Object value) throws SocketException { |
| this.option = option; |
| this.value = value; |
| } |
| } |
| |
| class MySocket extends Socket { |
| public MySocket(SocketImpl impl) throws SocketException { |
| super(impl); |
| } |
| } |
| |
| MySocketImpl impl = new MySocketImpl(); |
| Socket s = new MySocket(impl); |
| |
| // Check that, as per the SocketOptions.setOption documentation, we pass false rather |
| // than -1 to the SocketImpl when setSoLinger is called with the first argument false. |
| s.setSoLinger(false, -1); |
| assertEquals(Boolean.FALSE, (Boolean) impl.value); |
| // We also check that SocketImpl.create was called. SocketChannelImpl.SocketAdapter |
| // subclasses Socket, and whether or not to call SocketImpl.create is the main behavioral |
| // difference. |
| assertEquals(true, impl.createCalled); |
| s.setSoLinger(false, 0); |
| assertEquals(Boolean.FALSE, (Boolean) impl.value); |
| s.setSoLinger(false, 1); |
| assertEquals(Boolean.FALSE, (Boolean) impl.value); |
| |
| // Check that otherwise, we pass down an Integer. |
| s.setSoLinger(true, 0); |
| assertEquals(Integer.valueOf(0), (Integer) impl.value); |
| s.setSoLinger(true, 1); |
| assertEquals(Integer.valueOf(1), (Integer) impl.value); |
| } |
| |
| public void test_setTrafficClass() throws Exception { |
| try (Socket s = new Socket()) { |
| for (int i = 0; i <= 255; ++i) { |
| s.setTrafficClass(i); |
| |
| // b/30909505 |
| // Linux does not set ECN bits for IP_TOS, but sets for IPV6_TCLASS. We should |
| // accept either output. |
| int actual = s.getTrafficClass(); |
| assertTrue(i == actual || // IPV6_TCLASS |
| (actual == (i & ~INET_ECN_MASK))); // IP_TOS: ECN bits should be 0 |
| } |
| } |
| } |
| |
| public void testReadAfterClose() throws Exception { |
| MockServer server = new MockServer(); |
| server.enqueue(new byte[]{5, 3}, 0); |
| Socket socket = new Socket("localhost", server.port); |
| InputStream in = socket.getInputStream(); |
| assertEquals(5, in.read()); |
| assertEquals(3, in.read()); |
| assertEquals(-1, in.read()); |
| assertEquals(-1, in.read()); |
| socket.close(); |
| in.close(); |
| |
| /* |
| * Rather astonishingly, read() doesn't throw even though the stream is |
| * closed. This is consistent with the RI's behavior. |
| */ |
| assertEquals(-1, in.read()); |
| assertEquals(-1, in.read()); |
| |
| server.shutdown(); |
| } |
| |
| public void testWriteAfterClose() throws Exception { |
| MockServer server = new MockServer(); |
| server.enqueue(new byte[0], 3); |
| Socket socket = new Socket("localhost", server.port); |
| OutputStream out = socket.getOutputStream(); |
| out.write(5); |
| out.write(3); |
| socket.close(); |
| out.close(); |
| |
| try { |
| out.write(9); |
| fail(); |
| } catch (IOException expected) { |
| } |
| |
| server.shutdown(); |
| } |
| |
| // http://b/5534202 |
| public void testAvailable() throws Exception { |
| for (int i = 0; i < 100; i++) { |
| assertAvailableReturnsZeroAfterSocketReadsAllData(); |
| System.out.println("Success on rep " + i); |
| } |
| } |
| |
| private void assertAvailableReturnsZeroAfterSocketReadsAllData() throws Exception { |
| final byte[] data = "foo".getBytes(); |
| final ServerSocket serverSocket = new ServerSocket(0); |
| |
| new Thread() { |
| @Override public void run() { |
| try { |
| Socket socket = serverSocket.accept(); |
| socket.getOutputStream().write(data); |
| socket.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| }.start(); |
| |
| Socket socket = new Socket("localhost", serverSocket.getLocalPort()); |
| byte[] readBuffer = new byte[128]; |
| InputStream in = socket.getInputStream(); |
| int total = 0; |
| // to prevent available() from cheating after EOF, stop reading before -1 is returned |
| while (total < data.length) { |
| total += in.read(readBuffer); |
| } |
| assertEquals(0, in.available()); |
| |
| socket.close(); |
| serverSocket.close(); |
| } |
| |
| public void testInitialState() throws Exception { |
| Socket s = new Socket(); |
| try { |
| assertFalse(s.isBound()); |
| assertFalse(s.isClosed()); |
| assertFalse(s.isConnected()); |
| assertEquals(-1, s.getLocalPort()); |
| assertTrue(s.getLocalAddress().isAnyLocalAddress()); |
| assertNull(s.getLocalSocketAddress()); |
| assertNull(s.getInetAddress()); |
| assertEquals(0, s.getPort()); |
| assertNull(s.getRemoteSocketAddress()); |
| assertFalse(s.getReuseAddress()); |
| assertNull(s.getChannel()); |
| } finally { |
| s.close(); |
| } |
| } |
| |
| public void testStateAfterClose() throws Exception { |
| Socket s = new Socket(); |
| s.bind(new InetSocketAddress(Inet4Address.getLocalHost(), 0)); |
| InetSocketAddress boundAddress = (InetSocketAddress) s.getLocalSocketAddress(); |
| s.close(); |
| |
| assertTrue(s.isBound()); |
| assertTrue(s.isClosed()); |
| assertFalse(s.isConnected()); |
| assertTrue(s.getLocalAddress().isAnyLocalAddress()); |
| assertEquals(boundAddress.getPort(), s.getLocalPort()); |
| |
| InetSocketAddress localAddressAfterClose = (InetSocketAddress) s.getLocalSocketAddress(); |
| assertTrue(localAddressAfterClose.getAddress().isAnyLocalAddress()); |
| assertEquals(boundAddress.getPort(), localAddressAfterClose.getPort()); |
| } |
| |
| public void testCloseDuringConnect() throws Exception { |
| // This address is reserved for documentation: should never be reachable and therefore |
| // is expected to produce block behavior when attempting to connect(). |
| final InetSocketAddress unreachableIp = new InetSocketAddress("192.0.2.0", 80); |
| |
| final Socket s = new Socket(); |
| |
| // A Callable that executes a connect() that should block and ultimately throw an exception. |
| // Inverts usual behavior for code: expected to *return* a throwable for analysis if one is |
| // thrown, but throws an Error if no exception is thrown. |
| Callable<Throwable> connectWorker = () -> { |
| try { |
| // This method should not return naturally. |
| s.connect(unreachableIp, 0 /* infinite */); |
| throw new AssertionError( |
| "connect() to address(" + unreachableIp + ") did not block as required"); |
| } catch (Exception exception) { |
| // Return the exception so that it can be inspected. |
| return exception; |
| } |
| }; |
| Future<Throwable> connectResult = |
| Executors.newSingleThreadScheduledExecutor().submit(connectWorker); |
| |
| // Wait sufficient time for the connectWorker thread to run and start connect(). |
| Thread.sleep(2000); |
| |
| // Check for unexpected early termination. We require an environment where connect() will |
| // block forever with the specified IP and we can fail early if we detect the block hasn't |
| // happened. |
| if (connectResult.isDone()) { |
| // We expect an ExecutionError here. If not something has gone wrong with the test |
| // logic. |
| Throwable error = connectResult.get(); |
| throw new AssertionError("Unexpected result from connectWorker", error); |
| } |
| |
| // Close the socket that connectWorker should currently be blocked in connect(). |
| s.close(); |
| |
| // connectWorker should unblock so get() should obtain the exception that we expect to be |
| // thrown. |
| Throwable result = connectResult.get(2000, TimeUnit.MILLISECONDS); |
| if (result instanceof SocketException) { |
| if (result.getMessage().contains("Socket closed")) { |
| // This is the only case we accept. |
| return; |
| } |
| throw new AssertionError( |
| "Unexpected SocketException message: " + result.getMessage(), result); |
| } else { |
| throw new AssertionError("Unexpected exception encountered", result); |
| } |
| } |
| |
| // http://b/29092095 |
| public void testSocketWithProxySet() throws Exception { |
| ProxySelector ps = ProxySelector.getDefault(); |
| try { |
| ProxySelector.setDefault(new ProxySelector() { |
| @Override |
| public List<Proxy> select(URI uri) { |
| fail("ProxySelector#select was called"); |
| return null; // unreachable. |
| } |
| |
| @Override |
| public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { |
| fail("ProxySelector#connectFail was called"); |
| } |
| }); |
| |
| ServerSocket server = new ServerSocket(0); |
| Socket client = new Socket(InetAddress.getLocalHost(), server.getLocalPort()); |
| client.close(); |
| server.close(); |
| } finally { |
| ProxySelector.setDefault(ps); |
| } |
| } |
| |
| |
| // b/25805791 + b/26470377 |
| public void testFileDescriptorStaysSame() throws Exception { |
| // SocketImplementation FileDescriptor object shouldn't change after calling |
| // bind (and many other methods). |
| Socket s = new Socket(); |
| |
| // There's an assumption that newly created |
| // socket has a non-null file-descriptor (b/26169052, b/26084000) |
| FileDescriptor fd1 = s.getFileDescriptor$(); |
| assertNotNull(fd1); |
| int fd1Val = fd1.getInt$(); |
| assertEquals(-1, fd1Val); |
| |
| s.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0)); |
| FileDescriptor fd2 = s.getFileDescriptor$(); |
| assertSame(fd1, fd2); |
| int fd2Val = fd2.getInt$(); |
| assertTrue(fd1Val != fd2Val); // The actual fd should be different |
| |
| s.close(); |
| FileDescriptor fd3 = s.getFileDescriptor$(); |
| assertSame(fd1, fd3); |
| assertFalse(fd3.valid()); |
| } |
| |
| static class MockServer { |
| private ExecutorService executor; |
| private ServerSocket serverSocket; |
| private int port = -1; |
| |
| MockServer() throws IOException { |
| executor = Executors.newCachedThreadPool(); |
| serverSocket = new ServerSocket(0); |
| serverSocket.setReuseAddress(true); |
| port = serverSocket.getLocalPort(); |
| } |
| |
| public Future<byte[]> enqueue(final byte[] sendBytes, final int receiveByteCount) |
| throws IOException { |
| return executor.submit(new Callable<byte[]>() { |
| @Override public byte[] call() throws Exception { |
| Socket socket = serverSocket.accept(); |
| OutputStream out = socket.getOutputStream(); |
| out.write(sendBytes); |
| |
| InputStream in = socket.getInputStream(); |
| byte[] result = new byte[receiveByteCount]; |
| int total = 0; |
| while (total < receiveByteCount) { |
| total += in.read(result, total, result.length - total); |
| } |
| socket.close(); |
| return result; |
| } |
| }); |
| } |
| |
| public void shutdown() throws IOException { |
| serverSocket.close(); |
| executor.shutdown(); |
| } |
| } |
| |
| // b/26354315 |
| public void testDoNotCallCloseFromSocketCtor() { |
| // Original openJdk7 Socket implementation may call Socket#close() inside a constructor. |
| // In this case, classes that extend Socket wont be fully constructed when they |
| // receive #close() call. This test makes sure this won't happen |
| |
| // Extend Socket |
| class SocketThatFailOnClose extends Socket { |
| public SocketThatFailOnClose(String host, int port) |
| throws UnknownHostException, IOException { |
| super(host, port); |
| } |
| public SocketThatFailOnClose(InetAddress address, int port) throws IOException { |
| super(address, port); |
| } |
| public SocketThatFailOnClose(String host, int port, InetAddress localAddr, |
| int localPort) throws IOException { |
| super(host, port, localAddr, localPort); |
| } |
| public SocketThatFailOnClose(InetAddress address, int port, InetAddress localAddr, |
| int localPort) throws IOException { |
| super(address, port, localAddr, localPort); |
| } |
| public SocketThatFailOnClose(String host, int port, boolean stream) throws IOException { |
| super(host, port, stream); |
| } |
| public SocketThatFailOnClose(InetAddress host, int port, boolean stream) |
| throws IOException { |
| super(host, port, stream); |
| } |
| |
| @Override |
| public void close() { |
| fail("Do not call close from the Socket constructor"); |
| } |
| } |
| |
| // Test all Socket ctors |
| try { |
| new SocketThatFailOnClose("localhost", 1); |
| fail(); |
| } catch(IOException expected) {} |
| try { |
| new SocketThatFailOnClose(InetAddress.getLocalHost(), 1); |
| fail(); |
| } catch(IOException expected) {} |
| try { |
| new SocketThatFailOnClose("localhost", 1, null, 0); |
| fail(); |
| } catch(IOException expected) {} |
| try { |
| new SocketThatFailOnClose(InetAddress.getLocalHost(), 1, null, 0); |
| fail(); |
| } catch(IOException expected) {} |
| try { |
| new SocketThatFailOnClose("localhost", 1, true); |
| fail(); |
| } catch(IOException expected) {} |
| try { |
| new SocketThatFailOnClose(InetAddress.getLocalHost(), 1, true); |
| fail(); |
| } catch(IOException expected) {} |
| } |
| |
| // b/30007735 |
| public void testSocketTestAllAddresses() throws Exception { |
| // Socket Ctor should try all sockets. |
| // |
| // This test creates a server socket bound to 127.0.0.1 or ::1 only, and connects using a |
| // hostname that resolves to both addresses. We should be able to connect to the server |
| // socket in either setup. |
| final String loopbackHost = ALL_LOOPBACK_HOSTNAME; |
| |
| assertTrue("Loopback DNS record is unreachable or is invalid.", checkLoopbackHost( |
| loopbackHost)); |
| |
| final int port = 9999; |
| for (InetAddress addr : new InetAddress[]{ Inet4Address.LOOPBACK, Inet6Address.LOOPBACK }) { |
| try (ServerSocket ss = new ServerSocket(port, 0, addr)) { |
| new Thread(() -> { |
| try { |
| ss.accept(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| }).start(); |
| |
| assertTrue(canConnect(loopbackHost, port)); |
| } |
| } |
| } |
| |
| /** Confirm the supplied hostname maps to only loopback addresses. */ |
| private static boolean checkLoopbackHost(String host) { |
| try { |
| List<InetAddress> addrs = Arrays.asList(InetAddress.getAllByName(host)); |
| return addrs.stream().allMatch(InetAddress::isLoopbackAddress) && |
| addrs.contains(Inet4Address.LOOPBACK) && addrs.contains(Inet6Address.LOOPBACK); |
| } catch (UnknownHostException e) { |
| return false; |
| } |
| } |
| |
| private static boolean canConnect(String host, int port) { |
| try(Socket sock = new Socket(host, port)) { |
| return sock.isConnected(); |
| } catch (IOException e) { |
| return false; |
| } |
| } |
| } |