001/* 002 * Copyright 2015-2018 the original author or authors 003 * 004 * This software is licensed under the Apache License, Version 2.0, 005 * the GNU Lesser General Public License version 2 or later ("LGPL") 006 * and the WTFPL. 007 * You may choose either license to govern your use of this software only 008 * upon the condition that you accept all of the terms of either 009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. 010 */ 011package org.minidns; 012 013import org.minidns.MiniDnsFuture.InternalMiniDnsFuture; 014import org.minidns.dnsmessage.DnsMessage; 015import org.minidns.dnsserverlookup.AndroidUsingExec; 016import org.minidns.dnsserverlookup.AndroidUsingReflection; 017import org.minidns.dnsserverlookup.DnsServerLookupMechanism; 018import org.minidns.dnsserverlookup.UnixUsingEtcResolvConf; 019import org.minidns.util.CollectionsUtil; 020import org.minidns.util.ExceptionCallback; 021import org.minidns.util.InetAddressUtil; 022import org.minidns.util.SuccessCallback; 023import org.minidns.util.MultipleIoException; 024 025import java.io.IOException; 026import java.net.Inet4Address; 027import java.net.Inet6Address; 028import java.net.InetAddress; 029import java.net.UnknownHostException; 030import java.util.ArrayList; 031import java.util.Collections; 032import java.util.Iterator; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.Set; 036import java.util.concurrent.ConcurrentHashMap; 037import java.util.concurrent.CopyOnWriteArrayList; 038import java.util.concurrent.CopyOnWriteArraySet; 039import java.util.logging.Level; 040 041/** 042 * A minimal DNS client for SRV/A/AAAA/NS and CNAME lookups, with IDN support. 043 * This circumvents the missing javax.naming package on android. 044 */ 045public class DnsClient extends AbstractDnsClient { 046 047 static final List<DnsServerLookupMechanism> LOOKUP_MECHANISMS = new CopyOnWriteArrayList<>(); 048 049 static final Set<Inet4Address> STATIC_IPV4_DNS_SERVERS = new CopyOnWriteArraySet<>(); 050 static final Set<Inet6Address> STATIC_IPV6_DNS_SERVERS = new CopyOnWriteArraySet<>(); 051 052 static { 053 addDnsServerLookupMechanism(AndroidUsingExec.INSTANCE); 054 addDnsServerLookupMechanism(AndroidUsingReflection.INSTANCE); 055 addDnsServerLookupMechanism(UnixUsingEtcResolvConf.INSTANCE); 056 057 try { 058 Inet4Address googleV4Dns = InetAddressUtil.ipv4From("8.8.8.8"); 059 STATIC_IPV4_DNS_SERVERS.add(googleV4Dns); 060 } catch (IllegalArgumentException e) { 061 LOGGER.log(Level.WARNING, "Could not add static IPv4 DNS Server", e); 062 } 063 064 try { 065 Inet6Address googleV6Dns = InetAddressUtil.ipv6From("[2001:4860:4860::8888]"); 066 STATIC_IPV6_DNS_SERVERS.add(googleV6Dns); 067 } catch (IllegalArgumentException e) { 068 LOGGER.log(Level.WARNING, "Could not add static IPv6 DNS Server", e); 069 } 070 } 071 072 private static final Set<String> blacklistedDnsServers = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(4)); 073 074 private final Set<InetAddress> nonRaServers = Collections.newSetFromMap(new ConcurrentHashMap<InetAddress, Boolean>(4)); 075 076 private boolean askForDnssec = false; 077 private boolean disableResultFilter = false; 078 079 private boolean useHardcodedDnsServers = true; 080 081 /** 082 * Create a new DNS client using the global default cache. 083 */ 084 public DnsClient() { 085 super(); 086 } 087 088 public DnsClient(DnsCache dnsCache) { 089 super(dnsCache); 090 } 091 092 @Override 093 protected DnsMessage.Builder newQuestion(DnsMessage.Builder message) { 094 message.setRecursionDesired(true); 095 message.getEdnsBuilder().setUdpPayloadSize(dataSource.getUdpPayloadSize()).setDnssecOk(askForDnssec); 096 return message; 097 } 098 099 private List<InetAddress> getServerAddresses() { 100 List<InetAddress> dnsServerAddresses = findDnsAddresses(); 101 102 InetAddress[] selectedHardcodedDnsServerAddresses = new InetAddress[2]; 103 if (useHardcodedDnsServers) { 104 InetAddress primaryHardcodedDnsServer = null, secondaryHardcodedDnsServer = null; 105 switch (ipVersionSetting) { 106 case v4v6: 107 primaryHardcodedDnsServer = getRandomHardcodedIpv4DnsServer(); 108 secondaryHardcodedDnsServer = getRandomHarcodedIpv6DnsServer(); 109 break; 110 case v6v4: 111 primaryHardcodedDnsServer = getRandomHarcodedIpv6DnsServer(); 112 secondaryHardcodedDnsServer = getRandomHardcodedIpv4DnsServer(); 113 break; 114 case v4only: 115 primaryHardcodedDnsServer = getRandomHardcodedIpv4DnsServer(); 116 break; 117 case v6only: 118 primaryHardcodedDnsServer = getRandomHarcodedIpv6DnsServer(); 119 break; 120 } 121 selectedHardcodedDnsServerAddresses[0] = primaryHardcodedDnsServer; 122 selectedHardcodedDnsServerAddresses[1] = secondaryHardcodedDnsServer; 123 } 124 for (InetAddress selectedHardcodedDnsServerAddress : selectedHardcodedDnsServerAddresses) { 125 if (selectedHardcodedDnsServerAddress == null) continue; 126 dnsServerAddresses.add(selectedHardcodedDnsServerAddress); 127 } 128 129 return dnsServerAddresses; 130 } 131 132 @Override 133 public DnsMessage query(DnsMessage.Builder queryBuilder) throws IOException { 134 DnsMessage q = newQuestion(queryBuilder).build(); 135 // While this query method does in fact re-use query(Question, String) 136 // we still do a cache lookup here in order to avoid unnecessary 137 // findDNS()calls, which are expensive on Android. Note that we do not 138 // put the results back into the Cache, as this is already done by 139 // query(Question, String). 140 DnsMessage responseMessage = (cache == null) ? null : cache.get(q); 141 if (responseMessage != null) { 142 return responseMessage; 143 } 144 145 List<InetAddress> dnsServerAddresses = getServerAddresses(); 146 147 List<IOException> ioExceptions = new ArrayList<>(dnsServerAddresses.size()); 148 for (InetAddress dns : dnsServerAddresses) { 149 if (nonRaServers.contains(dns)) { 150 LOGGER.finer("Skipping " + dns + " because it was marked as \"recursion not available\""); 151 continue; 152 } 153 154 try { 155 responseMessage = query(q, dns); 156 if (responseMessage == null) { 157 continue; 158 } 159 160 if (!responseMessage.recursionAvailable) { 161 boolean newRaServer = nonRaServers.add(dns); 162 if (newRaServer) { 163 LOGGER.warning("The DNS server " 164 + dns 165 + " returned a response without the \"recursion available\" (RA) flag set. This likely indicates a misconfiguration because the server is not suitable for DNS resolution"); 166 } 167 continue; 168 } 169 170 if (disableResultFilter) { 171 return responseMessage; 172 } 173 174 switch (responseMessage.responseCode) { 175 case NO_ERROR: 176 case NX_DOMAIN: 177 break; 178 default: 179 String warning = "Response from " + dns + " asked for " + q.getQuestion() + " with error code: " 180 + responseMessage.responseCode + '.'; 181 if (!LOGGER.isLoggable(Level.FINE)) { 182 // Only append the responseMessage is log level is not fine. If it is fine or higher, the 183 // response has already been logged. 184 warning += "\n" + responseMessage; 185 } 186 LOGGER.warning(warning); 187 // TODO Create new IOException and add to ioExceptions. 188 continue; 189 } 190 191 return responseMessage; 192 } catch (IOException ioe) { 193 ioExceptions.add(ioe); 194 } 195 } 196 MultipleIoException.throwIfRequired(ioExceptions); 197 // TODO assert that we never return null here. 198 return null; 199 } 200 201 @Override 202 protected MiniDnsFuture<DnsMessage, IOException> queryAsync(DnsMessage.Builder queryBuilder) { 203 DnsMessage q = newQuestion(queryBuilder).build(); 204 // While this query method does in fact re-use query(Question, String) 205 // we still do a cache lookup here in order to avoid unnecessary 206 // findDNS()calls, which are expensive on Android. Note that we do not 207 // put the results back into the Cache, as this is already done by 208 // query(Question, String). 209 DnsMessage responseMessage = (cache == null) ? null : cache.get(q); 210 if (responseMessage != null) { 211 return MiniDnsFuture.from(responseMessage); 212 } 213 214 final List<InetAddress> dnsServerAddresses = getServerAddresses(); 215 216 final InternalMiniDnsFuture<DnsMessage, IOException> future = new InternalMiniDnsFuture<>(); 217 final List<IOException> exceptions = Collections.synchronizedList(new ArrayList<IOException>(dnsServerAddresses.size())); 218 219 // Filter loop. 220 Iterator<InetAddress> it = dnsServerAddresses.iterator(); 221 while (it.hasNext()) { 222 InetAddress dns = it.next(); 223 if (nonRaServers.contains(dns)) { 224 it.remove(); 225 LOGGER.finer("Skipping " + dns + " because it was marked as \"recursion not available\""); 226 continue; 227 } 228 } 229 230 List<MiniDnsFuture<DnsMessage, IOException>> futures = new ArrayList<>(dnsServerAddresses.size()); 231 // "Main" loop. 232 for (InetAddress dns : dnsServerAddresses) { 233 if (future.isDone()) { 234 for (MiniDnsFuture<DnsMessage, IOException> futureToCancel : futures) { 235 futureToCancel.cancel(true); 236 } 237 break; 238 } 239 240 MiniDnsFuture<DnsMessage, IOException> f = queryAsync(q, dns); 241 f.onSuccess(new SuccessCallback<DnsMessage>() { 242 @Override 243 public void onSuccess(DnsMessage result) { 244 future.setResult(result); 245 } 246 }); 247 f.onError(new ExceptionCallback<IOException>() { 248 @Override 249 public void processException(IOException exception) { 250 exceptions.add(exception); 251 if (exceptions.size() == dnsServerAddresses.size()) { 252 future.setException(MultipleIoException.toIOException(exceptions)); 253 } 254 } 255 }); 256 futures.add(f); 257 } 258 259 return future; 260 } 261 262 /** 263 * Retrieve a list of currently configured DNS servers IP addresses. This method does verify that only IP addresses are returned and 264 * nothing else (e.g. DNS names). 265 * <p> 266 * The addresses are discovered by using one (or more) of the configured {@link DnsServerLookupMechanism}s. 267 * </p> 268 * 269 * @return A list of DNS server IP addresses configured for this system. 270 */ 271 public static List<String> findDNS() { 272 List<String> res = null; 273 for (DnsServerLookupMechanism mechanism : LOOKUP_MECHANISMS) { 274 res = mechanism.getDnsServerAddresses(); 275 if (res == null) { 276 continue; 277 } 278 279 assert(!res.isEmpty()); 280 281 // We could cache if res only contains IP addresses and avoid the verification in case. Not sure if its really that beneficial 282 // though, because the list returned by the server mechanism is rather short. 283 284 // Verify the returned DNS servers: Ensure that only valid IP addresses are returned. We want to avoid that something else, 285 // especially a valid DNS name is returned, as this would cause the following String to InetAddress conversation using 286 // getByName(String) to cause a DNS lookup, which would be performed outside of the realm of MiniDNS and therefore also outside 287 // of its DNSSEC guarantees. 288 Iterator<String> it = res.iterator(); 289 while (it.hasNext()) { 290 String potentialDnsServer = it.next(); 291 if (!InetAddressUtil.isIpAddress(potentialDnsServer)) { 292 LOGGER.warning("The DNS server lookup mechanism '" + mechanism.getName() 293 + "' returned an invalid non-IP address result: '" + potentialDnsServer + "'"); 294 it.remove(); 295 } else if (blacklistedDnsServers.contains(potentialDnsServer)) { 296 LOGGER.fine("The DNS server lookup mechanism '" + mechanism.getName() 297 + "' returned a blacklisted result: '" + potentialDnsServer + "'"); 298 it.remove(); 299 } 300 } 301 302 if (!res.isEmpty()) { 303 break; 304 } else { 305 LOGGER.warning("The DNS server lookup mechanism '" + mechanism.getName() 306 + "' returned not a single valid IP address after sanitazion"); 307 } 308 } 309 310 return res; 311 } 312 313 /** 314 * Retrieve a list of currently configured DNS server addresses. 315 * <p> 316 * Note that unlike {@link #findDNS()}, the list returned by this method 317 * will take the IP version setting into account, and order the list by the 318 * preferred address types (IPv4/v6). The returned list is modifiable. 319 * </p> 320 * 321 * @return A list of DNS server addresses. 322 * @see #findDNS() 323 */ 324 public static List<InetAddress> findDnsAddresses() { 325 // The findDNS() method contract guarantees that only IP addresses will be returned. 326 List<String> res = findDNS(); 327 328 if (res == null) { 329 return new ArrayList<>(); 330 } 331 332 final IpVersionSetting setting = DEFAULT_IP_VERSION_SETTING; 333 334 List<Inet4Address> ipv4DnsServer = null; 335 List<Inet6Address> ipv6DnsServer = null; 336 if (setting.v4) { 337 ipv4DnsServer = new ArrayList<>(res.size()); 338 } 339 if (setting.v6) { 340 ipv6DnsServer = new ArrayList<>(res.size()); 341 } 342 343 for (String dnsServerString : res) { 344 // The following invariant must hold: "dnsServerString is a IP address". Therefore findDNS() must only return a List of Strings 345 // representing IP addresses. Otherwise the following call of getByName(String) may perform a DNS lookup without MiniDNS being 346 // involved. Something we want to avoid. 347 assert (InetAddressUtil.isIpAddress(dnsServerString)); 348 349 InetAddress dnsServerAddress; 350 try { 351 dnsServerAddress = InetAddress.getByName(dnsServerString); 352 } catch (UnknownHostException e) { 353 LOGGER.log(Level.SEVERE, "Could not transform '" + dnsServerString + "' to InetAddress", e); 354 continue; 355 } 356 if (dnsServerAddress instanceof Inet4Address) { 357 if (!setting.v4) { 358 continue; 359 } 360 Inet4Address ipv4DnsServerAddress = (Inet4Address) dnsServerAddress; 361 ipv4DnsServer.add(ipv4DnsServerAddress); 362 } else if (dnsServerAddress instanceof Inet6Address) { 363 if (!setting.v6) { 364 continue; 365 } 366 Inet6Address ipv6DnsServerAddress = (Inet6Address) dnsServerAddress; 367 ipv6DnsServer.add(ipv6DnsServerAddress); 368 } else { 369 throw new AssertionError("The address '" + dnsServerAddress + "' is neither of type Inet(4|6)Address"); 370 } 371 } 372 373 List<InetAddress> dnsServers = new LinkedList<>(); 374 375 switch (setting) { 376 case v4v6: 377 dnsServers.addAll(ipv4DnsServer); 378 dnsServers.addAll(ipv6DnsServer); 379 break; 380 case v6v4: 381 dnsServers.addAll(ipv6DnsServer); 382 dnsServers.addAll(ipv4DnsServer); 383 break; 384 case v4only: 385 dnsServers.addAll(ipv4DnsServer); 386 break; 387 case v6only: 388 dnsServers.addAll(ipv6DnsServer); 389 break; 390 } 391 return dnsServers; 392 } 393 394 public static void addDnsServerLookupMechanism(DnsServerLookupMechanism dnsServerLookup) { 395 if (!dnsServerLookup.isAvailable()) { 396 LOGGER.fine("Not adding " + dnsServerLookup.getName() + " as it is not available."); 397 return; 398 } 399 synchronized (LOOKUP_MECHANISMS) { 400 // We can't use Collections.sort(CopyOnWriteArrayList) with Java 7. So we first create a temp array, sort it, and replace 401 // LOOKUP_MECHANISMS with the result. For more information about the Java 7 Collections.sort(CopyOnWriteArarayList) issue see 402 // http://stackoverflow.com/a/34827492/194894 403 // TODO: Remove that workaround once MiniDNS is Java 8 only. 404 ArrayList<DnsServerLookupMechanism> tempList = new ArrayList<>(LOOKUP_MECHANISMS.size() + 1); 405 tempList.addAll(LOOKUP_MECHANISMS); 406 tempList.add(dnsServerLookup); 407 408 // Sadly, this Collections.sort() does not with the CopyOnWriteArrayList on Java 7. 409 Collections.sort(tempList); 410 411 LOOKUP_MECHANISMS.clear(); 412 LOOKUP_MECHANISMS.addAll(tempList); 413 } 414 } 415 416 public static boolean removeDNSServerLookupMechanism(DnsServerLookupMechanism dnsServerLookup) { 417 synchronized (LOOKUP_MECHANISMS) { 418 return LOOKUP_MECHANISMS.remove(dnsServerLookup); 419 } 420 } 421 422 public static boolean addBlacklistedDnsServer(String dnsServer) { 423 return blacklistedDnsServers.add(dnsServer); 424 } 425 426 public static boolean removeBlacklistedDnsServer(String dnsServer) { 427 return blacklistedDnsServers.remove(dnsServer); 428 } 429 430 public boolean isAskForDnssec() { 431 return askForDnssec; 432 } 433 434 public void setAskForDnssec(boolean askForDnssec) { 435 this.askForDnssec = askForDnssec; 436 } 437 438 public boolean isDisableResultFilter() { 439 return disableResultFilter; 440 } 441 442 public void setDisableResultFilter(boolean disableResultFilter) { 443 this.disableResultFilter = disableResultFilter; 444 } 445 446 public boolean isUseHardcodedDnsServersEnabled() { 447 return useHardcodedDnsServers; 448 } 449 450 public void setUseHardcodedDnsServers(boolean useHardcodedDnsServers) { 451 this.useHardcodedDnsServers = useHardcodedDnsServers; 452 } 453 454 public InetAddress getRandomHardcodedIpv4DnsServer() { 455 return CollectionsUtil.getRandomFrom(STATIC_IPV4_DNS_SERVERS, insecureRandom); 456 } 457 458 public InetAddress getRandomHarcodedIpv6DnsServer() { 459 return CollectionsUtil.getRandomFrom(STATIC_IPV6_DNS_SERVERS, insecureRandom); 460 } 461}