InetAddress.getLocalHost().getHostName() JDK 11 和 JDK 8 之间的不同行为

Posted

技术标签:

【中文标题】InetAddress.getLocalHost().getHostName() JDK 11 和 JDK 8 之间的不同行为【英文标题】:InetAddress.getLocalHost().getHostName() different behavior between JDK 11 and JDK 8 【发布时间】:2020-09-05 23:10:06 【问题描述】:

我写了一个简单的java程序基本可以运行:

System.out.println(InetAddress.getLocalHost().getHostName());

如果我编译它并在 Java 1.7.231 或 1.8.221 上运行它 在 RHEL 7.7 上,它返回 FQDN (computer.domain.com),但在同一服务器上,在 RHEL JDK 11.0.2 中编译它仅返回服务器名称。

据我了解,它应该进行反向 DNS 查找(基本上是主机名 -f),但对于 JDK 11,行为肯定是不同的。知道为什么会这样吗?

【问题讨论】:

【参考方案1】:

这可能与此处报告的问题相同:InetAddress.getLocalhost() does not give same result in java7 and java8。

归结为 JDK 的变化:

由于:http://hg.openjdk.java.net/jdk8/jdk8/jdk/rev/81987765cb81 被推送,我们调用 getaddrinfo / getnameinfo 来获取本地主机名,而不是旧的(已废弃)gethostbyname_r/gethostbyaddr_r 调用。

较新的调用尊重 localhosts /etc/nsswitch.conf 配置文件。对于这台机器,该文件告诉这些调用在引用其他命名服务之前先查看文件。

由于 /etc/hosts 文件包含此主机名/IP 组合的显式映射,因此返回的就是该映射。

在较旧的 JDK 中,gethostbyname_r 实际上忽略了本地机器设置并立即委托给命名服务。

【讨论】:

【参考方案2】:

在后台,为了获得 localhost 名称,SDK 对底层操作系统执行本机调用。

涉及的C函数是getLocalHostName。对于 IP 版本 4 和 6,您都可以找到适当的实现:如果您使用 IP 版本 6,则基本上它是相同的源代码,只需考虑很少的更改。

让我们假设例如 IP 版本 4 的代码。

对于 Java 11,对应的原生代码在 Inet4AddressImpl.c 中实现。这就是getLocalHostname 的实现方式:

/*
 * Class:     java_net_Inet4AddressImpl
 * Method:    getLocalHostName
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL
Java_java_net_Inet4AddressImpl_getLocalHostName(JNIEnv *env, jobject this) 
    char hostname[NI_MAXHOST + 1];


    hostname[0] = '\0';
    if (gethostname(hostname, sizeof(hostname)) != 0) 
        strcpy(hostname, "localhost");
     else 
#if defined(__solaris__)
        // try to resolve hostname via nameservice
        // if it is known but getnameinfo fails, hostname will still be the
        // value from gethostname
        struct addrinfo hints, *res;


        // make sure string is null-terminated
        hostname[NI_MAXHOST] = '\0';
        memset(&hints, 0, sizeof(hints));
        hints.ai_flags = AI_CANONNAME;
        hints.ai_family = AF_INET;


        if (getaddrinfo(hostname, NULL, &hints, &res) == 0) 
            getnameinfo(res->ai_addr, res->ai_addrlen, hostname, sizeof(hostname),
                        NULL, 0, NI_NAMEREQD);
            freeaddrinfo(res);
        
#else
        // make sure string is null-terminated
        hostname[NI_MAXHOST] = '\0';
#endif
    
    return (*env)->NewStringUTF(env, hostname);

如您所见,当使用与 Solaris 不同的东西时,代码似乎只依赖gethostname 来获取所需的值。这个限制是在this commit 中在this bug 的上下文中引入的。

Here你可以看到类似的IP 4版本Java 8的原生源代码实现。

在该源代码中,您可以找到与 Java 11 的前一个源代码的几个不同之处。

首先,根据以下定义是否适用,将代码分为两部分:

#if defined(__GLIBC__) || (defined(__FreeBSD__) && (__FreeBSD_version >= 601104))
#define HAS_GLIBC_GETHOSTBY_R   1
#endif




#if defined(_ALLBSD_SOURCE) && !defined(HAS_GLIBC_GETHOSTBY_R)

...

#else /* defined(_ALLBSD_SOURCE) && !defined(HAS_GLIBC_GETHOSTBY_R) */

...

如果the condition applies 或not,为getLocalHostName 提供的实现是不同的。

