Channels ▼
RSS

Parallel

Punching Holes with Java RMI


The authors are computer scientists at Konstanz University of Applied Sciences and can be reached at haase@htwg-konstanz.de, dmaier@fh-konstanz.de, and juergen.waesch@htwg-konstanz.de, respectively.


Java Remote Method Invocation (RMI) lets developers use the method invocation paradigm to realize communication between remote servers and clients. If the distribution model of an application, however, spans multiple administrative domains, communication needs to be able to traverse firewalls and network address translation (NAT) boxes.

By default, the Java remote method protocol (JRMP) uses HTTP tunneling to deal with these issues; more specifically, if: (1) A direct TCP connection between client and server cannot be set up, then (2) Java RMI encapsulates the JRMP messages in HTTP post requests; if the exchange of these messages fails, (3) the HTTP encapsulated messages are sent to port 80 rather than to the port indicated in the remote object reference.

This technique, however, can only deal with a limited number of possible distribution, firewall, and NAT scenarios. In addition, it is rather heavyweight in the sense that step (3) above requires installation of a CGI-script or servlet on the Web server that receives and forwards the HTTP tunneled JRMP message to the actual RMI server object. This installation often conicts with the administrative domain's security policies.

Hole Punching

Hole punching is a lightweight NAT and firewall traversal technique invented for VoIP and peer-to-peer systems. Skype is probably the most widely known application that employs hole punching.

NAT is a method by which IP addresses are mapped from one address realm to another (e.g., a public IP address to a private IP address of a private network and vice versa), providing transparent routing. The principle thereby is that an address mapping for a dedicated recipient can only be established -- i.e., a hole into the NAT is punched|from inside the network where a host resides. A hole can be used from outside the NAT from hosts for whom the hole was punched.

The idea of hole punching is now that a first host (e.g., a client) punches a hole from inside into the NAT and communicates its public IP address to a second host (e.g., a server)|by any means. The second host then uses this public IP address and the punched hole to establish a connection to the first host. To communicate public IP addresses between hosts, a mediator server residing in the public Internet can be utilized.

In the following, we illustrate the idea of hole punching by using a simple example (see Figure 1). First, the server registers with the mediator using a TCP connection. It sends its public IP address together with a unique identifier of the server. Thereby, a hole is punched into the server NAT, which can be used later by the mediator to contact the server. For this, it is important that the TCP connection from the server to the mediator is held open (step 1).

Figure 1: Hole Punching Overview

If a client wishes to establish a connection to the server, it sends a connection request to the mediator, including the identifier of the server to be connected with (step 2). The mediator answers with the public IP address of the server (step 3b).

At the same time, the mediator sends the clients's connection request together with the client's public IP address to the server (step 3a). This is possible since the hole in the server NAT is kept open. Now, the client and the server simultanously initiate the actual hole punching process. Both try to connect to the other entity, thereby punching a hole for client-server communication in their respective NAT. For the sake of simplicity, assume that the client first trys to connect to the server using the server's public IP address. The connection attempt fails, since there exists no hole, i.e., an appropriate address mapping, in the server NAT (step 4). On the other hand, a hole for the server is punched into the client NAT, i.e, an appropiate address mapping is created. Assume that afterwards the server trys to connect to the client. This connection attempt is successful, since there already exists a hole for the server in the client NAT (step 5).

