Monday October 22, 2007 ![]() |
JMX, SNMP, Java, etc...Daniel Fuchs blogs on JMX, SNMP, Java, etc... |
This post explains how you can configure your Java application to export a single port using JMX RMI Connector Server over SSL. This is particularly useful when your application is located behind a firewall, because you will only need to let through a single port. However, using a single port when SSL is enabled requires a little care, because it can only work if the same RMI Socket Factories are used everywhere: indeed the same port cannot be shared by two different RMI Socket Factories. This post is built as a follow-up of some previous entries:
The code posted here combines these threes techniques, and shows the additional configuration needed to use RMI over SSL. Update: see also my post on Building a Remotely Stoppable Connector. How does it work?
The pre-main method in the
The pre-main method also creates a CleanThread daemon, which tries to detect
the end of the application by waiting for the termination of any non daemon
threads (except those that are kept alive by the presence of a started
JMX RMI Connector Server). The The important lines with regard to the SSL configuration are these ones:
// We create a couple of SslRMIClientSocketFactory and
// SslRMIServerSocketFactory. We will use the same factories to export
// the RMI Registry and the JMX RMI Connector Server objects. This
// will allow us to use the same port for all the exported objects.
// If we didn't use the same factories everywhere, we would have to
// use at least two ports, because two different RMI Socket Factories
// cannot share the same port.
//
final SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
final SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory();
// Create the RMI Registry using the SSL socket factories above.
// In order to use a single port, we must use these factories
// everywhere, or nowhere. Since we want to use them in the JMX
// RMI Connector server, we must also use them in the RMI Registry.
// Otherwise, we wouldn't be able to use a single port.
//
LocateRegistry.createRegistry(port, csf, ssf);
// Now specify the SSL Socket Factories - use the same factories
// everywhere!
//
// For the client side (remote)
//
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE,csf);
// For the server side (local)
//
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,ssf);
// For binding the JMX RMI Connector Server with the registry
// created above:
//
env.put("com.sun.jndi.rmi.factory.socket", csf);
Full Code Of The ExampleThe full code is here:
Creating a Premain Agent Jar
With the NetBeans IDE you
can the use the ant target shown here
in order to generate the
agent jar: don't forget to specify the
Starting your application
Since you are now using SSL, you will need a valid keystore and trustore.
The Monitoring and Management guide explain how to create them.
For the sake of the example, you might want to use the dummy keystore
and trustore provide in the JDK 6 JMX example - they are located under
When starting your application, you will need to add the following flags on the Java command line: # add these flags to load our pre-main agent and start the connector. -Dexample.rmi.agent.port=<port> -javaagent:<agent.jar> # add these flags to configure the default SSL keystore and truststore: -Djavax.net.ssl.keyStore=<keystore> \ -Djavax.net.ssl.keyStorePassword=<password> \ -Djavax.net.ssl.trustStore=<truststore> \ -Djavax.net.ssl.trustStorePassword=<trustword> If you don'have an application yet, the you could use this one:
public class Test {
public static void main(String[] args) throws Exception {
System.out.println("Strike Enter to exit:");
System.in.read();
}
}
Now you should be seing something like this (assuming port=5945):
Create RMI registry on port 5945
Get the platform's MBean server
Initialize the environment map
Create an RMI connector server
creating server with URL: service:jmx:rmi://host:5945/jndi/rmi://host:5945/jmxrmi
Start the RMI connector server on port 5945
Server started at: service:jmx:rmi://host:5945/jndi/rmi://host:5945/jmxrmi
Waiting on main [id=1]
How To Connect with JConsoleStart jconsole with the following flags:
jconsole -J-Djavax.net.ssl.keyStore=<keystore> \
-J-Djavax.net.ssl.keyStorePassword=<password> \
-J-Djavax.net.ssl.trustStore=<truststore> \
-J-Djavax.net.ssl.trustStorePassword=<trustword>
Then in the JConsole connection window select Remote Connection and
simply type If for some reason, JConsole does not connect, then try to enable
the JMX traces by adding a
Hope you enjoyed the tour! Posted by dfuchs ( Oct 22 2007, 07:38:51 PM CEST ) Permalink Comments [9] |
Thanks Daniel, great post!
I do have one issue with JConsole though: I keep getting the following exception:
java.io.IOException: Failed to retrieve RMIServer stub: javax.naming.CommunicationException [Root exception is java.rmi.ConnectIOException: non-JRMP server at remote endpoint]
After a lot of trial and error I was able to connect a custom client by adding the SSL client socket factory to the environment map with key "com.sun.jndi.rmi.factory.socket" (in the client code as well as in the server code). For example:
// begin client code
SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory();
HashMap<String, Object> env = new HashMap<String, Object>();
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf);
// Needed to avoid "non-JRMP server at remote endpoint" error
env.put("com.sun.jndi.rmi.factory.socket", csf);
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://localhost:5945/jndi/rmi://localhost:5945/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url, env);
System.out.println("Client connected ok to " + url);
Any idea how to avoid the "non-JRMP server at remote endpoint" error with JConsole?
--Remko
Posted by Remko Popma on January 05, 2008 at 03:29 PM CET #
Hi Remko,
You will need to use JConsole for JDK 6 for this: I believe JConsole depends on
http://bugs.sun.com/view_bug.do?bug_id=5107423
which was fixed in JDK 6. You do not need to specify the com.sun.jndi.rmi.factory.socket property with JConsole - but I believe JDK 6 JConsole use it internally when establishing the secure connection.
Let me know if it still doesn't work.
-- daniel
Posted by daniel on January 07, 2008 at 10:44 AM CET #
Hi Daniel, thanks for getting back to me.
I was using JDK 6, but no joy...
My thinking is that this is a bug in JConsole: it looks like sun.tools.jconsole.ProxyClient only uses LocateRegistry.getRegistry(host, port, sslRMIClientSocketFactory) when connecting to a "host:port" address, not when connecting to a "service:jmx:" URL.
When connecting to a "service:jmx:" URL, eventually JMXConnectorFactory.connect(jmxUrl, envMap) is called with a Map that only contains credentials, without any socket factories...
That would explain the "non-JRMP server at remote endpoint" exception I keep running into.
What do you think? Did you get JConsole to connect successfully to a SSL-protected registry using a "service:jmx:" URL?
Posted by Remko Popma on January 07, 2008 at 02:46 PM CET #
Hi Remko,
It does work for me. However you do have to use the proper jmx service URL on the server side (that is bind the RMIServer object to the name "jmxrmi") and you do have to use the short remote form <hostname>:<port> when connecting with JConsole.
If you use the long service:jmx:rmi:.... form it won't work.
JConsole will only do the appropriate magic if it thinks it is connecting to the out-of-the-box agent - that's why it only works if you simply give <hostname>:<port> in the remote connection field.
Also make sure that you do give all the appropriate -J-Djavax.net.ssl... (don't forget the -J) on the JConsole command line.
-- daniel
Posted by daniel on January 07, 2008 at 03:19 PM CET #
Hi Daniel,
I was able to connect successfully using the short <hostname>:<port> form. Thanks a lot for your help!
Best regards,
-Remko
Posted by Remko Popma on January 09, 2008 at 04:24 AM CET #
Hi Daniel,
Do you know if connecting through firewalls using RMI over SSL and a single port works with Java 5?
I keep getting this exception when starting the JMXConnectorServer (error happens on the server, not on the client):
Cannot bind to URL [rmi://myhost:4545/jmxrmi]: javax.naming.CommunicationException [Root exception is java.rmi.ConnectIOException: non-JRMP server at remote endpoint]
Your example works with Java 6, but I'm having trouble with Java 5... (Different server though, still trying stuff, but losing hope...)
Any ideas?
Best regards, -Remko
Posted by Remko Popma on June 04, 2008 at 08:31 AM CEST #
Is there a way to make this work under Java 5? I am getting the same error.
Posted by Dana P'Simer on July 28, 2008 at 09:45 PM CEST #
Hi guys,
For making this work on Java 5 please see here:
http://blogs.sun.com/jmxetc/entry/java_5_premain_rmi_connectors
Of course the simplest way would be to switch to Java 6 ;-)
Hope this helps,
-- daniel
Posted by daniel on July 29, 2008 at 02:42 PM CEST #
I have a few questions on JMX with SSL. I have to create client with 2 SSL connections to different hosts (with different truststore/keystore). I can't modify servers but I now that they use secure JMX and I nknow all credentials (keystore/trustore/uisername/passwords). How can I do it? Usage of System.setProperty(...) doesn't workk for the second connection. Unfortunately, creation of my own factory (jmx.remote.tls.socket.factory) doesn't connect even to one server. DOes anybody know how can I perform my task?
Thanks
Posted by Andrey on July 02, 2009 at 01:53 PM CEST #