在我看来,对于 Redhat,条件不适用,因此,以下代码是运行时使用的代码:

/************************************************************************
 * Inet4AddressImpl
 */


/*
 * Class:     java_net_Inet4AddressImpl
 * Method:    getLocalHostName
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL
Java_java_net_Inet4AddressImpl_getLocalHostName(JNIEnv *env, jobject this) 
    char hostname[NI_MAXHOST+1];


    hostname[0] = '\0';
    if (JVM_GetHostName(hostname, sizeof(hostname))) 
        /* Something went wrong, maybe networking is not setup? */
        strcpy(hostname, "localhost");
     else 
        struct addrinfo hints, *res;
        int error;


        hostname[NI_MAXHOST] = '\0';
        memset(&hints, 0, sizeof(hints));
        hints.ai_flags = AI_CANONNAME;
        hints.ai_family = AF_INET;


        error = getaddrinfo(hostname, NULL, &hints, &res);


        if (error == 0) /* host is known to name service */
            getnameinfo(res->ai_addr,
                        res->ai_addrlen,
                        hostname,
                        NI_MAXHOST,
                        NULL,
                        0,
                        NI_NAMEREQD);


            /* if getnameinfo fails hostname is still the value
               from gethostname */


            freeaddrinfo(res);
        
    
    return (*env)->NewStringUTF(env, hostname);

如您所见,最后一个实现也首先调用gethostname,尽管间接使用JVM_GetHostName、wrapped in C++ code:

JVM_LEAF(int, JVM_GetHostName(char* name, int namelen))
  JVMWrapper("JVM_GetHostName");
  return os::get_host_name(name, namelen);
JVM_END

根据实际操作系统,os::get_host_name 将转换为不同的功能。对于linux,它将调用gethostname

inline int os::get_host_name(char* name, int namelen) 
  return ::gethostname(name, namelen);

如果对gethostname 的调用成功,则使用gethostname 返回的主机名调用getaddrinfo。如果反过来,最后一次调用成功,则调用getnameinfo,并使用getaddrinfo 返回的地址来获取最终主机名。

在某种程度上,这对我来说似乎很奇怪,我觉得我错过了一些东西,但这些差异很可能是你所经历的不同行为的原因;可以使用提供的本机代码测试假设并调试为您的系统获得的结果。

【讨论】:

【参考方案3】:

oracle 文档中的这个答案可能会对您有所帮助:

在 Red Hat Linux 安装中,InetAddress.getLocalHost() 可能会返回与环回地址 (127.0.0.1) 对应的 InetAddress。这是因为默认安装在 /etc/hosts 中创建了机器主机名和环回地址之间的关联。 为确保 InetAddress.getLocalHost() 返回实际主机地址,请在搜索主机之前更新 /etc/hosts 文件或名称服务配置文件 (/etc/nsswitch.conf) 以查询 dns 或 nis。

链接:https://docs.oracle.com/javase/7/docs/technotes/guides/idl/jidlFAQ.html

JDK 1.7 上的类似错误 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=7166687

【讨论】:

【参考方案4】:

在 JDK 11 上,localhostname 可能有一个内置的预定义 JDK 关键字,可以在检索 localhostname 时调用该关键字,并且您可能会使用您自己的变量调用覆盖系统预定义关键字,在其中调用 localhostname,因为有时我们不小心用我们自己的用户定义变量覆盖了一个内置变量,这导致原来的内置关键字失去了它的值,返回显示为空或其他一些结果 这可能不是您问题的最佳答案,但我建议您查看 JDK 内置关键字和 RHEL linux 内置关键字以进行 Inet 调用,以便在结果中返回 localhostname

【讨论】:

您能否提供更多关于如何检查内置关键字的详细信息?谢谢!

以上是关于InetAddress.getLocalHost().getHostName() JDK 11 和 JDK 8 之间的不同行为的主要内容,如果未能解决你的问题,请参考以下文章

InetAddress.getLocalHost()详解及异常处理

InetAddress.getLocalHost().getHostName() JDK 11 和 JDK 8 之间的不同行为

在linux下用 java 输出 InetAddress.getLocalHost();的结果是啥?

为啥 InetAddress.getLocalHost().getHostName() 返回与 bash“主机名”不同的值?

java InetAddress.getLocalHost();返回 127.0.0.1 ...如何获得真实 IP?

InetAddress.getLocalHost().getHostAddress()获取的ip为127.0.0.1