Snap for 9254005 from 4a583e9284f2c7955c2ca9d3a9c841987752f8f9 to mainline-cellbroadcast-release Change-Id: I696ec9e99a726cce52403a27dbf5ea548dada252
diff --git a/Android.bp b/Android.bp index a6cf966..0aba37c 100644 --- a/Android.bp +++ b/Android.bp
@@ -74,11 +74,18 @@ ] } +// Common defaults for NetworkStack integration tests, root tests and coverage tests +// to keep tests always running against the same target sdk version with NetworkStack. java_defaults { - name: "NetworkStackReleaseApiLevel", - sdk_version: module_33_version, + name: "NetworkStackReleaseTargetSdk", min_sdk_version: "29", target_sdk_version: "33", +} + +java_defaults { + name: "NetworkStackReleaseApiLevel", + defaults:["NetworkStackReleaseTargetSdk"], + sdk_version: module_33_version, libs: [ "framework-connectivity", "framework-connectivity-t", @@ -372,7 +379,7 @@ ], out: ["NetworkStackJarJarRules.txt"], cmd: "$(location jarjar-rules-generator) " + - "--jars $(location :NetworkStackApiStableLib{.jar}) " + + "$(location :NetworkStackApiStableLib{.jar}) " + "--prefix com.android.networkstack " + "--excludes $(location jarjar-excludes.txt) " + "--output $(out)",
diff --git a/TEST_MAPPING b/TEST_MAPPING index 1d0f234..c1bc9cf 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING
@@ -8,6 +8,9 @@ }, { "name": "NetworkStackIntegrationTests" + }, + { + "name": "NetworkStackRootTests" } ], "mainline-presubmit": [
diff --git a/apishim/29/com/android/networkstack/apishim/api29/VpnProfileStateShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/VpnProfileStateShimImpl.java deleted file mode 100644 index 10eb8b9..0000000 --- a/apishim/29/com/android/networkstack/apishim/api29/VpnProfileStateShimImpl.java +++ /dev/null
@@ -1,27 +0,0 @@ -/* - * Copyright (C) 2022 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.apishim.api29; - -import android.os.Build; - -import androidx.annotation.RequiresApi; - -import com.android.networkstack.apishim.common.VpnProfileStateShim; - -/** Implementation of {@link VpnProfileStateShim} for API 29. */ -@RequiresApi(Build.VERSION_CODES.Q) -public class VpnProfileStateShimImpl implements VpnProfileStateShim { -}
diff --git a/apishim/31/com/android/networkstack/apishim/api31/VpnProfileStateShimImpl.java b/apishim/31/com/android/networkstack/apishim/api31/VpnProfileStateShimImpl.java deleted file mode 100644 index 0bdd157..0000000 --- a/apishim/31/com/android/networkstack/apishim/api31/VpnProfileStateShimImpl.java +++ /dev/null
@@ -1,28 +0,0 @@ -/* - * Copyright (C) 2022 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.apishim.api31; - -import android.os.Build; - -import androidx.annotation.RequiresApi; - -import com.android.networkstack.apishim.common.VpnProfileStateShim; - -/** Implementation of {@link VpnProfileStateShim} for API 31. */ -@RequiresApi(Build.VERSION_CODES.S) -public class VpnProfileStateShimImpl - extends com.android.networkstack.apishim.api29.VpnProfileStateShimImpl { -}
diff --git a/jni/network_stack_utils_jni.cpp b/jni/network_stack_utils_jni.cpp index d7f6d78..5ff0288 100644 --- a/jni/network_stack_utils_jni.cpp +++ b/jni/network_stack_utils_jni.cpp
@@ -60,7 +60,7 @@ return true; } -static void network_stack_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray ethAddr, +static void network_stack_utils_addArpEntry(JNIEnv *env, jclass clazz, jbyteArray ethAddr, jbyteArray ipv4Addr, jstring ifname, jobject javaFd) { arpreq req = {}; sockaddr_in& netAddrStruct = *reinterpret_cast<sockaddr_in*>(&req.arp_pa); @@ -99,7 +99,7 @@ } } -static void network_stack_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd) { +static void network_stack_utils_attachDhcpFilter(JNIEnv *env, jclass clazz, jobject javaFd) { static sock_filter filter_code[] = { // Check the protocol is UDP. BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol), @@ -107,7 +107,7 @@ // Check this is not a fragment. BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset), - BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 4, 0), + BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_MF | IP_OFFMASK, 4, 0), // Get the IP header length. BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen), @@ -116,8 +116,10 @@ BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 0, 1), - // Accept or reject. + // Accept. BPF_STMT(BPF_RET | BPF_K, 0xffff), + + // Reject. BPF_STMT(BPF_RET | BPF_K, 0) }; static const sock_fprog filter = { @@ -131,7 +133,7 @@ } } -static void network_stack_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject javaFd, +static void network_stack_utils_attachRaFilter(JNIEnv *env, jclass clazz, jobject javaFd, jint hardwareAddressType) { if (hardwareAddressType != ARPHRD_ETHER) { jniThrowExceptionFmt(env, "java/net/SocketException", @@ -148,8 +150,10 @@ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1), - // Accept or reject. + // Accept. BPF_STMT(BPF_RET | BPF_K, 0xffff), + + // Reject. BPF_STMT(BPF_RET | BPF_K, 0) }; static const sock_fprog filter = { @@ -166,7 +170,7 @@ // TODO: Move all this filter code into libnetutils. static void network_stack_utils_attachControlPacketFilter( - JNIEnv *env, jobject clazz, jobject javaFd, jint hardwareAddressType) { + JNIEnv *env, jclass clazz, jobject javaFd, jint hardwareAddressType) { if (hardwareAddressType != ARPHRD_ETHER) { jniThrowExceptionFmt(env, "java/net/SocketException", "attachControlPacketFilter only supports ARPHRD_ETHER"); @@ -223,8 +227,10 @@ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, ND_ROUTER_SOLICIT, 0, 2), BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0), - // Accept or reject. + // Accept. BPF_STMT(BPF_RET | BPF_K, 0xffff), + + // Reject. BPF_STMT(BPF_RET | BPF_K, 0) }; static const sock_fprog filter = {
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index feda547..61a7149 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml
@@ -21,6 +21,6 @@ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informații despre locația rețelei"</string> <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificări afișate pentru a indica faptul că o rețea are o pagină cu informații despre locație"</string> <string name="connected" msgid="4563643884927480998">"Conectată"</string> - <string name="tap_for_info" msgid="6849746325626883711">"Conectat / atingeți pentru a vedea site-ul"</string> + <string name="tap_for_info" msgid="6849746325626883711">"Conectat / atinge pentru a vedea site-ul"</string> <string name="application_label" msgid="1322847171305285454">"Manager de rețea"</string> </resources>
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index 97c6990..694c4ee 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java
@@ -35,9 +35,11 @@ import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID; +import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_CLEAR_ADDRESSES_ON_STOP_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION; import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission; import android.annotation.SuppressLint; @@ -116,6 +118,7 @@ import com.android.networkstack.metrics.IpProvisioningMetrics; import com.android.networkstack.metrics.NetworkQuirkMetrics; import com.android.networkstack.packets.NeighborAdvertisement; +import com.android.networkstack.packets.NeighborSolicitation; import com.android.networkstack.util.NetworkStackUtils; import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; @@ -455,7 +458,7 @@ private static final int CMD_START = 3; private static final int CMD_CONFIRM = 4; private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5; - // Triggered by NetlinkTracker to communicate netlink events. + // Triggered by IpClientLinkObserver to communicate netlink events. private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6; private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7; private static final int CMD_UPDATE_HTTP_PROXY = 8; @@ -478,6 +481,16 @@ private static final int CMD_ADDRESSES_CLEARED = 100; private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101; private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102; + private static final int CMD_JUMP_STOPPING_TO_CLEAR_ADDRESSES_ON_STOP = 103; + private static final int EVENT_CLEAR_ADDRESSES_TIMEOUT = 104; + + // Used to time out the wait for IP addresses cleared. This timeout is + // necessary because netlink events will get lost if ENOBUFS happens, + // then RTM_DELADDR might never arrive, which results in never exiting + // ClearAddressesOnStopState. + @VisibleForTesting + static final String CONFIG_CLEAR_ADDRESSES_TIMEOUT = "ipclient_clear_addresses_timeout"; + private static final int DEFAULT_CLEAR_ADDRESSES_TIMEOUT_MS = 2000; // IpClient shares a handler with DhcpClient: commands must not overlap public static final int DHCPCLIENT_CMD_BASE = 1000; @@ -532,10 +545,11 @@ private final State mStoppedState = new StoppedState(); private final State mStoppingState = new StoppingState(); - private final State mClearingIpAddressesState = new ClearingIpAddressesState(); private final State mStartedState = new StartedState(); private final State mRunningState = new RunningState(); private final State mPreconnectingState = new PreconnectingState(); + private final State mClearAddressesOnStopState = new ClearAddressesOnStopState(); + private final State mClearAddressesOnStartState = new ClearAddressesOnStartState(); private final String mTag; private final Context mContext; @@ -557,6 +571,8 @@ private final InterfaceController mInterfaceCtrl; // Set of IPv6 addresses for which unsolicited gratuitous NA packets have been sent. private final Set<Inet6Address> mGratuitousNaTargetAddresses = new HashSet<>(); + // Set of IPv6 addresses from which multicast NS packets have been sent. + private final Set<Inet6Address> mMulticastNsSourceAddresses = new HashSet<>(); // Ignore nonzero RDNSS option lifetimes below this value. 0 = disabled. private final int mMinRdnssLifetimeSec; @@ -580,6 +596,7 @@ private long mStartTimeMillis; private MacAddress mCurrentBssid; private boolean mHasDisabledIpv6OrAcceptRaOnProvLoss; + private boolean mClearAddressesOnStop; /** * Reading the snapshot is an asynchronous operation initiated by invoking @@ -745,15 +762,18 @@ } @Override - public void onIpv6AddressRemoved(final Inet6Address targetIp) { - // The update of Gratuitous NA target addresses set should be only accessed - // from the handler thread of IpClient StateMachine, keeping the behaviour + public void onIpv6AddressRemoved(final Inet6Address address) { + // The update of Gratuitous NA target addresses set or unsolicited + // multicast NS source addresses set should be only accessed from the + // handler thread of IpClient StateMachine, keeping the behaviour // consistent with relying on the non-blocking NetworkObserver callbacks, // see {@link registerObserverForNonblockingCallback}. This can be done // by either sending a message to StateMachine or posting a handler. getHandler().post(() -> { - if (!mGratuitousNaTargetAddresses.contains(targetIp)) return; - updateGratuitousNaTargetSet(targetIp, false /* remove address */); + mLog.log("Remove IPv6 GUA " + address + + " from both Gratuituous NA and Multicast NS sets"); + mGratuitousNaTargetAddresses.remove(address); + mMulticastNsSourceAddresses.remove(address); }); } @@ -891,10 +911,11 @@ // CHECKSTYLE:OFF IndentationCheck addState(mStoppedState); addState(mStartedState); + addState(mClearAddressesOnStartState, mStartedState); addState(mPreconnectingState, mStartedState); - addState(mClearingIpAddressesState, mStartedState); addState(mRunningState, mStartedState); addState(mStoppingState); + addState(mClearAddressesOnStopState); // CHECKSTYLE:ON IndentationCheck setInitialState(mStoppedState); @@ -922,6 +943,11 @@ false /* defaultEnabled */); } + private boolean isMulticastNsEnabled() { + return mDependencies.isFeatureEnabled(mContext, IPCLIENT_MULTICAST_NS_VERSION, + false /* defaultEnabled */); + } + @VisibleForTesting static MacAddress getInitialBssid(final Layer2Information layer2Info, final ScanResultInfo scanResultInfo, boolean isAtLeastS) { @@ -951,6 +977,11 @@ true /* defaultEnabled */); } + private boolean shouldClearAddressesOnStop() { + return mDependencies.isFeatureEnabled(mContext, IPCLIENT_CLEAR_ADDRESSES_ON_STOP_VERSION, + false /* defaultEnabled */); + } + @Override protected void onQuitting() { mCallback.onQuit(); @@ -1636,6 +1667,22 @@ transmitPacket(packet, sockAddress, "Failed to send GARP"); } + private void sendMulticastNs(final Inet6Address srcIp, final Inet6Address dstIp, + final Inet6Address targetIp) { + final MacAddress dstMac = NetworkStackUtils.ipv6MulticastToEthernetMulticast(dstIp); + final ByteBuffer packet = NeighborSolicitation.build(mInterfaceParams.macAddr, dstMac, + srcIp, dstIp, targetIp); + final SocketAddress sockAddress = + SocketUtilsShimImpl.newInstance().makePacketSocketAddress(ETH_P_IPV6, + mInterfaceParams.index, dstMac.toByteArray()); + + if (DBG) { + mLog.log("send multicast NS from " + srcIp.getHostAddress() + " to " + + dstIp.getHostAddress() + " , target IP: " + targetIp.getHostAddress()); + } + transmitPacket(packet, sockAddress, "Failed to send multicast Neighbor Solicitation"); + } + @Nullable private static Inet6Address getIpv6LinkLocalAddress(final LinkProperties newLp) { for (LinkAddress la : newLp.getLinkAddresses()) { @@ -1646,16 +1693,6 @@ return null; } - private void updateGratuitousNaTargetSet(@NonNull final Inet6Address targetIp, boolean add) { - if (add) { - mGratuitousNaTargetAddresses.add(targetIp); - } else { - mGratuitousNaTargetAddresses.remove(targetIp); - } - mLog.log((add ? "Add" : "Remove") + " global IPv6 address " + targetIp - + (add ? " to" : " from") + " the set of gratuitous NA target address."); - } - private void maybeSendGratuitousNAs(final LinkProperties lp, boolean afterRoaming) { if (!lp.hasGlobalIpv6Address()) return; @@ -1665,7 +1702,7 @@ // TODO: add experiment with sending only one gratuitous NA packet instead of one // packet per address. for (LinkAddress la : lp.getLinkAddresses()) { - if (!la.isIpv6() || !la.isGlobalPreferred()) continue; + if (!NetworkStackUtils.isIPv6GUA(la)) continue; final Inet6Address targetIp = (Inet6Address) la.getAddress(); // Already sent gratuitous NA with this target global IPv6 address. But for // the L2 roaming case, device should always (re)transmit Gratuitous NA for @@ -1676,7 +1713,9 @@ + targetIp.getHostAddress() + (afterRoaming ? " after roaming" : "")); } sendGratuitousNA(srcIp, targetIp); - if (!afterRoaming) updateGratuitousNaTargetSet(targetIp, true /* add address */); + if (!afterRoaming) { + mGratuitousNaTargetAddresses.add(targetIp); + } } } @@ -1693,6 +1732,39 @@ } } + @Nullable + private static Inet6Address getIPv6DefaultGateway(final LinkProperties lp) { + for (RouteInfo r : lp.getRoutes()) { + // TODO: call {@link RouteInfo#isIPv6Default} directly after core networking modules + // are consolidated. + if (r.getType() == RTN_UNICAST && r.getDestination().getPrefixLength() == 0 + && r.getDestination().getAddress() instanceof Inet6Address) { + // Check if it's IPv6 default route, if yes, return the gateway address + // (i.e. default router's IPv6 link-local address) + return (Inet6Address) r.getGateway(); + } + } + return null; + } + + private void maybeSendMulticastNSes(final LinkProperties lp) { + if (!(lp.hasGlobalIpv6Address() && lp.hasIpv6DefaultRoute())) return; + + // Get the default router's IPv6 link-local address. + final Inet6Address targetIp = getIPv6DefaultGateway(lp); + if (targetIp == null) return; + final Inet6Address dstIp = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast(targetIp); + if (dstIp == null) return; + + for (LinkAddress la : lp.getLinkAddresses()) { + if (!NetworkStackUtils.isIPv6GUA(la)) continue; + final Inet6Address srcIp = (Inet6Address) la.getAddress(); + if (mMulticastNsSourceAddresses.contains(srcIp)) continue; + sendMulticastNs(srcIp, dstIp, targetIp); + mMulticastNsSourceAddresses.add(srcIp); + } + } + // Returns false if we have lost provisioning, true otherwise. private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { final LinkProperties newLp = assembleLinkProperties(); @@ -1707,6 +1779,17 @@ maybeSendGratuitousNAs(newLp, false /* isGratuitousNaAfterRoaming */); } + // Sending multicast NS from each new assigned IPv6 GUAs to the solicited-node multicast + // address based on the default router's IPv6 link-local address should trigger default + // router response with NA, and update the neighbor cache entry immediately, that would + // help speed up the connection to an IPv6-only network. + // + // TODO: stop sending this multicast NS after deployment of RFC9131 in the field, leverage + // the gratuitous NA to update the first-hop router's neighbor cache entry. + if (isMulticastNsEnabled()) { + maybeSendMulticastNSes(newLp); + } + // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update, // wait for the provisioning completion and record the latency. mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned()); @@ -2004,9 +2087,15 @@ class StoppedState extends State { @Override public void enter() { + // It's necessary to disable IPv6 stack at StoppedState#enter, which cleans up the + // IPv6 link-local address and default IPv6 link-local route(fe80::/64 -> ::) which + // are generated on interface creation. The IPv6 link-local address and route will + // come back later during provisioning, otherwise, there is no way to syncup the + // initial link-local address and route to mLinkProperties. stopAllIP(); mHasDisabledIpv6OrAcceptRaOnProvLoss = false; mGratuitousNaTargetAddresses.clear(); + mMulticastNsSourceAddresses.clear(); resetLinkProperties(); if (mStartTimeMillis > 0) { @@ -2030,8 +2119,9 @@ break; case CMD_START: + mClearAddressesOnStop = shouldClearAddressesOnStop(); mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj; - transitionTo(mClearingIpAddressesState); + transitionTo(mClearAddressesOnStartState); break; case EVENT_NETLINK_LINKPROPERTIES_CHANGED: @@ -2078,7 +2168,10 @@ public void enter() { if (mDhcpClient == null) { // There's no DHCPv4 for which to wait; proceed to stopped. - deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED)); + deferMessage(obtainMessage( + mClearAddressesOnStop + ? CMD_JUMP_STOPPING_TO_CLEAR_ADDRESSES_ON_STOP + : CMD_JUMP_STOPPING_TO_STOPPED)); } else { mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP); mDhcpClient.doQuit(); @@ -2095,6 +2188,10 @@ transitionTo(mStoppedState); break; + case CMD_JUMP_STOPPING_TO_CLEAR_ADDRESSES_ON_STOP: + transitionTo(mClearAddressesOnStopState); + break; + case CMD_STOP: break; @@ -2104,7 +2201,9 @@ case DhcpClient.CMD_ON_QUIT: mDhcpClient = null; - transitionTo(mStoppedState); + transitionTo(mClearAddressesOnStop + ? mClearAddressesOnStopState + : mStoppedState); break; default: @@ -2160,7 +2259,65 @@ isUsingPreconnection(), options)); } - class ClearingIpAddressesState extends State { + abstract class ClearAddressesState extends State { + protected abstract State getTargetState(); + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_ADDRESSES_CLEARED: + transitionTo(getTargetState()); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + handleLinkPropertiesUpdate(NO_CALLBACKS); + if (readyToProceed()) { + transitionTo(getTargetState()); + } + break; + + case EVENT_CLEAR_ADDRESSES_TIMEOUT: + transitionTo(mStoppedState); + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // Usually NetworkAgent will be unregistered along with stopping IpClient, then network will + // be destroyed and IPv6 relevant routes will be removed from interface, but IPv4 relevant + // routes won't be removed if any. Disabling IPv6 stack also results in the removal of all + // IPv6 addresses and relevant routes. + private boolean readyToProceed() { + return !mLinkProperties.hasIpv4Address() && !mLinkProperties.hasGlobalIpv6Address() + && !mLinkProperties.hasIpv6DefaultRoute(); + } + + protected void ensureIpAddressesCleared() { + if (readyToProceed()) { + deferMessage(obtainMessage(CMD_ADDRESSES_CLEARED)); + } else { + // Clear all IPv4 and IPv6 before proceeding to RunningState. + // Clean up any leftover state from an abnormal exit from + // tethering or during an IpClient restart. + stopAllIP(); + + // Set a timeout for waiting the RTM_DELADDR netlink events. + sendMessageDelayed(EVENT_CLEAR_ADDRESSES_TIMEOUT, + mDependencies.getDeviceConfigPropertyInt(CONFIG_CLEAR_ADDRESSES_TIMEOUT, + DEFAULT_CLEAR_ADDRESSES_TIMEOUT_MS)); + } + } + } + + class ClearAddressesOnStartState extends ClearAddressesState { + @Override + protected State getTargetState() { + return isUsingPreconnection() ? mPreconnectingState : mRunningState; + } + @Override public void enter() { // Ensure that interface parameters are fetched on the handler thread so they are @@ -2175,33 +2332,14 @@ } mLinkObserver.setInterfaceParams(mInterfaceParams); - - if (readyToProceed()) { - deferMessage(obtainMessage(CMD_ADDRESSES_CLEARED)); - } else { - // Clear all IPv4 and IPv6 before proceeding to RunningState. - // Clean up any leftover state from an abnormal exit from - // tethering or during an IpClient restart. - stopAllIP(); - } - mCallback.setNeighborDiscoveryOffload(true); + ensureIpAddressesCleared(); } @Override public boolean processMessage(Message msg) { + if (super.processMessage(msg) == HANDLED) return HANDLED; switch (msg.what) { - case CMD_ADDRESSES_CLEARED: - transitionTo(isUsingPreconnection() ? mPreconnectingState : mRunningState); - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - handleLinkPropertiesUpdate(NO_CALLBACKS); - if (readyToProceed()) { - transitionTo(isUsingPreconnection() ? mPreconnectingState : mRunningState); - } - break; - case CMD_STOP: case EVENT_PROVISIONING_TIMEOUT: // Fall through to StartedState. @@ -2217,9 +2355,29 @@ } return HANDLED; } + } - private boolean readyToProceed() { - return !mLinkProperties.hasIpv4Address() && !mLinkProperties.hasGlobalIpv6Address(); + class ClearAddressesOnStopState extends ClearAddressesState { + @Override + protected State getTargetState() { + return mStoppedState; + } + + @Override + public void enter() { + ensureIpAddressesCleared(); + } + + @Override + public boolean processMessage(Message msg) { + if (super.processMessage(msg) == HANDLED) return HANDLED; + switch (msg.what) { + default: + // Any messages which are not processed in ClearAddressesState will be + // deferred to StoppedState. + deferMessage(msg); + } + return HANDLED; } } @@ -2406,8 +2564,6 @@ mApfFilter.shutdown(); mApfFilter = null; } - - resetLinkProperties(); } private void enqueueJumpToStoppingState(final DisconnectCode code) {
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java index 08226ef..e73a913 100644 --- a/src/android/net/ip/IpClientLinkObserver.java +++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -60,6 +60,7 @@ import com.android.net.module.util.netlink.StructNdOptRdnss; import com.android.networkstack.apishim.NetworkInformationShimImpl; import com.android.networkstack.apishim.common.NetworkInformationShim; +import com.android.networkstack.util.NetworkStackUtils; import com.android.server.NetworkObserver; import java.net.Inet6Address; @@ -120,7 +121,7 @@ void update(boolean linkState); /** - * Called when an IPv6 address was removed from the interface. + * Called when an IPv6 global unicast address was removed from the interface. * * @param addr The removed IPv6 address. */ @@ -156,6 +157,7 @@ private final IpClient.Dependencies mDependencies; private final String mClatInterfaceName; private final MyNetlinkMonitor mNetlinkMonitor; + private final boolean mNetlinkEventParsingEnabled; private boolean mClatInterfaceExists; @@ -176,7 +178,7 @@ mContext = context; mInterfaceName = iface; mClatInterfaceName = CLAT_PREFIX + iface; - mTag = "NetlinkTracker/" + mInterfaceName; + mTag = "IpClientLinkObserver/" + mInterfaceName; mCallback = callback; mLinkProperties = new LinkProperties(); mLinkProperties.setInterfaceName(mInterfaceName); @@ -186,6 +188,8 @@ mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mDependencies = deps; + mNetlinkEventParsingEnabled = deps.isFeatureEnabled(context, + IPCLIENT_PARSE_NETLINK_EVENTS_VERSION, isAtLeastT() /* default value */); mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag); mHandler.post(() -> { if (!mNetlinkMonitor.start()) { @@ -215,11 +219,6 @@ } } - private boolean isNetlinkEventParsingEnabled() { - return mDependencies.isFeatureEnabled(mContext, IPCLIENT_PARSE_NETLINK_EVENTS_VERSION, - isAtLeastT() /* default value */); - } - private int getSocketReceiveBufferSize() { final int size = mDependencies.getDeviceConfigPropertyInt(CONFIG_SOCKET_RECV_BUFSIZE, SOCKET_RECV_BUFSIZE /* default value */); @@ -231,7 +230,7 @@ @Override public void onInterfaceAdded(String iface) { - if (isNetlinkEventParsingEnabled()) return; + if (mNetlinkEventParsingEnabled) return; maybeLog("interfaceAdded", iface); if (mClatInterfaceName.equals(iface)) { mCallback.onClatInterfaceStateUpdate(true /* add interface */); @@ -240,7 +239,7 @@ @Override public void onInterfaceRemoved(String iface) { - if (isNetlinkEventParsingEnabled()) return; + if (mNetlinkEventParsingEnabled) return; maybeLog("interfaceRemoved", iface); if (mClatInterfaceName.equals(iface)) { mCallback.onClatInterfaceStateUpdate(false /* remove interface */); @@ -251,7 +250,7 @@ @Override public void onInterfaceLinkStateChanged(String iface, boolean state) { - if (isNetlinkEventParsingEnabled()) return; + if (mNetlinkEventParsingEnabled) return; if (!mInterfaceName.equals(iface)) return; maybeLog("interfaceLinkStateChanged", iface + (state ? " up" : " down")); updateInterfaceLinkStateChanged(state); @@ -259,7 +258,7 @@ @Override public void onInterfaceAddressUpdated(LinkAddress address, String iface) { - if (isNetlinkEventParsingEnabled()) return; + if (mNetlinkEventParsingEnabled) return; if (!mInterfaceName.equals(iface)) return; maybeLog("addressUpdated", iface, address); updateInterfaceAddress(address, true /* add address */); @@ -267,7 +266,7 @@ @Override public void onInterfaceAddressRemoved(LinkAddress address, String iface) { - if (isNetlinkEventParsingEnabled()) return; + if (mNetlinkEventParsingEnabled) return; if (!mInterfaceName.equals(iface)) return; maybeLog("addressRemoved", iface, address); updateInterfaceAddress(address, false /* remove address */); @@ -275,7 +274,7 @@ @Override public void onRouteUpdated(RouteInfo route) { - if (isNetlinkEventParsingEnabled()) return; + if (mNetlinkEventParsingEnabled) return; if (!mInterfaceName.equals(route.getInterface())) return; maybeLog("routeUpdated", route); updateInterfaceRoute(route, true /* add route */); @@ -283,7 +282,7 @@ @Override public void onRouteRemoved(RouteInfo route) { - if (isNetlinkEventParsingEnabled()) return; + if (mNetlinkEventParsingEnabled) return; if (!mInterfaceName.equals(route.getInterface())) return; maybeLog("routeRemoved", route); updateInterfaceRoute(route, false /* remove route */); @@ -291,7 +290,7 @@ @Override public void onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { - if (isNetlinkEventParsingEnabled()) return; + if (mNetlinkEventParsingEnabled) return; if (!mInterfaceName.equals(iface)) return; updateInterfaceDnsServerInfo(lifetime, addresses); } @@ -326,7 +325,7 @@ } if (changed) { mCallback.update(linkState); - if (!add && address.isIpv6()) { + if (!add && NetworkStackUtils.isIPv6GUA(address)) { final Inet6Address addr = (Inet6Address) address.getAddress(); mCallback.onIpv6AddressRemoved(addr); } @@ -423,7 +422,7 @@ MyNetlinkMonitor(Handler h, SharedLog log, String tag) { super(h, log, tag, OsConstants.NETLINK_ROUTE, - !isNetlinkEventParsingEnabled() + !mNetlinkEventParsingEnabled ? NetlinkConstants.RTMGRP_ND_USEROPT : (NetlinkConstants.RTMGRP_ND_USEROPT | NetlinkConstants.RTMGRP_LINK | NetlinkConstants.RTMGRP_IPV4_IFADDR @@ -536,7 +535,7 @@ } private void processRdnssOption(StructNdOptRdnss opt) { - if (!isNetlinkEventParsingEnabled()) return; + if (!mNetlinkEventParsingEnabled) return; final String[] addresses = new String[opt.servers.length]; for (int i = 0; i < opt.servers.length; i++) { addresses[i] = opt.servers[i].getHostAddress(); @@ -587,7 +586,7 @@ } private void processRtNetlinkLinkMessage(RtNetlinkLinkMessage msg) { - if (!isNetlinkEventParsingEnabled()) return; + if (!mNetlinkEventParsingEnabled) return; // Check if receiving netlink link state update for clat interface. final String ifname = msg.getInterfaceName(); @@ -621,7 +620,7 @@ } private void processRtNetlinkAddressMessage(RtNetlinkAddressMessage msg) { - if (!isNetlinkEventParsingEnabled()) return; + if (!mNetlinkEventParsingEnabled) return; final StructIfaddrMsg ifaddrMsg = msg.getIfaddrHeader(); if (ifaddrMsg.index != mIfindex) return; @@ -646,7 +645,7 @@ } private void processRtNetlinkRouteMessage(RtNetlinkRouteMessage msg) { - if (!isNetlinkEventParsingEnabled()) return; + if (!mNetlinkEventParsingEnabled) return; if (msg.getInterfaceIndex() != mIfindex) return; // Ignore the unsupported route protocol and non-global unicast routes. if (!isSupportedRouteProtocol(msg)
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java index 00f6dfa..d16840d 100644 --- a/src/android/net/ip/IpReachabilityMonitor.java +++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -232,6 +232,7 @@ private int mInterSolicitIntervalMs; @NonNull private final Callback mCallback; + private final boolean mMulticastResolicitEnabled; public IpReachabilityMonitor( Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback, @@ -253,6 +254,8 @@ mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker; mCm = context.getSystemService(ConnectivityManager.class); mDependencies = dependencies; + mMulticastResolicitEnabled = dependencies.isFeatureEnabled(context, + IP_REACHABILITY_MCAST_RESOLICIT_VERSION, false /* defaultEnabled */); mMetricsLog = metricsLog; mNetd = netd; Preconditions.checkNotNull(mNetd); @@ -261,7 +264,7 @@ // In case the overylaid parameters specify an invalid configuration, set the parameters // to the hardcoded defaults first, then set them to the values used in the steady state. try { - int numResolicits = isMulticastResolicitEnabled() + int numResolicits = mMulticastResolicitEnabled ? NUD_MCAST_RESOLICIT_NUM : INVALID_NUD_MCAST_RESOLICIT_NUM; setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits); @@ -270,7 +273,7 @@ } setNeighbourParametersForSteadyState(); - mIpNeighborMonitor = mDependencies.makeIpNeighborMonitor(h, mLog, + mIpNeighborMonitor = dependencies.makeIpNeighborMonitor(h, mLog, (NeighborEvent event) -> { if (mInterfaceParams.index != event.ifindex) return; if (!mNeighborWatchList.containsKey(event.ip)) return; @@ -362,11 +365,6 @@ return false; } - private boolean isMulticastResolicitEnabled() { - return mDependencies.isFeatureEnabled(mContext, IP_REACHABILITY_MCAST_RESOLICIT_VERSION, - false /* defaultEnabled */); - } - public void updateLinkProperties(LinkProperties lp) { if (!mInterfaceParams.name.equals(lp.getInterfaceName())) { // TODO: figure out whether / how to cope with interface changes. @@ -406,7 +404,7 @@ private void handleNeighborReachable(@Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) { - if (isMulticastResolicitEnabled() + if (mMulticastResolicitEnabled && hasDefaultRouterNeighborMacAddressChanged(prev, event)) { // This implies device has confirmed the neighbor's reachability from // other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac @@ -529,7 +527,7 @@ private long getProbeWakeLockDuration() { final long gracePeriodMs = 500; final int numSolicits = - mNumSolicits + (isMulticastResolicitEnabled() ? NUD_MCAST_RESOLICIT_NUM : 0); + mNumSolicits + (mMulticastResolicitEnabled ? NUD_MCAST_RESOLICIT_NUM : 0); return (long) (numSolicits * mInterSolicitIntervalMs) + gracePeriodMs; }
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java index 2ec6841..6af742c 100755 --- a/src/com/android/networkstack/util/NetworkStackUtils.java +++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -17,11 +17,14 @@ package com.android.networkstack.util; import android.content.Context; +import android.net.LinkAddress; import android.net.MacAddress; import android.net.util.SocketUtils; import android.system.ErrnoException; +import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.net.module.util.DeviceConfigUtils; @@ -29,7 +32,9 @@ import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; +import java.net.InetAddress; import java.net.SocketException; +import java.net.UnknownHostException; /** * Collection of utilities for the network stack. @@ -221,6 +226,13 @@ public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version"; /** + * Experiment flag to send multicast NS from the global IPv6 GUA to the solicited-node + * multicast address based on the default router's IPv6 link-local address, which helps + * flush the first-hop routers' neighbor cache entry for the global IPv6 GUA. + */ + public static final String IPCLIENT_MULTICAST_NS_VERSION = "ipclient_multicast_ns_version"; + + /** * Experiment flag to enable sending Gratuitous APR and Gratuitous Neighbor Advertisement for * all assigned IPv4 and IPv6 GUAs after completing L2 roaming. */ @@ -247,6 +259,13 @@ public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION = "ip_reachability_mcast_resolicit_version"; + /** + * Experiment flag to wait for IP addresses cleared completely before transition to + * IpClient#StoppedState from IpClient#StoppingState. + */ + public static final String IPCLIENT_CLEAR_ADDRESSES_ON_STOP_VERSION = + "ipclient_clear_addresses_on_stop_version"; + static { System.loadLibrary("networkstackutilsjni"); } @@ -277,6 +296,36 @@ } /** + * Convert IPv6 unicast or anycast address to solicited node multicast address + * per RFC4291 section 2.7.1. + */ + @Nullable + public static Inet6Address ipv6AddressToSolicitedNodeMulticast( + @NonNull final Inet6Address addr) { + final byte[] address = new byte[16]; + address[0] = (byte) 0xFF; + address[1] = (byte) 0x02; + address[11] = (byte) 0x01; + address[12] = (byte) 0xFF; + address[13] = addr.getAddress()[13]; + address[14] = addr.getAddress()[14]; + address[15] = addr.getAddress()[15]; + try { + return (Inet6Address) InetAddress.getByAddress(address); + } catch (UnknownHostException e) { + Log.e(TAG, "Invalid host IP address " + addr.getHostAddress(), e); + return null; + } + } + + /** + * Check whether a link address is IPv6 global unicast address. + */ + public static boolean isIPv6GUA(@NonNull final LinkAddress address) { + return address.isIpv6() && address.isGlobalPreferred(); + } + + /** * Attaches a socket filter that accepts DHCP packets to the given socket. */ public static native void attachDhcpFilter(FileDescriptor fd) throws ErrnoException;
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index da6c289..61cb143 100755 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -442,6 +442,10 @@ private final String mCaptivePortalHttpsUrlFromSetting; private final String mCaptivePortalHttpUrlFromSetting; @Nullable + private final URL mTestCaptivePortalHttpsUrl; + @Nullable + private final URL mTestCaptivePortalHttpUrl; + @Nullable private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs; // The probing URLs may be updated after constructor if system notifies configuration changed. @@ -613,6 +617,9 @@ mDependencies.getSetting(context, CAPTIVE_PORTAL_HTTPS_URL, null); mCaptivePortalHttpUrlFromSetting = mDependencies.getSetting(context, CAPTIVE_PORTAL_HTTP_URL, null); + mTestCaptivePortalHttpsUrl = + getTestUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL, validationLogs, deps); + mTestCaptivePortalHttpUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTP_URL, validationLogs, deps); mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(); mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled(); mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, @@ -2065,8 +2072,9 @@ } @Nullable - private URL getTestUrl(@NonNull String key) { - final String strExpiration = mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, + private static URL getTestUrl(@NonNull String key, @NonNull SharedLog log, + @NonNull Dependencies deps) { + final String strExpiration = deps.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, TEST_URL_EXPIRATION_TIME, null); if (strExpiration == null) return null; @@ -2074,23 +2082,23 @@ try { expTime = Long.parseUnsignedLong(strExpiration); } catch (NumberFormatException e) { - loge("Invalid test URL expiration time format", e); + log.e("Invalid test URL expiration time format", e); return null; } final long now = System.currentTimeMillis(); if (expTime < now || (expTime - now) > TEST_URL_EXPIRATION_MS) { - logw("Skipping test URL with expiration " + expTime + ", now " + now); + log.w("Skipping test URL with expiration " + expTime + ", now " + now); return null; } - final String strUrl = mDependencies.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, + final String strUrl = deps.getDeviceConfigProperty(NAMESPACE_CONNECTIVITY, key, null /* defaultValue */); if (!isValidTestUrl(strUrl)) { - logw("Skipping invalid test URL " + strUrl); + log.w("Skipping invalid test URL " + strUrl); return null; } - return makeURL(strUrl); + return makeURL(strUrl, log); } private String getCaptivePortalServerHttpsUrl(@NonNull Context context) { @@ -2251,8 +2259,7 @@ } private URL[] makeCaptivePortalHttpsUrls(@NonNull Context context) { - final URL testUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL); - if (testUrl != null) return new URL[] { testUrl }; + if (mTestCaptivePortalHttpsUrl != null) return new URL[] { mTestCaptivePortalHttpsUrl }; final String firstUrl = getCaptivePortalServerHttpsUrl(context); try { @@ -2272,8 +2279,7 @@ } private URL[] makeCaptivePortalHttpUrls(@NonNull Context context) { - final URL testUrl = getTestUrl(TEST_CAPTIVE_PORTAL_HTTP_URL); - if (testUrl != null) return new URL[] { testUrl }; + if (mTestCaptivePortalHttpUrl != null) return new URL[] { mTestCaptivePortalHttpUrl }; final String firstUrl = getCaptivePortalServerHttpUrl(context); try { @@ -3171,12 +3177,18 @@ } } - private URL makeURL(String url) { + @Nullable + private URL makeURL(@Nullable String url) { + return makeURL(url, mValidationLogs); + } + + @Nullable + private static URL makeURL(@Nullable String url, @NonNull SharedLog log) { if (url != null) { try { return new URL(url); } catch (MalformedURLException e) { - validationLog("Bad URL: " + url); + log.w("Bad URL: " + url); } } return null;
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp index f21cf33..fca588d 100644 --- a/tests/integration/Android.bp +++ b/tests/integration/Android.bp
@@ -34,7 +34,10 @@ java_defaults { name: "NetworkStackIntegrationTestsDefaults", - defaults: ["framework-connectivity-test-defaults"], + defaults: [ + "framework-connectivity-test-defaults", + "NetworkStackReleaseTargetSdk", + ], srcs: [ "common/**/*.java", "common/**/*.kt", @@ -70,13 +73,14 @@ // Network stack integration tests. android_test { name: "NetworkStackIntegrationTests", - defaults: ["NetworkStackIntegrationTestsJniDefaults"], + defaults: [ + "NetworkStackReleaseTargetSdk", + "NetworkStackIntegrationTestsJniDefaults", + ], static_libs: ["NetworkStackIntegrationTestsLib"], certificate: "networkstack", platform_apis: true, test_suites: ["device-tests"], - min_sdk_version: "29", - target_sdk_version: "30", jarjar_rules: ":NetworkStackJarJarRules", test_config_template: "AndroidTestTemplate_Integration.xml", } @@ -129,11 +133,12 @@ name: "NetworkStackCoverageTests", certificate: "networkstack", platform_apis: true, - min_sdk_version: "29", - target_sdk_version: "30", test_suites: ["device-tests", "mts-networking"], test_config: "AndroidTest_Coverage.xml", - defaults: ["NetworkStackIntegrationTestsJniDefaults"], + defaults: [ + "NetworkStackReleaseTargetSdk", + "NetworkStackIntegrationTestsJniDefaults", + ], static_libs: [ "modules-utils-native-coverage-listener", "NetworkStackTestsLib",
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java index 1a4dab1..34d4a07 100644 --- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java +++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -642,6 +642,7 @@ when(mCb.getInterfaceVersion()).thenReturn(IpClient.VERSION_ADDED_REACHABILITY_FAILURE); mDependencies.setDeviceConfigProperty(IpClient.CONFIG_MIN_RDNSS_LIFETIME, 67); + mDependencies.setDeviceConfigProperty(IpClient.CONFIG_CLEAR_ADDRESSES_TIMEOUT, 50); mDependencies.setDeviceConfigProperty(DhcpClient.DHCP_RESTART_CONFIG_DELAY, 10); mDependencies.setDeviceConfigProperty(DhcpClient.ARP_FIRST_PROBE_DELAY_MS, 10); mDependencies.setDeviceConfigProperty(DhcpClient.ARP_PROBE_MIN_MS, 10); @@ -2186,7 +2187,7 @@ } @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required") - public void testIpClientClearingIpAddressState() throws Exception { + public void testClearAddressesOnStartState() throws Exception { doIPv4OnlyProvisioningAndExitWithLeftAddress(); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() @@ -2207,7 +2208,7 @@ } @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required") - public void testIpClientClearingIpAddressState_enablePreconnection() throws Exception { + public void testClearAddressesOnStartState_enablePreconnection() throws Exception { doIPv4OnlyProvisioningAndExitWithLeftAddress(); // Enter ClearingIpAddressesState to clear the remaining IPv4 addresses and transition to @@ -2812,7 +2813,7 @@ true /* shouldReplyNakOnRoam */); } - private void performDualStackProvisioning() throws Exception { + private LinkProperties performDualStackProvisioning() throws Exception { final InOrder inOrder = inOrder(mCb); final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); final String dnsServer = "2001:4860:4860::64"; @@ -2838,9 +2839,10 @@ assertTrue(lp.getDnsServers().contains(SERVER_ADDR)); reset(mCb); + return lp; } - private void doDualStackProvisioning(boolean shouldDisableAcceptRa) throws Exception { + private LinkProperties doDualStackProvisioning(boolean shouldDisableAcceptRa) throws Exception { when(mCm.shouldAvoidBadWifi()).thenReturn(true); final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() @@ -2855,7 +2857,7 @@ false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); mIpc.startProvisioning(config); - performDualStackProvisioning(); + return performDualStackProvisioning(); } @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck") @@ -3497,6 +3499,25 @@ assertTrue(target.isGlobalPreferred()); } + private void assertMulticastNsFromIpv6Gua(final NeighborSolicitation ns) throws Exception { + final Inet6Address solicitedNodeMulticast = + NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast(ROUTER_LINK_LOCAL); + final MacAddress etherMulticast = + NetworkStackUtils.ipv6MulticastToEthernetMulticast(solicitedNodeMulticast); + + assertEquals(etherMulticast, ns.ethHdr.dstMac); + assertEquals(ETH_P_IPV6, ns.ethHdr.etherType); + assertEquals(IPPROTO_ICMPV6, ns.ipv6Hdr.nextHeader); + assertEquals(0xff, ns.ipv6Hdr.hopLimit); + + final LinkAddress srcIp = new LinkAddress(ns.ipv6Hdr.srcIp.getHostAddress() + "/64"); + assertTrue(srcIp.isGlobalPreferred()); + assertEquals(solicitedNodeMulticast, ns.ipv6Hdr.dstIp); + assertEquals(ICMPV6_NEIGHBOR_SOLICITATION, ns.icmpv6Hdr.type); + assertEquals(0, ns.icmpv6Hdr.code); + assertEquals(ROUTER_LINK_LOCAL, ns.nsHdr.target); + } + @Test public void testGratuitousNaForNewGlobalUnicastAddresses() throws Exception { final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() @@ -4003,4 +4024,52 @@ assertFalse(hasRouteTo(lp, prefix)); assertFalse(lp.hasIpv6DefaultRoute()); } + + @Test @SignatureRequiredTest(reason = "Need MANAGE_TEST_NETWORKS perm to create NetworkAgent") + public void testClearAddressesOnStopState() throws Exception { + setFeatureEnabled(NetworkStackUtils.IPCLIENT_CLEAR_ADDRESSES_ON_STOP_VERSION, true); + mNetworkAgentThread = + new HandlerThread(IpClientIntegrationTestCommon.class.getSimpleName()); + mNetworkAgentThread.start(); + + final LinkProperties lp = doDualStackProvisioning(false /* shouldDisableAcceptRa */); + runAsShell(MANAGE_TEST_NETWORKS, () -> createTestNetworkAgentAndRegister(lp)); + + // Verify the link addresses and IPv6 routes get removed before transition to StoppedState.. + mNetworkAgent.unregister(); + mNetworkAgentThread.quitSafely(); + mIIpClient.shutdown(); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat( + x -> x.getAddresses().size() == 0 + && !x.hasIpv6DefaultRoute() + && x.getDnsServers().size() == 0)); + awaitIpClientShutdown(); + } + + @Test + public void testMulticastNsFromIPv6Gua() throws Exception { + final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIpReachabilityMonitor() + .withoutIPv4() + .build(); + + setFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION, + true /* isUnsolicitedNsEnabled */); + assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION, false)); + startIpClientProvisioning(config); + + doIpv6OnlyProvisioning(); + + final List<NeighborSolicitation> nsList = new ArrayList<>(); + NeighborSolicitation packet; + while ((packet = getNextNeighborSolicitation()) != null) { + // Filter out the NSes used for duplicate address detetction, whose target address + // is the global IPv6 address inside these NSes. + if (packet.nsHdr.target.isLinkLocalAddress()) { + assertMulticastNsFromIpv6Gua(packet); + nsList.add(packet); + } + } + assertEquals(2, nsList.size()); // from privacy address and stable privacy address + } }
diff --git a/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt b/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt index 9f5e5e7..9c40eff 100644 --- a/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt +++ b/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt
@@ -31,9 +31,10 @@ */ class TestNetworkStackServiceClient private constructor() : NetworkStackClientBase() { companion object { + private val testNetworkStackServiceAction = "android.net.INetworkStackConnector.Test" private val context by lazy { InstrumentationRegistry.getInstrumentation().context } private val networkStackVersion by lazy { - val component = getNetworkStackComponent(INetworkStackConnector::class.java.name) + val component = getNetworkStackComponent(testNetworkStackServiceAction) val info = context.packageManager.getPackageInfo(component.packageName, 0 /* flags */) info.longVersionCode } @@ -61,16 +62,22 @@ } } + // Inner class defined in subclass cannot access the protected methods of parent class directly, + // so have a method outside of the inner class: serviceConnection and call this method instead. + private fun onNetworkStackConnected(service: IBinder) { + onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(service)) + } + private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { - onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(service)) + onNetworkStackConnected(service) } override fun onServiceDisconnected(name: ComponentName) = Unit } private fun init() { - val bindIntent = Intent(INetworkStackConnector::class.java.name + ".Test") + val bindIntent = Intent(testNetworkStackServiceAction) bindIntent.component = getNetworkStackComponent(bindIntent.action) context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE) }
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt index 10b55d5..3800752 100644 --- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt +++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -261,6 +261,8 @@ }.`when`(dependencies).makeIpNeighborMonitor(any(), any(), any()) doReturn(mIpReachabilityMonitorMetrics) .`when`(dependencies).getIpReachabilityMonitorMetrics() + doReturn(true).`when`(dependencies).isFeatureEnabled(anyObject(), + eq(IP_REACHABILITY_MCAST_RESOLICIT_VERSION), anyBoolean()) val monitorFuture = CompletableFuture<IpReachabilityMonitor>() // IpReachabilityMonitor needs to be started from the handler thread @@ -348,9 +350,6 @@ newLp: LinkProperties, neighbor: InetAddress ) { - doReturn(true).`when`(dependencies).isFeatureEnabled(anyObject(), - eq(IP_REACHABILITY_MCAST_RESOLICIT_VERSION), anyBoolean()) - reachabilityMonitor.updateLinkProperties(newLp) neighborMonitor.enqueuePacket(makeNewNeighMessage(neighbor, NUD_REACHABLE,
diff --git a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt deleted file mode 100644 index 5ca20d8..0000000 --- a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt +++ /dev/null
@@ -1,375 +0,0 @@ -package android.net.testutils - -import android.annotation.SuppressLint -import android.net.LinkAddress -import android.net.LinkProperties -import android.net.Network -import android.net.NetworkCapabilities -import com.android.testutils.ConcurrentInterpreter -import com.android.testutils.InterpretMatcher -import com.android.testutils.RecorderCallback.CallbackEntry -import com.android.testutils.RecorderCallback.CallbackEntry.Companion.AVAILABLE -import com.android.testutils.RecorderCallback.CallbackEntry.Companion.BLOCKED_STATUS -import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LINK_PROPERTIES_CHANGED -import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOSING -import com.android.testutils.RecorderCallback.CallbackEntry.Companion.NETWORK_CAPS_UPDATED -import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOST -import com.android.testutils.RecorderCallback.CallbackEntry.Companion.RESUMED -import com.android.testutils.RecorderCallback.CallbackEntry.Companion.SUSPENDED -import com.android.testutils.RecorderCallback.CallbackEntry.Companion.UNAVAILABLE -import com.android.testutils.RecorderCallback.CallbackEntry.Available -import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus -import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged -import com.android.testutils.TestableNetworkCallback -import com.android.testutils.intArg -import com.android.testutils.strArg -import com.android.testutils.timeArg -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import kotlin.reflect.KClass -import kotlin.test.assertEquals -import kotlin.test.assertFails -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.test.fail - -const val SHORT_TIMEOUT_MS = 20L -const val DEFAULT_LINGER_DELAY_MS = 30000 -const val NOT_METERED = NetworkCapabilities.NET_CAPABILITY_NOT_METERED -const val WIFI = NetworkCapabilities.TRANSPORT_WIFI -const val CELLULAR = NetworkCapabilities.TRANSPORT_CELLULAR -const val TEST_INTERFACE_NAME = "testInterfaceName" - -@RunWith(JUnit4::class) -@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs. -class TestableNetworkCallbackTest { - private lateinit var mCallback: TestableNetworkCallback - - private fun makeHasNetwork(netId: Int) = object : TestableNetworkCallback.HasNetwork { - override val network: Network = Network(netId) - } - - @Before - fun setUp() { - mCallback = TestableNetworkCallback() - } - - @Test - fun testLastAvailableNetwork() { - // Make sure there is no last available network at first, then the last available network - // is returned after onAvailable is called. - val net2097 = Network(2097) - assertNull(mCallback.lastAvailableNetwork) - mCallback.onAvailable(net2097) - assertEquals(mCallback.lastAvailableNetwork, net2097) - - // Make sure calling onCapsChanged/onLinkPropertiesChanged don't affect the last available - // network. - mCallback.onCapabilitiesChanged(net2097, NetworkCapabilities()) - mCallback.onLinkPropertiesChanged(net2097, LinkProperties()) - assertEquals(mCallback.lastAvailableNetwork, net2097) - - // Make sure onLost clears the last available network. - mCallback.onLost(net2097) - assertNull(mCallback.lastAvailableNetwork) - - // Do the same but with a different network after onLost : make sure the last available - // network is the new one, not the original one. - val net2098 = Network(2098) - mCallback.onAvailable(net2098) - mCallback.onCapabilitiesChanged(net2098, NetworkCapabilities()) - mCallback.onLinkPropertiesChanged(net2098, LinkProperties()) - assertEquals(mCallback.lastAvailableNetwork, net2098) - - // Make sure onAvailable changes the last available network even if onLost was not called. - val net2099 = Network(2099) - mCallback.onAvailable(net2099) - assertEquals(mCallback.lastAvailableNetwork, net2099) - - // For legacy reasons, lastAvailableNetwork is null as soon as any is lost, not necessarily - // the last available one. Check that behavior. - mCallback.onLost(net2098) - assertNull(mCallback.lastAvailableNetwork) - - // Make sure that losing the really last available one still results in null. - mCallback.onLost(net2099) - assertNull(mCallback.lastAvailableNetwork) - - // Make sure multiple onAvailable in a row then onLost still results in null. - mCallback.onAvailable(net2097) - mCallback.onAvailable(net2098) - mCallback.onAvailable(net2099) - mCallback.onLost(net2097) - assertNull(mCallback.lastAvailableNetwork) - } - - @Test - fun testAssertNoCallback() { - mCallback.assertNoCallback(SHORT_TIMEOUT_MS) - mCallback.onAvailable(Network(100)) - assertFails { mCallback.assertNoCallback(SHORT_TIMEOUT_MS) } - } - - @Test - fun testAssertNoCallbackThat() { - val net = Network(101) - mCallback.assertNoCallbackThat { it is Available } - mCallback.onAvailable(net) - // Expect no blocked status change. Receive other callback does not fail the test. - mCallback.assertNoCallbackThat { it is BlockedStatus } - mCallback.onBlockedStatusChanged(net, true) - assertFails { mCallback.assertNoCallbackThat { it is BlockedStatus } } - mCallback.onBlockedStatusChanged(net, false) - mCallback.onCapabilitiesChanged(net, NetworkCapabilities()) - assertFails { mCallback.assertNoCallbackThat { it is CapabilitiesChanged } } - } - - @Test - fun testCapabilitiesWithAndWithout() { - val net = Network(101) - val matcher = makeHasNetwork(101) - val meteredNc = NetworkCapabilities() - val unmeteredNc = NetworkCapabilities().addCapability(NOT_METERED) - // Check that expecting caps (with or without) fails when no callback has been received. - assertFails { mCallback.expectCapabilitiesWith(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } - assertFails { mCallback.expectCapabilitiesWithout(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } - - // Add NOT_METERED and check that With succeeds and Without fails. - mCallback.onCapabilitiesChanged(net, unmeteredNc) - mCallback.expectCapabilitiesWith(NOT_METERED, matcher) - mCallback.onCapabilitiesChanged(net, unmeteredNc) - assertFails { mCallback.expectCapabilitiesWithout(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } - - // Don't add NOT_METERED and check that With fails and Without succeeds. - mCallback.onCapabilitiesChanged(net, meteredNc) - assertFails { mCallback.expectCapabilitiesWith(NOT_METERED, matcher, SHORT_TIMEOUT_MS) } - mCallback.onCapabilitiesChanged(net, meteredNc) - mCallback.expectCapabilitiesWithout(NOT_METERED, matcher) - } - - @Test - fun testExpectCallbackThat() { - val net = Network(193) - val netCaps = NetworkCapabilities().addTransportType(CELLULAR) - // Check that expecting callbackThat anything fails when no callback has been received. - assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { true } } - - // Basic test for true and false - mCallback.onAvailable(net) - mCallback.expectCallbackThat { true } - mCallback.onAvailable(net) - assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { false } } - - // Try a positive and a negative case - mCallback.onBlockedStatusChanged(net, true) - mCallback.expectCallbackThat { cb -> cb is BlockedStatus && cb.blocked } - mCallback.onCapabilitiesChanged(net, netCaps) - assertFails { mCallback.expectCallbackThat(SHORT_TIMEOUT_MS) { cb -> - cb is CapabilitiesChanged && cb.caps.hasTransport(WIFI) - } } - } - - @Test - fun testCapabilitiesThat() { - val net = Network(101) - val netCaps = NetworkCapabilities().addCapability(NOT_METERED).addTransportType(WIFI) - // Check that expecting capabilitiesThat anything fails when no callback has been received. - assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { true } } - - // Basic test for true and false - mCallback.onCapabilitiesChanged(net, netCaps) - mCallback.expectCapabilitiesThat(net) { true } - mCallback.onCapabilitiesChanged(net, netCaps) - assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { false } } - - // Try a positive and a negative case - mCallback.onCapabilitiesChanged(net, netCaps) - mCallback.expectCapabilitiesThat(net) { caps -> - caps.hasCapability(NOT_METERED) && - caps.hasTransport(WIFI) && - !caps.hasTransport(CELLULAR) - } - mCallback.onCapabilitiesChanged(net, netCaps) - assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { caps -> - caps.hasTransport(CELLULAR) - } } - - // Try a matching callback on the wrong network - mCallback.onCapabilitiesChanged(net, netCaps) - assertFails { mCallback.expectCapabilitiesThat(Network(100), SHORT_TIMEOUT_MS) { true } } - } - - @Test - fun testLinkPropertiesThat() { - val net = Network(112) - val linkAddress = LinkAddress("fe80::ace:d00d/64") - val mtu = 1984 - val linkProps = LinkProperties().apply { - this.mtu = mtu - interfaceName = TEST_INTERFACE_NAME - addLinkAddress(linkAddress) - } - - // Check that expecting linkPropsThat anything fails when no callback has been received. - assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { true } } - - // Basic test for true and false - mCallback.onLinkPropertiesChanged(net, linkProps) - mCallback.expectLinkPropertiesThat(net) { true } - mCallback.onLinkPropertiesChanged(net, linkProps) - assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { false } } - - // Try a positive and negative case - mCallback.onLinkPropertiesChanged(net, linkProps) - mCallback.expectLinkPropertiesThat(net) { lp -> - lp.interfaceName == TEST_INTERFACE_NAME && - lp.linkAddresses.contains(linkAddress) && - lp.mtu == mtu - } - mCallback.onLinkPropertiesChanged(net, linkProps) - assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { lp -> - lp.interfaceName != TEST_INTERFACE_NAME - } } - - // Try a matching callback on the wrong network - mCallback.onLinkPropertiesChanged(net, linkProps) - assertFails { mCallback.expectLinkPropertiesThat(Network(114), SHORT_TIMEOUT_MS) { lp -> - lp.interfaceName == TEST_INTERFACE_NAME - } } - } - - @Test - fun testExpectCallback() { - val net = Network(103) - // Test expectCallback fails when nothing was sent. - assertFails { mCallback.expectCallback<BlockedStatus>(net, SHORT_TIMEOUT_MS) } - - // Test onAvailable is seen and can be expected - mCallback.onAvailable(net) - mCallback.expectCallback<Available>(net, SHORT_TIMEOUT_MS) - - // Test onAvailable won't return calls with a different network - mCallback.onAvailable(Network(106)) - assertFails { mCallback.expectCallback<Available>(net, SHORT_TIMEOUT_MS) } - - // Test onAvailable won't return calls with a different callback - mCallback.onAvailable(net) - assertFails { mCallback.expectCallback<BlockedStatus>(net, SHORT_TIMEOUT_MS) } - } - - @Test - fun testPollForNextCallback() { - assertFails { mCallback.pollForNextCallback(SHORT_TIMEOUT_MS) } - TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1, - threadTransform = { cb -> cb.createLinkedCopy() }, spec = """ - sleep; onAvailable(133) | poll(2) = Available(133) time 1..4 - | poll(1) fails - onCapabilitiesChanged(108) | poll(1) = CapabilitiesChanged(108) time 0..3 - onBlockedStatus(199) | poll(1) = BlockedStatus(199) time 0..3 - """) - } - - @Test - fun testEventuallyExpect() { - // TODO: Current test does not verify the inline one. Also verify the behavior after - // aligning two eventuallyExpect() - val net1 = Network(100) - val net2 = Network(101) - mCallback.onAvailable(net1) - mCallback.onCapabilitiesChanged(net1, NetworkCapabilities()) - mCallback.onLinkPropertiesChanged(net1, LinkProperties()) - mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED) { - net1.equals(it.network) - } - // No further new callback. Expect no callback. - assertFails { mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED) } - - // Verify no predicate set. - mCallback.onAvailable(net2) - mCallback.onLinkPropertiesChanged(net2, LinkProperties()) - mCallback.onBlockedStatusChanged(net1, false) - mCallback.eventuallyExpect(BLOCKED_STATUS) { net1.equals(it.network) } - // Verify no callback received if the callback does not happen. - assertFails { mCallback.eventuallyExpect(LOSING) } - } - - @Test - fun testEventuallyExpectOnMultiThreads() { - TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1, - threadTransform = { cb -> cb.createLinkedCopy() }, spec = """ - onAvailable(100) | eventually(CapabilitiesChanged(100), 1) fails - sleep ; onCapabilitiesChanged(100) | eventually(CapabilitiesChanged(100), 2) - onAvailable(101) ; onBlockedStatus(101) | eventually(BlockedStatus(100), 2) fails - onSuspended(100) ; sleep ; onLost(100) | eventually(Lost(100), 2) - """) - } -} - -private object TNCInterpreter : ConcurrentInterpreter<TestableNetworkCallback>(interpretTable) - -val EntryList = CallbackEntry::class.sealedSubclasses.map { it.simpleName }.joinToString("|") -private fun callbackEntryFromString(name: String): KClass<out CallbackEntry> { - return CallbackEntry::class.sealedSubclasses.first { it.simpleName == name } -} - -@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs. -private val interpretTable = listOf<InterpretMatcher<TestableNetworkCallback>>( - // Interpret "Available(xx)" as "call to onAvailable with netId xx", and likewise for - // all callback types. This is implemented above by enumerating the subclasses of - // CallbackEntry and reading their simpleName. - Regex("""(.*)\s+=\s+($EntryList)\((\d+)\)""") to { i, cb, t -> - val record = i.interpret(t.strArg(1), cb) - assertTrue(callbackEntryFromString(t.strArg(2)).isInstance(record)) - // Strictly speaking testing for is CallbackEntry is useless as it's been tested above - // but the compiler can't figure things out from the isInstance call. It does understand - // from the assertTrue(is CallbackEntry) that this is true, which allows to access - // the 'network' member below. - assertTrue(record is CallbackEntry) - assertEquals(record.network.netId, t.intArg(3)) - }, - // Interpret "onAvailable(xx)" as calling "onAvailable" with a netId of xx, and likewise for - // all callback types. NetworkCapabilities and LinkProperties just get an empty object - // as their argument. Losing gets the default linger timer. Blocked gets false. - Regex("""on($EntryList)\((\d+)\)""") to { i, cb, t -> - val net = Network(t.intArg(2)) - when (t.strArg(1)) { - "Available" -> cb.onAvailable(net) - // PreCheck not used in tests. Add it here if it becomes useful. - "CapabilitiesChanged" -> cb.onCapabilitiesChanged(net, NetworkCapabilities()) - "LinkPropertiesChanged" -> cb.onLinkPropertiesChanged(net, LinkProperties()) - "Suspended" -> cb.onNetworkSuspended(net) - "Resumed" -> cb.onNetworkResumed(net) - "Losing" -> cb.onLosing(net, DEFAULT_LINGER_DELAY_MS) - "Lost" -> cb.onLost(net) - "Unavailable" -> cb.onUnavailable() - "BlockedStatus" -> cb.onBlockedStatusChanged(net, false) - else -> fail("Unknown callback type") - } - }, - Regex("""poll\((\d+)\)""") to { i, cb, t -> - cb.pollForNextCallback(t.timeArg(1)) - }, - // Interpret "eventually(Available(xx), timeout)" as calling eventuallyExpect that expects - // CallbackEntry.AVAILABLE with netId of xx within timeout*INTERPRET_TIME_UNIT timeout, and - // likewise for all callback types. - Regex("""eventually\(($EntryList)\((\d+)\),\s+(\d+)\)""") to { i, cb, t -> - val net = Network(t.intArg(2)) - val timeout = t.timeArg(3) - when (t.strArg(1)) { - "Available" -> cb.eventuallyExpect(AVAILABLE, timeout) { net == it.network } - "Suspended" -> cb.eventuallyExpect(SUSPENDED, timeout) { net == it.network } - "Resumed" -> cb.eventuallyExpect(RESUMED, timeout) { net == it.network } - "Losing" -> cb.eventuallyExpect(LOSING, timeout) { net == it.network } - "Lost" -> cb.eventuallyExpect(LOST, timeout) { net == it.network } - "Unavailable" -> cb.eventuallyExpect(UNAVAILABLE, timeout) { net == it.network } - "BlockedStatus" -> cb.eventuallyExpect(BLOCKED_STATUS, timeout) { net == it.network } - "CapabilitiesChanged" -> - cb.eventuallyExpect(NETWORK_CAPS_UPDATED, timeout) { net == it.network } - "LinkPropertiesChanged" -> - cb.eventuallyExpect(LINK_PROPERTIES_CHANGED, timeout) { net == it.network } - else -> fail("Unknown callback type") - } - } -)