// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.jmx; import java.net.ConnectException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Map; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import javax.rmi.ssl.SslRMIClientSocketFactory; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.After; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; /** * Running the tests of this class in the same JVM results often in * <pre> * Caused by: java.rmi.NoSuchObjectException: no such object in table * at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:276) * at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:253) * at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:379) * at sun.rmi.registry.RegistryImpl_Stub.bind(Unknown Source) * </pre> * Running each test method in a forked JVM makes these tests all pass, * therefore the issue is likely caused by use of stale stubs cached by the JDK. */ @Ignore public class ConnectorServerTest { private String objectName = "org.eclipse.jetty:name=rmiconnectorserver"; private ConnectorServer connectorServer; @After public void tearDown() throws Exception { if (connectorServer != null) connectorServer.stop(); } @Test public void testAddressAfterStart() throws Exception { connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi:///jmxrmi"), objectName); connectorServer.start(); JMXServiceURL address = connectorServer.getAddress(); Assert.assertTrue(address.toString().matches("service:jmx:rmi://[^:]+:\\d+/jndi/rmi://[^:]+:\\d+/jmxrmi")); } @Test public void testNoRegistryHostBindsToHost() throws Exception { connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi:///jmxrmi"), objectName); connectorServer.start(); // Verify that I can connect to the RMI registry using a non-loopback address. new Socket(InetAddress.getLocalHost(), 1099).close(); try { // Verify that I cannot connect to the RMI registry using the loopback address. new Socket(InetAddress.getLoopbackAddress(), 1099).close(); Assert.fail(); } catch (ConnectException ignored) { // Ignored. } } @Test public void testNoRegistryHostNonDefaultRegistryPort() throws Exception { ServerSocket serverSocket = new ServerSocket(0); int registryPort = serverSocket.getLocalPort(); serverSocket.close(); connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:" + registryPort + "/jmxrmi"), objectName); connectorServer.start(); // Verify that I can connect to the RMI registry using a non-loopback address. new Socket(InetAddress.getLocalHost(), registryPort).close(); try { // Verify that I cannot connect to the RMI registry using the loopback address. new Socket(InetAddress.getLoopbackAddress(), registryPort).close(); Assert.fail(); } catch (ConnectException ignored) { // Ignored. } } @Test public void testAnyRegistryHostBindsToAny() throws Exception { ServerSocket serverSocket = new ServerSocket(0); int registryPort = serverSocket.getLocalPort(); serverSocket.close(); connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi://0.0.0.0:" + registryPort + "/jmxrmi"), objectName); connectorServer.start(); // Verify that I can connect to the RMI registry using a non-loopback address. new Socket(InetAddress.getLocalHost(), registryPort).close(); // Verify that I can connect to the RMI registry using the loopback address. new Socket(InetAddress.getLoopbackAddress(), registryPort).close(); } @Test public void testLocalhostRegistryBindsToLoopback() throws Exception { connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"), objectName); connectorServer.start(); InetAddress localHost = InetAddress.getLocalHost(); if (!localHost.isLoopbackAddress()) { try { // Verify that I cannot connect to the RMIRegistry using a non-loopback address. new Socket(localHost, 1099); Assert.fail(); } catch (ConnectException ignored) { // Ignored. } } InetAddress loopback = InetAddress.getLoopbackAddress(); new Socket(loopback, 1099).close(); } @Test public void testNoRMIHostBindsToHost() throws Exception { connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi:///jmxrmi"), objectName); connectorServer.start(); // Verify that I can connect to the RMI server using a non-loopback address. new Socket(InetAddress.getLocalHost(), connectorServer.getAddress().getPort()).close(); try { // Verify that I cannot connect to the RMI server using the loopback address. new Socket(InetAddress.getLoopbackAddress(), connectorServer.getAddress().getPort()).close(); Assert.fail(); } catch (ConnectException ignored) { // Ignored. } } @Test public void testAnyRMIHostBindsToAny() throws Exception { connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi://0.0.0.0/jndi/rmi:///jmxrmi"), objectName); connectorServer.start(); // Verify that I can connect to the RMI server using a non-loopback address. new Socket(InetAddress.getLocalHost(), connectorServer.getAddress().getPort()).close(); // Verify that I can connect to the RMI server using the loopback address. new Socket(InetAddress.getLoopbackAddress(), connectorServer.getAddress().getPort()).close(); } @Test public void testLocalhostRMIBindsToLoopback() throws Exception { connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"), objectName); connectorServer.start(); JMXServiceURL address = connectorServer.getAddress(); InetAddress localHost = InetAddress.getLocalHost(); if (!localHost.isLoopbackAddress()) { try { // Verify that I cannot connect to the RMIRegistry using a non-loopback address. new Socket(localHost, address.getPort()); Assert.fail(); } catch (ConnectException ignored) { // Ignored. } } InetAddress loopback = InetAddress.getLoopbackAddress(); new Socket(loopback, address.getPort()).close(); } @Test public void testRMIServerPort() throws Exception { ServerSocket server = new ServerSocket(0); int port = server.getLocalPort(); server.close(); connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi://localhost:" + port + "/jndi/rmi:///jmxrmi"), objectName); connectorServer.start(); JMXServiceURL address = connectorServer.getAddress(); Assert.assertEquals(port, address.getPort()); InetAddress loopback = InetAddress.getLoopbackAddress(); new Socket(loopback, port).close(); } @Test public void testRMIServerAndRMIRegistryOnSameHostAndSamePort() throws Exception { // RMI can multiplex connections on the same address and port for different // RMI objects, in this case the RMI registry and the RMI server. In this // case, the RMIServerSocketFactory will be invoked only once. // The case with different address and same port is already covered by TCP, // that can listen to 192.168.0.1:1099 and 127.0.0.1:1099 without problems. String host = "localhost"; ServerSocket serverSocket = new ServerSocket(0); int port = serverSocket.getLocalPort(); serverSocket.close(); connectorServer = new ConnectorServer(new JMXServiceURL("rmi", host, port, "/jndi/rmi://" + host + ":" + port + "/jmxrmi"), objectName); connectorServer.start(); JMXServiceURL address = connectorServer.getAddress(); Assert.assertEquals(port, address.getPort()); } @Test public void testJMXOverTLS() throws Exception { SslContextFactory sslContextFactory = new SslContextFactory(); String keyStorePath = MavenTestingUtils.getTestResourcePath("keystore.jks").toString(); String keyStorePassword = "storepwd"; sslContextFactory.setKeyStorePath(keyStorePath); sslContextFactory.setKeyStorePassword(keyStorePassword); sslContextFactory.start(); // The RMIClientSocketFactory is stored within the RMI stub. // When using TLS, the stub is deserialized in a possibly different // JVM that does not have access to the server keystore, and there // is no way to provide TLS configuration during the deserialization // of the stub. Therefore the client must provide system properties // to specify the TLS configuration. For this test it needs the // trustStore because the server certificate is self-signed. // The server needs to contact the RMI registry and therefore also // needs these system properties. System.setProperty("javax.net.ssl.trustStore", keyStorePath); System.setProperty("javax.net.ssl.trustStorePassword", keyStorePassword); connectorServer = new ConnectorServer(new JMXServiceURL("rmi", null, 1100, "/jndi/rmi://localhost:1100/jmxrmi"), null, objectName, sslContextFactory); connectorServer.start(); // The client needs to talk TLS to the RMI registry to download // the RMI server stub, and this is independent from JMX. // The RMI server stub then contains the SslRMIClientSocketFactory // needed to talk to the RMI server. Map<String, Object> clientEnv = new HashMap<>(); clientEnv.put(ConnectorServer.RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE, new SslRMIClientSocketFactory()); try (JMXConnector client = JMXConnectorFactory.connect(connectorServer.getAddress(), clientEnv)) { client.getMBeanServerConnection().queryNames(null, null); } } }