I have a java process running (on java 7) on some remote server that I want to monitor using Java Mission Control. However, JMC is unable to connect, although I can telnet to the server using the port jmx remote port (12345 here, see below).
The remote java proces is started with
-Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.port=12345
-Djava.rmi.server.hostname=<some ip address>
-Dcom.sun.management.jmxremote.authenticate=false
and these seem to be correct values to me. Port 12345 has been opened on the firewall, but I suspect that the rmi server port is blocked by the firewall.
Thus, my question is: Is there any way (using netstat on the server or maybe even telnet from the client) to determine which rmi server port the java process is currently using on the server? (Using netstat, I see several ports being used by the java process. However, I don't have a clue which one is the rmi port.)
Adding '-Dcom.sun.management.jmxremote.rmi.port=12345' could help
See Why Java opens 3 ports when JMX is configured?
This should display where a JMX remote client is told to connect to (plus some internal info) possibly after timing out:
//nopackage -- move if you like
import java.rmi.Remote;
import java.rmi.registry.*;
import java.rmi.server.*;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
public class JMXTarget {
/*
* run: java JMXTarget host port
* where host (name or address) contains the JVM process offering JMX
* and port (number) is the registry specified by -Dcom.sun.management.jmxremote.port
*/
public static void main(String[] args) throws Exception {
Registry reg = LocateRegistry.getRegistry (args[0], Integer.parseInt(args[1]));
Remote r = reg.lookup ("jmxrmi");
RemoteObject ro = (RemoteObject)r;
RemoteRef rr = ro.getRef();
UnicastRef ur = (UnicastRef)rr;
LiveRef lr = ur.getLiveRef();
System.out.println (lr);
}
}
Note if sysprop java.rmi.server.hostname is specified as you did, its value (your "some ip address") is where the client connects. If that value is not an address of the target machine, or a name that resolves (for the client) to an address of the target machine, it won't connect. If you don't specify it, it defaults to (edit) the value determined by InetAddress.getLocalHost() which is or at least should be a valid address for that machine.
Related
I'm working on a project using Java RMI and with a client-server implementation.
On the server side, this is how I initialize my server:
final String url = "localhost";
final int port = 8090;
LocateRegistry.createRegistry(port);
IServerGame serverGame = new ServerGame();
Naming.rebind("//"+url+":"+port+"/undercover", serverGame);
System.out.println("Server running at //" + url + ":" + port + "/undercover")
Undercover is the name of my application. We chose to use the port 8090
arbitrarily.
Now, here is how I initialize the connection client-side:
try {
server = (IServerGame) Naming.lookup("//"+address+":8090/undercover");
System.out.println("Connected to server " + address + ".");
} catch (Exception e) {
System.out.println("Connection failed.");
e.printStackTrace();
}
address is a string in the parameter of the method which initializes the connection and is the IP of the server. We ask the client to enter this IP to allow us to connect to different servers if we want.
When I run my application in local, whether I use localhost or my private IP 192.168.x.x as address, everything works fine. The client connects to the server and I can use the application. The problem is that when I want to use the application via WAN, sending the client to a friend and starting the server on my local machine, it doesn't work anymore. I get this error:
java.rmi.ConnectException: Connection refused to host: (private IP of host machine); nested exception is:
java.net.ConnectException: Connection timed out: connect
I've already checked a lot of posts in StackOverflow with the exact same problem and the usual answer is to either set the SystemProperty(java.rmi.server.hostname,"192.168.x.x") or do it via the -Djava.rmi.server.hostname in command line. I'm using Gradle to compile. I run the application via the run task. The client is a basic fx application too. None of this works unfortunately. I've also tried to open the port 8090 in my firewall but it doesn't solve the problem either.
Now maybe I'm doing this wrong. I've also tried to replace my private IP 192.168.x.x, which is IPv4 that I found via ipconfig in the command shell, with my public IP 79.95.x.x. But, again, it doesn't work.
I tried adding the SystemProperty(java.rmi.server.hostname,"192.168.x.x") like the first line of code that appears in the server code I showed you above.
I'm connected to internet via 4G. I don't have a box connection, so I can't really go to the box settings to allow certain ports, if that's ever a thing that could fix the issue.
EDIT :
So i've tried to switch from naming implementation in server-side to Registry implementation as it was suggested bellow but it didn't make any difference. As i thought it could be just a connection problem, i asked a friend to ping the server with telnet on the port 8090 and in fact it didn't work. He ran telnet [domain name or ip] [port] and the error was :
Unable to connect to remote host: Connection timed out
So the problem is that the external clients cannot reach my server or connect to the port. As i'm using 4G as internet connection as i mentioned above, any idea on how i could make the port reachable ?
Try using class java.rmi.registry.Registry rather than class java.rmi.Naming.
On the server side:
final int port = 8090;
Registry registry = LocateRegistry.createRegistry(port);
ServerGame serverGame = new ServerGame();
IServerGame stub = (IServerGame) UnicastRemoteObject.exportObject(serverGame, 0);
registry.bind("undercover", stub);
On the client side:
Registry registry = LocateRegistry.getRegistry("server host name or IP", 8090);
IServerGame serverGame = (IServerGame) registry.lookup("undercover");
If I use this solution:
new ServerSocket(9090, 0, InetAddress.getByName("localhost"))
...and the user changes it's system hosts file to access my website as "localhost", will this fail to prevent access from non-local client?
(in response to the bounty call)
As always in computer security, guarantee depens on attacker capabilities.
The attacker is lame and knows nothing. Then yes, localhost guarantees the locality of the client.
The attacker has login access to the system and can run SSH to the outer world. Then no guarantees - SSH can forward internal ports through tunnels:
ssh -R *:8080:localhost:9090 some.external.server
Executing this command on the box with your java server will result in establishing a tunnel. All requests addressed to some.external.server:8080 will be delivered to localhost:9090 of the target box.
VPS nowdays costs almost nothing, so the attacker can easily rent such external box and use it as the proxy between your localhost and the whole world.
You may try to protect your server by filtering out all requests where Host header is not localhost. It could be easily countermeasured by including a header-rewriting proxy, such as nginx, to the forwarding chain.
Summary
As you can see, guarantee means that users in the target box must be severely limited: no forwarding software. It implies denying users access to system utilities like ssh or installing and/or running them with user privileges. This is highly unlikely unless the box is a set-top box without any user login or software reconfiguration.
Localhost address
The first comment to the question suggests a trick with localhost name resolution:
the user could probably override localhost to that it's no longer 127.0.0.1
The idea is to place a record to /etc/hosts or c:\Windows\System32\Drivers\etc\hosts that binds localhost name to another IP address.
If your box has an Ethernet connection with, say, address 1.2.3.4, then the line
1.2.3.4 localhost
might cause change of localhost address. If this happens, then the line
new ServerSocket(9090, 0, InetAddress.getByName("localhost"))
will bind the port 9090 on the external network interface, that is accessible from the outside of the box.
I tried this on Ubuntu 18.04, and it worked. I successfully connected to the app running on localhost in the box on the other side of Pasific.
BUT
Once upon a time MS Windows developers hardcoded localhost to be 127.0.0.1. Here is the Medium post about that.
I checked with my Windows 10 box. Confirmed: localhost resolves to 127.0.0.1. The test program
package org.example;
import java.net.*;
import java.io.*;
public class TryLocalhost {
public static void main(String[] args) throws IOException {
System.out.println("localhost: " + InetAddress.getByName("localhost"));
}
}
produces
localhost: localhost/127.0.0.1
while hosts file tried to bind localhost to the link-local address
# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost
192.168.0.198 localhost
The comment is original, from Microsoft.
I have a JMX enabled application which creates RMI Registry by doing:
rmiRegistry = LocateRegistry.createRegistry(registryPort);
and then later on it creates JMXConnectorServer by doing:
JMXServiceURL url = new JMXServiceURL(
"service:jmx:rmi://localhost:" + serverPort
+ "/jndi/rmi://:" + registryPort + "/jmxrmi");
connector = JMXConnectorServerFactory.newJMXConnectorServer(
url,
null,
ManagementFactory.getPlatformMBeanServer());
connector.start();
My problem is that I cannot connect to this JMX Server from a remote host using JConsole. I've followed the instructions for debugging/tracing JConsole and I can see that the problem is that JConsole tries to connect to a bogus IP address that it must have gotten from the Registry. Ok, it isn't exactly bogus because it happens to correspond to a virtual network interface I have on the JMX Server host machine. How on earth does JMX pick that up?
A netstat on my application reveals that both the serverPort and the registryPort are bound on the wildcard interface (on both IP4 and IP6) which is as expected since I haven't supplied socket factories. Basic connectivity works from the remote host, i.e. I can get the telnet test to pass on both ports.
I would have assumed that I would have to change 'localhost' in the JMXServiceURL to the name of my host (as other hosts sees it, not how it sees itself) but that doesn't help.
What do do ?
UPDATE1
After some more investigation I'm pretty sure this 'bogus IP address' comes because RMI just picks the first IP it finds on its own host and then uses that as its 'call-me-back' address. It then happens to find that virtual NIC that exists on this host. That's really not want I want. I want to be able to control explicitly the RMI 'call-me-back' address.
Welcome to RMI hell.
The end point resolve himself and send back to jconsole the ip to use.
If you can try to use JMXMP : http://meteatamel.wordpress.com/2012/02/13/jmx-rmi-vs-jmxmp/.
You can also try java.rmi.server.hostname :
http://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-properties2.html
I added a static DNS entry into my router, binding some IP address to builds-mac. I then attempted to look up its IP address using Java:
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Testcase
{
public static void main(String[] args) throws UnknownHostException
{
System.out.println("got: " + InetAddress.getByName("builds-mac"));
}
}
This failed with:
java.net.UnknownHostException: builds-mac
at java.net.Inet4AddressImpl.lookupAllHostAddr(Native Method)
at java.net.InetAddress$1.lookupAllHostAddr(InetAddress.java:901)
at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1293)
at java.net.InetAddress.getAllByName0(InetAddress.java:1246)
at java.net.InetAddress.getAllByName(InetAddress.java:1162)
at java.net.InetAddress.getAllByName(InetAddress.java:1098)
at java.net.InetAddress.getByName(InetAddress.java:1048)
but when I replaced the hostname with builds.home.local (updating the DNS entry accordingly) then the operation succeeded.
Why does InetAddress.getByName() require a hostname suffix? Is it possible to make it work for hostnames without a suffix?
(I am running JDK 1.7.0_60 under Windows7, 64-bit)
UPDATE: nslookup builds-mac always works whereas initially ping builds-mac fails (could not find builds-mac) but then when I launch the virtual machine in VMWare, ping begins to work. Once ping begins to work, so does InetAddress.getByName().
Why would ping fail to find a host when nslookup does?
Why would launching the VM cause ping to begin seeing the host? Remember, ping says could not find builds-mac as opposed to finding the host but not getting any response because the host is down. The latter is expected, but the former is not.
UPDATE2: So, I've discovered three more interesting clues...
Whenever ping works so does InetAddress.getByName(). This is not the case for nslookup.
By default, routers are not assigned a domain name. In this mode, nslookup always works but ping does not.
If I assign the router a domain name and renew [1] the lease of the computer doing the lookup, then ping begins to work.
[1] Rebooting the computer or running ipconfig /renew picks up the new domain name, but ipconfig /flushdns does not.
So, this brings us back to the original question:
Why does the Windows Client Resolver (what ping uses under the hood) require a domain name suffix? I mean, shouldn't it work even without one?
I am trying to create a WebService. I am not able to access the URL. If I try to connect to
http://192.168.10.203:8080/EchoBeanService/EchoBean?wsdl
I get an error:
Firefox can't establish a connection to the server at 192.168.10.203:8080
However, if I am able to connect to the using localhost in the URL:
http://localhost:8080/EchoBeanService/EchoBean?wsdl
Echo.java
package services;
public interface Echo {
public String printEcho();
public String printEchoParam(String str);
}
EchoBean.java
package model;
import javax.jws.WebService;
import javax.ejb.Stateless;
import javax.jws.WebMethod;
import services.Echo;
#Stateless
#WebService
public class EchoBean implements Echo {
public EchoBean(){}
#WebMethod
public String printEcho(){
return "WebServices Echo ";
}
#WebMethod
public String printEchoParam(String str){
return ("In PrintEcho( String " + str +" )" );
}
}
-H
I have absolutely no knowledge of java, but this symptom doesn't seem all that unique to web services in general. Maybe something to do with either this java app or the server by default binding only to or listening at localhost:8080? Maybe this would help to change that: How to change webservice url endpoint?
Similar instructions, specifically the 'Deploying the Web Service' section: http://today.java.net/article/2006/06/12/web-services-made-easy-jax-ws-20
Otherwise, like folks said above: check your local firewall to see if incoming connections are blocked, or verify the IP addressed assigned to the system this service is running on.
Check the following:
Is 192.168.10.203 your IP address?
Do you have any firewall on the appropriate network interface (the interface of 192.168.10.203), that might block the connection?
Also some application servers disable remote connections by default for security reasons. Your server might have to be setup to allow remote connections.
You can check network connectivity by using telnet to connect to the port.
telnet 192.168.10.203 8080
If you are running on Linux you might be blocked by IPTABLE. Try to disable that and see if you can connect from your remote IP/Hostname.
Either way it seems like a firewall issues or incorrect IP address.
Look up your IP Address with ifconfig (unix based) or ipconfig (win)
If this is a home network, I know that some routers/modems don't like looping back to themselves (I have seen this behavior with linksys devices). But localhost still works, because the OS catches it before it goes out to the network appliance. Have you tried connecting to any other ports on your machine, or just doing a ping of the IP address to see if it responds?
Thank you all,
Resolved the issue by reinstalling my glassfish server.