The Source for Java Technology Collaboration
User: Password:



Eamonn McManus

Eamonn McManus's Blog

Reimplementing the RMI protocol

Posted by emcmanus on January 11, 2007 at 03:10 AM | Comments (4)

In my last entry, I mentioned that I had reimplemented the RMI registry portably, before discovering that there was a much simpler solution to the security problem I was addressing. Here's the reimplementation for what it's worth. It allows you to go further than the socket factory hack. And if you ever need to understand gory details of the RMI protocol, this could come in useful. Because it does much less than the full-blown RMI implementation in the JDK, it's much easier to understand.

Here are some of things you can do with a reimplemented RMI registry that you can't do with just socket factories:

  • Show different subsets of the registry contents to different clients, for example depending on where they are connecting from or whether they have authenticated with SSL.
  • Only allow a bind or unbind operation to succeed if it adds a hidden password to the name. So if you want to bind "foo", the string you give would actually be "foosecretpassword". Anything else will fail.
  • Trigger additional actions when an object is bound in the registry, for example persisting the object or replicating it in another registry.

By the way, most of these advantages would evaporate if the registry API were augmented to allow you to supply your own Registry object to LocateRegistry.createRegistry.

My reimplementation separates out the registry functionality and the RMI reimplementation. The registry functionality is in the class RegistryImpl, which is almost completely uninteresting, except possibly for the use of ConcurrentHashMap to avoid having to synchronize explicitly across modifications.

The class RegistryServer is where the real action happens. Its public API consists of just a constructor that takes a Registry parameter (typically an instance of RegistryImpl or a subclass) and a port number. It could reasonably have a close method as well, which is left as the proverbial exercise for the reader.

If you're interested in the details of RMI's wire protocol, you could do worse than to study this class in addition to the official specification, especially since the latter has some known errors.

At a high level, an RMI request looks like (object-id, opnum, hash). The object-id is the ObjId of the remote object being invoked. For the RMI registry, this must be the distinguished value ObjID(REGISTRY_ID) because that is the id that LocateRegistry.getRegistry will send. Since we are not exporting any other RMI objects on our port, we don't actually need to check the id, but we do anyway.

The RMI protocol exists in two variants, the 1.1 variant that was used in JDK 1.1, and the 1.2 variant that was added in JDK 1.2. In the 1.1 variant, the opnum is an index within the methods of the given Remote interface, and the hash is a checksum or digest to make sure that both ends have the same understanding of what these methods are and what order they appear in. In the 1.2 variant, the opnum is -1 and the hash is a digest of the method signature.

For compatibility reasons, LocateRegistry.getRegistry only uses the 1.1 variant, but I have coded the server to recognize both, even though I can't think of any reason you would need the 1.2 variant in this case.

In addition to these variants, there are three communication styles. The "single op protocol" is used by RMI-over-HTTP and sends a single operation over a connection before closing it. The "stream protocol" can send any number of operations over a connection, but only one at a time. The "multiplex protocol" can send any number of operations, and can send more than one operation in parallel.

We're only interested in the stream protocol here because that's what LocateRegistry.getRegistry uses. In fact, in the JDK no RMI client will ever use the multiplex protocol, which is a pity because it would allow a more efficient use of TCP connections. The JDK can handle the multiplex protocol if it receives a client request that uses it, but will never originate such a request. That's my understanding from reading the code, at least.

One thing that is not immediately clear from the protocol specification is exactly when RMI switches over to using object serialization. You can see this in the reimplementation by looking at when we switch over from using DataInputStream and DataOutputStream to using ObjectInputStream and ObjectOutputStream.

The RMI protocol is not self-describing, in that you have to know the signature of the method being invoked in order to read its parameters and write its return value correctly. I think this is a flaw, by the way.

Another thing that is not very obvious from reading the specification is that you must include "class annotations" in the serialized data you send back, even if they are null. This is why we subclass ObjectOutputStream and override annotateClass.

I've done some basic testing of this code, but it is obviously not production-quality. Some performance tuning could be done (though performance of the registry is not usually critical). I am not sure that protocol errors will produce the same exceptions as "real" RMI, though I am not sure I care either.

The source files are RegistryImpl.java (the boring implementation of the Registry interface) and RegistryServer.java (the exciting implementation of a subset of the RMI protocol).


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Hi,

    I don't know much about customizing RMI, so could you please give some instructions on what to do with the RegistryImpl and RegistryServer classes, i.e. how to tell RMI to use these classes instead of the default classes provided by Sun?

    Thanks,

    Hendrik

    Posted by: hgani on August 14, 2007 at 12:25 AM

  • Hendrik, that's an excellent question, and I was remiss not to show the code for this. The standard way to create an RMI registry is this:

    Registry reg = LocateRegistry.createRegistry(portNumber);

    To achieve the same effect with the classes here, you do this:

    Registry impl = new RegistryImpl();
    new RegistryServer(impl, portNumber);

    Of course this is only interesting if you subclass or otherwise customize the RegistryImpl and/or RegistryServer classes.

    Posted by: emcmanus on August 16, 2007 at 02:52 AM

  • Hi,
    I just tried to use your rmiregistry implementation. The server code looks something like this:

    ...
    Registry impl = new RegistryImpl();
    new RegistryServer(impl, portNumber);
    // Create new remote object/
    Remote remote = new RmiEchoImpl();
    // Export the remote object to the registry with the port observed by the STUNT protocol
    UnicastRemoteObject.exportObject(remote, objPort);
    impl.rebind("RmiEcho", remote);
    ...


    Client code:

    ...
    // Locate the registry on a remote machine and set the client socket factory
    Registry remoteRegistry = LocateRegistry.getRegistry("localhost",1099);
    // Get remote object reference
    RmiEcho remote = (RmiEcho)remoteRegistry.lookup("RmiEcho");
    ...


    The client can not reach the remote object. It does nothing :-(

    But if I do a: Naming.list("localhost") I see the remote object.

    Another question: Is reimplementing the rmi registry (providing a registryImpl, and
    a registry server) apropriate for developing a rmiregistry which is capable register
    remote objects from a different machine?

    Posted by: wreiser on August 19, 2007 at 06:26 AM

  • wreiser, your code looks all right to me, assuming that the portNumber in the server code is 1099. When you say the client "does nothing", do you mean that it gets stuck in the lookup call? Or something else? If the client is stuck, could you try to get stack traces on the client and the server (for example using jstack)?

    For the particular problem of allowing remote modifications, you can certainly use this reimplementation, but you can also use the technique I described in the previous entry.

    Posted by: emcmanus on August 31, 2007 at 09:30 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds