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