8016521: InetAddress should not always re-order addresses returned from name service
authorvtewari
Tue, 24 May 2016 20:15:18 +0100
changeset 38552 ca398af91529
parent 38551 82c48058acc2
child 38553 0553095041d7
8016521: InetAddress should not always re-order addresses returned from name service Reviewed-by: chegar
jdk/src/java.base/share/classes/java/net/Inet6AddressImpl.java
jdk/src/java.base/share/classes/java/net/InetAddress.java
jdk/src/java.base/share/classes/java/net/doc-files/net-properties.html
jdk/src/java.base/share/native/libnet/InetAddress.c
jdk/src/java.base/unix/native/libnet/Inet6AddressImpl.c
jdk/src/java.base/windows/native/libnet/Inet6AddressImpl.c
jdk/test/java/net/Inet6Address/PreferIPv6AddressesTest.java
--- a/jdk/src/java.base/share/classes/java/net/Inet6AddressImpl.java	Tue May 24 10:14:41 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/net/Inet6AddressImpl.java	Tue May 24 20:15:18 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2016, 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
@@ -23,7 +23,10 @@
  * questions.
  */
 package java.net;
+
 import java.io.IOException;
+import static java.net.InetAddress.PREFER_IPV6_VALUE;
+import static java.net.InetAddress.PREFER_SYSTEM_VALUE;
 
 /*
  * Package private implementation of InetAddressImpl for dual
@@ -35,15 +38,23 @@
  *
  * @since 1.4
  */
