src/jdk.dns.client/share/classes/jdk/dns/client/internal/HostsFileResolver.java
branchaefimov-dns-client-branch
changeset 58870 35c438a6d45c
child 58971 465a15dd6bed
equal deleted inserted replaced
58869:cc66ac8c7646 58870:35c438a6d45c
       
     1 /*
       
     2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package jdk.dns.client.internal;
       
    27 
       
    28 import jdk.dns.client.AddressFamily;
       
    29 import jdk.dns.client.internal.util.ReloadTracker;
       
    30 import sun.net.util.IPAddressUtil;
       
    31 
       
    32 import java.net.InetAddress;
       
    33 import java.net.UnknownHostException;
       
    34 import java.nio.charset.StandardCharsets;
       
    35 import java.nio.file.Files;
       
    36 import java.nio.file.Path;
       
    37 import java.nio.file.Paths;
       
    38 import java.security.AccessController;
       
    39 import java.security.PrivilegedAction;
       
    40 import java.security.PrivilegedActionException;
       
    41 import java.security.PrivilegedExceptionAction;
       
    42 import java.util.Arrays;
       
    43 import java.util.Collections;
       
    44 import java.util.HashMap;
       
    45 import java.util.List;
       
    46 import java.util.Map;
       
    47 import java.util.Objects;
       
    48 import java.util.concurrent.locks.ReadWriteLock;
       
    49 import java.util.concurrent.locks.ReentrantReadWriteLock;
       
    50 import java.util.function.Predicate;
       
    51 import java.util.stream.Stream;
       
    52 
       
    53 public class HostsFileResolver {
       
    54     private static final String HOSTS_FILE_LOCATION_PROPERTY_VALUE =
       
    55             AccessController.doPrivileged((PrivilegedAction<String>)
       
    56                     () -> System.getProperty("jdk.net.hosts.file", "/etc/hosts")
       
    57             );
       
    58     private static final ReadWriteLock LOCK = new ReentrantReadWriteLock();
       
    59 
       
    60     // 300 seconds, similar to DnsResolverConfiguration in millis since Epoch
       
    61     private static final long REFRESH_TIMEOUT_MILLIS = 300_000;
       
    62     private static final ReloadTracker HOSTS_FILE_TRACKER;
       
    63     private static volatile Map<String, HostFileEntry> HOST_ADDRESSES = Collections.emptyMap();
       
    64 
       
    65     void loadHostsAddresses() {
       
    66         LOCK.readLock().lock();
       
    67         var rsf = HOSTS_FILE_TRACKER.getReloadStatus();
       
    68         try {
       
    69             if (!HOSTS_FILE_TRACKER.getReloadStatus().isReloadNeeded()) {
       
    70                 return;
       
    71             }
       
    72         } finally {
       
    73             LOCK.readLock().unlock();
       
    74         }
       
    75 
       
    76         LOCK.writeLock().lock();
       
    77         try {
       
    78             var rs = HOSTS_FILE_TRACKER.getReloadStatus();
       
    79             // Check if reload is still needed
       
    80             if (rs.isReloadNeeded()) {
       
    81                 if (rs.isFileExists()) {
       
    82                     HOST_ADDRESSES = parseHostsFile();
       
    83                     HOSTS_FILE_TRACKER.updateTimestamps(rs);
       
    84                 } else {
       
    85                     HOST_ADDRESSES = Collections.emptyMap();
       
    86                 }
       
    87             }
       
    88         } finally {
       
    89             LOCK.writeLock().unlock();
       
    90         }
       
    91     }
       
    92 
       
    93     private static String removeComments(String hostsEntry) {
       
    94         String filteredEntry = hostsEntry;
       
    95         int hashIndex;
       
    96 
       
    97         if ((hashIndex = hostsEntry.indexOf("#")) != -1) {
       
    98             filteredEntry = hostsEntry.substring(0, hashIndex);
       
    99         }
       
   100         return filteredEntry;
       
   101     }
       
   102 
       
   103     private static class HostFileEntry {
       
   104         final List<String> names; // Might need to split into aliases and name
       
   105         final InetAddress address;
       
   106         final boolean isValid;
       
   107         final boolean isHostname;
       
   108 
       
   109         HostFileEntry(String[] data) {
       
   110             assert data.length > 1;
       
   111             List<String> ln = List.of(Arrays.copyOfRange(data, 1, data.length));
       
   112             String addressString = data[0];
       
   113             names = ln;
       
   114             address = parseAddress(ln.isEmpty() ? null : ln.get(0), addressString);
       
   115             isValid = address != null;
       
   116             isHostname = false;
       
   117         }
       
   118 
       
   119         @Override
       
   120         public String toString() {
       
   121             return names+"/"+address;
       
   122         }
       
   123 
       
   124         private HostFileEntry(String name, InetAddress address, boolean isHostname) {
       
   125             this.names = List.of(name);
       
   126             this.address = address;
       
   127             this.isValid = address != null;
       
   128             this.isHostname = isHostname;
       
   129         }
       
   130 
       
   131         boolean isValid() {
       
   132             return isValid;
       
   133         }
       
   134         boolean isHostname() {return isHostname;}
       
   135 
       
   136         String getHostName() {
       
   137             return names.get(0);
       
   138         }
       
   139 
       
   140         Stream<HostFileEntry> oneNameStream() {
       
   141             HostFileEntry hostName = new HostFileEntry(names.get(0), address, true);
       
   142             Stream<HostFileEntry> aliases = names.stream()
       
   143                     .skip(1)
       
   144                     .map(n -> new HostFileEntry(n, address, false));
       
   145             return Stream.concat(Stream.of(hostName), aliases);
       
   146         }
       
   147 
       
   148         private InetAddress parseAddress(String hostName, String addressString) {
       
   149             // TODO: Revisit
       
   150             Objects.requireNonNull(hostName);
       
   151 
       
   152             // IPAddressUtil is from
       
   153             var pa = (PrivilegedAction<byte[]>) () -> {
       
   154                 if (IPAddressUtil.isIPv4LiteralAddress(addressString)) {
       
   155                     return IPAddressUtil.textToNumericFormatV4(addressString);
       
   156                 } else if (IPAddressUtil.isIPv6LiteralAddress(addressString)) {
       
   157                     return IPAddressUtil.textToNumericFormatV6(addressString);
       
   158                 }
       
   159                 return null;
       
   160             };
       
   161             byte[] addr = System.getSecurityManager() == null ?
       
   162                     pa.run() : AccessController.doPrivileged(pa);
       
   163 
       
   164             if (addr != null) {
       
   165                 try {
       
   166                     // if (hostName == null) hostName = addressString
       
   167                     return InetAddress.getByAddress(hostName, addr);
       
   168                 } catch (UnknownHostException e) {
       
   169                 }
       
   170             }
       
   171             return null;
       
   172         }
       
   173     }
       
   174 
       
   175     private Map<String, HostFileEntry> parseHostsFile() {
       
   176         Path hf = Paths.get(HOSTS_FILE_LOCATION_PROPERTY_VALUE);
       
   177         try {
       
   178             // TODO: Revisit
       
   179             var pea = (PrivilegedExceptionAction<Boolean>) () -> Files.isRegularFile(hf);
       
   180             boolean isRegularFile = System.getSecurityManager() == null ? pea.run()
       
   181                     : AccessController.doPrivileged(pea);
       
   182 
       
   183             if (isRegularFile) {
       
   184                 var result = new HashMap<String, HostFileEntry>();
       
   185                 var pea2 = (PrivilegedExceptionAction<List<String>>) () -> Files.readAllLines(hf, StandardCharsets.UTF_8);
       
   186                 var lines = System.getSecurityManager() == null ? pea2.run()
       
   187                         : AccessController.doPrivileged(pea2);
       
   188 
       
   189                 lines.stream()
       
   190                         .map(HostsFileResolver::removeComments)
       
   191                         .filter(Predicate.not(String::isBlank))
       
   192                         .map(s -> s.split("\\s+"))
       
   193                         .filter(a -> a.length > 1)
       
   194                         .map(HostFileEntry::new)
       
   195                         .filter(HostFileEntry::isValid)
       
   196                         .flatMap(HostFileEntry::oneNameStream)
       
   197                         .forEachOrdered(
       
   198                                 // If the same host name is listed multiple times then
       
   199                                 // use the first encountered line
       
   200                                 hfe -> result.putIfAbsent(hfe.names.get(0), hfe)
       
   201                         );
       
   202                 return Map.copyOf(result);
       
   203             }
       
   204         } catch (PrivilegedActionException pae) {
       
   205             throw new RuntimeException("Can't read hosts file", pae.getCause());
       
   206         } catch (Exception e) {
       
   207             throw new RuntimeException("Can't read hosts file", e);
       
   208         }
       
   209         return Collections.emptyMap();
       
   210     }
       
   211 
       
   212     public InetAddress getHostAddress(String hostName) throws UnknownHostException {
       
   213         return getHostAddress(hostName, AddressFamily.ANY);
       
   214     }
       
   215 
       
   216     public InetAddress getHostAddress(String hostName, AddressFamily family) throws UnknownHostException {
       
   217 
       
   218         loadHostsAddresses();
       
   219         var map = HOST_ADDRESSES;
       
   220         var he = map.get(hostName);
       
   221         if (he == null) {
       
   222             throw new UnknownHostException(hostName);
       
   223         }
       
   224         var addr = he.address;
       
   225         if (!family.sameFamily(addr)) {
       
   226             throw new UnknownHostException(hostName);
       
   227         }
       
   228         return addr;
       
   229     }
       
   230 
       
   231     public String getByAddress(final InetAddress ha) throws UnknownHostException {
       
   232         loadHostsAddresses();
       
   233         var map = HOST_ADDRESSES;
       
   234         var entry = map.values().stream()
       
   235                 .filter(HostFileEntry::isHostname)
       
   236                 .filter(e -> isAddressBytesTheSame(ha.getAddress(), e.address.getAddress()))
       
   237                 .findFirst();
       
   238         if (entry.isEmpty()) {
       
   239             throw new UnknownHostException(ha.toString());
       
   240         }
       
   241         return entry.get().getHostName();
       
   242     }
       
   243 
       
   244     private static boolean isAddressBytesTheSame(byte [] addr1, byte [] addr2) {
       
   245         if (addr1 == null || addr2 == null) {
       
   246             return false;
       
   247         }
       
   248         if (addr1.length != addr2.length) {
       
   249             return false;
       
   250         }
       
   251         for (int i=0; i<addr1.length; i++) {
       
   252             if (addr1[i] != addr2[i])
       
   253                 return false;
       
   254         }
       
   255         return true;
       
   256     }
       
   257 
       
   258     static {
       
   259         // TODO: Revisit
       
   260         try {
       
   261             var pea = (PrivilegedExceptionAction<ReloadTracker>) () ->
       
   262                     ReloadTracker.newInstance(Paths.get(HOSTS_FILE_LOCATION_PROPERTY_VALUE), REFRESH_TIMEOUT_MILLIS);
       
   263             HOSTS_FILE_TRACKER = System.getSecurityManager() == null ? pea.run() :
       
   264                     AccessController.doPrivileged(pea);
       
   265         } catch (PrivilegedActionException pae) {
       
   266             throw new RuntimeException("Error registering hosts file watch service", pae.getCause());
       
   267         } catch (Exception e) {
       
   268             throw new RuntimeException("Error registering hosts file watch service", e);
       
   269         }
       
   270     }
       
   271 }