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.e(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