InetAddress.getCanonicalHostName()返回的是IP地址而不是主机名

19

我在 Stack Overflow 上寻找如何在 Java 中进行 IP 查找的方法,但答案与我已经在做的事情相同,并没有解决我的问题。

这是我的代码:

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

InetAddresses是guava库中的一个实用类,用于获取InetAddress

问题是:该代码对某些IP地址有效,对另一些则无效。

一个有效示例

例如,对于IP 157.55.39.29,输出结果为:

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

根据 Linux host 命令,这个结果似乎是正确的:

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

一个不起作用的示例

对于IP地址123.125.71.75,host命令返回:

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

但是我的Java代码的输出结果是:

123.125.71.75

期望的输出应该是

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

getCanonicalHostName方法的Javadoc说:

返回值:
此IP地址的完全限定域名,如果该操作未经安全检查允许,则返回IP地址的文本表示形式。

但我相信这不是安全检查的问题...或者我不理解哪里出了问题。

您有什么建议来解释这种行为吗?您有没有解决方法?

编辑 #1

在寻找解决方案时,我尝试逐步调试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);

在这段代码中,变量host包含正确的值,但是调用getAllByName0的最后一个语句抛出了一个UnknownHostException异常,该异常通过返回所请求的IP地址来处理。异常由内部方法getAddressesFromNameService抛出,错误信息如下: "java.net.UnknownHostException: baiduspider-123-125-71-75.crawl.baidu.com"

我不知道为什么。

我可以绕过内部异常获取host变量的值吗?


你是如何运行这段代码的?容器是什么?使用了单元测试运行器、IDE还是已部署? - Martin Spamer
在本地机器上使用IDE(调试和非调试模式),以及在部署的构建JAR文件上运行,两者行为相同。 - Prim
问题出在他们的DNS设置上,baiduspider-123-125-71-75.crawl.baidu.com实际上无法正确解析。 - Martin Spamer
2个回答

17

问题在于 java.net.InetAddress 采取了某种方式来防止IP欺骗。

它首先将名称解析为一个或多个IP地址。这个过程很顺利。 在您的情况下,结果是两个IP地址。InetAddress 然后检查至少其中一个地址是否解析为原始输入名称。

如果没有,它只会返回原始IP地址。下面的图片显示了在检测 baiduspider-123-125-71-75.crawl.baidu.com 后的情况:

The debugger after the check for `original ip reverse lookup -> name -> dns lookup of name

注意:由 getAllByName0 解析的IP地址与通过 nslookup 获得的相同,即:

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

使用dnsjava库可能是一个解决方案。 它跳过了欺骗检查,因此可以正常使用。

dnsjava示例:

String addr = Address.getHostName(InetAddress.getByName("123.125.71.75")); 输出与预期相同的baiduspider-123-125-71-75.crawl.baidu.com

免责声明:由于我是Java开发人员而不是安全专家,我并不完全了解使用欺骗IP地址的安全影响。


很奇怪,我正要发布我的nslookup结果,但看到你的回答了...在我的电脑上,它无法检索IP地址。 - ahirata
可以通过Java和Shell进行重现吗? - Hendrik Jander

6
我并未深入研究此事,因此不知道为什么会发生这种情况,但一些在线工具(例如此类工具)检查DNS服务器的健康状况时显示它们存在一些问题,这些问题可能与此有关,也可能不相关。
正如@jah所说,Java尝试进行双重检查以查看主机名是否具有其所说的IP。该异常是在本地代码中抛出的,当尝试执行该操作时。实际上,在我的情况下,尝试在命令行上进行验证时,nslookup无法从名称获取IP,这表明DNS服务器上存在某些配置阻止了这种情况(也许是故意的?我对DNS也不是专家)。
当它工作时:
$ 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

当它不起作用时:
$ 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

当它工作时:

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

当它不这样做时:
$ getent hosts baiduspider-123-125-71-75.crawl.baidu.com
$

作为替代方案,您可以使用JNDI的DNS服务提供商。文档中有一个例子,但我会留下一个可用的片段供您测试:
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());

谢谢回复 :) 我认为特定DNS配置不是问题(我可能错了),因为调试时,JDK内部的代码得到了正确的值,但它没有返回。请查看我的编辑以获取更多详细信息。 - Prim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接