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

And*_*ipp 11 java rhel7 openjdk-11

我写了一个简单的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,行为肯定有所不同。知道为什么会发生这种情况吗?

Bas*_*sen 9

这可能与此处报告的问题相同: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 实际上忽略了本地计算机设置并立即委托给命名服务。


jcc*_*ero 5

在底层,为了获取本地主机名称,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);
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,当使用与 Solaris 不同的东西时,代码似乎只依赖于gethostname获取所需的值。此限制是在该 bug的上下文中引入的。

在这里您可以看到 Java 8 的类似 IP 4 版本本机源代码实现。

在该源代码中,您可以发现与 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) */

...
Run Code Online (Sandbox Code Playgroud)

条件适用与否,规定的实施方式也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);
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,最后一个实现调用gethostname也是首先调用的,尽管是间接使用JVM_GetHostName,包装在 C++ 代码中

JVM_LEAF(int, JVM_GetHostName(char* name, int namelen))
  JVMWrapper("JVM_GetHostName");
  return os::get_host_name(name, namelen);
JVM_END
Run Code Online (Sandbox Code Playgroud)

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

inline int os::get_host_name(char* name, int namelen) {
  return ::gethostname(name, namelen);
}
Run Code Online (Sandbox Code Playgroud)

如果调用gethostname成功,getaddrinfo则使用 所返回的主机名进行调用gethostname。如果反过来,最后一个调用成功,getnameinfo则调用返回的地址getaddrinfo以获取最终主机名。

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