diff options
author | Scott Lobdell <slobdell@google.com> | 2019-05-23 10:20:12 -0700 |
---|---|---|
committer | Scott Lobdell <slobdell@google.com> | 2019-05-30 11:21:08 -0700 |
commit | 9caf34febf01086c96266e38d024f7a0315b892d (patch) | |
tree | 17b546332f8646bd04368fca81eac7775df7b551 /packages/NetworkStack | |
parent | 29b9aa9d17b0c4cf5daeec11869ea89d225fe9f0 (diff) | |
parent | 073a6d2dbebfe06c5cc60ed8125c010acfe35581 (diff) |
Merge QP1A.190523.001
Change-Id: I48bbfdee20d40d144a8628ca32c8e1ca97f172b2
Diffstat (limited to 'packages/NetworkStack')
13 files changed, 744 insertions, 214 deletions
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 62de2ba45455..aefa882f0ce8 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -98,8 +98,6 @@ java_defaults { optimize: { proguard_flags_files: ["proguard.flags"], }, - // The permission configuration *must* be included to ensure security of the device - required: ["NetworkPermissionConfig"], } // Non-updatable network stack running in the system server process for devices not using the module @@ -108,6 +106,12 @@ android_app { defaults: ["NetworkStackAppCommon"], certificate: "platform", manifest: "AndroidManifest_InProcess.xml", + // InProcessNetworkStack is a replacement for NetworkStack + overrides: ["NetworkStack"], + // The permission configuration *must* be included to ensure security of the device + // The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces + // the default CaptivePortalLogin. + required: ["PlatformNetworkPermissionConfig", "PlatformCaptivePortalLogin"], } // Updatable network stack packaged as an application @@ -116,6 +120,9 @@ android_app { defaults: ["NetworkStackAppCommon"], certificate: "networkstack", manifest: "AndroidManifest.xml", + use_embedded_native_libs: true, + // The permission configuration *must* be included to ensure security of the device + required: ["NetworkPermissionConfig"], } genrule { diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index 252b90fea840..4c4448482e03 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -41,7 +41,9 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <!-- Signature permission defined in NetworkStackStub --> <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" /> - <application> + <application + android:extractNativeLibs="false" + android:persistent="true"> <service android:name="com.android.server.NetworkStackService"> <intent-filter> <action android:name="android.net.INetworkStackConnector"/> diff --git a/packages/NetworkStack/AndroidManifestBase.xml b/packages/NetworkStack/AndroidManifestBase.xml index 3da566f88659..d00a55143605 100644 --- a/packages/NetworkStack/AndroidManifestBase.xml +++ b/packages/NetworkStack/AndroidManifestBase.xml @@ -24,7 +24,6 @@ android:label="NetworkStack" android:defaultToDeviceProtectedStorage="true" android:directBootAware="true" - android:persistent="true" android:usesCleartextTraffic="true"> <service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService" diff --git a/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java b/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java index 475f8261fdc1..41715b2a4798 100644 --- a/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java +++ b/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java @@ -19,6 +19,9 @@ package android.net; import android.annotation.NonNull; import android.content.Context; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; + /** * service used to communicate with the ip memory store service in network stack, * which is running in the same module. @@ -35,8 +38,7 @@ public class NetworkStackIpMemoryStore extends IpMemoryStoreClient { } @Override - @NonNull - protected IIpMemoryStore getService() { - return mService; + protected void runWhenServiceReady(Consumer<IIpMemoryStore> cb) throws ExecutionException { + cb.accept(mService); } } diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 359c85983a94..f05431968684 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -155,7 +155,8 @@ public class ApfFilter { DROPPED_ETHERTYPE_BLACKLISTED, DROPPED_ARP_REPLY_SPA_NO_HOST, DROPPED_IPV4_KEEPALIVE_ACK, - DROPPED_IPV6_KEEPALIVE_ACK; + DROPPED_IPV6_KEEPALIVE_ACK, + DROPPED_IPV4_NATT_KEEPALIVE; // Returns the negative byte offset from the end of the APF data segment for // a given counter. @@ -857,12 +858,104 @@ public class ApfFilter { } } - // A class to hold keepalive ack information. - private abstract static class TcpKeepaliveAck { + // TODO: Refactor these subclasses to avoid so much repetition. + private abstract static class KeepalivePacket { // Note that the offset starts from IP header. // These must be added ether header length when generating program. static final int IP_HEADER_OFFSET = 0; + static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; + // Append a filter for this keepalive ack to {@code gen}. + // Jump to drop if it matches the keepalive ack. + // Jump to the next filter if packet doesn't match the keepalive ack. + abstract void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException; + } + + // A class to hold NAT-T keepalive ack information. + private class NattKeepaliveResponse extends KeepalivePacket { + static final int UDP_LENGTH_OFFSET = 4; + static final int UDP_HEADER_LEN = 8; + + protected class NattKeepaliveResponseData { + public final byte[] srcAddress; + public final int srcPort; + public final byte[] dstAddress; + public final int dstPort; + + NattKeepaliveResponseData(final NattKeepalivePacketDataParcelable sentKeepalivePacket) { + srcAddress = sentKeepalivePacket.dstAddress; + srcPort = sentKeepalivePacket.dstPort; + dstAddress = sentKeepalivePacket.srcAddress; + dstPort = sentKeepalivePacket.srcPort; + } + } + + protected final NattKeepaliveResponseData mPacket; + protected final byte[] mSrcDstAddr; + protected final byte[] mPortFingerprint; + // NAT-T keepalive packet + protected final byte[] mPayload = {(byte) 0xff}; + + NattKeepaliveResponse(final NattKeepalivePacketDataParcelable sentKeepalivePacket) { + mPacket = new NattKeepaliveResponseData(sentKeepalivePacket); + mSrcDstAddr = concatArrays(mPacket.srcAddress, mPacket.dstAddress); + mPortFingerprint = generatePortFingerprint(mPacket.srcPort, mPacket.dstPort); + } + + byte[] generatePortFingerprint(int srcPort, int dstPort) { + final ByteBuffer fp = ByteBuffer.allocate(4); + fp.order(ByteOrder.BIG_ENDIAN); + fp.putShort((short) srcPort); + fp.putShort((short) dstPort); + return fp.array(); + } + + @Override + void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked(); + + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); + gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); + + // A NAT-T keepalive packet contains 1 byte payload with the value 0xff + // Check payload length is 1 + gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addAdd(UDP_HEADER_LEN); + gen.addSwap(); + gen.addLoad16(Register.R0, IPV4_TOTAL_LENGTH_OFFSET); + gen.addNeg(Register.R1); + gen.addAddR1(); + gen.addJumpIfR0NotEquals(1, nextFilterLabel); + + // Check that the ports match + gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addAdd(ETH_HEADER_LEN); + gen.addJumpIfBytesNotEqual(Register.R0, mPortFingerprint, nextFilterLabel); + + // Payload offset = R0 + UDP header length + gen.addAdd(UDP_HEADER_LEN); + gen.addJumpIfBytesNotEqual(Register.R0, mPayload, nextFilterLabel); + + maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE); + gen.addJump(mCountAndDropLabel); + gen.defineLabel(nextFilterLabel); + } + + public String toString() { + try { + return String.format("%s -> %s", + NetworkStackUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort), + NetworkStackUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort)); + } catch (UnknownHostException e) { + return "Unknown host"; + } + } + } + + // A class to hold TCP keepalive ack information. + private abstract static class TcpKeepaliveAck extends KeepalivePacket { protected static class TcpKeepaliveAckData { public final byte[] srcAddress; public final int srcPort; @@ -870,6 +963,7 @@ public class ApfFilter { public final int dstPort; public final int seq; public final int ack; + // Create the characteristics of the ack packet from the sent keepalive packet. TcpKeepaliveAckData(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { srcAddress = sentKeepalivePacket.dstAddress; @@ -902,28 +996,18 @@ public class ApfFilter { return fp.array(); } - static byte[] concatArrays(final byte[]... arr) { - int size = 0; - for (byte[] a : arr) { - size += a.length; - } - final byte[] result = new byte[size]; - int offset = 0; - for (byte[] a : arr) { - System.arraycopy(a, 0, result, offset, a.length); - offset += a.length; - } - return result; - } - public String toString() { - return String.format("%s(%d) -> %s(%d), seq=%d, ack=%d", - mPacket.srcAddress, - mPacket.srcPort, - mPacket.dstAddress, - mPacket.dstPort, - mPacket.seq, - mPacket.ack); + try { + return String.format("%s -> %s , seq=%d, ack=%d", + NetworkStackUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort), + NetworkStackUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort), + Integer.toUnsignedLong(mPacket.seq), + Integer.toUnsignedLong(mPacket.ack)); + } catch (UnknownHostException e) { + return "Unknown host"; + } } // Append a filter for this keepalive ack to {@code gen}. @@ -933,7 +1017,6 @@ public class ApfFilter { } private class TcpKeepaliveAckV4 extends TcpKeepaliveAck { - private static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { this(new TcpKeepaliveAckData(sentKeepalivePacket)); @@ -987,7 +1070,7 @@ public class ApfFilter { @Override void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { - throw new UnsupportedOperationException("IPv6 Keepalive is not supported yet"); + throw new UnsupportedOperationException("IPv6 TCP Keepalive is not supported yet"); } } @@ -997,7 +1080,7 @@ public class ApfFilter { @GuardedBy("this") private ArrayList<Ra> mRas = new ArrayList<>(); @GuardedBy("this") - private SparseArray<TcpKeepaliveAck> mKeepaliveAcks = new SparseArray<>(); + private SparseArray<KeepalivePacket> mKeepalivePackets = new SparseArray<>(); // There is always some marginal benefit to updating the installed APF program when an RA is // seen because we can extend the program's lifetime slightly, but there is some cost to @@ -1171,9 +1254,12 @@ public class ApfFilter { gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel); } - // If any keepalive filter matches, drop + // If any TCP keepalive filter matches, drop generateV4KeepaliveFilters(gen); + // If any NAT-T keepalive filter matches, drop + generateV4NattKeepaliveFilters(gen); + // Otherwise, this is an IPv4 unicast, pass // If L2 broadcast packet, drop. // TODO: can we invert this condition to fall through to the common pass case below? @@ -1184,6 +1270,7 @@ public class ApfFilter { gen.addJump(mCountAndDropLabel); } else { generateV4KeepaliveFilters(gen); + generateV4NattKeepaliveFilters(gen); } // Otherwise, pass @@ -1191,25 +1278,36 @@ public class ApfFilter { gen.addJump(mCountAndPassLabel); } - private void generateV4KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { - final String skipV4KeepaliveFilter = "skip_v4_keepalive_filter"; - final boolean haveV4KeepaliveAcks = NetworkStackUtils.any(mKeepaliveAcks, - ack -> ack instanceof TcpKeepaliveAckV4); + private void generateKeepaliveFilters(ApfGenerator gen, Class<?> filterType, int proto, + int offset, String label) throws IllegalInstructionException { + final boolean haveKeepaliveResponses = NetworkStackUtils.any(mKeepalivePackets, + ack -> filterType.isInstance(ack)); - // If no keepalive acks - if (!haveV4KeepaliveAcks) return; + // If no keepalive packets of this type + if (!haveKeepaliveResponses) return; - // If not tcp, skip keepalive filters - gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); - gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipV4KeepaliveFilter); + // If not the right proto, skip keepalive filters + gen.addLoad8(Register.R0, offset); + gen.addJumpIfR0NotEquals(proto, label); - // Drop IPv4 Keepalive acks - for (int i = 0; i < mKeepaliveAcks.size(); ++i) { - final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i); - if (ack instanceof TcpKeepaliveAckV4) ack.generateFilterLocked(gen); + // Drop Keepalive responses + for (int i = 0; i < mKeepalivePackets.size(); ++i) { + final KeepalivePacket response = mKeepalivePackets.valueAt(i); + if (filterType.isInstance(response)) response.generateFilterLocked(gen); } - gen.defineLabel(skipV4KeepaliveFilter); + gen.defineLabel(label); + } + + private void generateV4KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + generateKeepaliveFilters(gen, TcpKeepaliveAckV4.class, IPPROTO_TCP, IPV4_PROTOCOL_OFFSET, + "skip_v4_keepalive_filter"); + } + + private void generateV4NattKeepaliveFilters(ApfGenerator gen) + throws IllegalInstructionException { + generateKeepaliveFilters(gen, NattKeepaliveResponse.class, + IPPROTO_UDP, IPV4_PROTOCOL_OFFSET, "skip_v4_nattkeepalive_filter"); } /** @@ -1294,24 +1392,8 @@ public class ApfFilter { } private void generateV6KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { - final String skipV6KeepaliveFilter = "skip_v6_keepalive_filter"; - final boolean haveV6KeepaliveAcks = NetworkStackUtils.any(mKeepaliveAcks, - ack -> ack instanceof TcpKeepaliveAckV6); - - // If no keepalive acks - if (!haveV6KeepaliveAcks) return; - - // If not tcp, skip keepalive filters - gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET); - gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipV6KeepaliveFilter); - - // Drop IPv6 Keepalive acks - for (int i = 0; i < mKeepaliveAcks.size(); ++i) { - final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i); - if (ack instanceof TcpKeepaliveAckV6) ack.generateFilterLocked(gen); - } - - gen.defineLabel(skipV6KeepaliveFilter); + generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET, + "skip_v6_keepalive_filter"); } /** @@ -1701,26 +1783,34 @@ public class ApfFilter { public synchronized void addTcpKeepalivePacketFilter(final int slot, final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { log("Adding keepalive ack(" + slot + ")"); - if (null != mKeepaliveAcks.get(slot)) { + if (null != mKeepalivePackets.get(slot)) { throw new IllegalArgumentException("Keepalive slot " + slot + " is occupied"); } final int ipVersion = sentKeepalivePacket.srcAddress.length == 4 ? 4 : 6; - mKeepaliveAcks.put(slot, (ipVersion == 4) + mKeepalivePackets.put(slot, (ipVersion == 4) ? new TcpKeepaliveAckV4(sentKeepalivePacket) : new TcpKeepaliveAckV6(sentKeepalivePacket)); installNewProgramLocked(); } /** - * Add NATT keepalive packet filter. - * This will add a filter to drop NATT keepalive packet which is passed as an argument. + * Add NAT-T keepalive packet filter. + * This will add a filter to drop NAT-T keepalive packet which is passed as an argument. * * @param slot The index used to access the filter. * @param sentKeepalivePacket The attributes of the sent keepalive packet. */ public synchronized void addNattKeepalivePacketFilter(final int slot, final NattKeepalivePacketDataParcelable sentKeepalivePacket) { - Log.e(TAG, "APF add NATT keepalive filter is not implemented"); + log("Adding NAT-T keepalive packet(" + slot + ")"); + if (null != mKeepalivePackets.get(slot)) { + throw new IllegalArgumentException("NAT-T Keepalive slot " + slot + " is occupied"); + } + if (sentKeepalivePacket.srcAddress.length != 4) { + throw new IllegalArgumentException("NAT-T keepalive is only supported on IPv4"); + } + mKeepalivePackets.put(slot, new NattKeepaliveResponse(sentKeepalivePacket)); + installNewProgramLocked(); } /** @@ -1729,7 +1819,8 @@ public class ApfFilter { * @param slot The index used to access the filter. */ public synchronized void removeKeepalivePacketFilter(int slot) { - mKeepaliveAcks.remove(slot); + log("Removing keepalive packet(" + slot + ")"); + mKeepalivePackets.remove(slot); installNewProgramLocked(); } @@ -1785,14 +1876,29 @@ public class ApfFilter { } pw.decreaseIndent(); - pw.println("Keepalive filters:"); + pw.println("TCP Keepalive filters:"); pw.increaseIndent(); - for (int i = 0; i < mKeepaliveAcks.size(); ++i) { - final TcpKeepaliveAck keepaliveAck = mKeepaliveAcks.valueAt(i); - pw.print("Slot "); - pw.print(mKeepaliveAcks.keyAt(i)); - pw.print(" : "); - pw.println(keepaliveAck); + for (int i = 0; i < mKeepalivePackets.size(); ++i) { + final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i); + if (keepalivePacket instanceof TcpKeepaliveAck) { + pw.print("Slot "); + pw.print(mKeepalivePackets.keyAt(i)); + pw.print(": "); + pw.println(keepalivePacket); + } + } + pw.decreaseIndent(); + + pw.println("NAT-T Keepalive filters:"); + pw.increaseIndent(); + for (int i = 0; i < mKeepalivePackets.size(); ++i) { + final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i); + if (keepalivePacket instanceof NattKeepaliveResponse) { + pw.print("Slot "); + pw.print(mKeepalivePackets.keyAt(i)); + pw.print(": "); + pw.println(keepalivePacket); + } } pw.decreaseIndent(); @@ -1858,4 +1964,18 @@ public class ApfFilter { + (uint8(bytes[2]) << 8) + (uint8(bytes[3])); } + + private static byte[] concatArrays(final byte[]... arr) { + int size = 0; + for (byte[] a : arr) { + size += a.length; + } + final byte[] result = new byte[size]; + int offset = 0; + for (byte[] a : arr) { + System.arraycopy(a, 0, result, offset, a.length); + offset += a.length; + } + return result; + } } diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index e48b065409be..e37b0d3adeae 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -373,10 +373,6 @@ public class IpClient extends StateMachine { private boolean mMulticastFiltering; private long mStartTimeMillis; - /* This must match the definition in KeepaliveTracker.KeepaliveInfo */ - private static final int TYPE_NATT = 1; - private static final int TYPE_TCP = 2; - /** * Reading the snapshot is an asynchronous operation initiated by invoking * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an @@ -706,7 +702,7 @@ public class IpClient extends StateMachine { * keepalive offload. */ public void addKeepalivePacketFilter(int slot, @NonNull TcpKeepalivePacketDataParcelable pkt) { - sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, TYPE_TCP, pkt); + sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */, pkt); } /** @@ -715,7 +711,7 @@ public class IpClient extends StateMachine { */ public void addNattKeepalivePacketFilter(int slot, @NonNull NattKeepalivePacketDataParcelable pkt) { - sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, TYPE_NATT, pkt); + sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */ , pkt); } /** @@ -1637,13 +1633,12 @@ public class IpClient extends StateMachine { case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: { final int slot = msg.arg1; - final int type = msg.arg2; if (mApfFilter != null) { - if (type == TYPE_NATT) { + if (msg.obj instanceof NattKeepalivePacketDataParcelable) { mApfFilter.addNattKeepalivePacketFilter(slot, (NattKeepalivePacketDataParcelable) msg.obj); - } else { + } else if (msg.obj instanceof TcpKeepalivePacketDataParcelable) { mApfFilter.addTcpKeepalivePacketFilter(slot, (TcpKeepalivePacketDataParcelable) msg.obj); } diff --git a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java index 2934e1cf0c82..9bf1b967e397 100644 --- a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java +++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java @@ -24,6 +24,8 @@ import android.util.SparseArray; import java.io.FileDescriptor; import java.io.IOException; import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; import java.net.SocketException; import java.util.List; import java.util.function.Predicate; @@ -228,4 +230,13 @@ public class NetworkStackUtils { private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname, FileDescriptor fd) throws IOException; + + /** + * Return IP address and port in a string format. + */ + public static String addressAndPortToString(InetAddress address, int port) { + return String.format( + (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d", + address.getHostAddress(), port); + } } diff --git a/packages/NetworkStack/src/com/android/networkstack/util/DnsUtils.java b/packages/NetworkStack/src/com/android/networkstack/util/DnsUtils.java new file mode 100644 index 000000000000..4767d5574a00 --- /dev/null +++ b/packages/NetworkStack/src/com/android/networkstack/util/DnsUtils.java @@ -0,0 +1,130 @@ +/* + * 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 com.android.networkstack.util; + +import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP; +import static android.net.DnsResolver.TYPE_A; +import static android.net.DnsResolver.TYPE_AAAA; + +import android.annotation.NonNull; +import android.net.DnsResolver; +import android.net.Network; +import android.net.TrafficStats; +import android.util.Log; + +import com.android.internal.util.TrafficStatsConstants; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Collection of utilities for dns query. + */ +public class DnsUtils { + // Decide what queries to make depending on what IP addresses are on the system. + public static final int TYPE_ADDRCONFIG = -1; + private static final String TAG = DnsUtils.class.getSimpleName(); + + /** + * Return both A and AAAA query results regardless the ip address type of the giving network. + * Used for probing in NetworkMonitor. + */ + @NonNull + public static InetAddress[] getAllByName(@NonNull final DnsResolver dnsResolver, + @NonNull final Network network, @NonNull String host, int timeout) + throws UnknownHostException { + final List<InetAddress> result = new ArrayList<InetAddress>(); + + try { + result.addAll(Arrays.asList( + getAllByName(dnsResolver, network, host, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP, + timeout))); + } catch (UnknownHostException e) { + // Might happen if the host is v4-only, still need to query TYPE_A + } + try { + result.addAll(Arrays.asList( + getAllByName(dnsResolver, network, host, TYPE_A, FLAG_NO_CACHE_LOOKUP, + timeout))); + } catch (UnknownHostException e) { + // Might happen if the host is v6-only, still need to return AAAA answers + } + if (result.size() == 0) { + throw new UnknownHostException(host); + } + return result.toArray(new InetAddress[0]); + } + + /** + * Return dns query result based on the given QueryType(TYPE_A, TYPE_AAAA) or TYPE_ADDRCONFIG. + * Used for probing in NetworkMonitor. + */ + @NonNull + public static InetAddress[] getAllByName(@NonNull final DnsResolver dnsResolver, + @NonNull final Network network, @NonNull final String host, int type, int flag, + int timeoutMs) throws UnknownHostException { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference<List<InetAddress>> resultRef = new AtomicReference<>(); + + final DnsResolver.Callback<List<InetAddress>> callback = + new DnsResolver.Callback<List<InetAddress>>() { + @Override + public void onAnswer(List<InetAddress> answer, int rcode) { + if (rcode == 0) { + resultRef.set(answer); + } + latch.countDown(); + } + + @Override + public void onError(@NonNull DnsResolver.DnsException e) { + Log.d(TAG, "DNS error resolving " + host + ": " + e.getMessage()); + latch.countDown(); + } + }; + final int oldTag = TrafficStats.getAndSetThreadStatsTag( + TrafficStatsConstants.TAG_SYSTEM_PROBE); + + if (type == TYPE_ADDRCONFIG) { + dnsResolver.query(network, host, flag, r -> r.run(), null /* cancellationSignal */, + callback); + } else { + dnsResolver.query(network, host, type, flag, r -> r.run(), + null /* cancellationSignal */, callback); + } + + TrafficStats.setThreadStatsTag(oldTag); + + try { + latch.await(timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + } + + final List<InetAddress> result = resultRef.get(); + if (result == null || result.size() == 0) { + throw new UnknownHostException(host); + } + + return result.toArray(new InetAddress[0]); + } +} diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index a6d74842f631..2fae0c703084 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -49,6 +49,7 @@ import android.net.shared.PrivateDnsConfig; import android.net.util.SharedLog; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; @@ -117,6 +118,13 @@ public class NetworkStackService extends Service { @GuardedBy("mValidationLogs") private final ArrayDeque<SharedLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS); + private static final String DUMPSYS_ARG_VERSION = "version"; + + /** Version of the framework AIDL interfaces observed. Should hold only one value. */ + @GuardedBy("mFrameworkAidlVersions") + private final ArraySet<Integer> mFrameworkAidlVersions = new ArraySet<>(1); + private final int mNetdAidlVersion; + private SharedLog addValidationLogs(Network network, String name) { final SharedLog log = new SharedLog(NUM_VALIDATION_LOG_LINES, network + " - " + name); synchronized (mValidationLogs) { @@ -136,6 +144,15 @@ public class NetworkStackService extends Service { mCm = context.getSystemService(ConnectivityManager.class); mIpMemoryStoreService = new IpMemoryStoreService(context); + int netdVersion; + try { + netdVersion = mNetd.getInterfaceVersion(); + } catch (RemoteException e) { + mLog.e("Error obtaining INetd version", e); + netdVersion = -1; + } + mNetdAidlVersion = netdVersion; + try { mObserverRegistry.register(mNetd); } catch (RemoteException e) { @@ -143,6 +160,12 @@ public class NetworkStackService extends Service { } } + private void updateSystemAidlVersion(final int version) { + synchronized (mFrameworkAidlVersions) { + mFrameworkAidlVersions.add(version); + } + } + @NonNull private final SharedLog mLog = new SharedLog(TAG); @@ -150,6 +173,7 @@ public class NetworkStackService extends Service { public void makeDhcpServer(@NonNull String ifName, @NonNull DhcpServingParamsParcel params, @NonNull IDhcpServerCallbacks cb) throws RemoteException { checkNetworkStackCallingPermission(); + updateSystemAidlVersion(cb.getInterfaceVersion()); final DhcpServer server; try { server = new DhcpServer( @@ -171,6 +195,7 @@ public class NetworkStackService extends Service { @Override public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) throws RemoteException { + updateSystemAidlVersion(cb.getInterfaceVersion()); final SharedLog log = addValidationLogs(network, name); final NetworkMonitor nm = new NetworkMonitor(mContext, cb, network, log); cb.onNetworkMonitorCreated(new NetworkMonitorImpl(nm)); @@ -178,6 +203,7 @@ public class NetworkStackService extends Service { @Override public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException { + updateSystemAidlVersion(cb.getInterfaceVersion()); final IpClient ipClient = new IpClient(mContext, ifName, cb, mObserverRegistry, this); synchronized (mIpClients) { @@ -202,6 +228,7 @@ public class NetworkStackService extends Service { @Override public void fetchIpMemoryStore(@NonNull final IIpMemoryStoreCallbacks cb) throws RemoteException { + updateSystemAidlVersion(cb.getInterfaceVersion()); cb.onIpMemoryStoreFetched(mIpMemoryStoreService); } @@ -209,7 +236,16 @@ public class NetworkStackService extends Service { protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { checkDumpPermission(); + final IndentingPrintWriter pw = new IndentingPrintWriter(fout, " "); + pw.println("NetworkStack version:"); + dumpVersion(pw); + pw.println(); + + if (args != null && args.length >= 1 && DUMPSYS_ARG_VERSION.equals(args[0])) { + return; + } + pw.println("NetworkStack logs:"); mLog.dump(fd, pw, args); @@ -252,6 +288,17 @@ public class NetworkStackService extends Service { } } + /** + * Dump version information of the module and detected system version. + */ + private void dumpVersion(@NonNull PrintWriter fout) { + fout.println("NetworkStackConnector: " + this.VERSION); + synchronized (mFrameworkAidlVersions) { + fout.println("SystemServer: " + mFrameworkAidlVersions); + } + fout.println("Netd: " + mNetdAidlVersion); + } + @Override public int getInterfaceVersion() { return this.VERSION; diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index bacec78e5699..8e9350d8cbbc 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -23,6 +23,7 @@ import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.DnsResolver.FLAG_EMPTY; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; @@ -56,6 +57,8 @@ import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS; import static android.net.util.NetworkStackUtils.NAMESPACE_CONNECTIVITY; import static android.net.util.NetworkStackUtils.isEmpty; +import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PendingIntent; @@ -113,6 +116,7 @@ import com.android.internal.util.TrafficStatsConstants; import com.android.networkstack.R; import com.android.networkstack.metrics.DataStallDetectionStats; import com.android.networkstack.metrics.DataStallStatsUtils; +import com.android.networkstack.util.DnsUtils; import java.io.IOException; import java.net.HttpURLConnection; @@ -129,7 +133,6 @@ import java.util.Random; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; /** @@ -279,8 +282,8 @@ public class NetworkMonitor extends StateMachine { private final Context mContext; private final INetworkMonitorCallbacks mCallback; + private final Network mCleartextDnsNetwork; private final Network mNetwork; - private final Network mNonPrivateDnsBypassNetwork; private final TelephonyManager mTelephonyManager; private final WifiManager mWifiManager; private final ConnectivityManager mCm; @@ -370,8 +373,8 @@ public class NetworkMonitor extends StateMachine { mCallback = cb; mDependencies = deps; mDetectionStatsUtils = detectionStatsUtils; - mNonPrivateDnsBypassNetwork = network; - mNetwork = deps.getPrivateDnsBypassNetwork(network); + mNetwork = network; + mCleartextDnsNetwork = deps.getPrivateDnsBypassNetwork(network); mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); @@ -496,7 +499,7 @@ public class NetworkMonitor extends StateMachine { @Override protected void log(String s) { - if (DBG) Log.d(TAG + "/" + mNetwork.toString(), s); + if (DBG) Log.d(TAG + "/" + mCleartextDnsNetwork.toString(), s); } private void validationLog(int probeType, Object url, String msg) { @@ -517,6 +520,9 @@ public class NetworkMonitor extends StateMachine { return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities); } + private boolean isPrivateDnsValidationRequired() { + return NetworkMonitorUtils.isPrivateDnsValidationRequired(mNetworkCapabilities); + } private void notifyNetworkTested(int result, @Nullable String redirectUrl) { try { @@ -604,7 +610,7 @@ public class NetworkMonitor extends StateMachine { return HANDLED; case CMD_PRIVATE_DNS_SETTINGS_CHANGED: { final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj; - if (!isValidationRequired() || cfg == null || !cfg.inStrictMode()) { + if (!isPrivateDnsValidationRequired() || cfg == null || !cfg.inStrictMode()) { // No DNS resolution required. // // We don't force any validation in opportunistic mode @@ -769,7 +775,7 @@ public class NetworkMonitor extends StateMachine { case CMD_LAUNCH_CAPTIVE_PORTAL_APP: final Bundle appExtras = new Bundle(); // OneAddressPerFamilyNetwork is not parcelable across processes. - final Network network = new Network(mNetwork); + final Network network = new Network(mCleartextDnsNetwork); appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network); final CaptivePortalProbeResult probeRes = mLastPortalProbeResult; appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl); @@ -840,9 +846,20 @@ public class NetworkMonitor extends StateMachine { // the network so don't bother validating here. Furthermore sending HTTP // packets over the network may be undesirable, for example an extremely // expensive metered network, or unwanted leaking of the User Agent string. + // + // On networks that need to support private DNS in strict mode (e.g., VPNs, but + // not networks that don't provide Internet access), we still need to perform + // private DNS server resolution. if (!isValidationRequired()) { - validationLog("Network would not satisfy default request, not validating"); - transitionTo(mValidatedState); + if (isPrivateDnsValidationRequired()) { + validationLog("Network would not satisfy default request, " + + "resolving private DNS"); + transitionTo(mEvaluatingPrivateDnsState); + } else { + validationLog("Network would not satisfy default request, " + + "not validating"); + transitionTo(mValidatedState); + } return HANDLED; } mEvaluateAttempts++; @@ -881,7 +898,7 @@ public class NetworkMonitor extends StateMachine { CustomIntentReceiver(String action, int token, int what) { mToken = token; mWhat = what; - mAction = action + "_" + mNetwork.getNetworkHandle() + "_" + token; + mAction = action + "_" + mCleartextDnsNetwork.getNetworkHandle() + "_" + token; mContext.registerReceiver(this, new IntentFilter(mAction)); } public PendingIntent getPendingIntent() { @@ -994,7 +1011,8 @@ public class NetworkMonitor extends StateMachine { private void resolveStrictModeHostname() { try { // Do a blocking DNS resolution using the network-assigned nameservers. - final InetAddress[] ips = mNetwork.getAllByName(mPrivateDnsProviderHostname); + final InetAddress[] ips = DnsUtils.getAllByName(mDependencies.getDnsResolver(), + mCleartextDnsNetwork, mPrivateDnsProviderHostname, getDnsProbeTimeout()); mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips); validationLog("Strict mode hostname resolved: " + mPrivateDnsConfig); } catch (UnknownHostException uhe) { @@ -1033,7 +1051,7 @@ public class NetworkMonitor extends StateMachine { + oneTimeHostnameSuffix; final Stopwatch watch = new Stopwatch().start(); try { - final InetAddress[] ips = mNonPrivateDnsBypassNetwork.getAllByName(host); + final InetAddress[] ips = mNetwork.getAllByName(host); final long time = watch.stop(); final String strIps = Arrays.toString(ips); final boolean success = (ips != null && ips.length > 0); @@ -1488,39 +1506,8 @@ public class NetworkMonitor extends StateMachine { @VisibleForTesting protected InetAddress[] sendDnsProbeWithTimeout(String host, int timeoutMs) throws UnknownHostException { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference<List<InetAddress>> resultRef = new AtomicReference<>(); - final DnsResolver.Callback<List<InetAddress>> callback = - new DnsResolver.Callback<List<InetAddress>>() { - public void onAnswer(List<InetAddress> answer, int rcode) { - if (rcode == 0) { - resultRef.set(answer); - } - latch.countDown(); - } - public void onError(@NonNull DnsResolver.DnsException e) { - validationLog("DNS error resolving " + host + ": " + e.getMessage()); - latch.countDown(); - } - }; - - final int oldTag = TrafficStats.getAndSetThreadStatsTag( - TrafficStatsConstants.TAG_SYSTEM_PROBE); - mDependencies.getDnsResolver().query(mNetwork, host, DnsResolver.FLAG_EMPTY, - r -> r.run() /* executor */, null /* cancellationSignal */, callback); - TrafficStats.setThreadStatsTag(oldTag); - - try { - latch.await(timeoutMs, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - } - - List<InetAddress> result = resultRef.get(); - if (result == null || result.size() == 0) { - throw new UnknownHostException(host); - } - - return result.toArray(new InetAddress[0]); + return DnsUtils.getAllByName(mDependencies.getDnsResolver(), mCleartextDnsNetwork, host, + TYPE_ADDRCONFIG, FLAG_EMPTY, timeoutMs); } /** Do a DNS resolution of the given server. */ @@ -1565,7 +1552,7 @@ public class NetworkMonitor extends StateMachine { final int oldTag = TrafficStats.getAndSetThreadStatsTag( TrafficStatsConstants.TAG_SYSTEM_PROBE); try { - urlConnection = (HttpURLConnection) mNetwork.openConnection(url); + urlConnection = (HttpURLConnection) mCleartextDnsNetwork.openConnection(url); urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC); urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); @@ -1814,7 +1801,7 @@ public class NetworkMonitor extends StateMachine { private void logNetworkEvent(int evtype) { int[] transports = mNetworkCapabilities.getTransportTypes(); - mMetricsLog.log(mNetwork, transports, new NetworkEvent(evtype)); + mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype)); } private int networkEventType(ValidationStage s, EvaluationResult r) { @@ -1836,7 +1823,7 @@ public class NetworkMonitor extends StateMachine { private void maybeLogEvaluationResult(int evtype) { if (mEvaluationTimer.isRunning()) { int[] transports = mNetworkCapabilities.getTransportTypes(); - mMetricsLog.log(mNetwork, transports, + mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype, mEvaluationTimer.stop())); mEvaluationTimer.reset(); } @@ -1850,7 +1837,7 @@ public class NetworkMonitor extends StateMachine { .setReturnCode(probeResult) .setDurationMs(durationMs) .build(); - mMetricsLog.log(mNetwork, transports, ev); + mMetricsLog.log(mCleartextDnsNetwork, transports, ev); } @VisibleForTesting diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java index 6a6bf83bd3c8..8312dfeb1a3b 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java @@ -410,6 +410,10 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { }); } + @Override + public void factoryReset() { + } + /** Get db size threshold. */ @VisibleForTesting protected int getDbSizeThreshold() { diff --git a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java index 93ab3be28fc7..8f2b96807860 100644 --- a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java +++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java @@ -40,6 +40,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.NattKeepalivePacketDataParcelable; import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfFilter.ApfConfiguration; import android.net.apf.ApfGenerator.IllegalInstructionException; @@ -998,47 +999,54 @@ public class ApfTest { } } - private static final int ETH_HEADER_LEN = 14; - private static final int ETH_DEST_ADDR_OFFSET = 0; - private static final int ETH_ETHERTYPE_OFFSET = 12; + private static final int ETH_HEADER_LEN = 14; + private static final int ETH_DEST_ADDR_OFFSET = 0; + private static final int ETH_ETHERTYPE_OFFSET = 12; private static final byte[] ETH_BROADCAST_MAC_ADDRESS = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; - private static final int IPV4_HEADER_LEN = 20; - private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0; + private static final int IPV4_HEADER_LEN = 20; + private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0; private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; - private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9; - private static final int IPV4_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 12; - private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; - private static final int IPV4_TCP_HEADER_LEN = 20; - private static final int IPV4_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN; - private static final int IPV4_TCP_SRC_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 0; - private static final int IPV4_TCP_DEST_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 2; - private static final int IPV4_TCP_SEQ_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 4; - private static final int IPV4_TCP_ACK_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 8; + private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9; + private static final int IPV4_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 12; + private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; + + private static final int IPV4_TCP_HEADER_LEN = 20; + private static final int IPV4_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN; + private static final int IPV4_TCP_SRC_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 0; + private static final int IPV4_TCP_DEST_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 2; + private static final int IPV4_TCP_SEQ_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 4; + private static final int IPV4_TCP_ACK_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 8; private static final int IPV4_TCP_HEADER_LENGTH_OFFSET = IPV4_TCP_HEADER_OFFSET + 12; - private static final int IPV4_TCP_HEADER_FLAG_OFFSET = IPV4_TCP_HEADER_OFFSET + 13; + private static final int IPV4_TCP_HEADER_FLAG_OFFSET = IPV4_TCP_HEADER_OFFSET + 13; + + private static final int IPV4_UDP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN;; + private static final int IPV4_UDP_SRC_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 0; + private static final int IPV4_UDP_DEST_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 2; + private static final int IPV4_UDP_LENGTH_OFFSET = IPV4_UDP_HEADER_OFFSET + 4; + private static final int IPV4_UDP_PAYLOAD_OFFSET = IPV4_UDP_HEADER_OFFSET + 8; private static final byte[] IPV4_BROADCAST_ADDRESS = {(byte) 255, (byte) 255, (byte) 255, (byte) 255}; - private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6; - private static final int IPV6_HEADER_LEN = 40; - private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8; - private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24; - private static final int IPV6_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; - private static final int IPV6_TCP_SRC_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 0; - private static final int IPV6_TCP_DEST_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 2; - private static final int IPV6_TCP_SEQ_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 4; - private static final int IPV6_TCP_ACK_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 8; + private static final int IPV6_HEADER_LEN = 40; + private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6; + private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8; + private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24; + private static final int IPV6_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; + private static final int IPV6_TCP_SRC_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 0; + private static final int IPV6_TCP_DEST_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 2; + private static final int IPV6_TCP_SEQ_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 4; + private static final int IPV6_TCP_ACK_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 8; // The IPv6 all nodes address ff02::1 - private static final byte[] IPV6_ALL_NODES_ADDRESS = + private static final byte[] IPV6_ALL_NODES_ADDRESS = { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; private static final byte[] IPV6_ALL_ROUTERS_ADDRESS = { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }; - private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; - private static final int ICMP6_ROUTER_SOLICITATION = 133; - private static final int ICMP6_ROUTER_ADVERTISEMENT = 134; + private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; + private static final int ICMP6_ROUTER_SOLICITATION = 133; + private static final int ICMP6_ROUTER_ADVERTISEMENT = 134; private static final int ICMP6_NEIGHBOR_SOLICITATION = 135; private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136; @@ -1050,9 +1058,9 @@ public class ApfTest { private static final int ICMP6_RA_OPTION_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN; - private static final int ICMP6_PREFIX_OPTION_TYPE = 3; - private static final int ICMP6_PREFIX_OPTION_LEN = 32; - private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4; + private static final int ICMP6_PREFIX_OPTION_TYPE = 3; + private static final int ICMP6_PREFIX_OPTION_LEN = 32; + private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4; private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8; // From RFC6106: Recursive DNS Server option @@ -1063,17 +1071,17 @@ public class ApfTest { // From RFC4191: Route Information option private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24; // Above three options all have the same format: - private static final int ICMP6_4_BYTE_OPTION_LEN = 8; + private static final int ICMP6_4_BYTE_OPTION_LEN = 8; private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4; - private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4; + private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4; - private static final int UDP_HEADER_LEN = 8; + private static final int UDP_HEADER_LEN = 8; private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22; - private static final int DHCP_CLIENT_PORT = 68; + private static final int DHCP_CLIENT_PORT = 68; private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48; - private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN; + private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN; private static final byte[] ARP_IPV4_REQUEST_HEADER = { 0, 1, // Hardware type: Ethernet (1) 8, 0, // Protocol type: IP (0x0800) @@ -1714,6 +1722,83 @@ public class ApfTest { return packet.array(); } + @Test + public void testApfFilterNattKeepalivePacket() throws Exception { + final MockIpClientCallback cb = new MockIpClientCallback(); + final ApfConfiguration config = getDefaultConfig(); + config.multicastFilter = DROP_MULTICAST; + config.ieee802_3Filter = DROP_802_3_FRAMES; + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog); + byte[] program; + final int srcPort = 1024; + final int dstPort = 4500; + final int slot1 = 1; + // NAT-T keepalive + final byte[] kaPayload = {(byte) 0xff}; + final byte[] nonKaPayload = {(byte) 0xfe}; + + // src: 10.0.0.5, port: 1024 + // dst: 10.0.0.6, port: 4500 + InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR); + InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR); + + final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable(); + parcel.srcAddress = srcAddr.getAddress(); + parcel.srcPort = srcPort; + parcel.dstAddress = dstAddr.getAddress(); + parcel.dstPort = dstPort; + + apfFilter.addNattKeepalivePacketFilter(slot1, parcel); + program = cb.getApfProgram(); + + // Verify IPv4 keepalive packet is dropped + // src: 10.0.0.6, port: 4500 + // dst: 10.0.0.5, port: 1024 + byte[] pkt = ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, + IPV4_KEEPALIVE_SRC_ADDR, dstPort, srcPort, 1 /* dataLength */); + System.arraycopy(kaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, kaPayload.length); + assertDrop(program, pkt); + + // Verify a packet with payload length 1 byte but it is not 0xff will pass the filter. + System.arraycopy(nonKaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, nonKaPayload.length); + assertPass(program, pkt); + + // Verify IPv4 non-keepalive response packet from the same source address is passed + assertPass(program, + ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, 10 /* dataLength */)); + + // Verify IPv4 non-keepalive response packet from other source address is passed + assertPass(program, + ipv4UdpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, 10 /* dataLength */)); + + apfFilter.removeKeepalivePacketFilter(slot1); + apfFilter.shutdown(); + } + + private static byte[] ipv4UdpPacket(byte[] sip, byte[] dip, int sport, + int dport, int dataLength) { + final int totalLength = dataLength + IPV4_HEADER_LEN + UDP_HEADER_LEN; + final int udpLength = UDP_HEADER_LEN + dataLength; + ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]); + + // ether type + packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP); + + // IPv4 header + packet.put(IPV4_VERSION_IHL_OFFSET, (byte) 0x45); + packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength); + packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_UDP); + put(packet, IPV4_SRC_ADDR_OFFSET, sip); + put(packet, IPV4_DEST_ADDR_OFFSET, dip); + packet.putShort(IPV4_UDP_SRC_PORT_OFFSET, (short) sport); + packet.putShort(IPV4_UDP_DEST_PORT_OFFSET, (short) dport); + packet.putShort(IPV4_UDP_LENGTH_OFFSET, (short) udpLength); + + return packet.array(); + } + // Verify that the last program pushed to the IpClient.Callback properly filters the // given packet for the given lifetime. private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) { diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java index 0dc1cbf8a984..26186751c282 100644 --- a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java @@ -42,10 +42,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -66,6 +66,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.captiveportal.CaptivePortalProbeResult; import android.net.metrics.IpConnectivityLog; +import android.net.shared.PrivateDnsConfig; import android.net.util.SharedLog; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; @@ -73,6 +74,7 @@ import android.os.Bundle; import android.os.ConditionVariable; import android.os.Handler; import android.os.Looper; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.provider.Settings; @@ -131,7 +133,8 @@ public class NetworkMonitorTest { private @Mock Random mRandom; private @Mock NetworkMonitor.Dependencies mDependencies; private @Mock INetworkMonitorCallbacks mCallbacks; - private @Spy Network mNetwork = new Network(TEST_NETID); + private @Spy Network mCleartextDnsNetwork = new Network(TEST_NETID); + private @Mock Network mNetwork; private @Mock DataStallStatsUtils mDataStallStatsUtils; private @Mock WifiInfo mWifiInfo; private @Captor ArgumentCaptor<String> mNetworkTestedRedirectUrlCaptor; @@ -166,35 +169,108 @@ public class NetworkMonitorTest { private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - private void setDnsAnswers(String[] answers) throws UnknownHostException { - if (answers == null) { - doThrow(new UnknownHostException()).when(mNetwork).getAllByName(any()); - doNothing().when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any()); - return; + /** + * Fakes DNS responses. + * + * Allows test methods to configure the IP addresses that will be resolved by + * Network#getAllByName and by DnsResolver#query. + */ + class FakeDns { + private final ArrayMap<String, List<InetAddress>> mAnswers = new ArrayMap<>(); + private boolean mNonBypassPrivateDnsWorking = true; + + /** Whether DNS queries on mNonBypassPrivateDnsWorking should succeed. */ + private void setNonBypassPrivateDnsWorking(boolean working) { + mNonBypassPrivateDnsWorking = working; } - List<InetAddress> answerList = new ArrayList<>(); - for (String answer : answers) { - answerList.add(InetAddresses.parseNumericAddress(answer)); + /** Clears all DNS entries. */ + private synchronized void clearAll() { + mAnswers.clear(); } - InetAddress[] answerArray = answerList.toArray(new InetAddress[0]); - doReturn(answerArray).when(mNetwork).getAllByName(any()); + /** Returns the answer for a given name on the given mock network. */ + private synchronized List<InetAddress> getAnswer(Object mock, String hostname) { + if (mock == mNetwork && !mNonBypassPrivateDnsWorking) { + return null; + } + if (mAnswers.containsKey(hostname)) { + return mAnswers.get(hostname); + } + return mAnswers.get("*"); + } - doAnswer((invocation) -> { - Executor executor = (Executor) invocation.getArgument(3); - DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5); - new Handler(Looper.getMainLooper()).post(() -> { - executor.execute(() -> callback.onAnswer(answerList, 0)); - }); - return null; - }).when(mDnsResolver).query(eq(mNetwork), any(), anyInt(), any(), any(), any()); + /** Sets the answer for a given name. */ + private synchronized void setAnswer(String hostname, String[] answer) + throws UnknownHostException { + if (answer == null) { + mAnswers.remove(hostname); + } else { + List<InetAddress> answerList = new ArrayList<>(); + for (String addr : answer) { + answerList.add(InetAddresses.parseNumericAddress(addr)); + } + mAnswers.put(hostname, answerList); + } + } + + /** Simulates a getAllByName call for the specified name on the specified mock network. */ + private InetAddress[] getAllByName(Object mock, String hostname) + throws UnknownHostException { + List<InetAddress> answer = getAnswer(mock, hostname); + if (answer == null || answer.size() == 0) { + throw new UnknownHostException(hostname); + } + return answer.toArray(new InetAddress[0]); + } + + /** Starts mocking DNS queries. */ + private void startMocking() throws UnknownHostException { + // Queries on mNetwork using getAllByName. + doAnswer(invocation -> { + return getAllByName(invocation.getMock(), invocation.getArgument(0)); + }).when(mNetwork).getAllByName(any()); + + // Queries on mCleartextDnsNetwork using DnsResolver#query. + doAnswer(invocation -> { + String hostname = (String) invocation.getArgument(1); + Executor executor = (Executor) invocation.getArgument(3); + DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5); + + List<InetAddress> answer = getAnswer(invocation.getMock(), hostname); + if (answer != null && answer.size() > 0) { + new Handler(Looper.getMainLooper()).post(() -> { + executor.execute(() -> callback.onAnswer(answer, 0)); + }); + } + // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE. + return null; + }).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any()); + + // Queries on mCleartextDnsNetwork using using DnsResolver#query with QueryType. + doAnswer(invocation -> { + String hostname = (String) invocation.getArgument(1); + Executor executor = (Executor) invocation.getArgument(4); + DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(6); + + List<InetAddress> answer = getAnswer(invocation.getMock(), hostname); + if (answer != null && answer.size() > 0) { + new Handler(Looper.getMainLooper()).post(() -> { + executor.execute(() -> callback.onAnswer(answer, 0)); + }); + } + // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE. + return null; + }).when(mDnsResolver).query(any(), any(), anyInt(), anyInt(), any(), any(), any()); + } } + private FakeDns mFakeDns; + @Before public void setUp() throws IOException { MockitoAnnotations.initMocks(this); - when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mNetwork); + when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mCleartextDnsNetwork); when(mDependencies.getDnsResolver()).thenReturn(mDnsResolver); when(mDependencies.getRandom()).thenReturn(mRandom); when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())) @@ -206,7 +282,7 @@ public class NetworkMonitorTest { when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), any())) .thenReturn(TEST_HTTPS_URL); - doReturn(mNetwork).when(mNetwork).getPrivateDnsBypassingCopy(); + doReturn(mCleartextDnsNetwork).when(mNetwork).getPrivateDnsBypassingCopy(); when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm); when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony); @@ -221,6 +297,10 @@ public class NetworkMonitorTest { setOtherFallbackUrls(TEST_OTHER_FALLBACK_URL); setFallbackSpecs(null); // Test with no fallback spec by default when(mRandom.nextInt()).thenReturn(0); + // DNS probe timeout should not be defined more than half of HANDLER_TIMEOUT_MS. Otherwise, + // it will fail the test because of timeout expired for querying AAAA and A sequentially. + when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout))) + .thenReturn(200); doAnswer((invocation) -> { URL url = invocation.getArgument(0); @@ -237,11 +317,13 @@ public class NetworkMonitorTest { fail("URL not mocked: " + url.toString()); return null; } - }).when(mNetwork).openConnection(any()); + }).when(mCleartextDnsNetwork).openConnection(any()); when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>()); when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>()); - setDnsAnswers(new String[]{"2001:db8::1", "192.0.2.2"}); + mFakeDns = new FakeDns(); + mFakeDns.startMocking(); + mFakeDns.setAnswer("*", new String[]{"2001:db8::1", "192.0.2.2"}); when(mContext.registerReceiver(any(BroadcastReceiver.class), any())).then((invocation) -> { mRegisteredReceivers.add(invocation.getArgument(0)); @@ -264,6 +346,7 @@ public class NetworkMonitorTest { @After public void tearDown() { + mFakeDns.clearAll(); assertTrue(mCreatedNetworkMonitors.size() > 0); // Make a local copy of mCreatedNetworkMonitors because during the iteration below, // WrappedNetworkMonitor#onQuitting will delete elements from it on the handler threads. @@ -284,8 +367,8 @@ public class NetworkMonitorTest { private final ConditionVariable mQuitCv = new ConditionVariable(false); WrappedNetworkMonitor() { - super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, mDependencies, - mDataStallStatsUtils); + super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, + mDependencies, mDataStallStatsUtils); } @Override @@ -314,23 +397,22 @@ public class NetworkMonitorTest { } } - private WrappedNetworkMonitor makeMonitor() { + private WrappedNetworkMonitor makeMonitor(NetworkCapabilities nc) { final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(); nm.start(); + setNetworkCapabilities(nm, nc); waitForIdle(nm.getHandler()); mCreatedNetworkMonitors.add(nm); return nm; } private WrappedNetworkMonitor makeMeteredNetworkMonitor() { - final WrappedNetworkMonitor nm = makeMonitor(); - setNetworkCapabilities(nm, METERED_CAPABILITIES); + final WrappedNetworkMonitor nm = makeMonitor(METERED_CAPABILITIES); return nm; } private WrappedNetworkMonitor makeNotMeteredNetworkMonitor() { - final WrappedNetworkMonitor nm = makeMonitor(); - setNetworkCapabilities(nm, NOT_METERED_CAPABILITIES); + final WrappedNetworkMonitor nm = makeMonitor(NOT_METERED_CAPABILITIES); return nm; } @@ -595,7 +677,7 @@ public class NetworkMonitorTest { @Test public void testNoInternetCapabilityValidated() throws Exception { runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_TEST_RESULT_VALID); - verify(mNetwork, never()).openConnection(any()); + verify(mCleartextDnsNetwork, never()).openConnection(any()); } @Test @@ -603,7 +685,7 @@ public class NetworkMonitorTest { setSslException(mHttpsConnection); setPortal302(mHttpConnection); - final NetworkMonitor nm = makeMonitor(); + final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES); nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, METERED_CAPABILITIES); verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) @@ -638,6 +720,63 @@ public class NetworkMonitorTest { } @Test + public void testPrivateDnsSuccess() throws Exception { + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::53"}); + + WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); + wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null)); + } + + @Test + public void testPrivateDnsResolutionRetryUpdate() throws Exception { + // Set a private DNS hostname that doesn't resolve and expect validation to fail. + mFakeDns.setAnswer("dns.google", new String[0]); + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + + WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); + wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null)); + + // Fix DNS and retry, expect validation to succeed. + reset(mCallbacks); + mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"}); + + wnm.forceReevaluation(Process.myUid()); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null)); + + // Change configuration to an invalid DNS name, expect validation to fail. + reset(mCallbacks); + mFakeDns.setAnswer("dns.bad", new String[0]); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0])); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null)); + + // Change configuration back to working again, but make private DNS not work. + // Expect validation to fail. + reset(mCallbacks); + mFakeDns.setNonBypassPrivateDnsWorking(false); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null)); + + // Make private DNS work again. Expect validation to succeed. + reset(mCallbacks); + mFakeDns.setNonBypassPrivateDnsWorking(true); + wnm.forceReevaluation(Process.myUid()); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null)); + } + + @Test public void testDataStall_StallSuspectedAndSendMetrics() throws IOException { WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); @@ -728,25 +867,27 @@ public class NetworkMonitorTest { WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor(); final int shortTimeoutMs = 200; + // Clear the wildcard DNS response created in setUp. + mFakeDns.setAnswer("*", null); + String[] expected = new String[]{"2001:db8::"}; - setDnsAnswers(expected); + mFakeDns.setAnswer("www.google.com", expected); InetAddress[] actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs); assertIpAddressArrayEquals(expected, actual); expected = new String[]{"2001:db8::", "192.0.2.1"}; - setDnsAnswers(expected); - actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs); + mFakeDns.setAnswer("www.googleapis.com", expected); + actual = wnm.sendDnsProbeWithTimeout("www.googleapis.com", shortTimeoutMs); assertIpAddressArrayEquals(expected, actual); - expected = new String[0]; - setDnsAnswers(expected); + mFakeDns.setAnswer("www.google.com", new String[0]); try { wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs); fail("No DNS results, expected UnknownHostException"); } catch (UnknownHostException e) { } - setDnsAnswers(null); + mFakeDns.setAnswer("www.google.com", null); try { wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs); fail("DNS query timed out, expected UnknownHostException"); @@ -841,7 +982,7 @@ public class NetworkMonitorTest { } private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) { - final NetworkMonitor monitor = makeMonitor(); + final NetworkMonitor monitor = makeMonitor(nc); monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc); try { verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) |