changeset 59497:811be96aa6d4

8244958: preferIPv4Stack and preferIPv6Addresses do not affect addresses returned by HostsFileNameService Reviewed-by: dfuchs, alanb, vtewari
author aefimov
date Fri, 29 May 2020 13:39:16 +0100
parents f67f90f4e8e2
children cfe86b9b00f9
files src/java.base/share/classes/java/net/InetAddress.java test/jdk/java/net/InetAddress/HostsFileOrderingTest.java
diffstat 2 files changed, 226 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/net/InetAddress.java	Fri May 29 14:28:13 2020 +0200
+++ b/src/java.base/share/classes/java/net/InetAddress.java	Fri May 29 13:39:16 2020 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2020, 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
@@ -25,11 +25,11 @@
 
 package java.net;
 
+import java.util.List;
 import java.util.NavigableSet;
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.Scanner;
-import java.security.AccessController;
 import java.io.File;
 import java.io.ObjectStreamException;
 import java.io.ObjectStreamField;
@@ -954,29 +954,18 @@
      */
     private static final class HostsFileNameService implements NameService {
 
+        private static final InetAddress[] EMPTY_ARRAY = new InetAddress[0];
+
+        // Specify if only IPv4 addresses should be returned by HostsFileService implementation
+        private static final boolean preferIPv4Stack = Boolean.parseBoolean(
+                GetPropertyAction.privilegedGetProperty("java.net.preferIPv4Stack"));
+
         private final String hostsFile;
 
-        public HostsFileNameService (String hostsFileName) {
+        public HostsFileNameService(String hostsFileName) {
             this.hostsFile = hostsFileName;
         }
 
-        private  String addrToString(byte addr[]) {
-          String stringifiedAddress = null;
-
-            if (addr.length == Inet4Address.INADDRSZ) {
-                stringifiedAddress = Inet4Address.numericToTextFormat(addr);
-            } else { // treat as an IPV6 jobby
-                byte[] newAddr
-                    = IPAddressUtil.convertFromIPv4MappedAddress(addr);
-                if (newAddr != null) {
-                   stringifiedAddress = Inet4Address.numericToTextFormat(addr);
-                } else {
-                    stringifiedAddress = Inet6Address.numericToTextFormat(addr);
-                }
-            }
-            return stringifiedAddress;
-        }
-
         /**
          * Lookup the host name  corresponding to the IP address provided.
          * Search the configured host file a host name corresponding to
@@ -1037,15 +1026,15 @@
         public InetAddress[] lookupAllHostAddr(String host)
                 throws UnknownHostException {
             String hostEntry;
-            String addrStr = null;
-            InetAddress[] res = null;
-            byte addr[] = new byte[4];
-            ArrayList<InetAddress> inetAddresses = null;
+            String addrStr;
+            byte addr[];
+            List<InetAddress> inetAddresses = new ArrayList<>();
+            List<InetAddress> inet4Addresses = new ArrayList<>();
+            List<InetAddress> inet6Addresses = new ArrayList<>();
 
             // lookup the file and create a list InetAddress for the specified host
             try (Scanner hostsFileScanner = new Scanner(new File(hostsFile),
-                                                        UTF_8.INSTANCE))
-            {
+                                                        UTF_8.INSTANCE)) {
                 while (hostsFileScanner.hasNextLine()) {
                     hostEntry = hostsFileScanner.nextLine();
                     if (!hostEntry.startsWith("#")) {
@@ -1054,11 +1043,15 @@
                             addrStr = extractHostAddr(hostEntry, host);
                             if ((addrStr != null) && (!addrStr.isEmpty())) {
                                 addr = createAddressByteArray(addrStr);
-                                if (inetAddresses == null) {
-                                    inetAddresses = new ArrayList<>(1);
-                                }
                                 if (addr != null) {
-                                    inetAddresses.add(InetAddress.getByAddress(host, addr));
+                                    InetAddress address = InetAddress.getByAddress(host, addr);
+                                    inetAddresses.add(address);
+                                    if (address instanceof Inet4Address) {
+                                        inet4Addresses.add(address);
+                                    }
+                                    if (address instanceof Inet6Address) {
+                                        inet6Addresses.add(address);
+                                    }
                                 }
                             }
                         }
@@ -1069,13 +1062,32 @@
                         + " as hosts file " + hostsFile + " not found ");
             }
 
-            if (inetAddresses != null) {
-                res = inetAddresses.toArray(new InetAddress[inetAddresses.size()]);
+            List<InetAddress> res;
+            // If "preferIPv4Stack" system property is set to "true" then return
+            // only IPv4 addresses
+            if (preferIPv4Stack) {
+                res = inet4Addresses;
             } else {
+                // Otherwise, analyse "preferIPv6Addresses" value
+                res = switch (preferIPv6Address) {
+                    case PREFER_IPV4_VALUE -> concatAddresses(inet4Addresses, inet6Addresses);
+                    case PREFER_IPV6_VALUE -> concatAddresses(inet6Addresses, inet4Addresses);
+                    default -> inetAddresses;
+                };
+            }
+
+            if (res.isEmpty()) {
                 throw new UnknownHostException("Unable to resolve host " + host
                         + " in hosts file " + hostsFile);
             }
-            return res;
+            return res.toArray(EMPTY_ARRAY);
+        }
+
+        private static List<InetAddress> concatAddresses(List<InetAddress> firstPart,
+                                                         List<InetAddress> secondPart) {
+            List<InetAddress> result = new ArrayList<>(firstPart);
+            result.addAll(secondPart);
+            return result;
         }
 
         private String removeComments(String hostsEntry) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/InetAddress/HostsFileOrderingTest.java	Fri May 29 13:39:16 2020 +0100
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.testng.Assert;
+
+
+/* @test
+ * @bug 8244958
+ * @summary Test that "jdk.net.hosts.file" NameService implementation returns addresses
+ *          with respect to "java.net.preferIPv4Stack" and "java.net.preferIPv6Addresses" system
+ *          property values
+ * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt
+ *    -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=true HostsFileOrderingTest
+ * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt
+ *    -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false HostsFileOrderingTest
+ * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt
+ *    -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=system HostsFileOrderingTest
+ * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt
+ *    -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=notVALID HostsFileOrderingTest
+ * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt
+ *    -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=true HostsFileOrderingTest
+ * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt
+ *    -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=false HostsFileOrderingTest
+ * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt
+ *    -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=system HostsFileOrderingTest
+ * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt
+ *    -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=notVALID HostsFileOrderingTest
+ * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt HostsFileOrderingTest
+ */
+
+public class HostsFileOrderingTest {
+
+    /*
+     * Generate hosts file with the predefined list of IP addresses
+     */
+    @BeforeClass
+    public void generateHostsFile() throws Exception {
+        String content = ADDRESSES_LIST.stream()
+                .map(addr -> addr + " " + TEST_HOST_NAME)
+                .collect(
+                        Collectors.joining(System.lineSeparator(),
+                                "# Generated hosts file"+System.lineSeparator(),
+                                System.lineSeparator())
+                );
+        Files.write(HOSTS_FILE_PATH, content.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /*
+     * Test that HostsFile name service returns addresses in order that complies with the
+     *  'sun.net.preferIPv4Stack' and 'preferIPv6Addresses'
+     */
+    @Test
+    public void testOrdering() throws Exception {
+        String [] resolvedAddresses = Arrays.stream(InetAddress.getAllByName("hostname.test.com"))
+                .map(InetAddress::getHostAddress).toArray(String[]::new);
+        String [] expectedAddresses = getExpectedAddressesArray();
+
+        if (Arrays.deepEquals(resolvedAddresses, expectedAddresses)) {
+            System.err.println("Test passed: The expected list of IP addresses is returned");
+        } else {
+            System.err.printf("Expected addresses:%n%s%n", Arrays.deepToString(expectedAddresses));
+            System.err.printf("Resolved addresses:%n%s%n", Arrays.deepToString(resolvedAddresses));
+            Assert.fail("Wrong host resolution result is returned");
+        }
+    }
+
+    /*
+     * Calculate expected order of IP addresses based on the "preferIPv6Addresses" and "preferIPv4Stack"
+     * system property values
+     */
+    static ExpectedOrder getExpectedOrderFromSystemProperties() {
+        if (PREFER_IPV4_STACK_VALUE != null &&
+            PREFER_IPV4_STACK_VALUE.equalsIgnoreCase("true")) {
+            return ExpectedOrder.IPV4_ONLY;
+        }
+
+        if (PREFER_IPV6_ADDRESSES_VALUE != null) {
+            return switch(PREFER_IPV6_ADDRESSES_VALUE.toLowerCase()) {
+                case "true" -> ExpectedOrder.IPV6_IPV4;
+                case "system" -> ExpectedOrder.NO_MODIFICATION;
+                default -> ExpectedOrder.IPV4_IPV6;
+            };
+        }
+        return ExpectedOrder.IPV4_IPV6;
+    }
+
+
+    /*
+     * Return array expected to be returned by InetAddress::getAllByName call
+     */
+    static String[] getExpectedAddressesArray() {
+        List<String> resList = switch (getExpectedOrderFromSystemProperties()) {
+            case IPV4_ONLY -> IPV4_LIST;
+            case IPV6_IPV4 -> IPV6_THEN_IPV4_LIST;
+            case IPV4_IPV6 -> IPV4_THEN_IPV6_LIST;
+            case NO_MODIFICATION -> ADDRESSES_LIST;
+        };
+        return resList.toArray(String[]::new);
+    }
+
+
+    // Possible types of addresses order
+    enum ExpectedOrder {
+        IPV4_ONLY,
+        IPV6_IPV4,
+        IPV4_IPV6,
+        NO_MODIFICATION;
+    }
+
+    // Addresses list
+    private static final List<String> ADDRESSES_LIST = List.of(
+            "192.168.239.11",
+            "2001:db8:85a3:0:0:8a2e:370:7334",
+            "192.168.14.10",
+            "2001:85a3:db8:0:0:8a2e:7334:370",
+            "192.168.129.16",
+            "2001:dead:beef:0:0:8a2e:1239:212"
+    );
+
+    // List of IPv4 addresses. The order is as in hosts file
+    private static final List<String> IPV4_LIST = ADDRESSES_LIST.stream()
+            .filter(ips -> ips.contains("."))
+            .collect(Collectors.toUnmodifiableList());
+
+    // List of IPv6 addresses. The order is as in hosts file
+    private static final List<String> IPV6_LIST = ADDRESSES_LIST.stream()
+            .filter(ip -> ip.contains(":"))
+            .collect(Collectors.toUnmodifiableList());
+
+    // List of IPv4 then IPv6 addresses. Orders inside each address type block is the same
+    // as in hosts file
+    private static final List<String> IPV4_THEN_IPV6_LIST = Stream.of(IPV4_LIST, IPV6_LIST)
+            .flatMap(Collection::stream).collect(Collectors.toList());
+
+    // List of IPv6 then IPv4 addresses. Orders inside each address type block is the same
+    // as in hosts file
+    private static final List<String> IPV6_THEN_IPV4_LIST = Stream.of(IPV6_LIST, IPV4_LIST)
+            .flatMap(Collection::stream).collect(Collectors.toList());
+
+    private static final String HOSTS_FILE_NAME = "TestHostsFile.txt";
+    private static final Path HOSTS_FILE_PATH = Paths.get(
+            System.getProperty("user.dir", ".")).resolve(HOSTS_FILE_NAME);
+    private static final String TEST_HOST_NAME = "hostname.test.com";
+    private static final String PREFER_IPV6_ADDRESSES_VALUE =
+            System.getProperty("java.net.preferIPv6Addresses");
+    private static final String PREFER_IPV4_STACK_VALUE =
+            System.getProperty("java.net.preferIPv4Stack");
+}