From 37cd8bfedd72cce7cf5099bb3d7ffaca232bc9cb Mon Sep 17 00:00:00 2001 From: Chuansheng Lu Date: Wed, 6 Mar 2019 17:41:37 +0800 Subject: [PATCH] [CoreClasslib] Fixed slow DNS resolution bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: - Port fix https://aone.alibaba-inc.com/project/355606/issue/8443788 to dragonwell - Switch implementation of java/net/Inet4AddressImpl.lookupAllHostAddr based on Linux glibc versions. Test Plan: jdk/test/net/ Reviewers: 井桐 Reviewed By: 井桐 Differential Revision: https://aone.alibaba-inc.com/code/D849274 --- .../native/java/net/Inet4AddressImpl.c | 200 ++++++++++++++++-- .../net/Inet4Address/LookUpAllAddrsTest.sh | 147 +++++++++++++ 2 files changed, 333 insertions(+), 14 deletions(-) create mode 100644 test/java/net/Inet4Address/LookUpAllAddrsTest.sh diff --git a/src/solaris/native/java/net/Inet4AddressImpl.c b/src/solaris/native/java/net/Inet4AddressImpl.c index 7e193a931..c42e21558 100644 --- a/src/solaris/native/java/net/Inet4AddressImpl.c +++ b/src/solaris/native/java/net/Inet4AddressImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -51,6 +51,49 @@ #define HAS_GLIBC_GETHOSTBY_R 1 #endif +static jclass ni_iacls; +static jclass ni_ia4cls; +static jmethodID ni_ia4ctrID; + +#if defined(__GLIBC__) + +#include + +static int glibc_major_version = 0; +static int glibc_minor_version = 0; + +static void initialize_glibc_version() { + const char* ver_str = gnu_get_libc_version(); + // version string is in format of "2.6" + char *p = NULL; + glibc_major_version = strtol(ver_str, &p, 10); + ++p; + glibc_minor_version = strtol(p, NULL, 10); +} +#endif // __GLIBC__ + +static jboolean initializeInetClasses(JNIEnv *env) +{ + static int initialized = 0; + if (!initialized) { +#if defined(__GLIBC__) + initialize_glibc_version(); +#endif + ni_iacls = (*env)->FindClass(env, "java/net/InetAddress"); + CHECK_NULL_RETURN(ni_iacls, JNI_FALSE); + ni_iacls = (*env)->NewGlobalRef(env, ni_iacls); + CHECK_NULL_RETURN(ni_iacls, JNI_FALSE); + ni_ia4cls = (*env)->FindClass(env, "java/net/Inet4Address"); + CHECK_NULL_RETURN(ni_ia4cls, JNI_FALSE); + ni_ia4cls = (*env)->NewGlobalRef(env, ni_ia4cls); + CHECK_NULL_RETURN(ni_ia4cls, JNI_FALSE); + ni_ia4ctrID = (*env)->GetMethodID(env, ni_ia4cls, "", "()V"); + CHECK_NULL_RETURN(ni_ia4ctrID, JNI_FALSE); + initialized = 1; + } + return JNI_TRUE; +} + #if defined(_ALLBSD_SOURCE) && !defined(HAS_GLIBC_GETHOSTBY_R) extern jobjectArray lookupIfLocalhost(JNIEnv *env, const char *hostname, jboolean includeV6); @@ -367,20 +410,120 @@ Java_java_net_Inet4AddressImpl_getLocalHostName(JNIEnv *env, jobject this) { return (*env)->NewStringUTF(env, hostname); } -/* - * Find an internet address for a given hostname. Note that this - * code only works for addresses of type INET. The translation - * of %d.%d.%d.%d to an address (int) occurs in java now, so the - * String "host" shouldn't *ever* be a %d.%d.%d.%d string - * - * Class: java_net_Inet4AddressImpl - * Method: lookupAllHostAddr - * Signature: (Ljava/lang/String;)[[B - */ +#ifdef __GLIBC__ -JNIEXPORT jobjectArray JNICALL -Java_java_net_Inet4AddressImpl_lookupAllHostAddr(JNIEnv *env, jobject this, - jstring host) { +/* the initial size of our hostent buffers */ +#define HENT_BUF_SIZE 1024 +#define BIG_HENT_BUF_SIZE 10240 /* a jumbo-sized one */ + +// The implementation code was copied from JDK7u respository +// http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/58e586f18da6/src/solaris/native/java/net/Inet4AddressImpl.c +static jobjectArray +lookupAllHostAddrs_gethostbyname(JNIEnv *env, jobject this, jstring host) { + const char *hostname; + jobjectArray ret = 0; + struct hostent res, *hp = 0; + // this buffer must be pointer-aligned so is declared + // with pointer type + char *buf[HENT_BUF_SIZE/(sizeof (char *))]; + + /* temporary buffer, on the off chance we need to expand */ + char *tmp = NULL; + int h_error = 0; + + if (!initializeInetClasses(env)) + return NULL; + + if (IS_NULL(host)) { + JNU_ThrowNullPointerException(env, "host is null"); + return 0; + } + hostname = JNU_GetStringPlatformChars(env, host, JNI_FALSE); + CHECK_NULL_RETURN(hostname, NULL); + +#ifdef __solaris__ + /* + * Workaround for Solaris bug 4160367 - if a hostname contains a + * white space then 0.0.0.0 is returned + */ + if (isspace((unsigned char)hostname[0])) { + JNU_ThrowByName(env, JNU_JAVANETPKG "UnknownHostException", + (char *)hostname); + JNU_ReleaseStringPlatformChars(env, host, hostname); + return NULL; + } +#endif + + /* Try once, with our static buffer. */ +#ifdef HAS_GLIBC_GETHOSTBY_R + gethostbyname_r(hostname, &res, (char*)buf, sizeof(buf), &hp, &h_error); +#else + hp = gethostbyname_r(hostname, &res, (char*)buf, sizeof(buf), &h_error); +#endif + + /* With the re-entrant system calls, it's possible that the buffer + * we pass to it is not large enough to hold an exceptionally + * large DNS entry. This is signaled by errno->ERANGE. We try once + * more, with a very big size. + */ + if (hp == NULL && errno == ERANGE) { + if ((tmp = (char*)malloc(BIG_HENT_BUF_SIZE))) { +#ifdef HAS_GLIBC_GETHOSTBY_R + gethostbyname_r(hostname, &res, tmp, BIG_HENT_BUF_SIZE, + &hp, &h_error); +#else + hp = gethostbyname_r(hostname, &res, tmp, BIG_HENT_BUF_SIZE, + &h_error); +#endif + } + } + if (hp != NULL) { + struct in_addr **addrp = (struct in_addr **) hp->h_addr_list; + int i = 0; + + while (*addrp != (struct in_addr *) 0) { + i++; + addrp++; + } + + ret = (*env)->NewObjectArray(env, i, ni_iacls, NULL); + if (IS_NULL(ret)) { + /* we may have memory to free at the end of this */ + goto cleanupAndReturn; + } + addrp = (struct in_addr **) hp->h_addr_list; + i = 0; + while (*addrp) { + jobject iaObj = (*env)->NewObject(env, ni_ia4cls, ni_ia4ctrID); + if (IS_NULL(iaObj)) { + ret = NULL; + goto cleanupAndReturn; + } + setInetAddress_addr(env, iaObj, ntohl((*addrp)->s_addr)); + setInetAddress_hostName(env, iaObj, host); + (*env)->SetObjectArrayElement(env, ret, i, iaObj); + addrp++; + i++; + } + } else { + JNU_ThrowByName(env, JNU_JAVANETPKG "UnknownHostException", + (char *)hostname); + ret = NULL; + } + +cleanupAndReturn: + JNU_ReleaseStringPlatformChars(env, host, hostname); + if (tmp != NULL) { + free(tmp); + } + return ret; +} + +#endif //__GLIBC__ + +// lookupAllHostAddr impl uses getaddrinfo +static jobjectArray +lookupAllHostAddrs_getaddrinfo(JNIEnv *env, jobject this, jstring host) { const char *hostname; jobjectArray ret = 0; int retLen = 0; @@ -508,6 +651,35 @@ Java_java_net_Inet4AddressImpl_lookupAllHostAddr(JNIEnv *env, jobject this, return ret; } +/* + * Find an internet address for a given hostname. Note that this + * code only works for addresses of type INET. The translation + * of %d.%d.%d.%d to an address (int) occurs in java now, so the + * String "host" shouldn't *ever* be a %d.%d.%d.%d string + * + * Class: java_net_Inet4AddressImpl + * Method: lookupAllHostAddr + * Signature: (Ljava/lang/String;)[[B + */ + +JNIEXPORT jobjectArray JNICALL +Java_java_net_Inet4AddressImpl_lookupAllHostAddr(JNIEnv *env, jobject this, + jstring host) { + if (!initializeInetClasses(env)) { + return NULL; + } + +#if defined(__GLIBC__) + if (glibc_major_version >= 2 && glibc_minor_version >= 12) { + return lookupAllHostAddrs_getaddrinfo(env, this, host); + } else { + return lookupAllHostAddrs_gethostbyname(env, this, host); + } +#else + return lookupAllHostAddrs_getaddrinfo(env, this, host); +#endif +} + /* * Class: java_net_Inet4AddressImpl * Method: getHostByAddr diff --git a/test/java/net/Inet4Address/LookUpAllAddrsTest.sh b/test/java/net/Inet4Address/LookUpAllAddrsTest.sh new file mode 100644 index 000000000..79d7dbc31 --- /dev/null +++ b/test/java/net/Inet4Address/LookUpAllAddrsTest.sh @@ -0,0 +1,147 @@ +# +# Copyright (c) 2019 Alibaba Group Holding Limited. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Alibaba designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# +# @test LookUpAllAddrsTest.sh +# @summary test calling InetAddress.getAllByName() without triggering DNS reverse resolution +# @run shell/timeout=30 LookUpAllAddrsTest.sh +# + +# NOTE: this test only works on RPM based Linux distributions + +set -x + +if [ "${TESTSRC}" = "" ] +then + TESTSRC=${PWD} + echo "TESTSRC not set. Using "${TESTSRC}" as default" +fi +echo "TESTSRC=${TESTSRC}" +FS=/ +JAVA=${TESTJAVA}${FS}bin${FS}java +JAVAC=${TESTJAVA}${FS}bin${FS}javac +TEST_CLASS=T +TEST_SOURCE=${TEST_CLASS}.java + +# pre-condition checking +which gdb +if [ $? != 0 ] +then + echo "Cannot find GDB, please install it!" + exit 1 +fi + +which rpm +if [ $? != 0 ] +then + echo Cannot find tool 'rpm', unsupported platform + exit 1 +fi + +GLIBC_VER_MAJOR=$(rpm -qa | sort | uniq | perl -e 'for (<>) { if ($_ =~ /^glibc-(\d+)\.(\d+)\D\S+/ ) { print "$1\n"; exit; } }') +GLIBC_VER_MINOR=$(rpm -qa | sort | uniq | perl -e 'for (<>) { if ($_ =~ /^glibc-(\d+)\.(\d+)\D\S+/ ) { print "$2\n"; exit; } }') + +# Prepare class +cat >> $TEST_SOURCE << EOF +import java.net.*; +import java.util.*; + +public class T { + static { + System.setProperty("sun.net.inetaddr.ttl", "0"); + System.setProperty("sun.net.inetaddr.negative.ttl", "0"); + } + public static void main(String[] args) throws InterruptedException, UnknownHostException { + System.out.println("Test begin!"); + for (int i = 0; i < 10; ++i) { + long t = System.currentTimeMillis(); + InetAddress[] addresses = InetAddress.getAllByName("tbapi.alipay.com"); + System.out.println("COST:" + (System.currentTimeMillis() - t) + " " + Arrays.toString(addresses)); + } + } +} +EOF + +# GDB command file to verify the invocation path +cat >> debug.gdb << EOF +# needed options +handle SIGSEGV nostop pass noprint +handle SIGPIPE nostop pass noprint +set breakpoint pending on + +# set up breakpoints to print callflow info +break getaddrinfo +command + print "#####getaddrinfo" + continue +end + +break gethostbyname_r +command + print "#####gethostbyname_r" + continue +end + +# start execution and exit +run +quit + +EOF + +# Do compilation +${JAVAC} -g $TEST_SOURCE +if [ $? != '0' ] +then + printf "Failed to compile $TEST_SOURCE" + exit 1 +fi + +# Run test +if [ $GLIBC_VER_MAJOR -ge 2 ] && [ $GLIBC_VER_MINOR -ge 12 ] +then + # GLIBC_VER >= 2.12, using getaddrinfo + if [ "x" == "x`gdb --command=debug.gdb --arg $JAVA $TEST_CLASS 2>&1 | grep '#####getaddrinfo'`" ] + then + echo "Failed!" + exit 1 + fi + + if [ "x" != "x`gdb --command=debug.gdb --arg $JAVA $TEST_CLASS 2>&1 | grep '#####gethostbyname_r'`" ] + then + echo "Failed!" + exit 1 + fi +else + # GLIBC_VER < 2.12, using gethostbyname_r + if [ "x" != "x`gdb --command=debug.gdb --arg $JAVA $TEST_CLASS 2>&1 | grep '#####getaddrinfo'`" ] + then + echo "Failed!" + exit 1 + fi + + if [ "x" == "x`gdb --command=debug.gdb --arg $JAVA $TEST_CLASS 2>&1 | grep '#####gethostbyname_r'`" ] + then + echo "Failed!" + exit 1 + fi +fi + + -- GitLab