Home | History | Annotate | Download | only in net
      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      */
    116     public static @NonNull DnsResolver getInstance() {
    117         return sInstance;
    118     }
    119 
    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          */
    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          */
    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 
    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      */
    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      */
    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 
    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 
    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 
    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
    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
    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      */
    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      */
    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;
    461         public DnsResponse(@NonNull byte[] answerbuf, int rcode) {
    462             this.answerbuf = answerbuf;
    463             this.rcode = rcode;
    464         }
    465     }
    466 
    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 
    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 
    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 
    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 
    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