| /* |
| * 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) { |
| final ArrayList<SortableAddress> sortableAnswerList = new ArrayList<>(); |
| for (InetAddress addr : answers) { |
| sortableAnswerList.add(new SortableAddress(addr, findSrcAddress(network, addr))); |
| } |
| |
| Collections.sort(sortableAnswerList, sRfc6724Comparator); |
| |
| final List<InetAddress> sortedAnswers = new ArrayList<>(); |
| for (SortableAddress ans : sortableAnswerList) { |
| 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; |
| } |
| } |