Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ec214477 authored by Luke Huang's avatar Luke Huang
Browse files

API council feedbacks for DnsResolver

To address the API review feedback provided by
the API council.

Bug: 129261432
Test: atest DnsResolverTest

Merged-In: I5737cf293264bf9d492e7bd56b62bee4d49002eb
(cherry picked from commit 454fe010)

Change-Id: I429dd93285f50314e9d757f4ec8539a3ba40e61b
parent ebc321bd
Loading
Loading
Loading
Loading
+11 −20
Original line number Diff line number Diff line
@@ -28754,10 +28754,13 @@ package android.net {
  public final class DnsResolver {
    method @NonNull public static android.net.DnsResolver getInstance();
    method public <T> void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.AnswerCallback<T>);
    method public <T> void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.AnswerCallback<T>);
    method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.InetAddressAnswerCallback);
    method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
    method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
    method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
    method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
    field public static final int CLASS_IN = 1; // 0x1
    field public static final int ERROR_PARSE = 0; // 0x0
    field public static final int ERROR_SYSTEM = 1; // 0x1
    field public static final int FLAG_EMPTY = 0; // 0x0
    field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
    field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
@@ -28766,23 +28769,13 @@ package android.net {
    field public static final int TYPE_AAAA = 28; // 0x1c
  }
  public abstract static class DnsResolver.AnswerCallback<T> {
    ctor public DnsResolver.AnswerCallback(@NonNull android.net.DnsResolver.AnswerParser<T>);
    method public abstract void onAnswer(@NonNull T);
    method public abstract void onParseException(@NonNull android.net.ParseException);
    method public abstract void onQueryException(@NonNull android.system.ErrnoException);
  public static interface DnsResolver.Callback<T> {
    method public void onAnswer(@NonNull T, int);
    method public void onError(@NonNull android.net.DnsResolver.DnsException);
  }
  public static interface DnsResolver.AnswerParser<T> {
    method @NonNull public T parse(@NonNull byte[]) throws android.net.ParseException;
  }
  public abstract static class DnsResolver.InetAddressAnswerCallback extends android.net.DnsResolver.AnswerCallback<java.util.List<java.net.InetAddress>> {
    ctor public DnsResolver.InetAddressAnswerCallback();
  }
  public abstract static class DnsResolver.RawAnswerCallback extends android.net.DnsResolver.AnswerCallback<byte[]> {
    ctor public DnsResolver.RawAnswerCallback();
  public static class DnsResolver.DnsException extends java.lang.Exception {
    field public final int code;
  }
  public class InetAddresses {
@@ -29108,8 +29101,6 @@ package android.net {
  }
  public class ParseException extends java.lang.RuntimeException {
    ctor public ParseException(@NonNull String);
    ctor public ParseException(@NonNull String, @NonNull Throwable);
    field public String response;
  }
+160 −124
Original line number Diff line number Diff line
@@ -93,6 +93,23 @@ public final class DnsResolver {
    public static final int FLAG_NO_CACHE_STORE = 1 << 1;
    public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;

    @IntDef(prefix = { "ERROR_" }, value = {
            ERROR_PARSE,
            ERROR_SYSTEM
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface DnsError {}
    /**
     * Indicates that there was an error parsing the response the query.
     * The cause of this error is available via getCause() and is a ParseException.
     */
    public static final int ERROR_PARSE = 0;
    /**
     * Indicates that there was an error sending the query.
     * The cause of this error is available via getCause() and is an ErrnoException.
     */
    public static final int ERROR_SYSTEM = 1;

    private static final int NETID_UNSET = 0;

    private static final DnsResolver sInstance = new DnsResolver();
@@ -107,97 +124,57 @@ public final class DnsResolver {
    private DnsResolver() {}

    /**
     * Answer parser for parsing raw answers
     *
     * @param <T> The type of the parsed answer
     */
    public interface AnswerParser<T> {
        /**
         * Creates a <T> answer by parsing the given raw answer.
     * Base interface for answer callbacks
     *
         * @param rawAnswer the raw answer to be parsed
         * @return a parsed <T> answer
         * @throws ParseException if parsing failed
     * @param <T> The type of the answer
     */
        @NonNull T parse(@NonNull byte[] rawAnswer) throws ParseException;
    }

    /**
     * Base class for answer callbacks
     *
     * @param <T> The type of the parsed answer
     */
    public abstract static class AnswerCallback<T> {
        /** @hide */
        public final AnswerParser<T> parser;

        public AnswerCallback(@NonNull AnswerParser<T> parser) {
            this.parser = parser;
        };

    public interface Callback<T> {
        /**
         * Success response to
         * {@link android.net.DnsResolver#query query()}.
         * {@link android.net.DnsResolver#query query()} or
         * {@link android.net.DnsResolver#rawQuery rawQuery()}.
         *
         * Invoked when the answer to a query was successfully parsed.
         *
         * @param answer parsed answer to the query.
         * @param answer <T> answer to the query.
         * @param rcode The response code in the DNS response.
         *
         * {@see android.net.DnsResolver#query query()}
         */
        public abstract void onAnswer(@NonNull T answer);

        void onAnswer(@NonNull T answer, int rcode);
        /**
         * Error response to
         * {@link android.net.DnsResolver#query query()}.
         * {@link android.net.DnsResolver#query query()} or
         * {@link android.net.DnsResolver#rawQuery rawQuery()}.
         *
         * Invoked when there is no valid answer to
         * {@link android.net.DnsResolver#query query()}
         * {@link android.net.DnsResolver#rawQuery rawQuery()}.
         *
         * @param exception a {@link ParseException} object with additional
         * @param error a {@link DnsException} object with additional
         *    detail regarding the failure
         */
        public abstract void onParseException(@NonNull ParseException exception);

        /**
         * Error response to
         * {@link android.net.DnsResolver#query query()}.
         *
         * Invoked if an error happens when
         * issuing the DNS query or receiving the result.
         * {@link android.net.DnsResolver#query query()}
         *
         * @param exception an {@link ErrnoException} object with additional detail
         *    regarding the failure
         */
        public abstract void onQueryException(@NonNull ErrnoException exception);
        void onError(@NonNull DnsException error);
    }

    /**
     * Callback for receiving raw answers
     * Class to represent DNS error
     */
    public abstract static class RawAnswerCallback extends AnswerCallback<byte[]> {
        public RawAnswerCallback() {
            super(rawAnswer -> rawAnswer);
        }
    }

    public static class DnsException extends Exception {
       /**
     * Callback for receiving parsed {@link InetAddress} answers
     *
     * Note that if the answer does not contain any IP addresses,
     * onAnswer will be called with an empty list.
        * DNS error code as one of the ERROR_* constants
        */
    public abstract static class InetAddressAnswerCallback
            extends AnswerCallback<List<InetAddress>> {
        public InetAddressAnswerCallback() {
            super(rawAnswer -> new DnsAddressAnswer(rawAnswer).getAddresses());
        @DnsError public final int code;

        DnsException(@DnsError int code, @Nullable Throwable cause) {
            super(cause);
            this.code = code;
        }
    }

    /**
     * Send a raw DNS query.
     * The answer will be provided asynchronously through the provided {@link AnswerCallback}.
     * The answer will be provided asynchronously through the provided {@link Callback}.
     *
     * @param network {@link Network} specifying which network to query on.
     *         {@code null} for query on default network.
@@ -206,13 +183,13 @@ public final class DnsResolver {
     * @param executor The {@link Executor} that the callback should be executed on.
     * @param cancellationSignal used by the caller to signal if the query should be
     *    cancelled. May be {@code null}.
     * @param callback an {@link AnswerCallback} which will be called to notify the caller
     * @param callback a {@link Callback} which will be called to notify the caller
     *    of the result of dns query.
     */
    public <T> void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
    public void rawQuery(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
            @NonNull @CallbackExecutor Executor executor,
            @Nullable CancellationSignal cancellationSignal,
            @NonNull AnswerCallback<T> callback) {
            @NonNull Callback<? super byte[]> callback) {
        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
            return;
        }
@@ -222,9 +199,7 @@ public final class DnsResolver {
            queryfd = resNetworkSend((network != null
                ? network.netId : NETID_UNSET), query, query.length, flags);
        } catch (ErrnoException e) {
            executor.execute(() -> {
                callback.onQueryException(e);
            });
            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
            return;
        }

@@ -237,7 +212,7 @@ public final class DnsResolver {

    /**
     * Send a DNS query with the specified name, class and query type.
     * The answer will be provided asynchronously through the provided {@link AnswerCallback}.
     * The answer will be provided asynchronously through the provided {@link Callback}.
     *
     * @param network {@link Network} specifying which network to query on.
     *         {@code null} for query on default network.
@@ -248,14 +223,14 @@ public final class DnsResolver {
     * @param executor The {@link Executor} that the callback should be executed on.
     * @param cancellationSignal used by the caller to signal if the query should be
     *    cancelled. May be {@code null}.
     * @param callback an {@link AnswerCallback} which will be called to notify the caller
     * @param callback a {@link Callback} which will be called to notify the caller
     *    of the result of dns query.
     */
    public <T> void query(@Nullable Network network, @NonNull String domain,
    public void rawQuery(@Nullable Network network, @NonNull String domain,
            @QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags,
            @NonNull @CallbackExecutor Executor executor,
            @Nullable CancellationSignal cancellationSignal,
            @NonNull AnswerCallback<T> callback) {
            @NonNull Callback<? super byte[]> callback) {
        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
            return;
        }
@@ -265,9 +240,7 @@ public final class DnsResolver {
            queryfd = resNetworkQuery((network != null
                    ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
        } catch (ErrnoException e) {
            executor.execute(() -> {
                callback.onQueryException(e);
            });
            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
            return;
        }
        synchronized (lock)  {
@@ -277,27 +250,28 @@ public final class DnsResolver {
        }
    }

    private class InetAddressAnswerAccumulator extends InetAddressAnswerCallback {
    private class InetAddressAnswerAccumulator implements Callback<byte[]> {
        private final List<InetAddress> mAllAnswers;
        private ParseException mParseException;
        private ErrnoException mErrnoException;
        private final InetAddressAnswerCallback mUserCallback;
        private int mRcode;
        private DnsException mDnsException;
        private final Callback<? super List<InetAddress>> mUserCallback;
        private final int mTargetAnswerCount;
        private int mReceivedAnswerCount = 0;

        InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerCallback callback) {
        InetAddressAnswerAccumulator(int size,
                @NonNull Callback<? super List<InetAddress>> callback) {
            mTargetAnswerCount = size;
            mAllAnswers = new ArrayList<>();
            mUserCallback = callback;
        }

        private boolean maybeReportException() {
            if (mErrnoException != null) {
                mUserCallback.onQueryException(mErrnoException);
        private boolean maybeReportError() {
            if (mRcode != 0) {
                mUserCallback.onAnswer(mAllAnswers, mRcode);
                return true;
            }
            if (mParseException != null) {
                mUserCallback.onParseException(mParseException);
            if (mDnsException != null) {
                mUserCallback.onError(mDnsException);
                return true;
            }
            return false;
@@ -305,34 +279,43 @@ public final class DnsResolver {

        private void maybeReportAnswer() {
            if (++mReceivedAnswerCount != mTargetAnswerCount) return;
            if (mAllAnswers.isEmpty() && maybeReportException()) return;
            if (mAllAnswers.isEmpty() && maybeReportError()) return;
            // TODO: Do RFC6724 sort.
            mUserCallback.onAnswer(mAllAnswers);
            mUserCallback.onAnswer(mAllAnswers, mRcode);
        }

        @Override
        public void onAnswer(@NonNull List<InetAddress> answer) {
            mAllAnswers.addAll(answer);
            maybeReportAnswer();
        public void onAnswer(@NonNull byte[] answer, int rcode) {
            // If at least one query succeeded, return an rcode of 0.
            // Otherwise, arbitrarily return the first rcode received.
            if (mReceivedAnswerCount == 0 || rcode == 0) {
                mRcode = rcode;
            }
            try {
                mAllAnswers.addAll(new DnsAddressAnswer(answer).getAddresses());
            } catch (ParseException e) {
                mDnsException = new DnsException(ERROR_PARSE, e);
            }

        @Override
        public void onParseException(@NonNull ParseException e) {
            mParseException = e;
            maybeReportAnswer();
        }

        @Override
        public void onQueryException(@NonNull ErrnoException e) {
            mErrnoException = e;
        public void onError(@NonNull DnsException error) {
            mDnsException = error;
            maybeReportAnswer();
        }
    }

    /**
     * Send a DNS query with the specified name, get back a set of InetAddresses asynchronously.
     * The answer will be provided asynchronously through the provided
     * {@link InetAddressAnswerCallback}.
     * Send a DNS query with the specified name on a network with both IPv4 and IPv6,
     * get back a set of InetAddresses asynchronously.
     *
     * This method will examine the connection ability on given network, and query IPv4
     * and IPv6 if connection is available.
     *
     * If at least one query succeeded with valid answer, rcode will be 0
     *
     * The answer will be provided asynchronously through the provided {@link Callback}.
     *
     * @param network {@link Network} specifying which network to query on.
     *         {@code null} for query on default network.
@@ -341,13 +324,13 @@ public final class DnsResolver {
     * @param executor The {@link Executor} that the callback should be executed on.
     * @param cancellationSignal used by the caller to signal if the query should be
     *    cancelled. May be {@code null}.
     * @param callback an {@link InetAddressAnswerCallback} which will be called to notify the
     * @param callback a {@link Callback} which will be called to notify the
     *    caller of the result of dns query.
     */
    public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
            @NonNull @CallbackExecutor Executor executor,
            @Nullable CancellationSignal cancellationSignal,
            @NonNull InetAddressAnswerCallback callback) {
            @NonNull Callback<? super List<InetAddress>> callback) {
        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
            return;
        }
@@ -365,9 +348,7 @@ public final class DnsResolver {
                v6fd = resNetworkQuery((network != null
                        ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags);
            } catch (ErrnoException e) {
                executor.execute(() -> {
                    callback.onQueryException(e);
                });
                executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
                return;
            }
            queryCount++;
@@ -377,7 +358,9 @@ public final class DnsResolver {
        // Avoiding gateways drop packets if queries are sent too close together
        try {
            Thread.sleep(SLEEP_TIME_MS);
        } catch (InterruptedException ex) { }
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }

        if (queryIpv4) {
            try {
@@ -385,9 +368,7 @@ public final class DnsResolver {
                        ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags);
            } catch (ErrnoException e) {
                if (queryIpv6) resNetworkCancel(v6fd);  // Closes fd, marks it invalid.
                executor.execute(() -> {
                    callback.onQueryException(e);
                });
                executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
                return;
            }
            queryCount++;
@@ -413,34 +394,89 @@ public final class DnsResolver {
        }
    }

    private <T> void registerFDListener(@NonNull Executor executor,
            @NonNull FileDescriptor queryfd, @NonNull AnswerCallback<T> answerCallback,
    /**
     * Send a DNS query with the specified name and query type, get back a set of
     * InetAddresses asynchronously.
     *
     * The answer will be provided asynchronously through the provided {@link Callback}.
     *
     * @param network {@link Network} specifying which network to query on.
     *         {@code null} for query on default network.
     * @param domain domain name to query
     * @param nsType dns resource record (RR) type as one of the TYPE_* constants
     * @param flags flags as a combination of the FLAGS_* constants
     * @param executor The {@link Executor} that the callback should be executed on.
     * @param cancellationSignal used by the caller to signal if the query should be
     *    cancelled. May be {@code null}.
     * @param callback a {@link Callback} which will be called to notify the caller
     *    of the result of dns query.
     */
    public void query(@Nullable Network network, @NonNull String domain,
            @QueryType int nsType, @QueryFlag int flags,
            @NonNull @CallbackExecutor Executor executor,
            @Nullable CancellationSignal cancellationSignal,
            @NonNull Callback<? super List<InetAddress>> callback) {
        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
            return;
        }
        final Object lock = new Object();
        final FileDescriptor queryfd;
        try {
            queryfd = resNetworkQuery((network != null
                    ? network.netId : NETID_UNSET), domain, CLASS_IN, nsType, flags);
        } catch (ErrnoException e) {
            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
            return;
        }
        final InetAddressAnswerAccumulator accumulator =
                new InetAddressAnswerAccumulator(1, callback);
        synchronized (lock)  {
            registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock);
            if (cancellationSignal == null) return;
            addCancellationSignal(cancellationSignal, queryfd, lock);
        }
    }

    /**
     * Class to retrieve DNS response
     *
     * @hide
     */
    public static final class DnsResponse {
        public final @NonNull byte[] answerbuf;
        public final int rcode;
        public DnsResponse(@NonNull byte[] answerbuf, int rcode) {
            this.answerbuf = answerbuf;
            this.rcode = rcode;
        }
    }

    private void registerFDListener(@NonNull Executor executor,
            @NonNull FileDescriptor queryfd, @NonNull Callback<? super byte[]> answerCallback,
            @Nullable CancellationSignal cancellationSignal, @NonNull Object lock) {
        Looper.getMainLooper().getQueue().addOnFileDescriptorEventListener(
                queryfd,
                FD_EVENTS,
                (fd, events) -> {
                    executor.execute(() -> {
                        DnsResponse resp = null;
                        ErrnoException exception = null;
                        synchronized (lock) {
                            if (cancellationSignal != null && cancellationSignal.isCanceled()) {
                                return;
                            }
                            byte[] answerbuf = null;
                            try {
                                answerbuf = resNetworkResult(fd);  // Closes fd, marks it invalid.
                                resp = resNetworkResult(fd);  // Closes fd, marks it invalid.
                            } catch (ErrnoException e) {
                                Log.e(TAG, "resNetworkResult:" + e.toString());
                                answerCallback.onQueryException(e);
                                return;
                                exception = e;
                            }

                            try {
                                answerCallback.onAnswer(
                                        answerCallback.parser.parse(answerbuf));
                            } catch (ParseException e) {
                                answerCallback.onParseException(e);
                        }
                        if (exception != null) {
                            answerCallback.onError(new DnsException(ERROR_SYSTEM, exception));
                            return;
                        }
                        answerCallback.onAnswer(resp.answerbuf, resp.rcode);
                    });
                    // Unregister this fd listener
                    return 0;
+3 −2
Original line number Diff line number Diff line
@@ -145,9 +145,10 @@ public class NetworkUtils {
    /**
     * DNS resolver series jni method.
     * Read a result for the query associated with the {@code fd}.
     * @return a byte array containing blob answer
     * @return DnsResponse containing blob answer and rcode
     */
    public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException;
    public static native DnsResolver.DnsResponse resNetworkResult(FileDescriptor fd)
            throws ErrnoException;

    /**
     * DNS resolver series jni method.
+2 −2
Original line number Diff line number Diff line
@@ -25,12 +25,12 @@ import android.annotation.NonNull;
public class ParseException extends RuntimeException {
    public String response;

    public ParseException(@NonNull String response) {
    ParseException(@NonNull String response) {
        super(response);
        this.response = response;
    }

    public ParseException(@NonNull String response, @NonNull Throwable cause) {
    ParseException(@NonNull String response, @NonNull Throwable cause) {
        super(response, cause);
        this.response = response;
    }
+6 −3

File changed.

Preview size limit exceeded, changes collapsed.