4 Replies Latest reply on Dec 12, 2013 2:33 PM by 37456a26-51cf-4550-9da9-26ba5c776ce5

    RMI over two paths, one NAT'd

    37456a26-51cf-4550-9da9-26ba5c776ce5

      Hi,

       

      I'm working on a server which interfaces with various types of clients:

      - "Full featured clients" may only be able to see a load balancer / firewall VIP; the server IP may be NAT'd from them.

      - "Admin clients" typically connect directly to the server.

       

      It's very surprising that RMI / NAT is not handled better by Java.  Have seen various postings with references to 'java.rmi.server.hostname'.  However, it applies to the entire server process so won't work here.

       

      Instead, I noticed that a single call to Registry.lookup() serializes multiple instances of RMIClientSocketFactory to the client (i.e. with different stack traces):

      1) In the first instance, the host parameter of RMIClientSocketFactory.createSocket() is the VIP.

      2) In future instances, it is the server's IP.


      The solution that appeared to work for me is to use the host from that first instance in all future calls to RMIClientSocketFactory.createSocket().

       

      Has anyone encountered this situation?  Did you find any better alternatives?

       

      Thanks in advance.

        • 1. Re: RMI over two paths, one NAT'd
          EJP

          I don't know what 'with different stack traces' means, but you are certainly getting a serialized copy of the same RMIClientSocketFactory with every lookup, unless you have multiple objects that you're looking up. It may then be *invoked* with a different 'host' parameter, but that's not serialized with the factory, it comes from the remote object stub, so if they're different you must have multiple stubs bound in the Registry. In short your question doesn't presently make sense.

           

          However I agree that RMI and multiply-homed-hosts could have been implemented a lot better. The java.rmi.server.hostname solution is clumsy in the extreme and assumes there is a 'most public' IP address for every server, that every client can see. They should have made it an option when exporting the object, or better still done something automatic about it, which as far as I can see wouldn't have been hard, just rewrite the stub if it is being exported from the original host, to change the hostname to the local-host of the current socket, which is the IP address the client used to connect.

          • 2. Re: RMI over two paths, one NAT'd
            37456a26-51cf-4550-9da9-26ba5c776ce5

            Sorry if I was not clear what I meant by 'different stack traces'.  Will put it another way:

             

            - Create a custom SslRMIClientSocketFactory and put a breakpoint on the createSocket() method.

            - Call Registry.lookup().

            - Before lookup() returns, the breakpoint will be hit twice:

             

             

            Stack trace #1:

            pool-1-thread-1@2396, prio=5, in group 'main', status: 'RUNNING'

               at com.myco.common.util.remote.MySslRMIClientSocketFactory.createSocket(MySslRMIClientSocketFactory.java:43)

               at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:613)

               at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:216)

               at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:202)

               at sun.rmi.server.UnicastRef.newCall(UnicastRef.java:341)

               at sun.rmi.registry.RegistryImpl_Stub.lookup(Unknown Source:-1)

               at com.myco.client.authentication.AuthenticationManager.authenticate(AuthenticationManager.java:208)

            ...

             

            Stack trace #2:

            pool-1-thread-1@2396, prio=5, in group 'main', status: 'RUNNING'

               at com.myco.common.util.remote.MySslRMIClientSocketFactory.createSocket(MySslRMIClientSocketFactory.java:43)

               at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:613)

               at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:216)

               at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:202)

               at sun.rmi.server.UnicastRef.newCall(UnicastRef.java:341)

               at sun.rmi.transport.DGCImpl_Stub.dirty(Unknown Source:-1)

               at sun.rmi.transport.DGCClient$EndpointEntry.makeDirtyCall(DGCClient.java:360)

               at sun.rmi.transport.DGCClient$EndpointEntry.registerRefs(DGCClient.java:303)

               at sun.rmi.transport.DGCClient.registerRefs(DGCClient.java:139)

               at sun.rmi.transport.ConnectionInputStream.registerRefs(ConnectionInputStream.java:94)

               at sun.rmi.transport.StreamRemoteCall.releaseInputStream(StreamRemoteCall.java:156)

               at sun.rmi.transport.StreamRemoteCall.done(StreamRemoteCall.java:312)

               at sun.rmi.server.UnicastRef.done(UnicastRef.java:450)

               at sun.rmi.registry.RegistryImpl_Stub.lookup(Unknown Source:-1)

               at com.myco.AuthenticationManager.authenticate(AuthenticationManager.java:208)

             

             

            The host parameter in the first createSocket() call will contain the VIP.  The second will contain the IP from the server.  Sounds like I made incorrect assumptions about when instances were being serialized.  However, notice the different stack traces and host parameters.

             

            As you say, this should have been better implemented by Oracle / Sun: Any thoughts about the workaround?

            • 3. Re: RMI over two paths, one NAT'd
              EJP

              The first stack trace results directly from Registry.lookup(), and it uses the hostname you supplied to LocateRegistry.getRegistry(). So you can control that from the client.

               

              The second stack trace is from DGC, and it uses the hostname provided by the DGC callback stub from the Registry, which in turn comes from java.rmi.server.hostname in the Registry JVM, if set, otherwise something like InetAddress.getLocalAddress(), which is a lottery.

               

              If there is a 'most public' setting you can use for the server hosts that applies to all clients, e.g. the public side of the NAT, you need to:

               

              1. Set -Djava.rmi.server.hostname to that IP address in all the server JVMs *and the Registry JVM*. This is one of many reasons why it's better to use LocateRegistry().createRegistry() rather than rmiregistry.exe, codebase being another one. In both cases the correct settings are already there in the JVM and don't need to be set again.

               

              2. Export everything on the same port, and forward that port via the NAT. If you use createRegistry(), and all remote objects are in the same JVM as well, and there are no client socket factories or you have implemented RMIClientSocketFactory.equals() correctly. you can use port 1099 for everything. You shouldn't really need a CSF unless you're using SSL. In other circumstances they are a bit of an admission of defeat.

              • 4. Re: RMI over two paths, one NAT'd
                37456a26-51cf-4550-9da9-26ba5c776ce5

                Thanks for the response.


                I am using SSL. 


                There is no 'most public' setting that applies to all clients.  As mentioned above, need to support two network paths; admin clients will not generally see the VIP.