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