From 51b9f6c069d6ff392c5fdd063d062a4ab97a22d9 Mon Sep 17 00:00:00 2001 From: Luke Huang Date: Thu, 23 May 2019 06:14:28 -0700 Subject: [PATCH] Add Rfc6724 style sort for DnsResolver and fix potential bug 1. pass default network explicitly to fix potential mis-sync network problem in DnsResolver#query 2. Add rfc6724 sort and related test 3. DnsResolver do rfc6724 sort before response InetAddress answers 4. move haveIpv* function from DnsResolver to DnsUtils Bug: 129530368 Test: atest DnsResolverTest DnsUtilsTest Merged-In: I0323f5c7f32fc3fa589b9e87f8e7c9caf744dbd4 (cherry picked from commit d352f4ca85ff8418a5a58d32fb03b85d7e0b843b) Change-Id: I98455045fa43cc5a5902a08232251c1734feaac3 --- core/java/android/net/DnsResolver.java | 106 ++--- core/java/android/net/NetworkUtils.java | 7 + core/java/android/net/util/DnsUtils.java | 376 ++++++++++++++++++ core/jni/android_net_NetUtils.cpp | 29 +- .../java/android/net/util/DnsUtilsTest.java | 221 ++++++++++ 5 files changed, 666 insertions(+), 73 deletions(-) create mode 100644 core/java/android/net/util/DnsUtils.java create mode 100644 tests/net/java/android/net/util/DnsUtilsTest.java diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java index 68826cbeb8..4b2b4c35b2 100644 --- a/core/java/android/net/DnsResolver.java +++ b/core/java/android/net/DnsResolver.java @@ -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 { private final List mAllAnswers; + private final Network mNetwork; private int mRcode; private DnsException mDnsException; private final Callback> mUserCallback; private final int mTargetAnswerCount; private int mReceivedAnswerCount = 0; - InetAddressAnswerAccumulator(int size, + InetAddressAnswerAccumulator(@NonNull Network network, int size, @NonNull Callback> 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; diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index c06a13269d..a640f83ea5 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -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}. * diff --git a/core/java/android/net/util/DnsUtils.java b/core/java/android/net/util/DnsUtils.java new file mode 100644 index 0000000000..e6abd50590 --- /dev/null +++ b/core/java/android/net/util/DnsUtils.java @@ -0,0 +1,376 @@ +/* + * 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 sRfc6724Comparator = new Rfc6724Comparator(); + + /** + * Comparator to sort SortableAddress in Rfc6724 style. + */ + public static class Rfc6724Comparator implements Comparator { + // 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 rfc6724Sort(@Nullable Network network, + @NonNull List answers) { + List sortableAnswerList = new ArrayList<>(); + answers.forEach(addr -> sortableAnswerList.add( + new SortableAddress(addr, findSrcAddress(network, addr)))); + + Collections.sort(sortableAnswerList, sRfc6724Comparator); + + final List 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; + } +} diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index c5fc9b3628..00e0e3a74d 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -18,26 +18,27 @@ #include -#include "jni.h" -#include -#include -#include "NetdClient.h" -#include -#include -#include #include -#include #include #include #include +#include #include #include #include #include #include -#include +#include +#include +#include +#include +#include +#include + +#include "NetdClient.h" #include "core_jni_helpers.h" +#include "jni.h" extern "C" { int ifc_enable(const char *ifname); @@ -303,6 +304,15 @@ static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobjec jniSetFileDescriptorOfFD(env, javaFd, -1); } +static jint android_net_utils_getDnsNetId(JNIEnv *env, jobject thiz) { + int dnsNetId = getNetworkForDns(); + if (dnsNetId < 0) { + throwErrnoException(env, "getDnsNetId", -dnsNetId); + } + + return dnsNetId; +} + static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { if (javaFd == NULL) { jniThrowNullPointerException(env, NULL); @@ -359,6 +369,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult }, { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel }, + { "getDnsNetId", "()I", (void*) android_net_utils_getDnsNetId }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/tests/net/java/android/net/util/DnsUtilsTest.java b/tests/net/java/android/net/util/DnsUtilsTest.java new file mode 100644 index 0000000000..e5cb09f237 --- /dev/null +++ b/tests/net/java/android/net/util/DnsUtilsTest.java @@ -0,0 +1,221 @@ +/* + * 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.net.util.DnsUtils.IPV6_ADDR_SCOPE_GLOBAL; +import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_LINKLOCAL; +import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_SITELOCAL; +import static android.net.util.DnsUtils.rfc6724Sort; + +import static org.junit.Assert.assertEquals; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.InetAddresses; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DnsUtilsTest { + private InetAddress stringToAddress(@NonNull String addr) { + return InetAddresses.parseNumericAddress(addr); + } + + private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr) { + return makeSortableAddress(addr, null); + } + + private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr, + @Nullable String srcAddr) { + return new DnsUtils.SortableAddress(stringToAddress(addr), + srcAddr != null ? stringToAddress(srcAddr) : null); + } + + @Test + public void testRfc6724Sort() { + final List testAddresses = Arrays.asList( + stringToAddress("172.217.24.14"), + stringToAddress("216.58.200.46"), + stringToAddress("2404:6800:4008:802::200e")); + + final List expected = Arrays.asList( + stringToAddress("2404:6800:4008:802::200e"), + stringToAddress("172.217.24.14"), + stringToAddress("216.58.200.46")); + + final List result = rfc6724Sort(null, testAddresses); + + assertEquals(result.size(), testAddresses.size()); + assertEquals(result, expected); + } + + @Test + public void testRfc6724Comparator() { + final List test = Arrays.asList( + makeSortableAddress("216.58.200.36"), // Ipv4 + makeSortableAddress("2404:6800:4008:801::2004"), // global + makeSortableAddress("::1"), // loop back + makeSortableAddress("fe80::c46f:1cff:fe04:39b4"), // link local + makeSortableAddress("::ffff:192.168.95.3"), // IPv4-mapped IPv6 + makeSortableAddress("2001::47c1"), // teredo tunneling + makeSortableAddress("::216.58.200.36"), // IPv4-compatible + makeSortableAddress("3ffe::1234:5678")); // 6bone + + final List expected = Arrays.asList( + stringToAddress("::1"), // loop back + stringToAddress("fe80::c46f:1cff:fe04:39b4"), // link local + stringToAddress("2404:6800:4008:801::2004"), // global + stringToAddress("216.58.200.36"), // Ipv4 + stringToAddress("::ffff:192.168.95.3"), // IPv4-mapped IPv6 + stringToAddress("2001::47c1"), // teredo tunneling + stringToAddress("::216.58.200.36"), // IPv4-compatible + stringToAddress("3ffe::1234:5678")); // 6bone + + Collections.sort(test, new DnsUtils.Rfc6724Comparator()); + + for (int i = 0; i < test.size(); ++i) { + assertEquals(test.get(i).address, expected.get(i)); + } + + // TODO: add more combinations + } + + @Test + public void testV4SortableAddress() { + // Test V4 address + DnsUtils.SortableAddress test = makeSortableAddress("216.58.200.36"); + assertEquals(test.hasSrcAddr, 0); + assertEquals(test.prefixMatchLen, 0); + assertEquals(test.address, stringToAddress("216.58.200.36")); + assertEquals(test.labelMatch, 0); + assertEquals(test.scopeMatch, 0); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 4); + assertEquals(test.precedence, 35); + + // Test V4 loopback address with the same source address + test = makeSortableAddress("127.1.2.3", "127.1.2.3"); + assertEquals(test.hasSrcAddr, 1); + assertEquals(test.prefixMatchLen, 0); + assertEquals(test.address, stringToAddress("127.1.2.3")); + assertEquals(test.labelMatch, 1); + assertEquals(test.scopeMatch, 1); + assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL); + assertEquals(test.label, 4); + assertEquals(test.precedence, 35); + } + + @Test + public void testV6SortableAddress() { + // Test global address + DnsUtils.SortableAddress test = makeSortableAddress("2404:6800:4008:801::2004"); + assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004")); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 1); + assertEquals(test.precedence, 40); + + // Test global address with global source address + test = makeSortableAddress("2404:6800:4008:801::2004", + "2401:fa00:fc:fd00:6d6c:7199:b8e7:41d6"); + assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004")); + assertEquals(test.hasSrcAddr, 1); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.labelMatch, 1); + assertEquals(test.scopeMatch, 1); + assertEquals(test.label, 1); + assertEquals(test.precedence, 40); + assertEquals(test.prefixMatchLen, 13); + + // Test global address with linklocal source address + test = makeSortableAddress("2404:6800:4008:801::2004", "fe80::c46f:1cff:fe04:39b4"); + assertEquals(test.hasSrcAddr, 1); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.labelMatch, 1); + assertEquals(test.scopeMatch, 0); + assertEquals(test.label, 1); + assertEquals(test.precedence, 40); + assertEquals(test.prefixMatchLen, 0); + + // Test loopback address with the same source address + test = makeSortableAddress("::1", "::1"); + assertEquals(test.hasSrcAddr, 1); + assertEquals(test.prefixMatchLen, 16 * 8); + assertEquals(test.labelMatch, 1); + assertEquals(test.scopeMatch, 1); + assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL); + assertEquals(test.label, 0); + assertEquals(test.precedence, 50); + + // Test linklocal address + test = makeSortableAddress("fe80::c46f:1cff:fe04:39b4"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL); + assertEquals(test.label, 1); + assertEquals(test.precedence, 40); + + // Test linklocal address + test = makeSortableAddress("fe80::"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL); + assertEquals(test.label, 1); + assertEquals(test.precedence, 40); + + // Test 6to4 address + test = makeSortableAddress("2002:c000:0204::"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 2); + assertEquals(test.precedence, 30); + + // Test unique local address + test = makeSortableAddress("fc00::c000:13ab"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 13); + assertEquals(test.precedence, 3); + + // Test teredo tunneling address + test = makeSortableAddress("2001::47c1"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 5); + assertEquals(test.precedence, 5); + + // Test IPv4-compatible addresses + test = makeSortableAddress("::216.58.200.36"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 3); + assertEquals(test.precedence, 1); + + // Test site-local address + test = makeSortableAddress("fec0::cafe:3ab2"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_SITELOCAL); + assertEquals(test.label, 11); + assertEquals(test.precedence, 1); + + // Test 6bone address + test = makeSortableAddress("3ffe::1234:5678"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 12); + assertEquals(test.precedence, 1); + } +}