In practice, you can find several types of NATs, e.g., Cone NATs, Port-restricted Cone NATs or Symmetric NATs. Hole punching works with a variety of NATs, but not with Symmetric NATs. On the other hand, Symmetric NATs account only for a small percentage. For Symmetric NATs, a relaying approach (e.g., TURN) can be used. Hole punching and TURN can be integrated into an overall solution called "Interactive Connectivity Establishment" (http://www.isoc.org/tools/blogs/ietfjournal/?p=117).

Extending Java RMI with Custom Sockets

When an RMI server object is exported, essentially two things happen:

  • The RMI runtime on the server side creates a server socket that listens for incoming remote method invocations.
  • A client stub is generated for later usage on the client side. By default, the RMI runtime on the client side will use standard TCP sockets to establish a connection to the server object.

There is, however, an overloaded version of the export method that expects as parameters a server socket factory and a client socket factory, implementing the two interfaces RMIServerSocketFactory and RMIClientSocketFactory, respectively.

The server socket factory controls the creation of the server socket, while the custom socket factory is put into the client stub and will then control the creation of the client socket on the client side. If an RMI stub contains a custom socket factory or not, is completely transparent to the client application. This way, the connection set-up process can be modified without knowledge of the clients; the server application, however, needs to use the appropiate version of the export method and pass in the custom socket factories.

Extending Java RMI with Hole Punching

To make hole punching readily available to a broad array of applications, we have integrated it into Java RMI. We have implemented a custom socket factory that starts a HolePunchingServerSocket when the RMI server is exported, and a HolePunchingSocket on the client side when the client issues a remote method invocation.

The HolePunchingServerSocket registers itself with the mediator server -- which is also part of our solution -- and then waits for incoming connection requests from the mediator server. Each time such a request arrives, the HolePunchingServerSocket starts a HolePuncher that performs the actual connection set-up.

The HolePunchingSocket on the client side sends a connection request to the mediator server and gets in the result the private and the public IP address of the server object. The private IP is needed in case both server and client happen to reside in the same subnet. With this information at hand, the HolePunchingSocket starts a HolePuncher, just like on the server side, because the actual hole punching process is symmetric and does not distinguish between clients and servers. Figure 2 illustrates the hole punching RMI architecture graphically.

Figure 2: Hole punching RMI architecture in FMC notation: (1) An RMI server object registers with the mediator during its export process and (2) (re)binds with the RMI registry. (3) An RMI client looks up the registry to get the remote reference. (4) The client requests the server endpoints from the mediator. (5) The mediator sends the clients endpoints to the server object. (6) Server and client initiate the hole punching process.

Listing One shows a simplified excerpt from the doHolePunching() method of class HolePuncher. The two SocketConnector objects represent threads that actively try to connect with the peer's private and public IP address, respectively. The SocketListener object is a thread that listens for incoming requests from the peer that simultaneously performs the same process.


FoundSocket foundSocket = new FoundSocket();
SocketConnector socketToLocal = new SocketConnector(
   localSocketAddress,ownPort,foundSocket);
SocketConnector socketToRemote = new SocketConnector(
   remoteSocketAddress,ownPort,foundSocket) ;
SocketListener socketListener = new SocketListener(ownPort,
   foundSocket);
synchronized (foundSocket) 
   socketToLocal.start();
   socketToRemote.start();
   socketListener.start();
   foundSocket.wait(60000);
   socketToLocal.interrupt();
   socketToRemote.interrupt();
   socketListener.stopThread();

Listing One: Excerpt from method doHolePunching() of class HolePuncher

These three "bonding threads" are passed in the necessary addresses and a shared object of type FoundSocket that will hold the connected client socket after successful completion of the hole punching process. In addition, this shared object's monitor is used for thread synchronization: after the three bonding threads have been started in the synchronized block, the invocation of the wait method releases the foundSocket's monitor and waits for one of the bonding threads to invoke notify -- or the guard timer of 60000 msecs to expire. When one of the bonding threads could establish a connection to the peer, it sets the foundSocket object and calls the notify method. In either case, i.e., whether notify was called or the guard timer expired, the two SocketConnector threads are interrupted, and the SocketListener's stopThread method is invoked. As opposed to the SocketConnector threads, the SocketListener cannot be interrupted, because a server socket's accept call is not interruptable. Instead, the stopThread simply closes the server socket, which leads to a SocketException that can be used to stop the SocketListener thread.

Implementation Challenges

When we first tested our hole punching RMI implementation, we encountered problems with distributed garbage collection that were hard to trace back to their cause. And because every multi-threaded custom socket implementation for RMI has to face the same effect, we find it beneficial to discuss this effect here: Distributed garbage collection (DGC) uses a reference counting mechanism that allows the RMI server object to keep track of the number of live remote references.

To make this work, the RMI runtime of each remote client periodically signals to the RMI server object that it holds a remote reference to it. If a remote reference contains a custom socket factory, then the RMI runtime uses that factory for the keep-alive signaling message exchange with the remote server object.

Because the RMI runtime runs in a privileged thread, it is not allowed to start the three bonding threads that perform the hole punching -- or any other threads in any custom socket factory, for that matter -- in the same thread group as the RMI runtime.

If no explicit thread group is specified, however, the spawning thread tries to start the new threads in its own thread group. What makes the problem hard to trace back is the fact, that the RMI runtime silently catches the exception that is thrown when starting the bonding threads fails and simply does not send the keep-alive message to the remote server object. As a consequence, the server object can be mistakenly garbage collected while there still are remote clients holding references to it.

To solve this problem, the bonding threads must be explicitly started in a user-level thread group. To get access to such a group, we store the thread group that is active when the HolePuncher class is loaded, in a static variable. Because it is the RMI client code that causes the loading of the HolePuncher, this will always be a user-level thread group that can be used to start the bonding threads without running into the problems described above.

Usage

We illustrate the server as well as the client side usage of hole punching RMI by means of a simply hello world example. Listings Two shows the remote interface Hello, the remote object implementation HelloImpl, and the set-up class HelloSetup. The latter class sets the codebase from where clients can download the server interface, as well as the code for the hole punching socket factory.

It then instantiates and exports the server object; for the export, it uses class HolePunchingRemoteObject. We have provided this convenience class not only to make the export slightly shorter and easier|as opposed to directly using the export method of class UnicastRemoteObject as usual -- but also to ensure that the same hole punching factory object is used on both server and client side. This is necessary because the common factory instance carries the UID under which the server object registers with the mediator. Finally, the HelloSetup registers the RMI server object with the remote RMI registry. Please note that the usage of our remote RMI registry is completely transparent to the RMI server, i.e., no modifications have been made to the standard code for registry location and usage, see also www.ddj.com/java/212001090.


// Remote interface
public interface Hello extends Remote {
   String getHello() throws RemoteException;
}
// Implementation class
public class HelloImplimplements Hello {
   public String getHello() throws RemoteException {
      return "hello , world";
   }
}
// Startup of hole punching enabled RMI server object
public class HelloSetup {
   public static void main (String[] args) throws
      UnknownHostException, RemoteException,
      MalformedURLException, NotBoundException,
      InterruptedException, AlreadyBoundException {
   System.setProperty ("java.rmi.server.codebase",
      "http://141.37.121.130/hello.jar "
      + "http://141.37.121.130/HPSocketFactory.jar");
   Hello stub = (Hello) HolePunchingRemoteObject.exportObject (
      new HelloImpl(),0,
      new InetSocketAddress ("141.37.121.130",8765));
   LocateRegistry.getRegistry ("141.37.121.130").rebind (
      "hello",stub);
   }
}

Listing Two: Example: Hello RMI server object

Listings Three and Four show a sample RMI client that uses the hole punching enabled hello RMI server object, as well as the policy file which is needed for the dynamic download of our custom socket factory (or any other custom socket factory). If, however, the hole punching socket factory already resides on the client machine, no security manager and thus no policy file are necessary.


public class HelloClient {
   public static void main(String[] args) throws RemoteException,
      NotBoundException {
      System.setProperty ("java.security.policy",
         HelloClient.class.getClassLoader().
           getResource("client.policy").toExternalForm());
      System.setSecurityManager (new SecurityManager());
      Registry registry =
         LocateRegistry.getRegistry("141.37.121.130");
      Hello server = (Hello)registry.lookup("hello");
      System.out.println(server.getHello());
   }
}

Listing Three: Example: Hello RMI client


grant {
   permission java.net.SocketPermission"*:*" ,
      "connect,accept,resolve,listen" ;
};

Listing Four: client.policy policy file

Exactly as with the standard RMI architecture, the client locates the (remote) RMI-Registry, looks up the hello server object, and then invokes methods at the server object.

Conclusion

Hole Punching RMI can establish connectivity in many distribution scenarios that regular RMI will fail in. A solution that is completely transparent to the RMI clients needs to resolve several interesting challenges, such as synchronizing the bonding threads, and keeping distributed garbage collection intact. The complete source code, as well as the custom socket factory and the executable mediator jar file can be downloaded here) or from the project homepage.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video