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 }