/* * Copyright (c) 2015, Red Hat Inc * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.Socket; import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import javax.management.remote.rmi.RMIConnectorServer; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.rmi.ssl.SslRMIClientSocketFactory; /** * Tests client connections to the JDK's built-in JMX agent server on the given * ports/interface combinations. * * @see JMXInterfaceBindingTest * * @author Severin Gehwolf * * Usage: * * SSL: * java -Dcom.sun.management.jmxremote.ssl.need.client.auth=true \ * -Dcom.sun.management.jmxremote.host=127.0.0.1 \ * -Dcom.sun.management.jmxremote.port=9111 \ * -Dcom.sun.management.jmxremote.rmi.port=9112 \ * -Dcom.sun.management.jmxremote.authenticate=false \ * -Dcom.sun.management.jmxremote.ssl=true \ * -Dcom.sun.management.jmxremote.registry.ssl=true * -Djavax.net.ssl.keyStore=... \ * -Djavax.net.ssl.keyStorePassword=... \ * JMXAgentInterfaceBinding 127.0.0.1 9111 9112 true * * Non-SSL: * java -Dcom.sun.management.jmxremote.host=127.0.0.1 \ * -Dcom.sun.management.jmxremote.port=9111 \ * -Dcom.sun.management.jmxremote.rmi.port=9112 \ * -Dcom.sun.management.jmxremote.authenticate=false \ * -Dcom.sun.management.jmxremote.ssl=false \ * JMXAgentInterfaceBinding 127.0.0.1 9111 9112 false * */ public class JMXAgentInterfaceBinding { private final MainThread mainThread; public JMXAgentInterfaceBinding(InetAddress bindAddress, int jmxPort, int rmiPort, boolean useSSL) { this.mainThread = new MainThread(bindAddress, jmxPort, rmiPort, useSSL); } public void startEndpoint() { mainThread.start(); try { mainThread.join(); } catch (InterruptedException e) { throw new RuntimeException("Test failed", e); } if (mainThread.isFailed()) { mainThread.rethrowException(); } } public static void main(String[] args) { if (args.length != 4) { throw new RuntimeException( "Test failed. usage: java JMXInterfaceBindingTest {true|false}"); } int jmxPort = parsePortFromString(args[1]); int rmiPort = parsePortFromString(args[2]); boolean useSSL = Boolean.parseBoolean(args[3]); String strBindAddr = args[0]; System.out.println( "DEBUG: Running test for triplet (hostname,jmxPort,rmiPort) = (" + strBindAddr + "," + jmxPort + "," + rmiPort + "), useSSL = " + useSSL); InetAddress bindAddress; try { bindAddress = InetAddress.getByName(args[0]); } catch (UnknownHostException e) { throw new RuntimeException("Test failed. Unknown ip: " + args[0]); } JMXAgentInterfaceBinding test = new JMXAgentInterfaceBinding(bindAddress, jmxPort, rmiPort, useSSL); test.startEndpoint(); // Expect for main test to terminate process } private static int parsePortFromString(String port) { try { return Integer.parseInt(port); } catch (NumberFormatException e) { throw new RuntimeException( "Invalid port specified. Not an integer! Value was: " + port); } } private static class JMXConnectorThread extends Thread { private final String addr; private final int jmxPort; private final int rmiPort; private final boolean useSSL; private final CountDownLatch latch; private boolean failed; private boolean jmxConnectWorked; private boolean rmiConnectWorked; private JMXConnectorThread(String addr, int jmxPort, int rmiPort, boolean useSSL, CountDownLatch latch) { this.addr = addr; this.jmxPort = jmxPort; this.rmiPort = rmiPort; this.latch = latch; this.useSSL = useSSL; } @Override public void run() { try { connect(); } catch (IOException e) { failed = true; } } private void connect() throws IOException { System.out.println( "JMXConnectorThread: Attempting JMX connection on: " + addr + " on port " + jmxPort); JMXServiceURL url; try { url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + addr + ":" + jmxPort + "/jmxrmi"); } catch (MalformedURLException e) { throw new RuntimeException("Test failed.", e); } Map env = new HashMap<>(); if (useSSL) { SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory(); env.put("com.sun.jndi.rmi.factory.socket", csf); env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf); } // connect and immediately close JMXConnector c = JMXConnectorFactory.connect(url, env); c.close(); System.out.println("JMXConnectorThread: connection to JMX worked"); jmxConnectWorked = true; checkRmiSocket(); latch.countDown(); // signal we are done. } private void checkRmiSocket() throws IOException { Socket rmiConnection; if (useSSL) { rmiConnection = SSLSocketFactory.getDefault().createSocket(); } else { rmiConnection = new Socket(); } SocketAddress target = new InetSocketAddress(addr, rmiPort); rmiConnection.connect(target); if (useSSL) { ((SSLSocket)rmiConnection).startHandshake(); } System.out.println( "JMXConnectorThread: connection to rmi socket worked host/port = " + addr + "/" + rmiPort); rmiConnectWorked = true; // Closing the channel without sending any data will cause an // java.io.EOFException on the server endpoint. We don't care about this // though, since we only want to test if we can connect. rmiConnection.close(); } public boolean isFailed() { return failed; } public boolean jmxConnectionWorked() { return jmxConnectWorked; } public boolean rmiConnectionWorked() { return rmiConnectWorked; } } private static class MainThread extends Thread { private static final int WAIT_FOR_JMX_AGENT_TIMEOUT_MS = 500; private final String addr; private final int jmxPort; private final int rmiPort; private final boolean useSSL; private boolean terminated = false; private boolean jmxAgentStarted = false; private Exception excptn; private MainThread(InetAddress bindAddress, int jmxPort, int rmiPort, boolean useSSL) { this.addr = wrapAddress(bindAddress.getHostAddress()); this.jmxPort = jmxPort; this.rmiPort = rmiPort; this.useSSL = useSSL; } @Override public void run() { try { waitUntilReadyForConnections(); // Do nothing, but wait for termination. try { while (!terminated) { Thread.sleep(100); } } catch (InterruptedException e) { // ignore } System.out.println("MainThread: Thread stopped."); } catch (Exception e) { this.excptn = e; } } private void waitUntilReadyForConnections() { CountDownLatch latch = new CountDownLatch(1); JMXConnectorThread connectionTester = new JMXConnectorThread( addr, jmxPort, rmiPort, useSSL, latch); connectionTester.start(); boolean expired = false; try { expired = !latch.await(WAIT_FOR_JMX_AGENT_TIMEOUT_MS, TimeUnit.MILLISECONDS); System.out.println( "MainThread: Finished waiting for JMX agent to become available: expired == " + expired); jmxAgentStarted = !expired; } catch (InterruptedException e) { throw new RuntimeException("Test failed", e); } if (!jmxAgentStarted) { throw new RuntimeException( "Test failed. JMX server agents not becoming available."); } if (connectionTester.isFailed() || !connectionTester.jmxConnectionWorked() || !connectionTester.rmiConnectionWorked()) { throw new RuntimeException( "Test failed. JMX agent does not seem ready. See log output for details."); } // The main test expects this exact message being printed System.out.println("MainThread: Ready for connections"); } private boolean isFailed() { return excptn != null; } private void rethrowException() throws RuntimeException { throw new RuntimeException(excptn); } } /** * Will wrap IPv6 address in '[]' */ static String wrapAddress(String address) { if (address.contains(":")) { return "[" + address + "]"; } return address; } }