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