1 /*
  2  * Copyright (c) 2021, 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 testlib;
 27 
 28 import java.io.IOException;
 29 import java.net.InetAddress;
 30 import java.net.UnknownHostException;
 31 import java.net.spi.InetAddressResolver.LookupPolicy;
 32 import java.nio.file.Files;
 33 import java.nio.file.Path;
 34 import java.nio.file.Paths;
 35 import java.util.ArrayList;
 36 import java.util.Arrays;
 37 import java.util.Collections;
 38 import java.util.List;
 39 import java.util.Map;
 40 import java.util.Objects;
 41 import java.util.concurrent.ConcurrentHashMap;
 42 import java.util.function.Predicate;
 43 import java.util.logging.Level;
 44 import java.util.logging.Logger;
 45 import java.util.stream.Collectors;
 46 import java.util.stream.Stream;
 47 import java.util.Comparator;
 48 
 49 import static java.net.spi.InetAddressResolver.LookupPolicy.*;
 50 
 51 public class ResolutionRegistry {
 52 
 53     // Map to store hostName -> InetAddress mappings
 54     private final Map<String, List<byte[]>> registry;
 55     private static final int IPV4_RAW_LEN = 4;
 56     private static final int IPV6_RAW_LEN = 16;
 57 
 58     private static final Logger LOGGER = Logger.getLogger(ResolutionRegistry.class.getName());
 59 
 60     public ResolutionRegistry() {
 61 
 62         // Populate registry from test data file
 63         String fileName = System.getProperty("test.dataFileName", "addresses.txt");
 64         Path addressesFile = Paths.get(System.getProperty("test.src", ".")).resolve(fileName);
 65         LOGGER.info("Creating ResolutionRegistry instance from file:" + addressesFile);
 66         registry = parseDataFile(addressesFile);
 67     }
 68 
 69     private Map<String, List<byte[]>> parseDataFile(Path addressesFile) {
 70         try {
 71             if (addressesFile.toFile().isFile()) {
 72                 Map<String, List<byte[]>> resReg = new ConcurrentHashMap<>();
 73                 // Prepare list of hostname/address entries
 74                 List<String[]> entriesList = Files.readAllLines(addressesFile).stream()
 75                         .map(String::trim)
 76                         .filter(Predicate.not(String::isBlank))
 77                         .filter(s -> !s.startsWith("#"))
 78                         .map(s -> s.split("\\s+"))
 79                         .filter(sarray -> sarray.length == 2)
 80                         .filter(ResolutionRegistry::hasLiteralAddress)
 81                         .filter(Objects::nonNull)
 82                         .collect(Collectors.toList());
 83                 // Convert list of entries into registry Map
 84                 for (var entry : entriesList) {
 85                     String ipAddress = entry[0].trim();
 86                     String hostName = entry[1].trim();
 87                     byte[] addrBytes = toByteArray(ipAddress);
 88                     if (addrBytes != null) {
 89                         var list = resReg.containsKey(hostName) ? resReg.get(hostName) : new ArrayList();
 90                         list.add(addrBytes);
 91                         if (!resReg.containsKey(hostName)) {
 92                             resReg.put(hostName, list);
 93                         }
 94                     }
 95                 }
 96                 resReg.replaceAll((k, v) -> Collections.unmodifiableList(v));
 97                 // Print constructed registry
 98                 StringBuilder sb = new StringBuilder("Constructed addresses registry:" + System.lineSeparator());
 99                 for (var entry : resReg.entrySet()) {
100                     sb.append("\t" + entry.getKey() + ": ");
101                     for (byte[] addr : entry.getValue()) {
102                         sb.append(addressBytesToString(addr) + " ");
103                     }
104                     sb.append(System.lineSeparator());
105                 }
106                 LOGGER.info(sb.toString());
107                 return resReg;
108             } else {
109                 // If file doesn't exist - return empty map
110                 return Collections.emptyMap();
111             }
112         } catch (IOException ioException) {
113             // If any problems parsing the file - log a warning and return an empty map
114             LOGGER.log(Level.WARNING, "Error reading data file", ioException);
115             return Collections.emptyMap();
116         }
117     }
118 
119     // Line is not a blank and not a comment
120     private static boolean hasLiteralAddress(String[] lineFields) {
121         String addressString = lineFields[0].trim();
122         return addressString.charAt(0) == '[' ||
123                 Character.digit(addressString.charAt(0), 16) != -1 ||
124                 (addressString.charAt(0) == ':');
125     }
126 
127     // Line is not blank and not comment
128     private static byte[] toByteArray(String addressString) {
129         InetAddress address;
130         // Will reuse InetAddress functionality to parse literal IP address
131         // strings. This call is guarded by 'hasLiteralAddress' method.
132         try {
133             address = InetAddress.getByName(addressString);
134         } catch (UnknownHostException unknownHostException) {
135             LOGGER.warning("Can't parse address string:'" + addressString + "'");
136             return null;
137         }
138         return address.getAddress();
139     }
140 
141     public Stream<InetAddress> lookupHost(String host, LookupPolicy lookupPolicy)
142             throws UnknownHostException {
143         LOGGER.info("Looking-up '" + host + "' address");
144         if (!registry.containsKey(host)) {
145             LOGGER.info("Registry doesn't contain addresses for '" + host + "'");
146             throw new UnknownHostException(host);
147         }
148 
149         int characteristics = lookupPolicy.characteristics();
150         // Filter IPV4 or IPV6 as needed. Then sort with
151         // comparator for IPV4_FIRST or IPV6_FIRST.
152         return registry.get(host)
153                 .stream()
154                 .filter(ba -> filterAddressByLookupPolicy(ba, characteristics))
155                 .sorted(new AddressOrderPref(characteristics))
156                 .map(ba -> constructInetAddress(host, ba))
157                 .filter(Objects::nonNull);
158     }
159 
160     private static boolean filterAddressByLookupPolicy(byte[] ba, int ch) {
161         // If 0011, return both. If 0001, IPv4. If 0010, IPv6
162         boolean ipv4Flag = (ch & IPV4) == IPV4;
163         boolean ipv6Flag = (ch & IPV6) == IPV6;
164 
165         if (ipv4Flag && ipv6Flag)
166             return true; // Return regardless of length
167         else if (ipv4Flag)
168             return (ba.length == IPV4_RAW_LEN);
169         else if (ipv6Flag)
170             return (ba.length == IPV6_RAW_LEN);
171 
172         throw new RuntimeException("Lookup policy characteristics were improperly set. " +
173                 "Characteristics: " + Integer.toString(ch, 2));
174     }
175 
176     private static InetAddress constructInetAddress(String host, byte[] address) {
177         try {
178             return InetAddress.getByAddress(host, address);
179         } catch (UnknownHostException unknownHostException) {
180             return null;
181         }
182     }
183 
184     public String lookupAddress(byte[] addressBytes) {
185         for (var entry : registry.entrySet()) {
186             if (entry.getValue()
187                     .stream()
188                     .filter(ba -> Arrays.equals(ba, addressBytes))
189                     .findAny()
190                     .isPresent()) {
191                 return entry.getKey();
192             }
193         }
194         try {
195             return InetAddress.getByAddress(addressBytes).getHostAddress();
196         } catch (UnknownHostException unknownHostException) {
197             throw new IllegalArgumentException();
198         }
199     }
200 
201     public boolean containsAddressMapping(InetAddress address) {
202         String hostName = address.getHostName();
203         if (registry.containsKey(hostName)) {
204             var mappedBytes = registry.get(address.getHostName());
205             for (byte[] mappedAddr : mappedBytes) {
206                 if (Arrays.equals(mappedAddr, address.getAddress())) {
207                     return true;
208                 }
209             }
210         }
211         return false;
212     }
213 
214     public static String addressBytesToString(byte[] bytes) {
215         try {
216             return InetAddress.getByAddress(bytes).toString();
217         } catch (UnknownHostException unknownHostException) {
218             return Arrays.toString(bytes);
219         }
220     }
221 
222     private class AddressOrderPref implements Comparator<byte[]> {
223 
224         private final int ch;
225 
226         AddressOrderPref(int ch) {
227             this.ch = ch;
228         }
229 
230         @Override
231         public int compare(byte[] o1, byte[] o2) {
232             // Compares based on address length, 4 bytes for IPv4,
233             // 16 bytes for IPv6.
234             return ((ch & IPV4_FIRST) == IPV4_FIRST) ?
235                     Integer.compare(o1.length, o2.length) :
236                     Integer.compare(o2.length, o1.length);
237         }
238     }
239 }