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

Commit 0edeaa36 authored by Luke Huang's avatar Luke Huang Committed by Android (Google) Code Review
Browse files

Merge changes from topic "am-851f19c8d7d34bb48accd48868aae501-qt-dev" into qt-dev

* changes:
  Remove broken test testRfc6724Sort
  Add Rfc6724 style sort for DnsResolver and fix potential bug
parents a80dd06e ad7c294c
Loading
Loading
Loading
Loading
+42 −64
Original line number Diff line number Diff line
@@ -16,16 +16,17 @@

package android.net;

import static android.net.NetworkUtils.getDnsNetId;
import static android.net.NetworkUtils.resNetworkCancel;
import static android.net.NetworkUtils.resNetworkQuery;
import static android.net.NetworkUtils.resNetworkResult;
import static android.net.NetworkUtils.resNetworkSend;
import static android.net.util.DnsUtils.haveIpv4;
import static android.net.util.DnsUtils.haveIpv6;
import static android.net.util.DnsUtils.rfc6724Sort;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.ENONET;

import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -34,18 +35,12 @@ import android.annotation.Nullable;
import android.os.CancellationSignal;
import android.os.Looper;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;

import libcore.io.IoUtils;

import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
@@ -196,8 +191,8 @@ public final class DnsResolver {
        final Object lock = new Object();
        final FileDescriptor queryfd;
        try {
            queryfd = resNetworkSend((network != null
                ? network.getNetIdForResolv() : NETID_UNSET), query, query.length, flags);
            queryfd = resNetworkSend((network != null)
                    ? network.getNetIdForResolv() : NETID_UNSET, query, query.length, flags);
        } catch (ErrnoException e) {
            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
            return;
@@ -237,8 +232,8 @@ public final class DnsResolver {
        final Object lock = new Object();
        final FileDescriptor queryfd;
        try {
            queryfd = resNetworkQuery((network != null
                    ? network.getNetIdForResolv() : NETID_UNSET), domain, nsClass, nsType, flags);
            queryfd = resNetworkQuery((network != null)
                    ? network.getNetIdForResolv() : NETID_UNSET, domain, nsClass, nsType, flags);
        } catch (ErrnoException e) {
            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
            return;
@@ -252,14 +247,16 @@ public final class DnsResolver {

    private class InetAddressAnswerAccumulator implements Callback<byte[]> {
        private final List<InetAddress> mAllAnswers;
        private final Network mNetwork;
        private int mRcode;
        private DnsException mDnsException;
        private final Callback<? super List<InetAddress>> mUserCallback;
        private final int mTargetAnswerCount;
        private int mReceivedAnswerCount = 0;

        InetAddressAnswerAccumulator(int size,
        InetAddressAnswerAccumulator(@NonNull Network network, int size,
                @NonNull Callback<? super List<InetAddress>> callback) {
            mNetwork = network;
            mTargetAnswerCount = size;
            mAllAnswers = new ArrayList<>();
            mUserCallback = callback;
@@ -280,8 +277,7 @@ public final class DnsResolver {
        private void maybeReportAnswer() {
            if (++mReceivedAnswerCount != mTargetAnswerCount) return;
            if (mAllAnswers.isEmpty() && maybeReportError()) return;
            // TODO: Do RFC6724 sort.
            mUserCallback.onAnswer(mAllAnswers, mRcode);
            mUserCallback.onAnswer(rfc6724Sort(mNetwork, mAllAnswers), mRcode);
        }

        @Override
@@ -308,7 +304,7 @@ public final class DnsResolver {

    /**
     * Send a DNS query with the specified name on a network with both IPv4 and IPv6,
     * get back a set of InetAddresses asynchronously.
     * get back a set of InetAddresses with rfc6724 sorting style asynchronously.
     *
     * This method will examine the connection ability on given network, and query IPv4
     * and IPv6 if connection is available.
@@ -335,8 +331,23 @@ public final class DnsResolver {
            return;
        }
        final Object lock = new Object();
        final boolean queryIpv6 = haveIpv6(network);
        final boolean queryIpv4 = haveIpv4(network);
        final Network queryNetwork;
        try {
            queryNetwork = (network != null) ? network : new Network(getDnsNetId());
        } catch (ErrnoException e) {
            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
            return;
        }
        final boolean queryIpv6 = haveIpv6(queryNetwork);
        final boolean queryIpv4 = haveIpv4(queryNetwork);

        // This can only happen if queryIpv4 and queryIpv6 are both false.
        // This almost certainly means that queryNetwork does not exist or no longer exists.
        if (!queryIpv6 && !queryIpv4) {
            executor.execute(() -> callback.onError(
                    new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET))));
            return;
        }

        final FileDescriptor v4fd;
        final FileDescriptor v6fd;
@@ -345,9 +356,8 @@ public final class DnsResolver {

        if (queryIpv6) {
            try {
                v6fd = resNetworkQuery((network != null
                        ? network.getNetIdForResolv() : NETID_UNSET),
                        domain, CLASS_IN, TYPE_AAAA, flags);
                v6fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN,
                        TYPE_AAAA, flags);
            } catch (ErrnoException e) {
                executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
                return;
@@ -355,7 +365,6 @@ public final class DnsResolver {
            queryCount++;
        } else v6fd = null;

        // TODO: Use device flag to control the sleep time.
        // Avoiding gateways drop packets if queries are sent too close together
        try {
            Thread.sleep(SLEEP_TIME_MS);
@@ -365,9 +374,8 @@ public final class DnsResolver {

        if (queryIpv4) {
            try {
                v4fd = resNetworkQuery((network != null
                        ? network.getNetIdForResolv() : NETID_UNSET),
                        domain, CLASS_IN, TYPE_A, flags);
                v4fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, TYPE_A,
                        flags);
            } catch (ErrnoException e) {
                if (queryIpv6) resNetworkCancel(v6fd);  // Closes fd, marks it invalid.
                executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
@@ -377,7 +385,7 @@ public final class DnsResolver {
        } else v4fd = null;

        final InetAddressAnswerAccumulator accumulator =
                new InetAddressAnswerAccumulator(queryCount, callback);
                new InetAddressAnswerAccumulator(queryNetwork, queryCount, callback);

        synchronized (lock)  {
            if (queryIpv6) {
@@ -398,7 +406,7 @@ public final class DnsResolver {

    /**
     * Send a DNS query with the specified name and query type, get back a set of
     * InetAddresses asynchronously.
     * InetAddresses with rfc6724 sorting style asynchronously.
     *
     * The answer will be provided asynchronously through the provided {@link Callback}.
     *
@@ -423,15 +431,17 @@ public final class DnsResolver {
        }
        final Object lock = new Object();
        final FileDescriptor queryfd;
        final Network queryNetwork;
        try {
            queryfd = resNetworkQuery((network != null
                    ? network.getNetIdForResolv() : NETID_UNSET), domain, CLASS_IN, nsType, flags);
            queryNetwork = (network != null) ? network : new Network(getDnsNetId());
            queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), 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);
                new InetAddressAnswerAccumulator(queryNetwork, 1, callback);
        synchronized (lock)  {
            registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock);
            if (cancellationSignal == null) return;
@@ -500,38 +510,6 @@ public final class DnsResolver {
        });
    }

    // These two functions match the behaviour of have_ipv4 and have_ipv6 in the native resolver.
    private boolean haveIpv4(@Nullable Network network) {
        final SocketAddress addrIpv4 =
                new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0);
        return checkConnectivity(network, AF_INET, addrIpv4);
    }

    private boolean haveIpv6(@Nullable Network network) {
        final SocketAddress addrIpv6 =
                new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0);
        return checkConnectivity(network, AF_INET6, addrIpv6);
    }

    private boolean checkConnectivity(@Nullable Network network,
            int domain, @NonNull SocketAddress addr) {
        final FileDescriptor socket;
        try {
            socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
        } catch (ErrnoException e) {
            return false;
        }
        try {
            if (network != null) network.bindSocket(socket);
            Os.connect(socket, addr);
        } catch (IOException | ErrnoException e) {
            return false;
        } finally {
            IoUtils.closeQuietly(socket);
        }
        return true;
    }

    private static class DnsAddressAnswer extends DnsPacket {
        private static final String TAG = "DnsResolver.DnsAddressAnswer";
        private static final boolean DBG = false;
+7 −0
Original line number Diff line number Diff line
@@ -156,6 +156,13 @@ public class NetworkUtils {
     */
    public static native void resNetworkCancel(FileDescriptor fd);

    /**
     * DNS resolver series jni method.
     * Attempts to get netid of network which resolver will
     * use if no network is explicitly selected.
     */
    public static native int getDnsNetId() throws ErrnoException;

    /**
     * Get the tcp repair window associated with the {@code fd}.
     *
+376 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.net.util;

import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.InetAddresses;
import android.net.Network;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;

import com.android.internal.util.BitUtils;

import libcore.io.IoUtils;

import java.io.FileDescriptor;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @hide
 */
public class DnsUtils {
    private static final String TAG = "DnsUtils";
    private static final int CHAR_BIT = 8;
    public static final int IPV6_ADDR_SCOPE_NODELOCAL = 0x01;
    public static final int IPV6_ADDR_SCOPE_LINKLOCAL = 0x02;
    public static final int IPV6_ADDR_SCOPE_SITELOCAL = 0x05;
    public static final int IPV6_ADDR_SCOPE_GLOBAL = 0x0e;
    private static final Comparator<SortableAddress> sRfc6724Comparator = new Rfc6724Comparator();

    /**
     * Comparator to sort SortableAddress in Rfc6724 style.
     */
    public static class Rfc6724Comparator implements Comparator<SortableAddress> {
        // This function matches the behaviour of _rfc6724_compare in the native resolver.
        @Override
        public int compare(SortableAddress span1, SortableAddress span2) {
            // Rule 1: Avoid unusable destinations.
            if (span1.hasSrcAddr != span2.hasSrcAddr) {
                return span2.hasSrcAddr - span1.hasSrcAddr;
            }

            // Rule 2: Prefer matching scope.
            if (span1.scopeMatch != span2.scopeMatch) {
                return span2.scopeMatch - span1.scopeMatch;
            }

            // TODO: Implement rule 3: Avoid deprecated addresses.
            // TODO: Implement rule 4: Prefer home addresses.

            // Rule 5: Prefer matching label.
            if (span1.labelMatch != span2.labelMatch) {
                return span2.labelMatch - span1.labelMatch;
            }

            // Rule 6: Prefer higher precedence.
            if (span1.precedence != span2.precedence) {
                return span2.precedence - span1.precedence;
            }

            // TODO: Implement rule 7: Prefer native transport.

            // Rule 8: Prefer smaller scope.
            if (span1.scope != span2.scope) {
                return span1.scope - span2.scope;
            }

            // Rule 9: Use longest matching prefix. IPv6 only.
            if (span1.prefixMatchLen != span2.prefixMatchLen) {
                return span2.prefixMatchLen - span1.prefixMatchLen;
            }

            // Rule 10: Leave the order unchanged. Collections.sort is a stable sort.
            return 0;
        }
    }

    /**
     * Class used to sort with RFC 6724
     */
    public static class SortableAddress {
        public final int label;
        public final int labelMatch;
        public final int scope;
        public final int scopeMatch;
        public final int precedence;
        public final int prefixMatchLen;
        public final int hasSrcAddr;
        public final InetAddress address;

        public SortableAddress(@NonNull InetAddress addr, @Nullable InetAddress srcAddr) {
            address = addr;
            hasSrcAddr = (srcAddr != null) ? 1 : 0;
            label = findLabel(addr);
            scope = findScope(addr);
            precedence = findPrecedence(addr);
            labelMatch = ((srcAddr != null) && (label == findLabel(srcAddr))) ? 1 : 0;
            scopeMatch = ((srcAddr != null) && (scope == findScope(srcAddr))) ? 1 : 0;
            if (isIpv6Address(addr) && isIpv6Address(srcAddr)) {
                prefixMatchLen = compareIpv6PrefixMatchLen(srcAddr, addr);
            } else {
                prefixMatchLen = 0;
            }
        }
    }

    /**
     * Sort the given address list in RFC6724 order.
     * Will leave the list unchanged if an error occurs.
     *
     * This function matches the behaviour of _rfc6724_sort in the native resolver.
     */
    public static @NonNull List<InetAddress> rfc6724Sort(@Nullable Network network,
            @NonNull List<InetAddress> answers) {
        List<SortableAddress> sortableAnswerList = new ArrayList<>();
        answers.forEach(addr -> sortableAnswerList.add(
                new SortableAddress(addr, findSrcAddress(network, addr))));

        Collections.sort(sortableAnswerList, sRfc6724Comparator);

        final List<InetAddress> sortedAnswers = new ArrayList<>();
        sortableAnswerList.forEach(ans -> sortedAnswers.add(ans.address));

        return sortedAnswers;
    }

    private static @Nullable InetAddress findSrcAddress(@Nullable Network network,
            @NonNull InetAddress addr) {
        final int domain;
        if (isIpv4Address(addr)) {
            domain = AF_INET;
        } else if (isIpv6Address(addr)) {
            domain = AF_INET6;
        } else {
            return null;
        }
        final FileDescriptor socket;
        try {
            socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
        } catch (ErrnoException e) {
            Log.e(TAG, "findSrcAddress:" + e.toString());
            return null;
        }
        try {
            if (network != null) network.bindSocket(socket);
            Os.connect(socket, new InetSocketAddress(addr, 0));
            return ((InetSocketAddress) Os.getsockname(socket)).getAddress();
        } catch (IOException | ErrnoException e) {
            return null;
        } finally {
            IoUtils.closeQuietly(socket);
        }
    }

    /**
     * Get the label for a given IPv4/IPv6 address.
     * RFC 6724, section 2.1.
     *
     * Note that Java will return an IPv4-mapped address as an IPv4 address.
     */
    private static int findLabel(@NonNull InetAddress addr) {
        if (isIpv4Address(addr)) {
            return 4;
        } else if (isIpv6Address(addr)) {
            if (addr.isLoopbackAddress()) {
                return 0;
            } else if (isIpv6Address6To4(addr)) {
                return 2;
            } else if (isIpv6AddressTeredo(addr)) {
                return 5;
            } else if (isIpv6AddressULA(addr)) {
                return 13;
            } else if (((Inet6Address) addr).isIPv4CompatibleAddress()) {
                return 3;
            } else if (addr.isSiteLocalAddress()) {
                return 11;
            } else if (isIpv6Address6Bone(addr)) {
                return 12;
            } else {
                // All other IPv6 addresses, including global unicast addresses.
                return 1;
            }
        } else {
            // This should never happen.
            return 1;
        }
    }

    private static boolean isIpv6Address(@Nullable InetAddress addr) {
        return addr instanceof Inet6Address;
    }

    private static boolean isIpv4Address(@Nullable InetAddress addr) {
        return addr instanceof Inet4Address;
    }

    private static boolean isIpv6Address6To4(@NonNull InetAddress addr) {
        if (!isIpv6Address(addr)) return false;
        final byte[] byteAddr = addr.getAddress();
        return byteAddr[0] == 0x20 && byteAddr[1] == 0x02;
    }

    private static boolean isIpv6AddressTeredo(@NonNull InetAddress addr) {
        if (!isIpv6Address(addr)) return false;
        final byte[] byteAddr = addr.getAddress();
        return byteAddr[0] == 0x20 && byteAddr[1] == 0x01 && byteAddr[2] == 0x00
                && byteAddr[3] == 0x00;
    }

    private static boolean isIpv6AddressULA(@NonNull InetAddress addr) {
        return isIpv6Address(addr) && (addr.getAddress()[0] & 0xfe) == 0xfc;
    }

    private static boolean isIpv6Address6Bone(@NonNull InetAddress addr) {
        if (!isIpv6Address(addr)) return false;
        final byte[] byteAddr = addr.getAddress();
        return byteAddr[0] == 0x3f && byteAddr[1] == (byte) 0xfe;
    }

    private static int getIpv6MulticastScope(@NonNull InetAddress addr) {
        return !isIpv6Address(addr) ? 0 : (addr.getAddress()[1] & 0x0f);
    }

    private static int findScope(@NonNull InetAddress addr) {
        if (isIpv6Address(addr)) {
            if (addr.isMulticastAddress()) {
                return getIpv6MulticastScope(addr);
            } else if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
                /**
                 * RFC 4291 section 2.5.3 says loopback is to be treated as having
                 * link-local scope.
                 */
                return IPV6_ADDR_SCOPE_LINKLOCAL;
            } else if (addr.isSiteLocalAddress()) {
                return IPV6_ADDR_SCOPE_SITELOCAL;
            } else {
                return IPV6_ADDR_SCOPE_GLOBAL;
            }
        } else if (isIpv4Address(addr)) {
            if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
                return IPV6_ADDR_SCOPE_LINKLOCAL;
            } else {
                /**
                 * RFC 6724 section 3.2. Other IPv4 addresses, including private addresses
                 * and shared addresses (100.64.0.0/10), are assigned global scope.
                 */
                return IPV6_ADDR_SCOPE_GLOBAL;
            }
        } else {
            /**
             * This should never happen.
             * Return a scope with low priority as a last resort.
             */
            return IPV6_ADDR_SCOPE_NODELOCAL;
        }
    }

    /**
     * Get the precedence for a given IPv4/IPv6 address.
     * RFC 6724, section 2.1.
     *
     * Note that Java will return an IPv4-mapped address as an IPv4 address.
     */
    private static int findPrecedence(@NonNull InetAddress addr) {
        if (isIpv4Address(addr)) {
            return 35;
        } else if (isIpv6Address(addr)) {
            if (addr.isLoopbackAddress()) {
                return 50;
            } else if (isIpv6Address6To4(addr)) {
                return 30;
            } else if (isIpv6AddressTeredo(addr)) {
                return 5;
            } else if (isIpv6AddressULA(addr)) {
                return 3;
            } else if (((Inet6Address) addr).isIPv4CompatibleAddress() || addr.isSiteLocalAddress()
                    || isIpv6Address6Bone(addr)) {
                return 1;
            } else {
                // All other IPv6 addresses, including global unicast addresses.
                return 40;
            }
        } else {
            return 1;
        }
    }

    /**
     * Find number of matching initial bits between the two addresses.
     */
    private static int compareIpv6PrefixMatchLen(@NonNull InetAddress srcAddr,
            @NonNull InetAddress dstAddr) {
        final byte[] srcByte = srcAddr.getAddress();
        final byte[] dstByte = dstAddr.getAddress();

        // This should never happen.
        if (srcByte.length != dstByte.length) return 0;

        for (int i = 0; i < dstByte.length; ++i) {
            if (srcByte[i] == dstByte[i]) {
                continue;
            }
            int x = BitUtils.uint8(srcByte[i]) ^ BitUtils.uint8(dstByte[i]);
            return i * CHAR_BIT + (Integer.numberOfLeadingZeros(x) - 24);  // Java ints are 32 bits
        }
        return dstByte.length * CHAR_BIT;
    }

    /**
     * Check if given network has Ipv4 capability
     * This function matches the behaviour of have_ipv4 in the native resolver.
     */
    public static boolean haveIpv4(@Nullable Network network) {
        final SocketAddress addrIpv4 =
                new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0);
        return checkConnectivity(network, AF_INET, addrIpv4);
    }

    /**
     * Check if given network has Ipv6 capability
     * This function matches the behaviour of have_ipv6 in the native resolver.
     */
    public static boolean haveIpv6(@Nullable Network network) {
        final SocketAddress addrIpv6 =
                new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0);
        return checkConnectivity(network, AF_INET6, addrIpv6);
    }

    private static boolean checkConnectivity(@Nullable Network network,
            int domain, @NonNull SocketAddress addr) {
        final FileDescriptor socket;
        try {
            socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
        } catch (ErrnoException e) {
            return false;
        }
        try {
            if (network != null) network.bindSocket(socket);
            Os.connect(socket, addr);
        } catch (IOException | ErrnoException e) {
            return false;
        } finally {
            IoUtils.closeQuietly(socket);
        }
        return true;
    }
}
+19 −8

File changed.

Preview size limit exceeded, changes collapsed.

+202 −0

File added.

Preview size limit exceeded, changes collapsed.