1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net; 18 19 import static android.net.NetworkUtils.getDnsNetwork; 20 import static android.net.NetworkUtils.resNetworkCancel; 21 import static android.net.NetworkUtils.resNetworkQuery; 22 import static android.net.NetworkUtils.resNetworkResult; 23 import static android.net.NetworkUtils.resNetworkSend; 24 import static android.net.util.DnsUtils.haveIpv4; 25 import static android.net.util.DnsUtils.haveIpv6; 26 import static android.net.util.DnsUtils.rfc6724Sort; 27 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; 28 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; 29 import static android.system.OsConstants.ENONET; 30 31 import android.annotation.CallbackExecutor; 32 import android.annotation.IntDef; 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.os.CancellationSignal; 36 import android.os.Looper; 37 import android.os.MessageQueue; 38 import android.system.ErrnoException; 39 import android.util.Log; 40 41 import com.android.net.module.util.DnsPacket; 42 43 import java.io.FileDescriptor; 44 import java.lang.annotation.Retention; 45 import java.lang.annotation.RetentionPolicy; 46 import java.net.InetAddress; 47 import java.net.UnknownHostException; 48 import java.util.ArrayList; 49 import java.util.List; 50 import java.util.concurrent.Executor; 51 52 /** 53 * Dns resolver class for asynchronous dns querying 54 * 55 * Note that if a client sends a query with more than 1 record in the question section but 56 * the remote dns server does not support this, it may not respond at all, leading to a timeout. 57 * 58 */ 59 public final class DnsResolver { 60 private static final String TAG = "DnsResolver"; 61 private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; 62 private static final int MAXPACKET = 8 * 1024; 63 private static final int SLEEP_TIME_MS = 2; 64 65 @IntDef(prefix = { "CLASS_" }, value = { 66 CLASS_IN 67 }) 68 @Retention(RetentionPolicy.SOURCE) 69 @interface QueryClass {} 70 public static final int CLASS_IN = 1; 71 72 @IntDef(prefix = { "TYPE_" }, value = { 73 TYPE_A, 74 TYPE_AAAA 75 }) 76 @Retention(RetentionPolicy.SOURCE) 77 @interface QueryType {} 78 public static final int TYPE_A = 1; 79 public static final int TYPE_AAAA = 28; 80 // TODO: add below constants as part of QueryType and the public API 81 /** @hide */ 82 public static final int TYPE_PTR = 12; 83 /** @hide */ 84 public static final int TYPE_TXT = 16; 85 /** @hide */ 86 public static final int TYPE_SRV = 33; 87 /** @hide */ 88 public static final int TYPE_ANY = 255; 89 90 @IntDef(prefix = { "FLAG_" }, value = { 91 FLAG_EMPTY, 92 FLAG_NO_RETRY, 93 FLAG_NO_CACHE_STORE, 94 FLAG_NO_CACHE_LOOKUP 95 }) 96 @Retention(RetentionPolicy.SOURCE) 97 @interface QueryFlag {} 98 public static final int FLAG_EMPTY = 0; 99 public static final int FLAG_NO_RETRY = 1 << 0; 100 public static final int FLAG_NO_CACHE_STORE = 1 << 1; 101 public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2; 102 103 @IntDef(prefix = { "ERROR_" }, value = { 104 ERROR_PARSE, 105 ERROR_SYSTEM 106 }) 107 @Retention(RetentionPolicy.SOURCE) 108 @interface DnsError {} 109 /** 110 * Indicates that there was an error parsing the response the query. 111 * The cause of this error is available via getCause() and is a {@link ParseException}. 112 */ 113 public static final int ERROR_PARSE = 0; 114 /** 115 * Indicates that there was an error sending the query. 116 * The cause of this error is available via getCause() and is an ErrnoException. 117 */ 118 public static final int ERROR_SYSTEM = 1; 119 120 private static final int NETID_UNSET = 0; 121 122 private static final DnsResolver sInstance = new DnsResolver(); 123 124 /** 125 * Get instance for DnsResolver 126 */ getInstance()127 public static @NonNull DnsResolver getInstance() { 128 return sInstance; 129 } 130 DnsResolver()131 private DnsResolver() {} 132 133 /** 134 * Base interface for answer callbacks 135 * 136 * @param <T> The type of the answer 137 */ 138 public interface Callback<T> { 139 /** 140 * Success response to 141 * {@link android.net.DnsResolver#query query()} or 142 * {@link android.net.DnsResolver#rawQuery rawQuery()}. 143 * 144 * Invoked when the answer to a query was successfully parsed. 145 * 146 * @param answer <T> answer to the query. 147 * @param rcode The response code in the DNS response. 148 * 149 * @see android.net.DnsResolver#query query() 150 */ onAnswer(@onNull T answer, int rcode)151 void onAnswer(@NonNull T answer, int rcode); 152 /** 153 * Error response to 154 * {@link android.net.DnsResolver#query query()} or 155 * {@link android.net.DnsResolver#rawQuery rawQuery()}. 156 * 157 * Invoked when there is no valid answer to 158 * {@link android.net.DnsResolver#query query()} 159 * {@link android.net.DnsResolver#rawQuery rawQuery()}. 160 * 161 * @param error a {@link DnsException} object with additional 162 * detail regarding the failure 163 */ onError(@onNull DnsException error)164 void onError(@NonNull DnsException error); 165 } 166 167 /** 168 * Class to represent DNS error 169 */ 170 public static class DnsException extends Exception { 171 /** 172 * DNS error code as one of the ERROR_* constants 173 */ 174 @DnsError public final int code; 175 DnsException(@nsError int code, @Nullable Throwable cause)176 public DnsException(@DnsError int code, @Nullable Throwable cause) { 177 super(cause); 178 this.code = code; 179 } 180 } 181 182 /** 183 * Send a raw DNS query. 184 * The answer will be provided asynchronously through the provided {@link Callback}. 185 * 186 * @param network {@link Network} specifying which network to query on. 187 * {@code null} for query on default network. 188 * @param query blob message to query 189 * @param flags flags as a combination of the FLAGS_* constants 190 * @param executor The {@link Executor} that the callback should be executed on. 191 * @param cancellationSignal used by the caller to signal if the query should be 192 * cancelled. May be {@code null}. 193 * @param callback a {@link Callback} which will be called to notify the caller 194 * of the result of dns query. 195 */ rawQuery(@ullable Network network, @NonNull byte[] query, @QueryFlag int flags, @NonNull @CallbackExecutor Executor executor, @Nullable CancellationSignal cancellationSignal, @NonNull Callback<? super byte[]> callback)196 public void rawQuery(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags, 197 @NonNull @CallbackExecutor Executor executor, 198 @Nullable CancellationSignal cancellationSignal, 199 @NonNull Callback<? super byte[]> callback) { 200 if (cancellationSignal != null && cancellationSignal.isCanceled()) { 201 return; 202 } 203 final Object lock = new Object(); 204 final FileDescriptor queryfd; 205 try { 206 queryfd = resNetworkSend((network != null) 207 ? network.getNetIdForResolv() : NETID_UNSET, query, query.length, flags); 208 } catch (ErrnoException e) { 209 executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); 210 return; 211 } 212 213 synchronized (lock) { 214 registerFDListener(executor, queryfd, callback, cancellationSignal, lock); 215 if (cancellationSignal == null) return; 216 addCancellationSignal(cancellationSignal, queryfd, lock); 217 } 218 } 219 220 /** 221 * Send a DNS query with the specified name, class and query type. 222 * The answer will be provided asynchronously through the provided {@link Callback}. 223 * 224 * @param network {@link Network} specifying which network to query on. 225 * {@code null} for query on default network. 226 * @param domain domain name to query 227 * @param nsClass dns class as one of the CLASS_* constants 228 * @param nsType dns resource record (RR) type as one of the TYPE_* constants 229 * @param flags flags as a combination of the FLAGS_* constants 230 * @param executor The {@link Executor} that the callback should be executed on. 231 * @param cancellationSignal used by the caller to signal if the query should be 232 * cancelled. May be {@code null}. 233 * @param callback a {@link Callback} which will be called to notify the caller 234 * of the result of dns query. 235 */ rawQuery(@ullable Network network, @NonNull String domain, @QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags, @NonNull @CallbackExecutor Executor executor, @Nullable CancellationSignal cancellationSignal, @NonNull Callback<? super byte[]> callback)236 public void rawQuery(@Nullable Network network, @NonNull String domain, 237 @QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags, 238 @NonNull @CallbackExecutor Executor executor, 239 @Nullable CancellationSignal cancellationSignal, 240 @NonNull Callback<? super byte[]> callback) { 241 if (cancellationSignal != null && cancellationSignal.isCanceled()) { 242 return; 243 } 244 final Object lock = new Object(); 245 final FileDescriptor queryfd; 246 try { 247 queryfd = resNetworkQuery((network != null) 248 ? network.getNetIdForResolv() : NETID_UNSET, domain, nsClass, nsType, flags); 249 } catch (ErrnoException e) { 250 executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); 251 return; 252 } 253 synchronized (lock) { 254 registerFDListener(executor, queryfd, callback, cancellationSignal, lock); 255 if (cancellationSignal == null) return; 256 addCancellationSignal(cancellationSignal, queryfd, lock); 257 } 258 } 259 260 private class InetAddressAnswerAccumulator implements Callback<byte[]> { 261 private final List<InetAddress> mAllAnswers; 262 private final Network mNetwork; 263 private int mRcode; 264 private DnsException mDnsException; 265 private final Callback<? super List<InetAddress>> mUserCallback; 266 private final int mTargetAnswerCount; 267 private int mReceivedAnswerCount = 0; 268 InetAddressAnswerAccumulator(@onNull Network network, int size, @NonNull Callback<? super List<InetAddress>> callback)269 InetAddressAnswerAccumulator(@NonNull Network network, int size, 270 @NonNull Callback<? super List<InetAddress>> callback) { 271 mNetwork = network; 272 mTargetAnswerCount = size; 273 mAllAnswers = new ArrayList<>(); 274 mUserCallback = callback; 275 } 276 maybeReportError()277 private boolean maybeReportError() { 278 if (mRcode != 0) { 279 mUserCallback.onAnswer(mAllAnswers, mRcode); 280 return true; 281 } 282 if (mDnsException != null) { 283 mUserCallback.onError(mDnsException); 284 return true; 285 } 286 return false; 287 } 288 maybeReportAnswer()289 private void maybeReportAnswer() { 290 if (++mReceivedAnswerCount != mTargetAnswerCount) return; 291 if (mAllAnswers.isEmpty() && maybeReportError()) return; 292 mUserCallback.onAnswer(rfc6724Sort(mNetwork, mAllAnswers), mRcode); 293 } 294 295 @Override onAnswer(@onNull byte[] answer, int rcode)296 public void onAnswer(@NonNull byte[] answer, int rcode) { 297 // If at least one query succeeded, return an rcode of 0. 298 // Otherwise, arbitrarily return the first rcode received. 299 if (mReceivedAnswerCount == 0 || rcode == 0) { 300 mRcode = rcode; 301 } 302 try { 303 mAllAnswers.addAll(new DnsAddressAnswer(answer).getAddresses()); 304 } catch (DnsPacket.ParseException e) { 305 // Convert the com.android.net.module.util.DnsPacket.ParseException to an 306 // android.net.ParseException. This is the type that was used in Q and is implied 307 // by the public documentation of ERROR_PARSE. 308 // 309 // DnsPacket cannot throw android.net.ParseException directly because it's @hide. 310 ParseException pe = new ParseException(e.reason, e.getCause()); 311 pe.setStackTrace(e.getStackTrace()); 312 mDnsException = new DnsException(ERROR_PARSE, pe); 313 } 314 maybeReportAnswer(); 315 } 316 317 @Override onError(@onNull DnsException error)318 public void onError(@NonNull DnsException error) { 319 mDnsException = error; 320 maybeReportAnswer(); 321 } 322 } 323 324 /** 325 * Send a DNS query with the specified name on a network with both IPv4 and IPv6, 326 * get back a set of InetAddresses with rfc6724 sorting style asynchronously. 327 * 328 * This method will examine the connection ability on given network, and query IPv4 329 * and IPv6 if connection is available. 330 * 331 * If at least one query succeeded with valid answer, rcode will be 0 332 * 333 * The answer will be provided asynchronously through the provided {@link Callback}. 334 * 335 * @param network {@link Network} specifying which network to query on. 336 * {@code null} for query on default network. 337 * @param domain domain name to query 338 * @param flags flags as a combination of the FLAGS_* constants 339 * @param executor The {@link Executor} that the callback should be executed on. 340 * @param cancellationSignal used by the caller to signal if the query should be 341 * cancelled. May be {@code null}. 342 * @param callback a {@link Callback} which will be called to notify the 343 * caller of the result of dns query. 344 */ query(@ullable Network network, @NonNull String domain, @QueryFlag int flags, @NonNull @CallbackExecutor Executor executor, @Nullable CancellationSignal cancellationSignal, @NonNull Callback<? super List<InetAddress>> callback)345 public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags, 346 @NonNull @CallbackExecutor Executor executor, 347 @Nullable CancellationSignal cancellationSignal, 348 @NonNull Callback<? super List<InetAddress>> callback) { 349 if (cancellationSignal != null && cancellationSignal.isCanceled()) { 350 return; 351 } 352 final Object lock = new Object(); 353 final Network queryNetwork; 354 try { 355 queryNetwork = (network != null) ? network : getDnsNetwork(); 356 } catch (ErrnoException e) { 357 executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); 358 return; 359 } 360 final boolean queryIpv6 = haveIpv6(queryNetwork); 361 final boolean queryIpv4 = haveIpv4(queryNetwork); 362 363 // This can only happen if queryIpv4 and queryIpv6 are both false. 364 // This almost certainly means that queryNetwork does not exist or no longer exists. 365 if (!queryIpv6 && !queryIpv4) { 366 executor.execute(() -> callback.onError( 367 new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET)))); 368 return; 369 } 370 371 final FileDescriptor v4fd; 372 final FileDescriptor v6fd; 373 374 int queryCount = 0; 375 376 if (queryIpv6) { 377 try { 378 v6fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, 379 TYPE_AAAA, flags); 380 } catch (ErrnoException e) { 381 executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); 382 return; 383 } 384 queryCount++; 385 } else v6fd = null; 386 387 // Avoiding gateways drop packets if queries are sent too close together 388 try { 389 Thread.sleep(SLEEP_TIME_MS); 390 } catch (InterruptedException ex) { 391 Thread.currentThread().interrupt(); 392 } 393 394 if (queryIpv4) { 395 try { 396 v4fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, TYPE_A, 397 flags); 398 } catch (ErrnoException e) { 399 if (queryIpv6) resNetworkCancel(v6fd); // Closes fd, marks it invalid. 400 executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); 401 return; 402 } 403 queryCount++; 404 } else v4fd = null; 405 406 final InetAddressAnswerAccumulator accumulator = 407 new InetAddressAnswerAccumulator(queryNetwork, queryCount, callback); 408 409 synchronized (lock) { 410 if (queryIpv6) { 411 registerFDListener(executor, v6fd, accumulator, cancellationSignal, lock); 412 } 413 if (queryIpv4) { 414 registerFDListener(executor, v4fd, accumulator, cancellationSignal, lock); 415 } 416 if (cancellationSignal == null) return; 417 cancellationSignal.setOnCancelListener(() -> { 418 synchronized (lock) { 419 if (queryIpv4) cancelQuery(v4fd); 420 if (queryIpv6) cancelQuery(v6fd); 421 } 422 }); 423 } 424 } 425 426 /** 427 * Send a DNS query with the specified name and query type, get back a set of 428 * InetAddresses with rfc6724 sorting style asynchronously. 429 * 430 * The answer will be provided asynchronously through the provided {@link Callback}. 431 * 432 * @param network {@link Network} specifying which network to query on. 433 * {@code null} for query on default network. 434 * @param domain domain name to query 435 * @param nsType dns resource record (RR) type as one of the TYPE_* constants 436 * @param flags flags as a combination of the FLAGS_* constants 437 * @param executor The {@link Executor} that the callback should be executed on. 438 * @param cancellationSignal used by the caller to signal if the query should be 439 * cancelled. May be {@code null}. 440 * @param callback a {@link Callback} which will be called to notify the caller 441 * of the result of dns query. 442 */ query(@ullable Network network, @NonNull String domain, @QueryType int nsType, @QueryFlag int flags, @NonNull @CallbackExecutor Executor executor, @Nullable CancellationSignal cancellationSignal, @NonNull Callback<? super List<InetAddress>> callback)443 public void query(@Nullable Network network, @NonNull String domain, 444 @QueryType int nsType, @QueryFlag int flags, 445 @NonNull @CallbackExecutor Executor executor, 446 @Nullable CancellationSignal cancellationSignal, 447 @NonNull Callback<? super List<InetAddress>> callback) { 448 if (cancellationSignal != null && cancellationSignal.isCanceled()) { 449 return; 450 } 451 final Object lock = new Object(); 452 final FileDescriptor queryfd; 453 final Network queryNetwork; 454 try { 455 queryNetwork = (network != null) ? network : getDnsNetwork(); 456 queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, nsType, 457 flags); 458 } catch (ErrnoException e) { 459 executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); 460 return; 461 } 462 final InetAddressAnswerAccumulator accumulator = 463 new InetAddressAnswerAccumulator(queryNetwork, 1, callback); 464 synchronized (lock) { 465 registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock); 466 if (cancellationSignal == null) return; 467 addCancellationSignal(cancellationSignal, queryfd, lock); 468 } 469 } 470 471 /** 472 * Class to retrieve DNS response 473 * 474 * @hide 475 */ 476 public static final class DnsResponse { 477 public final @NonNull byte[] answerbuf; 478 public final int rcode; DnsResponse(@onNull byte[] answerbuf, int rcode)479 public DnsResponse(@NonNull byte[] answerbuf, int rcode) { 480 this.answerbuf = answerbuf; 481 this.rcode = rcode; 482 } 483 } 484 registerFDListener(@onNull Executor executor, @NonNull FileDescriptor queryfd, @NonNull Callback<? super byte[]> answerCallback, @Nullable CancellationSignal cancellationSignal, @NonNull Object lock)485 private void registerFDListener(@NonNull Executor executor, 486 @NonNull FileDescriptor queryfd, @NonNull Callback<? super byte[]> answerCallback, 487 @Nullable CancellationSignal cancellationSignal, @NonNull Object lock) { 488 final MessageQueue mainThreadMessageQueue = Looper.getMainLooper().getQueue(); 489 mainThreadMessageQueue.addOnFileDescriptorEventListener( 490 queryfd, 491 FD_EVENTS, 492 (fd, events) -> { 493 // b/134310704 494 // Unregister fd event listener before resNetworkResult is called to prevent 495 // race condition caused by fd reused. 496 // For example when querying v4 and v6, it's possible that the first query ends 497 // and the fd is closed before the second request starts, which might return 498 // the same fd for the second request. By that time, the looper must have 499 // unregistered the fd, otherwise another event listener can't be registered. 500 mainThreadMessageQueue.removeOnFileDescriptorEventListener(fd); 501 502 executor.execute(() -> { 503 DnsResponse resp = null; 504 ErrnoException exception = null; 505 synchronized (lock) { 506 if (cancellationSignal != null && cancellationSignal.isCanceled()) { 507 return; 508 } 509 try { 510 resp = resNetworkResult(fd); // Closes fd, marks it invalid. 511 } catch (ErrnoException e) { 512 Log.w(TAG, "resNetworkResult:" + e.toString()); 513 exception = e; 514 } 515 } 516 if (exception != null) { 517 answerCallback.onError(new DnsException(ERROR_SYSTEM, exception)); 518 return; 519 } 520 answerCallback.onAnswer(resp.answerbuf, resp.rcode); 521 }); 522 523 // The file descriptor has already been unregistered, so it does not really 524 // matter what is returned here. In spirit 0 (meaning "unregister this FD") 525 // is still the closest to what the looper needs to do. When returning 0, 526 // Looper knows to ignore the fd if it has already been unregistered. 527 return 0; 528 }); 529 } 530 cancelQuery(@onNull FileDescriptor queryfd)531 private void cancelQuery(@NonNull FileDescriptor queryfd) { 532 if (!queryfd.valid()) return; 533 Looper.getMainLooper().getQueue().removeOnFileDescriptorEventListener(queryfd); 534 resNetworkCancel(queryfd); // Closes fd, marks it invalid. 535 } 536 addCancellationSignal(@onNull CancellationSignal cancellationSignal, @NonNull FileDescriptor queryfd, @NonNull Object lock)537 private void addCancellationSignal(@NonNull CancellationSignal cancellationSignal, 538 @NonNull FileDescriptor queryfd, @NonNull Object lock) { 539 cancellationSignal.setOnCancelListener(() -> { 540 synchronized (lock) { 541 cancelQuery(queryfd); 542 } 543 }); 544 } 545 546 private static class DnsAddressAnswer extends DnsPacket { 547 private static final String TAG = "DnsResolver.DnsAddressAnswer"; 548 private static final boolean DBG = false; 549 550 private final int mQueryType; 551 DnsAddressAnswer(@onNull byte[] data)552 DnsAddressAnswer(@NonNull byte[] data) throws ParseException { 553 super(data); 554 if ((mHeader.getFlags() & (1 << 15)) == 0) { 555 throw new ParseException("Not an answer packet"); 556 } 557 if (mHeader.getRecordCount(QDSECTION) == 0) { 558 throw new ParseException("No question found"); 559 } 560 // Expect only one question in question section. 561 mQueryType = mRecords[QDSECTION].get(0).nsType; 562 } 563 getAddresses()564 public @NonNull List<InetAddress> getAddresses() { 565 final List<InetAddress> results = new ArrayList<InetAddress>(); 566 if (mHeader.getRecordCount(ANSECTION) == 0) return results; 567 568 for (final DnsRecord ansSec : mRecords[ANSECTION]) { 569 // Only support A and AAAA, also ignore answers if query type != answer type. 570 int nsType = ansSec.nsType; 571 if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) { 572 continue; 573 } 574 try { 575 results.add(InetAddress.getByAddress(ansSec.getRR())); 576 } catch (UnknownHostException e) { 577 if (DBG) { 578 Log.w(TAG, "rr to address fail"); 579 } 580 } 581 } 582 return results; 583 } 584 } 585 586 } 587