Merge "Rename method connectSocketToNetlink to connectToKernel" into main
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index f275d49..79d9a23 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -847,7 +847,38 @@
}
} catch (ServiceSpecificException | RemoteException e) {
mLog.e("Failed to add " + mIfaceName + " to local table: ", e);
- return;
+ }
+ }
+
+ private void addInterfaceForward(@NonNull final String fromIface,
+ @NonNull final String toIface) throws ServiceSpecificException, RemoteException {
+ if (null != mRoutingCoordinator.value) {
+ mRoutingCoordinator.value.addInterfaceForward(fromIface, toIface);
+ } else {
+ mNetd.tetherAddForward(fromIface, toIface);
+ mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
+ }
+ }
+
+ private void removeInterfaceForward(@NonNull final String fromIface,
+ @NonNull final String toIface) {
+ if (null != mRoutingCoordinator.value) {
+ try {
+ mRoutingCoordinator.value.removeInterfaceForward(fromIface, toIface);
+ } catch (ServiceSpecificException e) {
+ mLog.e("Exception in removeInterfaceForward", e);
+ }
+ } else {
+ try {
+ mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Exception in ipfwdRemoveInterfaceForward", e);
+ }
+ try {
+ mNetd.tetherRemoveForward(fromIface, toIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Exception in disableNat", e);
+ }
}
}
@@ -1375,16 +1406,7 @@
// to remove their rules, which generates errors.
// Just do the best we can.
mBpfCoordinator.maybeDetachProgram(mIfaceName, upstreamIface);
- try {
- mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
- } catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString());
- }
- try {
- mNetd.tetherRemoveForward(mIfaceName, upstreamIface);
- } catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Exception in disableNat: " + e.toString());
- }
+ removeInterfaceForward(mIfaceName, upstreamIface);
}
@Override
@@ -1440,10 +1462,9 @@
mBpfCoordinator.maybeAttachProgram(mIfaceName, ifname);
try {
- mNetd.tetherAddForward(mIfaceName, ifname);
- mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname);
+ addInterfaceForward(mIfaceName, ifname);
} catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Exception enabling NAT: " + e.toString());
+ mLog.e("Exception enabling iface forward", e);
cleanupUpstream();
mLastError = TETHER_ERROR_ENABLE_FORWARDING_ERROR;
transitionTo(mInitialState);
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 08fca4a..98b624b 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -338,6 +338,24 @@
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */);
+ // Simulate the behavior of RoutingCoordinator
+ if (null != mRoutingCoordinatorManager.value) {
+ doAnswer(it -> {
+ final String fromIface = (String) it.getArguments()[0];
+ final String toIface = (String) it.getArguments()[1];
+ mNetd.tetherAddForward(fromIface, toIface);
+ mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
+ return null;
+ }).when(mRoutingCoordinatorManager.value).addInterfaceForward(any(), any());
+ doAnswer(it -> {
+ final String fromIface = (String) it.getArguments()[0];
+ final String toIface = (String) it.getArguments()[1];
+ mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
+ mNetd.tetherRemoveForward(fromIface, toIface);
+ return null;
+ }).when(mRoutingCoordinatorManager.value).removeInterfaceForward(any(), any());
+ }
+
setUpDhcpServer();
}
diff --git a/framework-t/udc-extended-api/system-current.txt b/framework-t/udc-extended-api/system-current.txt
index 1549089..6f0119e 100644
--- a/framework-t/udc-extended-api/system-current.txt
+++ b/framework-t/udc-extended-api/system-current.txt
@@ -305,6 +305,7 @@
ctor public NetworkStats(long, int);
method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats);
method @NonNull public android.net.NetworkStats addEntry(@NonNull android.net.NetworkStats.Entry);
+ method public android.net.NetworkStats clone();
method public int describeContents();
method @NonNull public java.util.Iterator<android.net.NetworkStats.Entry> iterator();
method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats);
diff --git a/framework/src/android/net/BpfNetMapsReader.java b/framework/src/android/net/BpfNetMapsReader.java
index 9bce9cd..37c58f0 100644
--- a/framework/src/android/net/BpfNetMapsReader.java
+++ b/framework/src/android/net/BpfNetMapsReader.java
@@ -17,6 +17,8 @@
package android.net;
import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
+import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
+import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
@@ -213,13 +215,16 @@
* Return whether the network is blocked by firewall chains for the given uid.
*
* @param uid The target uid.
+ * @param isNetworkMetered Whether the target network is metered.
+ * @param isDataSaverEnabled Whether the data saver is enabled.
*
* @return True if the network is blocked. Otherwise, false.
* @throws ServiceSpecificException if the read fails.
*
* @hide
*/
- public boolean isUidBlockedByFirewallChains(final int uid) {
+ public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered,
+ boolean isDataSaverEnabled) {
throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
final long uidRuleConfig;
@@ -235,6 +240,13 @@
final boolean blockedByAllowChains = 0 != (uidRuleConfig & ~uidMatch & sMaskDropIfUnset);
final boolean blockedByDenyChains = 0 != (uidRuleConfig & uidMatch & sMaskDropIfSet);
- return blockedByAllowChains || blockedByDenyChains;
+ if (blockedByAllowChains || blockedByDenyChains) {
+ return true;
+ }
+
+ if (!isNetworkMetered) return false;
+ if ((uidMatch & PENALTY_BOX_MATCH) != 0) return true;
+ if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false;
+ return isDataSaverEnabled;
}
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index ad76012..f44fd0e 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -16,6 +16,8 @@
package android.net;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.content.pm.ApplicationInfo.FLAG_PERSISTENT;
+import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
import static android.net.NetworkRequest.Type.LISTEN;
@@ -25,6 +27,8 @@
import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
import static android.net.QosCallback.QosCallbackRegistrationException;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -37,12 +41,16 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TargetApi;
+import android.app.Application;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.net.ConnectivityDiagnosticsManager.DataStallReport.DetectionMethod;
import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.SocketKeepalive.Callback;
@@ -74,6 +82,7 @@
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import libcore.net.event.NetworkEventDispatcher;
@@ -95,6 +104,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -6199,13 +6209,99 @@
}
/**
- * Return whether the network is blocked for the given uid.
+ * Helper class to track data saver status.
+ *
+ * The class will fetch current data saver status from {@link NetworkPolicyManager} when
+ * initialized, and listening for status changed intent to cache the latest status.
+ *
+ * @hide
+ */
+ @TargetApi(Build.VERSION_CODES.TIRAMISU) // RECEIVER_NOT_EXPORTED requires T.
+ @VisibleForTesting(visibility = PRIVATE)
+ public static class DataSaverStatusTracker extends BroadcastReceiver {
+ private static final Object sDataSaverStatusTrackerLock = new Object();
+
+ private static volatile DataSaverStatusTracker sInstance;
+
+ /**
+ * Gets a static instance of the class.
+ *
+ * @param context A {@link Context} for initialization. Note that since the data saver
+ * status is global on a device, passing any context is equivalent.
+ * @return The static instance of a {@link DataSaverStatusTracker}.
+ */
+ public static DataSaverStatusTracker getInstance(@NonNull Context context) {
+ if (sInstance == null) {
+ synchronized (sDataSaverStatusTrackerLock) {
+ if (sInstance == null) {
+ sInstance = new DataSaverStatusTracker(context);
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ private final NetworkPolicyManager mNpm;
+ // The value updates on the caller's binder thread or UI thread.
+ private final AtomicBoolean mIsDataSaverEnabled;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public DataSaverStatusTracker(final Context context) {
+ // To avoid leaks, take the application context.
+ final Context appContext;
+ if (context instanceof Application) {
+ appContext = context;
+ } else {
+ appContext = context.getApplicationContext();
+ }
+
+ if ((appContext.getApplicationInfo().flags & FLAG_PERSISTENT) == 0
+ && (appContext.getApplicationInfo().flags & FLAG_SYSTEM) == 0) {
+ throw new IllegalStateException("Unexpected caller: "
+ + appContext.getApplicationInfo().packageName);
+ }
+
+ mNpm = appContext.getSystemService(NetworkPolicyManager.class);
+ final IntentFilter filter = new IntentFilter(
+ ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED);
+ // The receiver should not receive broadcasts from other Apps.
+ appContext.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
+ mIsDataSaverEnabled = new AtomicBoolean();
+ updateDataSaverEnabled();
+ }
+
+ // Runs on caller's UI thread.
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED)) {
+ updateDataSaverEnabled();
+ } else {
+ throw new IllegalStateException("Unexpected intent " + intent);
+ }
+ }
+
+ public boolean getDataSaverEnabled() {
+ return mIsDataSaverEnabled.get();
+ }
+
+ private void updateDataSaverEnabled() {
+ // Uid doesn't really matter, but use a fixed UID to make things clearer.
+ final int dataSaverForCallerUid = mNpm.getRestrictBackgroundStatus(Process.SYSTEM_UID);
+ mIsDataSaverEnabled.set(dataSaverForCallerUid
+ != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED);
+ }
+ }
+
+ /**
+ * Return whether the network is blocked for the given uid and metered condition.
*
* Similar to {@link NetworkPolicyManager#isUidNetworkingBlocked}, but directly reads the BPF
* maps and therefore considerably faster. For use by the NetworkStack process only.
*
* @param uid The target uid.
- * @return True if all networking is blocked. Otherwise, false.
+ * @param isNetworkMetered Whether the target network is metered.
+ *
+ * @return True if all networking with the given condition is blocked. Otherwise, false.
* @throws IllegalStateException if the map cannot be opened.
* @throws ServiceSpecificException if the read fails.
* @hide
@@ -6219,13 +6315,16 @@
// @SystemApi(client = MODULE_LIBRARIES)
@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
@RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
- public boolean isUidNetworkingBlocked(int uid) {
+ public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) {
final BpfNetMapsReader reader = BpfNetMapsReader.getInstance();
- return reader.isUidBlockedByFirewallChains(uid);
+ final boolean isDataSaverEnabled;
+ // TODO: For U-QPR3+ devices, get data saver status from bpf configuration map directly.
+ final DataSaverStatusTracker dataSaverStatusTracker =
+ DataSaverStatusTracker.getInstance(mContext);
+ isDataSaverEnabled = dataSaverStatusTracker.getDataSaverEnabled();
- // TODO: If isNetworkMetered is true, check the data saver switch, penalty box
- // and happy box rules.
+ return reader.isUidNetworkingBlocked(uid, isNetworkMetered, isDataSaverEnabled);
}
/** @hide */
diff --git a/framework/src/android/net/IRoutingCoordinator.aidl b/framework/src/android/net/IRoutingCoordinator.aidl
index a5cda98..cf02ec4 100644
--- a/framework/src/android/net/IRoutingCoordinator.aidl
+++ b/framework/src/android/net/IRoutingCoordinator.aidl
@@ -72,4 +72,24 @@
* unix errno.
*/
void removeInterfaceFromNetwork(int netId, in String iface);
+
+ /**
+ * Add forwarding ip rule
+ *
+ * @param fromIface interface name to add forwarding ip rule
+ * @param toIface interface name to add forwarding ip rule
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ void addInterfaceForward(in String fromIface, in String toIface);
+
+ /**
+ * Remove forwarding ip rule
+ *
+ * @param fromIface interface name to remove forwarding ip rule
+ * @param toIface interface name to remove forwarding ip rule
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ void removeInterfaceForward(in String fromIface, in String toIface);
}
diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java
index 00382f6..935dea1 100644
--- a/framework/src/android/net/NetworkScore.java
+++ b/framework/src/android/net/NetworkScore.java
@@ -46,7 +46,7 @@
KEEP_CONNECTED_NONE,
KEEP_CONNECTED_FOR_HANDOVER,
KEEP_CONNECTED_FOR_TEST,
- KEEP_CONNECTED_DOWNSTREAM_NETWORK
+ KEEP_CONNECTED_LOCAL_NETWORK
})
public @interface KeepConnectedReason { }
@@ -67,10 +67,10 @@
public static final int KEEP_CONNECTED_FOR_TEST = 2;
/**
* Keep this network connected even if there is no outstanding request for it, because
- * it is a downstream network.
+ * it is a local network.
* @hide
*/
- public static final int KEEP_CONNECTED_DOWNSTREAM_NETWORK = 3;
+ public static final int KEEP_CONNECTED_LOCAL_NETWORK = 3;
// Agent-managed policies
// This network should lose to a wifi that has ever been validated
diff --git a/framework/src/android/net/RoutingCoordinatorManager.java b/framework/src/android/net/RoutingCoordinatorManager.java
index 5576cb0..a9e7eef 100644
--- a/framework/src/android/net/RoutingCoordinatorManager.java
+++ b/framework/src/android/net/RoutingCoordinatorManager.java
@@ -123,4 +123,36 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Add forwarding ip rule
+ *
+ * @param fromIface interface name to add forwarding ip rule
+ * @param toIface interface name to add forwarding ip rule
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void addInterfaceForward(final String fromIface, final String toIface) {
+ try {
+ mService.addInterfaceForward(fromIface, toIface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove forwarding ip rule
+ *
+ * @param fromIface interface name to remove forwarding ip rule
+ * @param toIface interface name to remove forwarding ip rule
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void removeInterfaceForward(final String fromIface, final String toIface) {
+ try {
+ mService.removeInterfaceForward(fromIface, toIface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/framework/udc-extended-api/system-current.txt b/framework/udc-extended-api/system-current.txt
index 4a2ed8a..e812024 100644
--- a/framework/udc-extended-api/system-current.txt
+++ b/framework/udc-extended-api/system-current.txt
@@ -94,6 +94,7 @@
}
public final class DscpPolicy implements android.os.Parcelable {
+ method public int describeContents();
method @Nullable public java.net.InetAddress getDestinationAddress();
method @Nullable public android.util.Range<java.lang.Integer> getDestinationPortRange();
method public int getDscpValue();
@@ -101,6 +102,7 @@
method public int getProtocol();
method @Nullable public java.net.InetAddress getSourceAddress();
method public int getSourcePort();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.DscpPolicy> CREATOR;
field public static final int PROTOCOL_ANY = -1; // 0xffffffff
field public static final int SOURCE_PORT_ANY = -1; // 0xffffffff
diff --git a/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
index 80c315a..450f380 100644
--- a/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
+++ b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
@@ -25,9 +25,15 @@
#include <perfetto/tracing/platform.h>
#include <perfetto/tracing/tracing.h>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "netdbpf/BpfNetworkStats.h"
+
namespace android {
namespace bpf {
namespace internal {
+using ::android::base::StringPrintf;
void NetworkTracePoller::PollAndSchedule(perfetto::base::TaskRunner* runner,
uint32_t poll_ms) {
@@ -116,6 +122,28 @@
return res.ok();
}
+void NetworkTracePoller::TraceIfaces(const std::vector<PacketTrace>& packets) {
+ if (packets.empty()) return;
+
+ std::unordered_set<uint32_t> uniqueIfindex;
+ for (const PacketTrace& pkt : packets) {
+ uniqueIfindex.insert(pkt.ifindex);
+ }
+
+ for (uint32_t ifindex : uniqueIfindex) {
+ char ifname[IF_NAMESIZE] = {};
+ if (if_indextoname(ifindex, ifname) != ifname) continue;
+
+ StatsValue stats = {};
+ if (bpfGetIfIndexStats(ifindex, &stats) != 0) continue;
+
+ std::string rxTrack = StringPrintf("%s [%d] Rx Bytes", ifname, ifindex);
+ std::string txTrack = StringPrintf("%s [%d] Tx Bytes", ifname, ifindex);
+ ATRACE_INT64(rxTrack.c_str(), stats.rxBytes);
+ ATRACE_INT64(txTrack.c_str(), stats.txBytes);
+ }
+}
+
bool NetworkTracePoller::ConsumeAll() {
std::scoped_lock<std::mutex> lock(mMutex);
return ConsumeAllLocked();
@@ -137,6 +165,7 @@
ATRACE_INT("NetworkTracePackets", packets.size());
+ TraceIfaces(packets);
mCallback(packets);
return true;
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h
index 8433934..092ab64 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h
@@ -61,6 +61,11 @@
void PollAndSchedule(perfetto::base::TaskRunner* runner, uint32_t poll_ms);
bool ConsumeAllLocked() REQUIRES(mMutex);
+ // Record sparse iface stats via atrace. This queries the per-iface stats maps
+ // for any iface present in the vector of packets. This is inexact, but should
+ // have sufficient coverage given these are cumulative counters.
+ void TraceIfaces(const std::vector<PacketTrace>& packets) REQUIRES(mMutex);
+
std::mutex mMutex;
// Records the number of successfully started active sessions so that only the
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index f52a1a2..3dc5692 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -4156,7 +4156,14 @@
switch (msg.what) {
case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
- nai.setDeclaredCapabilities((NetworkCapabilities) arg.second);
+ final NetworkCapabilities proposed = (NetworkCapabilities) arg.second;
+ if (!nai.respectsNcStructuralConstraints(proposed)) {
+ Log.wtf(TAG, "Agent " + nai + " violates nc structural constraints : "
+ + nai.networkCapabilities + " -> " + proposed);
+ disconnectAndDestroyNetwork(nai);
+ return;
+ }
+ nai.setDeclaredCapabilities(proposed);
final NetworkCapabilities sanitized =
nai.getDeclaredCapabilitiesSanitized(mCarrierPrivilegeAuthenticator);
maybeUpdateWifiRoamTimestamp(nai, sanitized);
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index b0ad978..dacae20 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -428,12 +429,28 @@
private final boolean mHasAutomotiveFeature;
/**
+ * Checks that a proposed update to the NCs of this NAI satisfies structural constraints.
+ *
+ * Some changes to NetworkCapabilities are structurally not supported by the stack, and
+ * NetworkAgents are absolutely never allowed to try and do them. When one of these is
+ * violated, this method returns false, which has ConnectivityService disconnect the network ;
+ * this is meant to guarantee that no implementor ever tries to do this.
+ */
+ public boolean respectsNcStructuralConstraints(@NonNull final NetworkCapabilities proposedNc) {
+ if (networkCapabilities.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ != proposedNc.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Sets the capabilities sent by the agent for later retrieval.
- *
- * This method does not sanitize the capabilities ; instead, use
- * {@link #getDeclaredCapabilitiesSanitized} to retrieve a sanitized
- * copy of the capabilities as they were passed here.
- *
+ * <p>
+ * This method does not sanitize the capabilities before storing them ; instead, use
+ * {@link #getDeclaredCapabilitiesSanitized} to retrieve a sanitized copy of the capabilities
+ * as they were passed here.
+ * <p>
* This method makes a defensive copy to avoid issues where the passed object is later mutated.
*
* @param caps the caps sent by the agent
diff --git a/service/src/com/android/server/connectivity/RoutingCoordinatorService.java b/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
index 50e84d4..ede78ce 100644
--- a/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
+++ b/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
@@ -19,12 +19,17 @@
import static com.android.net.module.util.NetdUtils.toRouteInfoParcel;
import android.annotation.NonNull;
-import android.content.Context;
import android.net.INetd;
import android.net.IRoutingCoordinator;
import android.net.RouteInfo;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
/**
* Class to coordinate routing across multiple clients.
@@ -37,6 +42,7 @@
* synchronization.
*/
public class RoutingCoordinatorService extends IRoutingCoordinator.Stub {
+ private static final String TAG = RoutingCoordinatorService.class.getSimpleName();
private final INetd mNetd;
public RoutingCoordinatorService(@NonNull INetd netd) {
@@ -115,4 +121,99 @@
throws ServiceSpecificException, RemoteException {
mNetd.networkRemoveInterface(netId, iface);
}
+
+ private final Object mIfacesLock = new Object();
+ private static final class ForwardingPair {
+ @NonNull public final String fromIface;
+ @NonNull public final String toIface;
+ ForwardingPair(@NonNull final String fromIface, @NonNull final String toIface) {
+ this.fromIface = fromIface;
+ this.toIface = toIface;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ForwardingPair)) return false;
+
+ final ForwardingPair that = (ForwardingPair) o;
+
+ return fromIface.equals(that.fromIface) && toIface.equals(that.toIface);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = fromIface.hashCode();
+ result = 2 * result + toIface.hashCode();
+ return result;
+ }
+ }
+
+ @GuardedBy("mIfacesLock")
+ private final ArraySet<ForwardingPair> mForwardedInterfaces = new ArraySet<>();
+
+ /**
+ * Add forwarding ip rule
+ *
+ * @param fromIface interface name to add forwarding ip rule
+ * @param toIface interface name to add forwarding ip rule
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void addInterfaceForward(final String fromIface, final String toIface)
+ throws ServiceSpecificException, RemoteException {
+ Objects.requireNonNull(fromIface);
+ Objects.requireNonNull(toIface);
+ Log.i(TAG, "Adding interface forward " + fromIface + " → " + toIface);
+ synchronized (mIfacesLock) {
+ if (mForwardedInterfaces.size() == 0) {
+ mNetd.ipfwdEnableForwarding("RoutingCoordinator");
+ }
+ final ForwardingPair fwp = new ForwardingPair(fromIface, toIface);
+ if (mForwardedInterfaces.contains(fwp)) {
+ throw new IllegalStateException("Forward already exists between ifaces "
+ + fromIface + " → " + toIface);
+ }
+ mForwardedInterfaces.add(fwp);
+ // Enables NAT for v4 and filters packets from unknown interfaces
+ mNetd.tetherAddForward(fromIface, toIface);
+ mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
+ }
+ }
+
+ /**
+ * Remove forwarding ip rule
+ *
+ * @param fromIface interface name to remove forwarding ip rule
+ * @param toIface interface name to remove forwarding ip rule
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void removeInterfaceForward(final String fromIface, final String toIface)
+ throws ServiceSpecificException, RemoteException {
+ Objects.requireNonNull(fromIface);
+ Objects.requireNonNull(toIface);
+ Log.i(TAG, "Removing interface forward " + fromIface + " → " + toIface);
+ synchronized (mIfacesLock) {
+ final ForwardingPair fwp = new ForwardingPair(fromIface, toIface);
+ if (!mForwardedInterfaces.contains(fwp)) {
+ throw new IllegalStateException("No forward set up between interfaces "
+ + fromIface + " → " + toIface);
+ }
+ mForwardedInterfaces.remove(fwp);
+ try {
+ mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Exception in ipfwdRemoveInterfaceForward", e);
+ }
+ try {
+ mNetd.tetherRemoveForward(fromIface, toIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Exception in tetherRemoveForward", e);
+ }
+ if (mForwardedInterfaces.size() == 0) {
+ mNetd.ipfwdDisableForwarding("RoutingCoordinator");
+ }
+ }
+ }
}
diff --git a/staticlibs/netd/libnetdutils/Utils.cpp b/staticlibs/netd/libnetdutils/Utils.cpp
index 16ec882..9b0b3e0 100644
--- a/staticlibs/netd/libnetdutils/Utils.cpp
+++ b/staticlibs/netd/libnetdutils/Utils.cpp
@@ -16,6 +16,7 @@
*/
#include <map>
+#include <vector>
#include <net/if.h>
diff --git a/tests/native/utilities/firewall.cpp b/tests/native/utilities/firewall.cpp
index 6e35d07..22f83e8 100644
--- a/tests/native/utilities/firewall.cpp
+++ b/tests/native/utilities/firewall.cpp
@@ -28,8 +28,11 @@
result = mUidOwnerMap.init(UID_OWNER_MAP_PATH);
EXPECT_RESULT_OK(result) << "init mUidOwnerMap failed";
- result = mDataSaverEnabledMap.init(DATA_SAVER_ENABLED_MAP_PATH);
- EXPECT_RESULT_OK(result) << "init mDataSaverEnabledMap failed";
+ // Do not check whether DATA_SAVER_ENABLED_MAP_PATH init succeeded or failed since the map is
+ // defined in tethering module, but the user of this class may be in other modules. For example,
+ // DNS resolver tests statically link to this class. But when running MTS, the test infra
+ // installs only DNS resolver module without installing tethering module together.
+ mDataSaverEnabledMap.init(DATA_SAVER_ENABLED_MAP_PATH);
}
Firewall* Firewall::getInstance() {
@@ -122,6 +125,10 @@
Result<bool> Firewall::getDataSaverSetting() {
std::lock_guard guard(mMutex);
+ if (!mDataSaverEnabledMap.isValid()) {
+ return Errorf("init mDataSaverEnabledMap failed");
+ }
+
auto dataSaverSetting = mDataSaverEnabledMap.readValue(DATA_SAVER_ENABLED_KEY);
if (!dataSaverSetting.ok()) {
return Errorf("Cannot read the data saver setting: {}", dataSaverSetting.error().message());
@@ -131,6 +138,10 @@
Result<void> Firewall::setDataSaver(bool enabled) {
std::lock_guard guard(mMutex);
+ if (!mDataSaverEnabledMap.isValid()) {
+ return Errorf("init mDataSaverEnabledMap failed");
+ }
+
auto res = mDataSaverEnabledMap.writeValue(DATA_SAVER_ENABLED_KEY, enabled, BPF_EXIST);
if (!res.ok()) return Errorf("Failed to set data saver: {}", res.error().message());
diff --git a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
index 86a0acb..258e422 100644
--- a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
+++ b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
@@ -17,6 +17,8 @@
package android.net
import android.net.BpfNetMapsConstants.DOZABLE_MATCH
+import android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH
+import android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH
import android.net.BpfNetMapsConstants.STANDBY_MATCH
import android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY
import android.net.BpfNetMapsUtils.getMatchByFirewallChain
@@ -36,6 +38,7 @@
private const val TEST_UID1 = 1234
private const val TEST_UID2 = TEST_UID1 + 1
+private const val TEST_UID3 = TEST_UID2 + 1
private const val NO_IIF = 0
// pre-T devices does not support Bpf.
@@ -101,23 +104,26 @@
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(newConfig))
}
+ fun isUidNetworkingBlocked(uid: Int, metered: Boolean = false, dataSaver: Boolean = false) =
+ bpfNetMapsReader.isUidNetworkingBlocked(uid, metered, dataSaver)
+
@Test
fun testIsUidNetworkingBlockedByFirewallChains_allowChain() {
// With everything disabled by default, verify the return value is false.
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
- assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
+ assertFalse(isUidNetworkingBlocked(TEST_UID1))
// Enable dozable chain but does not provide allowed list. Verify the network is blocked
// for all uids.
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true)
- assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
- assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID2))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2))
// Add uid1 to dozable allowed list. Verify the network is not blocked for uid1, while
// uid2 is blocked.
testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, DOZABLE_MATCH))
- assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
- assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID2))
+ assertFalse(isUidNetworkingBlocked(TEST_UID1))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2))
}
@Test
@@ -126,14 +132,14 @@
// for all uids.
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true)
- assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
- assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID2))
+ assertFalse(isUidNetworkingBlocked(TEST_UID1))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2))
// Add uid1 to standby allowed list. Verify the network is blocked for uid1, while
// uid2 is not blocked.
testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, STANDBY_MATCH))
- assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
- assertFalse(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID2))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2))
}
@Test
@@ -143,6 +149,54 @@
testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_POWERSAVE, true)
mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_STANDBY, true)
- assertTrue(bpfNetMapsReader.isUidBlockedByFirewallChains(TEST_UID1))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1))
+ }
+
+ @IgnoreUpTo(VERSION_CODES.S_V2)
+ @Test
+ fun testIsUidNetworkingBlockedByDataSaver() {
+ // With everything disabled by default, verify the return value is false.
+ testConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, U32(0))
+ assertFalse(isUidNetworkingBlocked(TEST_UID1, metered = true))
+
+ // Add uid1 to penalty box, verify the network is blocked for uid1, while uid2 is not
+ // affected.
+ testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_MATCH))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true))
+
+ // Enable data saver, verify the network is blocked for uid1, uid2, but uid3 in happy box
+ // is not affected.
+ testUidOwnerMap.updateEntry(S32(TEST_UID3), UidOwnerValue(NO_IIF, HAPPY_BOX_MATCH))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
+
+ // Add uid1 to happy box as well, verify nothing is changed because penalty box has higher
+ // priority.
+ testUidOwnerMap.updateEntry(
+ S32(TEST_UID1),
+ UidOwnerValue(NO_IIF, PENALTY_BOX_MATCH or HAPPY_BOX_MATCH)
+ )
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
+
+ // Enable doze mode, verify uid3 is blocked even if it is in happy box.
+ mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, true)
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true, dataSaver = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true, dataSaver = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID3, metered = true, dataSaver = true))
+
+ // Disable doze mode and data saver, only uid1 which is in penalty box is blocked.
+ mockChainEnabled(ConnectivityManager.FIREWALL_CHAIN_DOZABLE, false)
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true))
+
+ // Make the network non-metered, nothing is blocked.
+ assertFalse(isUidNetworkingBlocked(TEST_UID1))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3))
}
}
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index 45a9dbc..b8c5447 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -16,6 +16,13 @@
package android.net;
+import static android.content.Context.RECEIVER_NOT_EXPORTED;
+import static android.content.pm.ApplicationInfo.FLAG_PERSISTENT;
+import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -39,6 +46,7 @@
import static com.android.testutils.MiscAsserts.assertThrows;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -51,6 +59,7 @@
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -61,7 +70,10 @@
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.net.ConnectivityManager.DataSaverStatusTracker;
import android.net.ConnectivityManager.NetworkCallback;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -95,6 +107,7 @@
@Mock Context mCtx;
@Mock IConnectivityManager mService;
+ @Mock NetworkPolicyManager mNpm;
@Before
public void setUp() {
@@ -510,4 +523,54 @@
assertNull("ConnectivityManager weak reference still not null after " + attempts
+ " attempts", ref.get());
}
+
+ @Test
+ public void testDataSaverStatusTracker() {
+ mockService(NetworkPolicyManager.class, Context.NETWORK_POLICY_SERVICE, mNpm);
+ // Mock proper application info.
+ doReturn(mCtx).when(mCtx).getApplicationContext();
+ final ApplicationInfo mockAppInfo = new ApplicationInfo();
+ mockAppInfo.flags = FLAG_PERSISTENT | FLAG_SYSTEM;
+ doReturn(mockAppInfo).when(mCtx).getApplicationInfo();
+ // Enable data saver.
+ doReturn(RESTRICT_BACKGROUND_STATUS_ENABLED).when(mNpm)
+ .getRestrictBackgroundStatus(anyInt());
+
+ final DataSaverStatusTracker tracker = new DataSaverStatusTracker(mCtx);
+ // Verify the data saver status is correct right after initialization.
+ assertTrue(tracker.getDataSaverEnabled());
+
+ // Verify the tracker register receiver with expected intent filter.
+ final ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+ verify(mCtx).registerReceiver(
+ any(), intentFilterCaptor.capture(), eq(RECEIVER_NOT_EXPORTED));
+ assertEquals(ACTION_RESTRICT_BACKGROUND_CHANGED,
+ intentFilterCaptor.getValue().getAction(0));
+
+ // Mock data saver status changed event and verify the tracker tracks the
+ // status accordingly.
+ doReturn(RESTRICT_BACKGROUND_STATUS_DISABLED).when(mNpm)
+ .getRestrictBackgroundStatus(anyInt());
+ tracker.onReceive(mCtx, new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED));
+ assertFalse(tracker.getDataSaverEnabled());
+
+ doReturn(RESTRICT_BACKGROUND_STATUS_WHITELISTED).when(mNpm)
+ .getRestrictBackgroundStatus(anyInt());
+ tracker.onReceive(mCtx, new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED));
+ assertTrue(tracker.getDataSaverEnabled());
+ }
+
+ private <T> void mockService(Class<T> clazz, String name, T service) {
+ doReturn(service).when(mCtx).getSystemService(name);
+ doReturn(name).when(mCtx).getSystemServiceName(clazz);
+
+ // If the test suite uses the inline mock maker library, such as for coverage tests,
+ // then the final version of getSystemService must also be mocked, as the real
+ // method will not be called by the test and null object is returned since no mock.
+ // Otherwise, mocking a final method will fail the test.
+ if (mCtx.getSystemService(clazz) == null) {
+ doReturn(service).when(mCtx).getSystemService(clazz);
+ }
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt b/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
new file mode 100644
index 0000000..12758c6
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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.server.connectivity
+
+import android.net.INetd
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import kotlin.test.assertFailsWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class RoutingCoordinatorServiceTest {
+ val mNetd = mock(INetd::class.java)
+ val mService = RoutingCoordinatorService(mNetd)
+
+ @Test
+ fun testInterfaceForward() {
+ val inOrder = inOrder(mNetd)
+
+ mService.addInterfaceForward("from1", "to1")
+ inOrder.verify(mNetd).ipfwdEnableForwarding(any())
+ inOrder.verify(mNetd).tetherAddForward("from1", "to1")
+ inOrder.verify(mNetd).ipfwdAddInterfaceForward("from1", "to1")
+
+ mService.addInterfaceForward("from2", "to1")
+ inOrder.verify(mNetd).tetherAddForward("from2", "to1")
+ inOrder.verify(mNetd).ipfwdAddInterfaceForward("from2", "to1")
+
+ assertFailsWith<IllegalStateException> {
+ // Can't add the same pair again
+ mService.addInterfaceForward("from2", "to1")
+ }
+
+ mService.removeInterfaceForward("from1", "to1")
+ inOrder.verify(mNetd).ipfwdRemoveInterfaceForward("from1", "to1")
+ inOrder.verify(mNetd).tetherRemoveForward("from1", "to1")
+
+ mService.removeInterfaceForward("from2", "to1")
+ inOrder.verify(mNetd).ipfwdRemoveInterfaceForward("from2", "to1")
+ inOrder.verify(mNetd).tetherRemoveForward("from2", "to1")
+
+ inOrder.verify(mNetd).ipfwdDisableForwarding(any())
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDestroyedNetworkTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSDestroyedNetworkTests.kt
index d40c035..572c7bb 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSDestroyedNetworkTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDestroyedNetworkTests.kt
@@ -19,12 +19,20 @@
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkCallback
import org.junit.Test
+import org.junit.runner.RunWith
private const val LONG_TIMEOUT_MS = 5_000
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class CSDestroyedNetworkTests : CSTest() {
@Test
fun testDestroyNetworkNotKeptWhenUnvalidated() {
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
index 6220e76..2126a09 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSKeepConnectedTest.kt
@@ -22,7 +22,7 @@
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
import android.net.NetworkScore
-import android.net.NetworkScore.KEEP_CONNECTED_DOWNSTREAM_NETWORK
+import android.net.NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK
import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
import android.os.Build
import androidx.test.filters.SmallTest
@@ -45,7 +45,7 @@
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
.build()
val keepConnectedAgent = Agent(nc = nc, score = FromS(NetworkScore.Builder()
- .setKeepConnectedReason(KEEP_CONNECTED_DOWNSTREAM_NETWORK)
+ .setKeepConnectedReason(KEEP_CONNECTED_LOCAL_NETWORK)
.build()),
lnc = LocalNetworkConfig.Builder().build())
val dontKeepConnectedAgent = Agent(nc = nc, lnc = LocalNetworkConfig.Builder().build())
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
index bd3efa9..d9f7f9f 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalAgentTests.kt
@@ -32,11 +32,11 @@
import android.os.Build
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.RecorderCallback
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.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkCallback
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,6 +65,8 @@
class CSLocalAgentTests : CSTest() {
@Test
fun testBadAgents() {
+ deps.setBuildSdk(VERSION_V)
+
assertFailsWith<IllegalArgumentException> {
Agent(nc = NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
@@ -78,6 +80,41 @@
}
@Test
+ fun testStructuralConstraintViolation() {
+ deps.setBuildSdk(VERSION_V)
+
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(NetworkRequest.Builder()
+ .clearCapabilities()
+ .build(),
+ cb)
+ val agent = Agent(nc = NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ lnc = LocalNetworkConfig.Builder().build())
+ agent.connect()
+ cb.expect<Available>(agent.network)
+ cb.expect<CapabilitiesChanged>(agent.network)
+ cb.expect<LinkPropertiesChanged>(agent.network)
+ cb.expect<BlockedStatus>(agent.network)
+ agent.sendNetworkCapabilities(NetworkCapabilities.Builder().build())
+ cb.expect<Lost>(agent.network)
+
+ val agent2 = Agent(nc = NetworkCapabilities.Builder()
+ .build(),
+ lnc = null)
+ agent2.connect()
+ cb.expect<Available>(agent2.network)
+ cb.expect<CapabilitiesChanged>(agent2.network)
+ cb.expect<LinkPropertiesChanged>(agent2.network)
+ cb.expect<BlockedStatus>(agent2.network)
+ agent2.sendNetworkCapabilities(NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build())
+ cb.expect<Lost>(agent2.network)
+ }
+
+ @Test
fun testUpdateLocalAgentConfig() {
deps.setBuildSdk(VERSION_V)
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index 8860895..f903e51 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -169,4 +169,5 @@
fun unregisterAfterReplacement(timeoutMs: Int) = agent.unregisterAfterReplacement(timeoutMs)
fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
+ fun sendNetworkCapabilities(nc: NetworkCapabilities) = agent.sendNetworkCapabilities(nc)
}