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