The problem can be caused by multiple reasons.
Reason 1: the IP address doesn't have a hostname
This is probably the most common reason, and has nothing to do with security managers.
If an IP address doesn't resolve to a hostname, because there is no hostname, then you would expect getHostName()
to return null
or throw a UnknownHostException
, but this doesn't happen. Instead getHostName()
simply returns the IP address as a string back again. For reasons unknown to me, this common situation is undocumented.
So if the IP address is the same as the result returned by getHostName()
, then the hostname doesn't exist.
Detailed explanation
The following JDK code is the cause of this undocumented problem:
https://github.com/openjdk/jdk/blob/jdk-17+35/src/java.base/share/classes/java/net/InetAddress.java#L697
public class InetAddress implements java.io.Serializable {
private static String getHostFromNameService(InetAddress addr, boolean check) {
String host = null;
try {
// first lookup the hostname
host = nameService.getHostByAddr(addr.getAddress());
/* check to see if calling code is allowed to know
* the hostname for this IP address, ie, connect to the host
*/
if (check) {
@SuppressWarnings("removal")
SecurityManager sec = System.getSecurityManager();
if (sec != null) {
sec.checkConnect(host, -1);
}
}
/* now get all the IP addresses for this hostname,
* and make sure one of them matches the original IP
* address. We do this to try and prevent spoofing.
*/
InetAddress[] arr = InetAddress.getAllByName0(host, check);
boolean ok = false;
if(arr != null) {
for(int i = 0; !ok && i < arr.length; i++) {
ok = addr.equals(arr[i]);
}
}
//XXX: if it looks a spoof just return the address?
if (!ok) {
host = addr.getHostAddress();
return host;
}
} catch (SecurityException e) {
host = addr.getHostAddress();
} catch (UnknownHostException e) {
host = addr.getHostAddress();
// let next provider resolve the hostname
}
return host;
}
}
So what happens is that the IP-address is passed to NameService.getHostByAddr()
(NameService
is a private interface), which has this (private) documentation in the source code:
Lookup the host corresponding to the IP address provided
@param addr byte array representing an IP address
@return {@code String} representing the host name mapping
@throws UnknownHostException if no host found for the specified IP address
So NameService.getHostByAddr()
throws an UnknownHostException
if the IP doesn't have a hostname, but InetAddress.getHostFromNameService()
swallows this exception and instead, it returns the provided IP-address itself!!! IMO it should have let the exception be thrown instead of swallowing it, because swallowing it makes it more difficult for the client to determine whether a hostname exists.
You can check if the IP address has a hostname by using the nslookup
commandline tool: nslookup 192.168.2.139
. If it returns something like:
** server can't find 139.2.168.192.in-addr.arpa: NXDOMAIN
(Linux) or *** can't find 192.168.2.139: Non-existent domain
(Windows) then there is no hostname.
Reason 2: a security manager is applied
By default, Java doesn't have a security manager enabled. In that case, this reason doesn't apply.
A security manager is an object that defines a security policy for an application. If you have a security manager and want to find out if it is the cause of your problem, then you should check whether it is allowing you to open a socket to the resolved hostname (if any). To do so, first use nslookup 192.168.2.139
and verify if a hostname is resolved. If no hostname is resolved, then your problem is caused by "Reason 1". If it does resolve to a hostname, for example myhostname
, then try this:
SecurityManager sec = System.getSecurityManager();
if (sec != null) {
sec.checkConnect("myhostname", -1);
}
If checkConnect()
throws a SecurityException
, then a SecurityManager is active and is causing the problem. So then you could look into how you can configure your securityManager to solve the problem.