Skip to content

Instantly share code, notes, and snippets.

@timmc
Created September 20, 2016 16:27
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save timmc/d0e59b967c0f6c0886864d9fc045d079 to your computer and use it in GitHub Desktop.
Save timmc/d0e59b967c0f6c0886864d9fc045d079 to your computer and use it in GitHub Desktop.

Port-forwarding JMX

Proof of concept:

  • Terminal 1:
    • SSH to remote host
    • Start a Java process with JMX registry port 50004, RMI callback port 50005, and RMI hostname pinned to localhost: java -Dcom.sun.management.jmxremote.port=50004 -Dcom.sun.management.jmxremote.rmi.port=50005 -Djava.rmi.server.hostname=localhost -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -cp /some/jar/file main.class
  • Terminal 2:
    • Port-forward local 50004 and 50005 to the host: ssh remote-host -L 50004:localhost:50004 -L 50005:localhost:50005
  • Terminal 3:
    • Make JMX call against localhost:50004

Explanation:

(First note that I'm waving my hands somewhat, as I don't really know JMX. This is just my working understanding at time of posting.)

When JMX is initialized, it listens on the specified registry port (here specified as -Dcom.sun.management.jmxremote.port=50004) for commands, but it also listens on a random port for RMI callbacks. (Here I'll use 4217 to indicate that random port.) When a command is issued, JMX responds saying "OK, but you need to talk to addr-1-2-3-4:4217 to get the response". Since it sends a hostname and port as data over the connection, there's no guarantee the caller (who is probably on the other side of at least one SSH tunnel and at least one NAT) can talk to the port, let alone resolve the hostname! There are two parts to the solution:

  • Pin the RMI callback port so that it can be predictably forwarded: -Dcom.sun.management.jmxremote.rmi.port=50005
  • Pin the declared hostname to localhost to ensure the connection doesn't go haring off into the void: -Djava.rmi.server.hostname=localhost

One downside is that since RMI forces the client to look at a specific host and port, and the port is what it listens on, we also have to forward that exact port locally. This means that if you have the same JMX settings for your app locally and in deployment, they can't both be running (and serving JMX) at the time you want to do some debugging. One might be able to use an alternative loopback IP address for the RMI hostname (e.g. 127.0.0.43), but this would again have to differ between local and deployed configurations.

Sources:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment