InetAddress.getCanonicalHostName() returns IP inst

2019-01-27 15:59发布

问题:

I looked for how to do IP lookup in Java on Stack Overflow but the answers match what I am already doing and do not resolve my problem.

Here is my code:

public void printHostname( String ip ) {
    System.out.println( InetAddresses.forString( ip ).getCanonicalHostName( ) );
}

InetAddresses is just a utility class from guava library to get a InetAdress.

The problem: This code works as expected with some IP adresses and not with some others.

A working example

For example, for IP 157.55.39.29, the output is:

msnbot-157-55-39-29.search.msn.com

This result seems correct according to Linux host command:

> host 157.55.39.29
29.39.55.157.in-addr.arpa domain name pointer msnbot-157-55-39-29.search.msn.com.

A not working example

For IP 123.125.71.75, the host command returns:

> host 123.125.71.75
75.71.125.123.in-addr.arpa domain name pointer baiduspider-123-125-71-75.crawl.baidu.com.

But the output of my Java code is:

123.125.71.75

whereas the expected output should be

baiduspider-123-125-71-75.crawl.baidu.com

The javadoc of getCanonicalHostName method says:

Returns:
the fully qualified domain name for this IP address, or if the operation is not allowed by the security check, the textual representation of the IP address.

but I’m pretty sure it's not really a problem with a security check... or I don't understand what is wrong.

Have you any suggestion to explain this behaviour? Do you have a workaround?

EDIT #1

When looking for a solution, I tried to step debug the implementation in JDK:

// 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) {
    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);

In this code, variable host contains the correct value, but the last statement calling getAllByName0 throws an UnknownHostException which is handled by returning just the requested IP. The exception is thrown by internal method getAddressesFromNameServicewith message: "java.net.UnknownHostException: baiduspider-123-125-71-75.crawl.baidu.com"

I don't know why.

Can I get the host variable value, bypassing the internal exception?

回答1:

The problem lies in the fact that the java.net.InetAdress has a certain procedure against ip-spoofing.

It first resolves the name into (an) ip address(es). This works fine. In your case the result are two IP adresses. InetAdress then checks back if (at least one of) these adresses resolve to the original input name.

If they do not, it just returns the original ip adress. The following picture shows the situation after the check for baiduspider-123-125-71-75.crawl.baidu.com

Note: The ip adresses resolved by getAllByName0 are the same as via nslookup, namely:

nslookup baiduspider-123-125-71-75.crawl.baidu.com
Server:     192.168.2.1
Address:    192.168.2.1#53

Non-authoritative answer:
Name:   baiduspider-123-125-71-75.crawl.baidu.com
Address: 62.157.140.133
Name:   baiduspider-123-125-71-75.crawl.baidu.com
Address: 80.156.86.78

A solution would be to use the dnsjava library. It skips the spoofing check and therefore works fine.

dnsjava example:

String addr = Address.getHostName(InetAddress.getByName("123.125.71.75")); outputs just as expected baiduspider-123-125-71-75.crawl.baidu.com

Disclaimer: As i am a Java developer and not a security expert, i am not totally aware of the security implications of using a spoofed ip address.



回答2:

I did not dig a lot into this so I don't know why it is happening, but some online tools (like this one) that checks the health of DNS servers indicates that they have some issues which may or may not be related.

As @jah said, Java tries to double check to see if the hostname has the ip it said it has. The exception is thrown on the native code while trying to do that. In fact, in my case, trying to verify on the command line, the nslookup fails to get the ip from the name, which indicates some configuration preventing this on the DNS Server (maybe on purpose? I'm no expert in DNS either).

When it works:

$ nslookup msnbot-157-55-39-29.search.msn.com
Server:         192.168.1.1
Address:        192.168.1.1#53

Non-authoritative answer:
Name:   msnbot-157-55-39-29.search.msn.com
Address: 157.55.39.29

When it doesnt work:

$ nslookup baiduspider-123-125-71-75.crawl.baidu.com
Server:         192.168.1.1
Address:        192.168.1.1#53

** server can't find baiduspider-123-125-71-75.crawl.baidu.com: NXDOMAIN

When it works:

$ getent hosts msnbot-157-55-39-29.search.msn.com
157.55.39.29    msnbot-157-55-39-29.search.msn.com

When it doesn't:

$ getent hosts baiduspider-123-125-71-75.crawl.baidu.com
$

As an alternative, you can use a DNS Service Provider for JNDI. The documentation has an example, but I'll leave a working snippet for you to test:

String[] octets = "123.125.71.75".split("\\.");
String host = String.join(".", octets[3], octets[2], octets[1], octets[0], "in-addr.arpa");

Properties props = new Properties();
props.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
DirContext dirContext = new InitialDirContext(props);

Attributes attrs = dirContext.getAttributes(host, new String[] {"PTR"});

System.out.println(attrs.get("PTR").get());