+class Inet6AddressImpl implements InetAddressImpl {
 
-class Inet6AddressImpl implements InetAddressImpl {
     public native String getLocalHostName() throws UnknownHostException;
-    public native InetAddress[]
-        lookupAllHostAddr(String hostname) throws UnknownHostException;
+
+    public native InetAddress[] lookupAllHostAddr(String hostname)
+        throws UnknownHostException;
+
     public native String getHostByAddr(byte[] addr) throws UnknownHostException;
-    private native boolean isReachable0(byte[] addr, int scope, int timeout, byte[] inf, int ttl, int if_scope) throws IOException;
+
+    private native boolean isReachable0(byte[] addr, int scope, int timeout,
+                                        byte[] inf, int ttl, int if_scope)
+        throws IOException;
 
-    public boolean isReachable(InetAddress addr, int timeout, NetworkInterface netif, int ttl) throws IOException {
+    public boolean isReachable(InetAddress addr, int timeout,
+                               NetworkInterface netif, int ttl)
+        throws IOException
+    {
         byte[] ifaddr = null;
         int scope = -1;
         int netif_scope = -1;
@@ -79,7 +90,8 @@
 
     public synchronized InetAddress anyLocalAddress() {
         if (anyLocalAddress == null) {
-            if (InetAddress.preferIPv6Address) {
+            if (InetAddress.preferIPv6Address == PREFER_IPV6_VALUE ||
+                InetAddress.preferIPv6Address == PREFER_SYSTEM_VALUE) {
                 anyLocalAddress = new Inet6Address();
                 anyLocalAddress.holder().hostName = "::";
             } else {
@@ -91,7 +103,8 @@
 
     public synchronized InetAddress loopbackAddress() {
         if (loopbackAddress == null) {
-             if (InetAddress.preferIPv6Address) {
+             if (InetAddress.preferIPv6Address == PREFER_IPV6_VALUE ||
+                 InetAddress.preferIPv6Address == PREFER_SYSTEM_VALUE) {
                  byte[] loopback =
                         {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
@@ -103,6 +116,6 @@
         return loopbackAddress;
     }
 
-    private InetAddress      anyLocalAddress;
-    private InetAddress      loopbackAddress;
+    private InetAddress anyLocalAddress;
+    private InetAddress loopbackAddress;
 }
--- a/jdk/src/java.base/share/classes/java/net/InetAddress.java	Tue May 24 10:14:41 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/net/InetAddress.java	Tue May 24 20:15:18 2016 +0100
@@ -26,8 +26,6 @@
 package java.net;
 
 import java.util.NavigableSet;
-import java.util.Iterator;
-import java.util.List;
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.Scanner;
@@ -41,6 +39,7 @@
 import java.io.ObjectInputStream.GetField;
 import java.io.ObjectOutputStream;
 import java.io.ObjectOutputStream.PutField;
+import java.lang.annotation.Native;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ConcurrentSkipListSet;
@@ -193,6 +192,11 @@
  */
 public
 class InetAddress implements java.io.Serializable {
+
+    @Native static final int PREFER_IPV4_VALUE = 0;
+    @Native static final int PREFER_IPV6_VALUE = 1;
+    @Native static final int PREFER_SYSTEM_VALUE = 2;
+
     /**
      * Specify the address family: Internet Protocol, Version 4
      * @since 1.4
@@ -206,8 +210,7 @@
     static final int IPv6 = 2;
 
     /* Specify address family preference */
-    static transient boolean preferIPv6Address = false;
-
+    static transient final int preferIPv6Address;
 
     static class InetAddressHolder {
         /**
@@ -293,8 +296,19 @@
      * Load net library into runtime, and perform initializations.
      */
     static {
-        preferIPv6Address = java.security.AccessController.doPrivileged(
-            new GetBooleanAction("java.net.preferIPv6Addresses")).booleanValue();
+        String str = java.security.AccessController.doPrivileged(
+                new GetPropertyAction("java.net.preferIPv6Addresses"));
+        if (str == null) {
+            preferIPv6Address = PREFER_IPV4_VALUE;
+        } else if (str.equalsIgnoreCase("true")) {
+            preferIPv6Address = PREFER_IPV6_VALUE;
+        } else if (str.equalsIgnoreCase("false")) {
+            preferIPv6Address = PREFER_IPV4_VALUE;
+        } else if (str.equalsIgnoreCase("system")) {
+            preferIPv6Address = PREFER_SYSTEM_VALUE;
+        } else {
+            preferIPv6Address = PREFER_IPV4_VALUE;
+        }
         AccessController.doPrivileged(
             new java.security.PrivilegedAction<>() {
                 public Void run() {
--- a/jdk/src/java.base/share/classes/java/net/doc-files/net-properties.html	Tue May 24 10:14:41 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/net/doc-files/net-properties.html	Tue May 24 20:15:18 2016 +0100
@@ -1,5 +1,5 @@
 <!--
- Copyright (c) 1998, 2012, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 1998, 2016, 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
@@ -35,7 +35,7 @@
 java.net package. Some are checked only once at startup of the VM,
 and therefore are best set using the -D option of the java command,
 while others have a more dynamic nature and can also be changed using
-the <a href="../../lang/System.html#setProperty(java.lang.String,%20java.lang.String)">System.setProperty()</a> API. 
+the <a href="../../lang/System.html#setProperty(java.lang.String,%20java.lang.String)">System.setProperty()</a> API.
 The purpose of this document is to list
 and detail all of these properties.</P>
 <P>If there is no special note, a property value is checked every time it is used.</P>
@@ -58,7 +58,8 @@
 	applications that depend on the representation of an IPv4 address
 	(e.g. 192.168.1.1). This property can be set to <B>true</B> to
 	change that preference and use IPv6 addresses over IPv4 ones where
-	possible.</P>
+	possible, or <B>system</B> to preserve the order of the addresses as
+    returned by the operating system.</P>
 </UL>
 <P>Both of these properties are checked only once, at startup.</P>
 <a name="Proxies"></a>
@@ -73,7 +74,7 @@
 	<P>The following proxy settings are used by the HTTP protocol handler.</P>
 	<UL>
 		<LI><P><B>http.proxyHost</B> (default: &lt;none&gt;)<BR>
-	        The hostname, or address, of the proxy server 
+	        The hostname, or address, of the proxy server
 		</P>
 		<LI><P><B>http.proxyPort</B> (default: 80)<BR>
 	        The port number of the proxy server.</P>
@@ -94,7 +95,7 @@
 	<P>The following proxy settings are used by the HTTPS protocol handler.</P>
 	<UL>
 		<LI><P><B>https.proxyHost</B>(default: &lt;none&gt;)<BR>
-	        The hostname, or address, of the proxy server 
+	        The hostname, or address, of the proxy server
 		</P>
 		<LI><P><B>https.proxyPort</B> (default: 443)<BR>
 	        The port number of the proxy server.</P>
@@ -105,7 +106,7 @@
 	<P>The following proxy settings are used by the FTP protocol handler.</P>
 	<UL>
 		<LI><P><B>ftp.proxyHost</B>(default: &lt;none&gt;)<BR>
-	        The hostname, or address, of the proxy server 
+	        The hostname, or address, of the proxy server
 		</P>
 		<LI><P><B>ftp.proxyPort</B> (default: 80)<BR>
 	        The port number of the proxy server.</P>
@@ -160,7 +161,7 @@
 	<LI><P><B>http.agent</B> (default: &ldquo;Java/&lt;version&gt;&rdquo;)<BR>
 	Defines the string sent in the User-Agent request header in http
 	requests. Note that the string &ldquo;Java/&lt;version&gt;&rdquo; will
-	be appended to the one provided in the property (e.g. if 
+	be appended to the one provided in the property (e.g. if
 	-Dhttp.agent=&rdquo;foobar&rdquo; is used, the User-Agent header will
 	contain &ldquo;foobar Java/1.5.0&rdquo; if the version of the VM is
 	1.5.0). This property is checked only once at startup.</P>
--- a/jdk/src/java.base/share/native/libnet/InetAddress.c	Tue May 24 10:14:41 2016 -0700
+++ b/jdk/src/java.base/share/native/libnet/InetAddress.c	Tue May 24 20:15:18 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2016, 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
@@ -61,7 +61,7 @@
         CHECK_NULL(iac_class);
         ia_holderID = (*env)->GetFieldID(env, ia_class, "holder", "Ljava/net/InetAddress$InetAddressHolder;");
         CHECK_NULL(ia_holderID);
-        ia_preferIPv6AddressID = (*env)->GetStaticFieldID(env, ia_class, "preferIPv6Address", "Z");
+        ia_preferIPv6AddressID = (*env)->GetStaticFieldID(env, ia_class, "preferIPv6Address", "I");
         CHECK_NULL(ia_preferIPv6AddressID);
 
         iac_addressID = (*env)->GetFieldID(env, iac_class, "address", "I");
--- a/jdk/src/java.base/unix/native/libnet/Inet6AddressImpl.c	Tue May 24 10:14:41 2016 -0700
+++ b/jdk/src/java.base/unix/native/libnet/Inet6AddressImpl.c	Tue May 24 20:15:18 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, 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
@@ -48,6 +48,7 @@
 
 #include "java_net_Inet4AddressImpl.h"
 #include "java_net_Inet6AddressImpl.h"
+#include "java_net_InetAddress.h"
 
 /* the initial size of our hostent buffers */
 #ifndef NI_MAXHOST
@@ -312,8 +313,8 @@
         JNU_ReleaseStringPlatformChars(env, host, hostname);
         return NULL;
     } else {
-        int i = 0;
-        int inetCount = 0, inet6Count = 0, inetIndex, inet6Index;
+        int i = 0, addressPreference = -1;
+        int inetCount = 0, inet6Count = 0, inetIndex = 0, inet6Index = 0, originalIndex = 0;
         struct addrinfo *itr, *last = NULL, *iterator = res;
         while (iterator != NULL) {
             int skip = 0;
@@ -394,14 +395,18 @@
             goto cleanupAndReturn;
         }
 
-        if ((*env)->GetStaticBooleanField(env, ia_class, ia_preferIPv6AddressID)) {
+        addressPreference = (*env)->GetStaticIntField(env, ia_class, ia_preferIPv6AddressID);
+
+        if (addressPreference == java_net_InetAddress_PREFER_IPV6_VALUE) {
             /* AF_INET addresses will be offset by inet6Count */
             inetIndex = inet6Count;
             inet6Index = 0;
-        } else {
+        } else if (addressPreference == java_net_InetAddress_PREFER_IPV4_VALUE) {
             /* AF_INET6 addresses will be offset by inetCount */
             inetIndex = 0;
             inet6Index = inetCount;
+        } else if (addressPreference == java_net_InetAddress_PREFER_SYSTEM_VALUE) {
+            inetIndex = inet6Index = originalIndex = 0;
         }
 
         while (iterator != NULL) {
@@ -414,7 +419,7 @@
                 }
                 setInetAddress_addr(env, iaObj, ntohl(((struct sockaddr_in*)iterator->ai_addr)->sin_addr.s_addr));
                 setInetAddress_hostName(env, iaObj, host);
-                (*env)->SetObjectArrayElement(env, ret, inetIndex, iaObj);
+                (*env)->SetObjectArrayElement(env, ret, (inetIndex | originalIndex), iaObj);
                 inetIndex++;
             } else if (iterator->ai_family == AF_INET6) {
                 jint scope = 0;
@@ -435,9 +440,13 @@
                     setInet6Address_scopeid(env, iaObj, scope);
                 }
                 setInetAddress_hostName(env, iaObj, host);
-                (*env)->SetObjectArrayElement(env, ret, inet6Index, iaObj);
+                (*env)->SetObjectArrayElement(env, ret, (inet6Index | originalIndex), iaObj);
                 inet6Index++;
             }
+            if (addressPreference == java_net_InetAddress_PREFER_SYSTEM_VALUE) {
+                originalIndex++;
+                inetIndex = inet6Index = 0;
+            }
             iterator = iterator->ai_next;
         }
     }
--- a/jdk/src/java.base/windows/native/libnet/Inet6AddressImpl.c	Tue May 24 10:14:41 2016 -0700
+++ b/jdk/src/java.base/windows/native/libnet/Inet6AddressImpl.c	Tue May 24 20:15:18 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, 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
@@ -97,7 +97,7 @@
 
     /* get the address preference */
     preferIPv6Address
-        = (*env)->GetStaticBooleanField(env, ia_class, ia_preferIPv6AddressID);
+        = (*env)->GetStaticIntField(env, ia_class, ia_preferIPv6AddressID);
 
     /* Try once, with our static buffer. */
     memset(&hints, 0, sizeof(hints));
@@ -122,7 +122,7 @@
         }
     } else {
         int i = 0;
-        int inetCount = 0, inet6Count = 0, inetIndex, inet6Index;
+        int inetCount = 0, inet6Count = 0, inetIndex = 0, inet6Index = 0, originalIndex = 0;
         struct addrinfo *itr, *last, *iterator = res;
         while (iterator != NULL) {
             int skip = 0;
@@ -203,12 +203,14 @@
             goto cleanupAndReturn;
         }
 
-        if (preferIPv6Address) {
+        if (preferIPv6Address == java_net_InetAddress_PREFER_IPV6_VALUE) {
             inetIndex = inet6Count;
             inet6Index = 0;
-        } else {
+        } else if (preferIPv6Address == java_net_InetAddress_PREFER_IPV4_VALUE) {
             inetIndex = 0;
             inet6Index = inetCount;
+        } else if (preferIPv6Address == java_net_InetAddress_PREFER_SYSTEM_VALUE) {
+            inetIndex = inet6Index = originalIndex = 0;
         }
 
         while (iterator != NULL) {
@@ -220,7 +222,7 @@
               }
               setInetAddress_addr(env, iaObj, ntohl(((struct sockaddr_in*)iterator->ai_addr)->sin_addr.s_addr));
               setInetAddress_hostName(env, iaObj, host);
-              (*env)->SetObjectArrayElement(env, ret, inetIndex, iaObj);
+              (*env)->SetObjectArrayElement(env, ret, (inetIndex | originalIndex), iaObj);
                 inetIndex ++;
             } else if (iterator->ai_family == AF_INET6) {
               jint scope = 0;
@@ -240,9 +242,13 @@
                 setInet6Address_scopeid(env, iaObj, scope);
               }
               setInetAddress_hostName(env, iaObj, host);
-              (*env)->SetObjectArrayElement(env, ret, inet6Index, iaObj);
+              (*env)->SetObjectArrayElement(env, ret, (inet6Index | originalIndex), iaObj);
               inet6Index ++;
             }
+            if (preferIPv6Address == java_net_InetAddress_PREFER_SYSTEM_VALUE) {
+                originalIndex++;
+                inetIndex = inet6Index = 0;
+            }
             iterator = iterator->ai_next;
         }
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/Inet6Address/PreferIPv6AddressesTest.java	Tue May 24 20:15:18 2016 +0100
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2016, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8016521
+ * @summary InetAddress should not always re-order addresses returned from name
+ *          service
+ * @run main/othervm -Djava.net.preferIPv6Addresses=false PreferIPv6AddressesTest
+ * @run main/othervm -Djava.net.preferIPv6Addresses=true PreferIPv6AddressesTest
+ * @run main/othervm -Djava.net.preferIPv6Addresses=system PreferIPv6AddressesTest
+ * @run main/othervm PreferIPv6AddressesTest
+ */
+
+import java.io.IOException;
+import java.net.*;
+import java.nio.channels.DatagramChannel;
+import java.util.Arrays;
+import java.util.stream.IntStream;
+import static java.lang.System.out;
+
+public class PreferIPv6AddressesTest {
+
+    // A name, that if resolves, returns both IPv4 and IPv6 addresses.
+    static final String HOST_NAME = "www.google.com";
+
+    static final InetAddress LOOPBACK = InetAddress.getLoopbackAddress();
+
+    static final String preferIPV6Address =
+            System.getProperty("java.net.preferIPv6Addresses", "false");
+
+    public static void main(String args[]) throws IOException {
+
+        InetAddress addrs[];
+        try {
+            addrs = InetAddress.getAllByName(HOST_NAME);
+        } catch (UnknownHostException e) {
+            out.println("Unknown host " + HOST_NAME + ", cannot run test.");
+            return;
+        }
+
+        int firstIPv4Address = IntStream.range(0, addrs.length)
+                .filter(x -> addrs[x] instanceof Inet4Address)
+                .findFirst().orElse(-1);
+        int firstIPv6Address = IntStream.range(0, addrs.length)
+                .filter(x -> addrs[x] instanceof Inet6Address)
+                .findFirst().orElse(-1);
+
+        out.println("IPv6 supported: " + IPv6Supported());
+        out.println("Addresses: " + Arrays.asList(addrs));
+
+        if (preferIPV6Address.equalsIgnoreCase("true") && firstIPv6Address != -1) {
+            int off = firstIPv4Address != -1 ? firstIPv4Address : addrs.length;
+            assertAllv6Addresses(addrs, 0, off);
+            assertAllv4Addresses(addrs, off, addrs.length);
+            assertLoopbackAddress(Inet6Address.class);
+            assertAnyLocalAddress(Inet6Address.class);
+        } else if (preferIPV6Address.equalsIgnoreCase("false") && firstIPv4Address != -1) {
+            int off = firstIPv6Address != -1 ? firstIPv6Address : addrs.length;
+            assertAllv4Addresses(addrs, 0, off);
+            assertAllv6Addresses(addrs, off, addrs.length);
+            assertLoopbackAddress(Inet4Address.class);
+            assertAnyLocalAddress(Inet4Address.class);
+        } else if (preferIPV6Address.equalsIgnoreCase("system") && IPv6Supported()) {
+            assertLoopbackAddress(Inet6Address.class);
+            assertAnyLocalAddress(Inet6Address.class);
+        } else if (preferIPV6Address.equalsIgnoreCase("system") && !IPv6Supported()) {
+            assertLoopbackAddress(Inet4Address.class);
+            assertAnyLocalAddress(Inet4Address.class);
+        }
+    }
+
+    static void assertAllv4Addresses(InetAddress[] addrs, int off, int len) {
+        IntStream.range(off, len)
+                 .mapToObj(x -> addrs[x])
+                 .forEach(x -> {
+                     if (!(x instanceof Inet4Address))
+                         throw new RuntimeException("Expected IPv4, got " + x);
+                 });
+    }
+
+    static void assertAllv6Addresses(InetAddress[] addrs, int off, int len) {
+        IntStream.range(off, len)
+                .mapToObj(x -> addrs[x])
+                .forEach(x -> {
+                    if (!(x instanceof Inet6Address))
+                        throw new RuntimeException("Expected IPv6, got " + x);
+                });
+    }
+
+    static void assertLoopbackAddress(Class<?> expectedType) {
+        if (!LOOPBACK.getClass().isAssignableFrom(expectedType))
+            throw new RuntimeException("Expected " + expectedType
+                    + ", got " + LOOPBACK.getClass());
+    }
+
+    static void assertAnyLocalAddress(Class<?> expectedType) {
+        InetAddress anyAddr = (new InetSocketAddress(0)).getAddress();
+        if (!anyAddr.getClass().isAssignableFrom(expectedType))
+            throw new RuntimeException("Expected " + expectedType
+                    + ", got " + anyAddr.getClass());
+    }
+
+    static boolean IPv6Supported() throws IOException {
+        try {
+            DatagramChannel.open(StandardProtocolFamily.INET6);
+            return true;
+        } catch (UnsupportedOperationException x) {
+            return false;
+        }
+    }
+}