Sunday 10 October 2010

JMX over NAT with the RMI connector


Connecting to an RMI server that's behind a NAT router seems to be a common problem - it's definitely one I have had. I am not going to try and explain NAT here - that is already done well elsewhere, but what is important is to remember that NAT rewrites addresses (and often ports) in IP packets.

JMX is commonly used with the RMI connector, and a quick search reveals that the problems with connecting with RMI over NAT are fairly well-known, if not necessarily well-understood. The problem is that when we export a remote object, the stub embeds its local IP address - and this is different from the IP address in the packet after it has been rewritten by the NAT router. So the client is able to download the stub, but cannot then use it to connect back to the skeleton/server, as the embedded IP address is not visible outside of the server's local network!


We had a similar problem when using IPSec to secure IP traffic - and in my experience, the solution I am going to describe applies in both cases.


Note that the server may not actually be a "real" server - if it is, there's a potentially easy solution; just set the java.rmi.server.hostname system property to the externally visible IP address (or host name). This is then embedded into the stub, so you must set this property before exporting the remote object. 

What however, if you don't know what to set the java.rmi.server.hostname property to? This is more common than you might think, especially if you are using RMI to connect to non-server type machines. I found that the key to solving this problem was the realisation that we need to get the client to somehow override the (incorrect) IP address embedded in the RMI stub. 

Enter the RMIClientSocketFactory - a way to customise the socket that the stub uses to connect back to the skeleton/server. In a couple of dozen lines of code, we can have our own implementation that allows the client to specify the IP address that the stub will use:

You'll want to make this snippet production-worthy before you use it in your own code, fleshing out the RMIClientSocketFactory delegate, and setting appropriate timeouts on any socket you create. Sun's documentation also recommends ensuring appropriate implementations of equals() and hashCode() methods. You can then register this while exporting your remote object and use it from your RMI client like:



I know it may seem obvious, but an important limitation of this solution is that you have to invoke methods on your RMI stub in the same thread in which you've initialised the IP address (and possibly port).

1 comment:

  1. RMI is and always has been the enemy for any 'real-world' network topology (firewalls, NAT). Thanks a lot for this solution, it is a clever hack around some limitations (but I wonder from where you get the 'real' target IP and port ?). And didn't you have to open a whole range of ports in your firewall in order to let RMI communication happen ?

    For a more firewall/nat friendly way for JMX remote communication you might want to have a look at Jolokia (www.jolokia.org), which uses JSON over HTTP as protocol stack. Sorry for the plug, but I think this approach is really superior when it comes to advanced network setups (although it has not such a nice programming model as JSR-160 since it is typeless by nature and doesn't use stubs for transparent remoting).

    ...roland

    ReplyDelete