Merge "Unify the verification for unregister a NetworkAgent"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index ef96d88..9a455ba 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -11,6 +11,12 @@
"exclude-annotation": "com.android.testutils.SkipPresubmit"
}
]
+ },
+ {
+ "name": "TetheringTests"
+ },
+ {
+ "name": "TetheringIntegrationTests"
}
],
"mainline-presubmit": [
@@ -23,11 +29,19 @@
]
}
],
- // Tests on physical devices with SIM cards: postsubmit only for capacity constraints
"mainline-postsubmit": [
+ // Tests on physical devices with SIM cards: postsubmit only for capacity constraints
{
"name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"keywords": ["sim"]
+ },
+ {
+ "name": "TetheringCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ }
+ ],
+ "imports": [
+ {
+ "path": "packages/modules/NetworkStack"
}
]
}
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 4eafc2a..79c2b9d 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -30,6 +30,7 @@
":services-tethering-shared-srcs",
],
static_libs: [
+ "NetworkStackApiStableShims",
"androidx.annotation_annotation",
"modules-utils-build",
"netlink-client",
diff --git a/Tethering/TEST_MAPPING b/Tethering/TEST_MAPPING
deleted file mode 100644
index 5617b0c..0000000
--- a/Tethering/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presubmit": [
- {
- "name": "TetheringTests"
- }
- ],
- "postsubmit": [
- {
- "name": "TetheringIntegrationTests"
- }
- ]
-}
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 164bda4..917bf21 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -24,8 +24,10 @@
// cannot build as updatable unless service-connectivity builds against stable API).
updatable: false,
// min_sdk_version: "30",
+ bootclasspath_fragments: [
+ "com.android.tethering-bootclasspath-fragment",
+ ],
java_libs: [
- "framework-tethering",
"service-connectivity",
],
jni_libs: [
@@ -56,6 +58,13 @@
certificate: "com.android.tethering",
}
+// Encapsulate the contributions made by the com.android.tethering to the bootclasspath.
+bootclasspath_fragment {
+ name: "com.android.tethering-bootclasspath-fragment",
+ contents: ["framework-tethering"],
+ apex_available: ["com.android.tethering"],
+}
+
override_apex {
name: "com.android.tethering.inprocess",
base: "com.android.tethering",
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index e310fb6..33f1c29 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -179,6 +179,18 @@
}
@Override
+ public boolean addDevMap(int ifIndex) {
+ /* no op */
+ return false;
+ }
+
+ @Override
+ public boolean removeDevMap(int ifIndex) {
+ /* no op */
+ return false;
+ }
+
+ @Override
public String toString() {
return "Netd used";
}
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index d7ce139..74ddcbc 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -36,6 +36,8 @@
import com.android.networkstack.tethering.Tether4Key;
import com.android.networkstack.tethering.Tether4Value;
import com.android.networkstack.tethering.Tether6Value;
+import com.android.networkstack.tethering.TetherDevKey;
+import com.android.networkstack.tethering.TetherDevValue;
import com.android.networkstack.tethering.TetherDownstream6Key;
import com.android.networkstack.tethering.TetherLimitKey;
import com.android.networkstack.tethering.TetherLimitValue;
@@ -85,6 +87,10 @@
@Nullable
private final BpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap;
+ // BPF map of interface index mapping for XDP.
+ @Nullable
+ private final BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
+
// Tracking IPv4 rule count while any rule is using the given upstream interfaces. Used for
// reducing the BPF map iteration query. The count is increased or decreased when the rule is
// added or removed successfully on mBpfDownstream4Map. Counting the rules on downstream4 map
@@ -108,6 +114,7 @@
mBpfUpstream6Map = deps.getBpfUpstream6Map();
mBpfStatsMap = deps.getBpfStatsMap();
mBpfLimitMap = deps.getBpfLimitMap();
+ mBpfDevMap = deps.getBpfDevMap();
// Clear the stubs of the maps for handling the system service crash if any.
// Doesn't throw the exception and clear the stubs as many as possible.
@@ -141,12 +148,18 @@
} catch (ErrnoException e) {
mLog.e("Could not clear mBpfLimitMap: " + e);
}
+ try {
+ if (mBpfDevMap != null) mBpfDevMap.clear();
+ } catch (ErrnoException e) {
+ mLog.e("Could not clear mBpfDevMap: " + e);
+ }
}
@Override
public boolean isInitialized() {
return mBpfDownstream4Map != null && mBpfUpstream4Map != null && mBpfDownstream6Map != null
- && mBpfUpstream6Map != null && mBpfStatsMap != null && mBpfLimitMap != null;
+ && mBpfUpstream6Map != null && mBpfStatsMap != null && mBpfLimitMap != null
+ && mBpfDevMap != null;
}
@Override
@@ -432,6 +445,32 @@
return mRule4CountOnUpstream.get(ifIndex) != null;
}
+ @Override
+ public boolean addDevMap(int ifIndex) {
+ if (!isInitialized()) return false;
+
+ try {
+ mBpfDevMap.updateEntry(new TetherDevKey(ifIndex), new TetherDevValue(ifIndex));
+ } catch (ErrnoException e) {
+ mLog.e("Could not add interface " + ifIndex + ": " + e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean removeDevMap(int ifIndex) {
+ if (!isInitialized()) return false;
+
+ try {
+ mBpfDevMap.deleteEntry(new TetherDevKey(ifIndex));
+ } catch (ErrnoException e) {
+ mLog.e("Could not delete interface " + ifIndex + ": " + e);
+ return false;
+ }
+ return true;
+ }
+
private String mapStatus(BpfMap m, String name) {
return name + "{" + (m != null ? "OK" : "ERROR") + "}";
}
@@ -444,7 +483,8 @@
mapStatus(mBpfDownstream4Map, "mBpfDownstream4Map"),
mapStatus(mBpfUpstream4Map, "mBpfUpstream4Map"),
mapStatus(mBpfStatsMap, "mBpfStatsMap"),
- mapStatus(mBpfLimitMap, "mBpfLimitMap")
+ mapStatus(mBpfLimitMap, "mBpfLimitMap"),
+ mapStatus(mBpfDevMap, "mBpfDevMap")
});
}
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index 79a628b..8a7a49c 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -166,5 +166,15 @@
* TODO: consider using InterfaceParams to replace interface name.
*/
public abstract boolean detachProgram(@NonNull String iface);
+
+ /**
+ * Add interface index mapping.
+ */
+ public abstract boolean addDevMap(int ifIndex);
+
+ /**
+ * Remove interface index mapping.
+ */
+ public abstract boolean removeDevMap(int ifIndex);
}
diff --git a/Tethering/bpf_progs/offload.c b/Tethering/bpf_progs/offload.c
index 36f6783..6ff370c 100644
--- a/Tethering/bpf_progs/offload.c
+++ b/Tethering/bpf_progs/offload.c
@@ -767,8 +767,7 @@
// ----- XDP Support -----
-DEFINE_BPF_MAP_GRW(tether_xdp_devmap, DEVMAP_HASH, uint32_t, uint32_t, 64,
- AID_NETWORK_STACK)
+DEFINE_BPF_MAP_GRW(tether_dev_map, DEVMAP_HASH, uint32_t, uint32_t, 64, AID_NETWORK_STACK)
static inline __always_inline int do_xdp_forward6(struct xdp_md *ctx, const bool is_ethernet,
const bool downstream) {
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index edd1ebb..105bab1 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -27,6 +27,8 @@
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+ field public static final int CONNECTIVITY_SCOPE_GLOBAL = 1; // 0x1
+ field public static final int CONNECTIVITY_SCOPE_LOCAL = 2; // 0x2
field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
field public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
@@ -72,6 +74,7 @@
public static interface TetheringManager.TetheringEventCallback {
method public default void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>);
method public default void onError(@NonNull String, int);
+ method public default void onLocalOnlyInterfacesChanged(@NonNull java.util.List<java.lang.String>);
method public default void onOffloadStatusChanged(int);
method public default void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
method public default void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
@@ -81,6 +84,7 @@
public static class TetheringManager.TetheringRequest {
method @Nullable public android.net.LinkAddress getClientStaticIpv4Address();
+ method public int getConnectivityScope();
method @Nullable public android.net.LinkAddress getLocalIpv4Address();
method public boolean getShouldShowEntitlementUi();
method public int getTetheringType();
@@ -90,6 +94,7 @@
public static class TetheringManager.TetheringRequest.Builder {
ctor public TetheringManager.TetheringRequest.Builder(int);
method @NonNull public android.net.TetheringManager.TetheringRequest build();
+ method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setConnectivityScope(int);
method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 97fb497..c64da8a 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -557,6 +557,28 @@
}
/**
+ * Indicates that this tethering connection will provide connectivity beyond this device (e.g.,
+ * global Internet access).
+ */
+ public static final int CONNECTIVITY_SCOPE_GLOBAL = 1;
+
+ /**
+ * Indicates that this tethering connection will only provide local connectivity.
+ */
+ public static final int CONNECTIVITY_SCOPE_LOCAL = 2;
+
+ /**
+ * Connectivity scopes for {@link TetheringRequest.Builder#setConnectivityScope}.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "CONNECTIVITY_SCOPE_", value = {
+ CONNECTIVITY_SCOPE_GLOBAL,
+ CONNECTIVITY_SCOPE_LOCAL,
+ })
+ public @interface ConnectivityScope {}
+
+ /**
* Use with {@link #startTethering} to specify additional parameters when starting tethering.
*/
public static class TetheringRequest {
@@ -579,6 +601,7 @@
mBuilderParcel.staticClientAddress = null;
mBuilderParcel.exemptFromEntitlementCheck = false;
mBuilderParcel.showProvisioningUi = true;
+ mBuilderParcel.connectivityScope = getDefaultConnectivityScope(type);
}
/**
@@ -624,7 +647,21 @@
return this;
}
- /** Build {@link TetheringRequest] with the currently set configuration. */
+ /**
+ * Sets the connectivity scope to be provided by this tethering downstream.
+ */
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ @NonNull
+ public Builder setConnectivityScope(@ConnectivityScope int scope) {
+ if (!checkConnectivityScope(mBuilderParcel.tetheringType, scope)) {
+ throw new IllegalArgumentException("Invalid connectivity scope " + scope);
+ }
+
+ mBuilderParcel.connectivityScope = scope;
+ return this;
+ }
+
+ /** Build {@link TetheringRequest} with the currently set configuration. */
@NonNull
public TetheringRequest build() {
return new TetheringRequest(mBuilderParcel);
@@ -655,6 +692,12 @@
return mRequestParcel.tetheringType;
}
+ /** Get connectivity type */
+ @ConnectivityScope
+ public int getConnectivityScope() {
+ return mRequestParcel.connectivityScope;
+ }
+
/** Check if exempt from entitlement check. */
public boolean isExemptFromEntitlementCheck() {
return mRequestParcel.exemptFromEntitlementCheck;
@@ -679,6 +722,26 @@
}
/**
+ * Returns the default connectivity scope for the given tethering type. Usually this is
+ * CONNECTIVITY_SCOPE_GLOBAL, except for NCM which for historical reasons defaults to local.
+ * @hide
+ */
+ public static @ConnectivityScope int getDefaultConnectivityScope(int tetheringType) {
+ return tetheringType != TETHERING_NCM
+ ? CONNECTIVITY_SCOPE_GLOBAL
+ : CONNECTIVITY_SCOPE_LOCAL;
+ }
+
+ /**
+ * Checks whether the requested connectivity scope is allowed.
+ * @hide
+ */
+ private static boolean checkConnectivityScope(int type, int scope) {
+ if (scope == CONNECTIVITY_SCOPE_GLOBAL) return true;
+ return type == TETHERING_USB || type == TETHERING_ETHERNET || type == TETHERING_NCM;
+ }
+
+ /**
* Get a TetheringRequestParcel from the configuration
* @hide
*/
@@ -940,6 +1003,15 @@
default void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {}
/**
+ * Called when there was a change in the list of local-only interfaces.
+ *
+ * <p>This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The list of 0 or more String of active local-only interface names.
+ */
+ default void onLocalOnlyInterfacesChanged(@NonNull List<String> interfaces) {}
+
+ /**
* Called when an error occurred configuring tethering.
*
* <p>This will be called immediately after the callback is registered if the latest status
@@ -1045,6 +1117,7 @@
private final HashMap<String, Integer> mErrorStates = new HashMap<>();
private String[] mLastTetherableInterfaces = null;
private String[] mLastTetheredInterfaces = null;
+ private String[] mLastLocalOnlyInterfaces = null;
@Override
public void onUpstreamChanged(Network network) throws RemoteException {
@@ -1082,6 +1155,14 @@
Collections.unmodifiableList(Arrays.asList(mLastTetheredInterfaces)));
}
+ private synchronized void maybeSendLocalOnlyIfacesChangedCallback(
+ final TetherStatesParcel newStates) {
+ if (Arrays.equals(mLastLocalOnlyInterfaces, newStates.localOnlyList)) return;
+ mLastLocalOnlyInterfaces = newStates.localOnlyList.clone();
+ callback.onLocalOnlyInterfacesChanged(
+ Collections.unmodifiableList(Arrays.asList(mLastLocalOnlyInterfaces)));
+ }
+
// Called immediately after the callbacks are registered.
@Override
public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
@@ -1092,6 +1173,7 @@
sendRegexpsChanged(parcel.config);
maybeSendTetherableIfacesChangedCallback(parcel.states);
maybeSendTetheredIfacesChangedCallback(parcel.states);
+ maybeSendLocalOnlyIfacesChangedCallback(parcel.states);
callback.onClientsChanged(parcel.tetheredClients);
callback.onOffloadStatusChanged(parcel.offloadStatus);
});
@@ -1122,6 +1204,7 @@
sendErrorCallbacks(states);
maybeSendTetherableIfacesChangedCallback(states);
maybeSendTetheredIfacesChangedCallback(states);
+ maybeSendLocalOnlyIfacesChangedCallback(states);
});
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
index c0280d3..f13c970 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
@@ -28,4 +28,5 @@
LinkAddress staticClientAddress;
boolean exemptFromEntitlementCheck;
boolean showProvisioningUi;
+ int connectivityScope;
}
diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
index 308dfb9..1611f9d 100644
--- a/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
+++ b/Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
@@ -36,6 +36,9 @@
// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c.
#define CLS_BPF_NAME_LEN 256
+// Classifier name. See cls_bpf_ops in net/sched/cls_bpf.c.
+#define CLS_BPF_KIND_NAME "bpf"
+
namespace android {
// Sync from system/netd/server/NetlinkCommands.h
const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
@@ -205,9 +208,9 @@
tcmsg t;
struct {
nlattr attr;
- // The maximum classifier name length is defined as IFNAMSIZ.
- // See tcf_proto_ops in include/net/sch_generic.h.
- char str[NLMSG_ALIGN(IFNAMSIZ)];
+ // The maximum classifier name length is defined in
+ // tcf_proto_ops in include/net/sch_generic.h.
+ char str[NLMSG_ALIGN(sizeof(CLS_BPF_KIND_NAME))];
} kind;
struct {
nlattr attr;
@@ -248,8 +251,7 @@
.nla_len = sizeof(req.kind),
.nla_type = TCA_KIND,
},
- // Classifier name. See cls_bpf_ops in net/sched/cls_bpf.c.
- .str = "bpf",
+ .str = CLS_BPF_KIND_NAME,
},
.options =
{
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index da15fa8..c45ce83 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -1426,7 +1426,7 @@
break;
case CMD_INTERFACE_DOWN:
transitionTo(mUnavailableState);
- mLog.i("Untethered (interface down) and restarting" + mIfaceName);
+ mLog.i("Untethered (interface down) and restarting " + mIfaceName);
mCallback.requestEnableTethering(mInterfaceType, true /* enabled */);
break;
default:
diff --git a/Tethering/src/android/net/util/TetheringUtils.java b/Tethering/src/android/net/util/TetheringUtils.java
index 9e7cc2f..29900d9 100644
--- a/Tethering/src/android/net/util/TetheringUtils.java
+++ b/Tethering/src/android/net/util/TetheringUtils.java
@@ -162,7 +162,8 @@
&& Objects.equals(request.localIPv4Address, otherRequest.localIPv4Address)
&& Objects.equals(request.staticClientAddress, otherRequest.staticClientAddress)
&& request.exemptFromEntitlementCheck == otherRequest.exemptFromEntitlementCheck
- && request.showProvisioningUi == otherRequest.showProvisioningUi;
+ && request.showProvisioningUi == otherRequest.showProvisioningUi
+ && request.connectivityScope == otherRequest.connectivityScope;
}
/** Get inet6 address for all nodes given scope ID. */
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index add4f37..8adcbd9 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -47,7 +47,6 @@
import android.net.util.InterfaceParams;
import android.net.util.SharedLog;
import android.net.util.TetheringUtils.ForwardedStats;
-import android.os.ConditionVariable;
import android.os.Handler;
import android.system.ErrnoException;
import android.text.TextUtils;
@@ -73,6 +72,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -105,6 +105,7 @@
private static final String TETHER_STATS_MAP_PATH = makeMapPath("stats");
private static final String TETHER_LIMIT_MAP_PATH = makeMapPath("limit");
private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error");
+ private static final String TETHER_DEV_MAP_PATH = makeMapPath("dev");
/** The names of all the BPF counters defined in bpf_tethering.h. */
public static final String[] sBpfCounterNames = getBpfCounterNames();
@@ -221,6 +222,11 @@
// Map for upstream and downstream pair.
private final HashMap<String, HashSet<String>> mForwardingPairs = new HashMap<>();
+ // Set for upstream and downstream device map. Used for caching BPF dev map status and
+ // reduce duplicate adding or removing map operations. Use LinkedHashSet because the test
+ // BpfCoordinatorTest needs predictable iteration order.
+ private final Set<Integer> mDeviceMapSet = new LinkedHashSet<>();
+
// Runnable that used by scheduling next polling of stats.
private final Runnable mScheduledPollingTask = () -> {
updateForwardedStats();
@@ -337,6 +343,18 @@
return null;
}
}
+
+ /** Get dev BPF map. */
+ @Nullable public BpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
+ if (!isAtLeastS()) return null;
+ try {
+ return new BpfMap<>(TETHER_DEV_MAP_PATH,
+ BpfMap.BPF_F_RDWR, TetherDevKey.class, TetherDevValue.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create dev map: " + e);
+ return null;
+ }
+ }
}
@VisibleForTesting
@@ -491,6 +509,9 @@
}
LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer);
+ // Add upstream and downstream interface index to dev map.
+ maybeAddDevMap(rule.upstreamIfindex, rule.downstreamIfindex);
+
// When the first rule is added to an upstream, setup upstream forwarding and data limit.
maybeSetLimit(rule.upstreamIfindex);
@@ -735,43 +756,40 @@
* be allowed to be accessed on the handler thread.
*/
public void dump(@NonNull IndentingPrintWriter pw) {
- final ConditionVariable dumpDone = new ConditionVariable();
- mHandler.post(() -> {
- pw.println("mIsBpfEnabled: " + mIsBpfEnabled);
- pw.println("Polling " + (mPollingStarted ? "started" : "not started"));
- pw.println("Stats provider " + (mStatsProvider != null
- ? "registered" : "not registered"));
- pw.println("Upstream quota: " + mInterfaceQuotas.toString());
- pw.println("Polling interval: " + getPollingInterval() + " ms");
- pw.println("Bpf shim: " + mBpfCoordinatorShim.toString());
+ pw.println("mIsBpfEnabled: " + mIsBpfEnabled);
+ pw.println("Polling " + (mPollingStarted ? "started" : "not started"));
+ pw.println("Stats provider " + (mStatsProvider != null
+ ? "registered" : "not registered"));
+ pw.println("Upstream quota: " + mInterfaceQuotas.toString());
+ pw.println("Polling interval: " + getPollingInterval() + " ms");
+ pw.println("Bpf shim: " + mBpfCoordinatorShim.toString());
- pw.println("Forwarding stats:");
- pw.increaseIndent();
- if (mStats.size() == 0) {
- pw.println("<empty>");
- } else {
- dumpStats(pw);
- }
- pw.decreaseIndent();
-
- pw.println("Forwarding rules:");
- pw.increaseIndent();
- dumpIpv6UpstreamRules(pw);
- dumpIpv6ForwardingRules(pw);
- dumpIpv4ForwardingRules(pw);
- pw.decreaseIndent();
-
- pw.println();
- pw.println("Forwarding counters:");
- pw.increaseIndent();
- dumpCounters(pw);
- pw.decreaseIndent();
-
- dumpDone.open();
- });
- if (!dumpDone.block(DUMP_TIMEOUT_MS)) {
- pw.println("... dump timed-out after " + DUMP_TIMEOUT_MS + "ms");
+ pw.println("Forwarding stats:");
+ pw.increaseIndent();
+ if (mStats.size() == 0) {
+ pw.println("<empty>");
+ } else {
+ dumpStats(pw);
}
+ pw.decreaseIndent();
+
+ pw.println("Forwarding rules:");
+ pw.increaseIndent();
+ dumpIpv6UpstreamRules(pw);
+ dumpIpv6ForwardingRules(pw);
+ dumpIpv4ForwardingRules(pw);
+ pw.decreaseIndent();
+
+ pw.println("Device map:");
+ pw.increaseIndent();
+ dumpDevmap(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Forwarding counters:");
+ pw.increaseIndent();
+ dumpCounters(pw);
+ pw.decreaseIndent();
}
private void dumpStats(@NonNull IndentingPrintWriter pw) {
@@ -899,6 +917,31 @@
}
}
+ private void dumpDevmap(@NonNull IndentingPrintWriter pw) {
+ try (BpfMap<TetherDevKey, TetherDevValue> map = mDeps.getBpfDevMap()) {
+ if (map == null) {
+ pw.println("No devmap support");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No interface index");
+ return;
+ }
+ pw.println("ifindex (iface) -> ifindex (iface)");
+ pw.increaseIndent();
+ map.forEach((k, v) -> {
+ // Only get upstream interface name. Just do the best to make the index readable.
+ // TODO: get downstream interface name because the index is either upstrema or
+ // downstream interface in dev map.
+ pw.println(String.format("%d (%s) -> %d (%s)", k.ifIndex, getIfName(k.ifIndex),
+ v.ifIndex, getIfName(v.ifIndex)));
+ });
+ } catch (ErrnoException e) {
+ pw.println("Error dumping dev map: " + e);
+ }
+ pw.decreaseIndent();
+ }
+
/** IPv6 forwarding rule class. */
public static class Ipv6ForwardingRule {
// The upstream6 and downstream6 rules are built as the following tables. Only raw ip
@@ -1238,6 +1281,7 @@
final Tether4Value downstream4Value = makeTetherDownstream4Value(e, tetherClient,
upstreamIndex);
+ maybeAddDevMap(upstreamIndex, tetherClient.downstreamIfindex);
maybeSetLimit(upstreamIndex);
mBpfCoordinatorShim.tetherOffloadRuleAdd(UPSTREAM, upstream4Key, upstream4Value);
mBpfCoordinatorShim.tetherOffloadRuleAdd(DOWNSTREAM, downstream4Key, downstream4Value);
@@ -1366,6 +1410,15 @@
return false;
}
+ // TODO: remove the index from map while the interface has been removed because the map size
+ // is 64 entries. See packages\modules\Connectivity\Tethering\bpf_progs\offload.c.
+ private void maybeAddDevMap(int upstreamIfindex, int downstreamIfindex) {
+ for (Integer index : new Integer[] {upstreamIfindex, downstreamIfindex}) {
+ if (mDeviceMapSet.contains(index)) continue;
+ if (mBpfCoordinatorShim.addDevMap(index)) mDeviceMapSet.add(index);
+ }
+ }
+
private void forwardingPairAdd(@NonNull String intIface, @NonNull String extIface) {
if (!mForwardingPairs.containsKey(extIface)) {
mForwardingPairs.put(extIface, new HashSet<String>());
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index b1e3cfe..60fcfd0 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -41,7 +41,6 @@
import android.content.IntentFilter;
import android.net.util.SharedLog;
import android.os.Bundle;
-import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Parcel;
import android.os.PersistableBundle;
@@ -516,25 +515,18 @@
* @param pw {@link PrintWriter} is used to print formatted
*/
public void dump(PrintWriter pw) {
- final ConditionVariable mWaiting = new ConditionVariable();
- mHandler.post(() -> {
- pw.print("isCellularUpstreamPermitted: ");
- pw.println(isCellularUpstreamPermitted());
- for (int type = mCurrentDownstreams.nextSetBit(0); type >= 0;
- type = mCurrentDownstreams.nextSetBit(type + 1)) {
- pw.print("Type: ");
- pw.print(typeString(type));
- if (mCurrentEntitlementResults.indexOfKey(type) > -1) {
- pw.print(", Value: ");
- pw.println(errorString(mCurrentEntitlementResults.get(type)));
- } else {
- pw.println(", Value: empty");
- }
+ pw.print("isCellularUpstreamPermitted: ");
+ pw.println(isCellularUpstreamPermitted());
+ for (int type = mCurrentDownstreams.nextSetBit(0); type >= 0;
+ type = mCurrentDownstreams.nextSetBit(type + 1)) {
+ pw.print("Type: ");
+ pw.print(typeString(type));
+ if (mCurrentEntitlementResults.indexOfKey(type) > -1) {
+ pw.print(", Value: ");
+ pw.println(errorString(mCurrentEntitlementResults.get(type)));
+ } else {
+ pw.println(", Value: empty");
}
- mWaiting.open();
- });
- if (!mWaiting.block(DUMP_TIMEOUT)) {
- pw.println("... dump timed out after " + DUMP_TIMEOUT + "ms");
}
pw.print("Exempted: [");
for (int type = mExemptedDownstreams.nextSetBit(0); type >= 0;
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java b/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java
new file mode 100644
index 0000000..4283c1b
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 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.tethering;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/** The key of BpfMap which is used for mapping interface index. */
+public class TetherDevKey extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long ifIndex; // interface index
+
+ public TetherDevKey(final long ifIndex) {
+ this.ifIndex = ifIndex;
+ }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java b/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java
new file mode 100644
index 0000000..1cd99b5
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 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.tethering;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/** The key of BpfMap which is used for mapping interface index. */
+public class TetherDevValue extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long ifIndex; // interface index
+
+ public TetherDevValue(final long ifIndex) {
+ this.ifIndex = ifIndex;
+ }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index f795747..0e8b2b5 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -29,6 +29,7 @@
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
@@ -63,6 +64,7 @@
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
+import static com.android.networkstack.tethering.UpstreamNetworkMonitor.isCellular;
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
@@ -90,6 +92,7 @@
import android.net.TetheredClient;
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
+import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringRequestParcel;
import android.net.ip.IpServer;
import android.net.shared.NetdUtils;
@@ -125,7 +128,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.MessageUtils;
@@ -144,8 +146,11 @@
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
/**
*
@@ -163,6 +168,9 @@
};
private static final SparseArray<String> sMagicDecoderRing =
MessageUtils.findMessageNames(sMessageClasses);
+
+ private static final int DUMP_TIMEOUT_MS = 10_000;
+
// Keep in sync with NETID_UNSET in system/netd/include/netid_client.h
private static final int NETID_UNSET = 0;
@@ -209,9 +217,6 @@
private final SparseArray<TetheringRequestParcel> mActiveTetheringRequests =
new SparseArray<>();
- // used to synchronize public access to members
- // TODO(b/153621704): remove mPublicSync to make Tethering lock free
- private final Object mPublicSync;
private final Context mContext;
private final ArrayMap<String, TetherState> mTetherStates;
private final BroadcastReceiver mStateReceiver;
@@ -237,8 +242,6 @@
private final BpfCoordinator mBpfCoordinator;
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
- // All the usage of mTetheringEventCallback should run in the same thread.
- private ITetheringEventCallback mTetheringEventCallback = null;
private volatile TetheringConfiguration mConfig;
private InterfaceSet mCurrentUpstreamIfaceSet;
@@ -252,11 +255,8 @@
private String mWifiP2pTetherInterface = null;
private int mOffloadStatus = TETHER_HARDWARE_OFFLOAD_STOPPED;
- @GuardedBy("mPublicSync")
private EthernetManager.TetheredInterfaceRequest mEthernetIfaceRequest;
- @GuardedBy("mPublicSync")
private String mConfiguredEthernetIface;
- @GuardedBy("mPublicSync")
private EthernetCallback mEthernetCallback;
public Tethering(TetheringDependencies deps) {
@@ -267,8 +267,6 @@
mLooper = mDeps.getTetheringLooper();
mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper);
- mPublicSync = new Object();
-
mTetherStates = new ArrayMap<>();
mConnectedClientsTracker = new ConnectedClientsTracker();
@@ -495,20 +493,18 @@
// Never called directly: only called from interfaceLinkStateChanged.
// See NetlinkHandler.cpp: notifyInterfaceChanged.
if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
- synchronized (mPublicSync) {
- if (up) {
- maybeTrackNewInterfaceLocked(iface);
+ if (up) {
+ maybeTrackNewInterfaceLocked(iface);
+ } else {
+ if (ifaceNameToType(iface) == TETHERING_BLUETOOTH
+ || ifaceNameToType(iface) == TETHERING_WIGIG) {
+ stopTrackingInterfaceLocked(iface);
} else {
- if (ifaceNameToType(iface) == TETHERING_BLUETOOTH
- || ifaceNameToType(iface) == TETHERING_WIGIG) {
- stopTrackingInterfaceLocked(iface);
- } else {
- // Ignore usb0 down after enabling RNDIS.
- // We will handle disconnect in interfaceRemoved.
- // Similarly, ignore interface down for WiFi. We monitor WiFi AP status
- // through the WifiManager.WIFI_AP_STATE_CHANGED_ACTION intent.
- if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
- }
+ // Ignore usb0 down after enabling RNDIS.
+ // We will handle disconnect in interfaceRemoved.
+ // Similarly, ignore interface down for WiFi. We monitor WiFi AP status
+ // through the WifiManager.WIFI_AP_STATE_CHANGED_ACTION intent.
+ if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
}
}
}
@@ -538,16 +534,12 @@
void interfaceAdded(String iface) {
if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
- synchronized (mPublicSync) {
- maybeTrackNewInterfaceLocked(iface);
- }
+ maybeTrackNewInterfaceLocked(iface);
}
void interfaceRemoved(String iface) {
if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
- synchronized (mPublicSync) {
- stopTrackingInterfaceLocked(iface);
- }
+ stopTrackingInterfaceLocked(iface);
}
void startTethering(final TetheringRequestParcel request, final IIntResultListener listener) {
@@ -632,17 +624,15 @@
private int setWifiTethering(final boolean enable) {
final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mPublicSync) {
- final WifiManager mgr = getWifiManager();
- if (mgr == null) {
- mLog.e("setWifiTethering: failed to get WifiManager!");
- return TETHER_ERROR_SERVICE_UNAVAIL;
- }
- if ((enable && mgr.startTetheredHotspot(null /* use existing softap config */))
- || (!enable && mgr.stopSoftAp())) {
- mWifiTetherRequested = enable;
- return TETHER_ERROR_NO_ERROR;
- }
+ final WifiManager mgr = getWifiManager();
+ if (mgr == null) {
+ mLog.e("setWifiTethering: failed to get WifiManager!");
+ return TETHER_ERROR_SERVICE_UNAVAIL;
+ }
+ if ((enable && mgr.startTetheredHotspot(null /* use existing softap config */))
+ || (!enable && mgr.stopSoftAp())) {
+ mWifiTetherRequested = enable;
+ return TETHER_ERROR_NO_ERROR;
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -694,18 +684,16 @@
private int setEthernetTethering(final boolean enable) {
final EthernetManager em = (EthernetManager) mContext.getSystemService(
Context.ETHERNET_SERVICE);
- synchronized (mPublicSync) {
- if (enable) {
- if (mEthernetCallback != null) {
- Log.d(TAG, "Ethernet tethering already started");
- return TETHER_ERROR_NO_ERROR;
- }
-
- mEthernetCallback = new EthernetCallback();
- mEthernetIfaceRequest = em.requestTetheredInterface(mExecutor, mEthernetCallback);
- } else {
- stopEthernetTetheringLocked();
+ if (enable) {
+ if (mEthernetCallback != null) {
+ Log.d(TAG, "Ethernet tethering already started");
+ return TETHER_ERROR_NO_ERROR;
}
+
+ mEthernetCallback = new EthernetCallback();
+ mEthernetIfaceRequest = em.requestTetheredInterface(mExecutor, mEthernetCallback);
+ } else {
+ stopEthernetTetheringLocked();
}
return TETHER_ERROR_NO_ERROR;
}
@@ -725,78 +713,82 @@
private class EthernetCallback implements EthernetManager.TetheredInterfaceCallback {
@Override
public void onAvailable(String iface) {
- synchronized (mPublicSync) {
- if (this != mEthernetCallback) {
- // Ethernet callback arrived after Ethernet tethering stopped. Ignore.
- return;
- }
- maybeTrackNewInterfaceLocked(iface, TETHERING_ETHERNET);
- changeInterfaceState(iface, IpServer.STATE_TETHERED);
- mConfiguredEthernetIface = iface;
+ if (this != mEthernetCallback) {
+ // Ethernet callback arrived after Ethernet tethering stopped. Ignore.
+ return;
}
+ maybeTrackNewInterfaceLocked(iface, TETHERING_ETHERNET);
+ changeInterfaceState(iface, getRequestedState(TETHERING_ETHERNET));
+ mConfiguredEthernetIface = iface;
}
@Override
public void onUnavailable() {
- synchronized (mPublicSync) {
- if (this != mEthernetCallback) {
- // onAvailable called after stopping Ethernet tethering.
- return;
- }
- stopEthernetTetheringLocked();
+ if (this != mEthernetCallback) {
+ // onAvailable called after stopping Ethernet tethering.
+ return;
}
+ stopEthernetTetheringLocked();
}
}
- int tether(String iface) {
- return tether(iface, IpServer.STATE_TETHERED);
+ void tether(String iface, int requestedState, final IIntResultListener listener) {
+ mHandler.post(() -> {
+ try {
+ listener.onResult(tether(iface, requestedState));
+ } catch (RemoteException e) { }
+ });
}
private int tether(String iface, int requestedState) {
if (DBG) Log.d(TAG, "Tethering " + iface);
- synchronized (mPublicSync) {
- TetherState tetherState = mTetherStates.get(iface);
- if (tetherState == null) {
- Log.e(TAG, "Tried to Tether an unknown iface: " + iface + ", ignoring");
- return TETHER_ERROR_UNKNOWN_IFACE;
- }
- // Ignore the error status of the interface. If the interface is available,
- // the errors are referring to past tethering attempts anyway.
- if (tetherState.lastState != IpServer.STATE_AVAILABLE) {
- Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring");
- return TETHER_ERROR_UNAVAIL_IFACE;
- }
- // NOTE: If a CMD_TETHER_REQUESTED message is already in the TISM's queue but not yet
- // processed, this will be a no-op and it will not return an error.
- //
- // This code cannot race with untether() because they both synchronize on mPublicSync.
- // TODO: reexamine the threading and messaging model to totally remove mPublicSync.
- final int type = tetherState.ipServer.interfaceType();
- final TetheringRequestParcel request = mActiveTetheringRequests.get(type, null);
- if (request != null) {
- mActiveTetheringRequests.delete(type);
- }
- tetherState.ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, requestedState, 0,
- request);
- return TETHER_ERROR_NO_ERROR;
+ TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState == null) {
+ Log.e(TAG, "Tried to Tether an unknown iface: " + iface + ", ignoring");
+ return TETHER_ERROR_UNKNOWN_IFACE;
}
+ // Ignore the error status of the interface. If the interface is available,
+ // the errors are referring to past tethering attempts anyway.
+ if (tetherState.lastState != IpServer.STATE_AVAILABLE) {
+ Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring");
+ return TETHER_ERROR_UNAVAIL_IFACE;
+ }
+ // NOTE: If a CMD_TETHER_REQUESTED message is already in the TISM's queue but not yet
+ // processed, this will be a no-op and it will not return an error.
+ //
+ // This code cannot race with untether() because they both run on the handler thread.
+ final int type = tetherState.ipServer.interfaceType();
+ final TetheringRequestParcel request = mActiveTetheringRequests.get(type, null);
+ if (request != null) {
+ mActiveTetheringRequests.delete(type);
+ }
+ tetherState.ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, requestedState, 0,
+ request);
+ return TETHER_ERROR_NO_ERROR;
+ }
+
+ void untether(String iface, final IIntResultListener listener) {
+ mHandler.post(() -> {
+ try {
+ listener.onResult(untether(iface));
+ } catch (RemoteException e) {
+ }
+ });
}
int untether(String iface) {
if (DBG) Log.d(TAG, "Untethering " + iface);
- synchronized (mPublicSync) {
- TetherState tetherState = mTetherStates.get(iface);
- if (tetherState == null) {
- Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
- return TETHER_ERROR_UNKNOWN_IFACE;
- }
- if (!tetherState.isCurrentlyServing()) {
- Log.e(TAG, "Tried to untether an inactive iface :" + iface + ", ignoring");
- return TETHER_ERROR_UNAVAIL_IFACE;
- }
- tetherState.ipServer.sendMessage(IpServer.CMD_TETHER_UNREQUESTED);
- return TETHER_ERROR_NO_ERROR;
+ TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState == null) {
+ Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
+ return TETHER_ERROR_UNKNOWN_IFACE;
}
+ if (!tetherState.isCurrentlyServing()) {
+ Log.e(TAG, "Tried to untether an inactive iface :" + iface + ", ignoring");
+ return TETHER_ERROR_UNAVAIL_IFACE;
+ }
+ tetherState.ipServer.sendMessage(IpServer.CMD_TETHER_UNREQUESTED);
+ return TETHER_ERROR_NO_ERROR;
}
void untetherAll() {
@@ -807,16 +799,15 @@
stopTethering(TETHERING_ETHERNET);
}
- int getLastTetherError(String iface) {
- synchronized (mPublicSync) {
- TetherState tetherState = mTetherStates.get(iface);
- if (tetherState == null) {
- Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface
- + ", ignoring");
- return TETHER_ERROR_UNKNOWN_IFACE;
- }
- return tetherState.lastError;
+ @VisibleForTesting
+ int getLastErrorForTest(String iface) {
+ TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState == null) {
+ Log.e(TAG, "Tried to getLastErrorForTest on an unknown iface :" + iface
+ + ", ignoring");
+ return TETHER_ERROR_UNKNOWN_IFACE;
}
+ return tetherState.lastError;
}
private boolean isProvisioningNeededButUnavailable() {
@@ -843,6 +834,22 @@
return true;
}
+ private int getRequestedState(int type) {
+ final TetheringRequestParcel request = mActiveTetheringRequests.get(type);
+
+ // The request could have been deleted before we had a chance to complete it.
+ // If so, assume that the scope is the default scope for this tethering type.
+ // This likely doesn't matter - if the request has been deleted, then tethering is
+ // likely going to be stopped soon anyway.
+ final int connectivityScope = (request != null)
+ ? request.connectivityScope
+ : TetheringRequest.getDefaultConnectivityScope(type);
+
+ return connectivityScope == CONNECTIVITY_SCOPE_LOCAL
+ ? IpServer.STATE_LOCAL_ONLY
+ : IpServer.STATE_TETHERED;
+ }
+
// TODO: Figure out how to update for local hotspot mode interfaces.
private void sendTetherStateChangedBroadcast() {
if (!isTetheringSupported()) return;
@@ -857,27 +864,25 @@
mTetherStatesParcel = new TetherStatesParcel();
int downstreamTypesMask = DOWNSTREAM_NONE;
- synchronized (mPublicSync) {
- for (int i = 0; i < mTetherStates.size(); i++) {
- TetherState tetherState = mTetherStates.valueAt(i);
- String iface = mTetherStates.keyAt(i);
- if (tetherState.lastError != TETHER_ERROR_NO_ERROR) {
- erroredList.add(iface);
- lastErrorList.add(tetherState.lastError);
- } else if (tetherState.lastState == IpServer.STATE_AVAILABLE) {
- availableList.add(iface);
- } else if (tetherState.lastState == IpServer.STATE_LOCAL_ONLY) {
- localOnlyList.add(iface);
- } else if (tetherState.lastState == IpServer.STATE_TETHERED) {
- if (cfg.isUsb(iface)) {
- downstreamTypesMask |= (1 << TETHERING_USB);
- } else if (cfg.isWifi(iface)) {
- downstreamTypesMask |= (1 << TETHERING_WIFI);
- } else if (cfg.isBluetooth(iface)) {
- downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
- }
- tetherList.add(iface);
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherState tetherState = mTetherStates.valueAt(i);
+ String iface = mTetherStates.keyAt(i);
+ if (tetherState.lastError != TETHER_ERROR_NO_ERROR) {
+ erroredList.add(iface);
+ lastErrorList.add(tetherState.lastError);
+ } else if (tetherState.lastState == IpServer.STATE_AVAILABLE) {
+ availableList.add(iface);
+ } else if (tetherState.lastState == IpServer.STATE_LOCAL_ONLY) {
+ localOnlyList.add(iface);
+ } else if (tetherState.lastState == IpServer.STATE_TETHERED) {
+ if (cfg.isUsb(iface)) {
+ downstreamTypesMask |= (1 << TETHERING_USB);
+ } else if (cfg.isWifi(iface)) {
+ downstreamTypesMask |= (1 << TETHERING_WIFI);
+ } else if (cfg.isBluetooth(iface)) {
+ downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
}
+ tetherList.add(iface);
}
}
@@ -975,19 +980,19 @@
// functions are ready to use.
//
// For more explanation, see b/62552150 .
- synchronized (Tethering.this.mPublicSync) {
- if (!usbConnected && mRndisEnabled) {
- // Turn off tethering if it was enabled and there is a disconnect.
- tetherMatchingInterfaces(IpServer.STATE_AVAILABLE, TETHERING_USB);
- mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_USB);
- } else if (usbConfigured && rndisEnabled) {
- // Tether if rndis is enabled and usb is configured.
- tetherMatchingInterfaces(IpServer.STATE_TETHERED, TETHERING_USB);
- } else if (usbConnected && ncmEnabled) {
- tetherMatchingInterfaces(IpServer.STATE_LOCAL_ONLY, TETHERING_NCM);
- }
- mRndisEnabled = usbConfigured && rndisEnabled;
+ if (!usbConnected && mRndisEnabled) {
+ // Turn off tethering if it was enabled and there is a disconnect.
+ tetherMatchingInterfaces(IpServer.STATE_AVAILABLE, TETHERING_USB);
+ mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_USB);
+ } else if (usbConfigured && rndisEnabled) {
+ // Tether if rndis is enabled and usb is configured.
+ final int state = getRequestedState(TETHERING_USB);
+ tetherMatchingInterfaces(state, TETHERING_USB);
+ } else if (usbConnected && ncmEnabled) {
+ final int state = getRequestedState(TETHERING_NCM);
+ tetherMatchingInterfaces(state, TETHERING_NCM);
}
+ mRndisEnabled = usbConfigured && rndisEnabled;
}
private void handleWifiApAction(Intent intent) {
@@ -995,23 +1000,21 @@
final String ifname = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME);
final int ipmode = intent.getIntExtra(EXTRA_WIFI_AP_MODE, IFACE_IP_MODE_UNSPECIFIED);
- synchronized (Tethering.this.mPublicSync) {
- switch (curState) {
- case WifiManager.WIFI_AP_STATE_ENABLING:
- // We can see this state on the way to both enabled and failure states.
- break;
- case WifiManager.WIFI_AP_STATE_ENABLED:
- enableWifiIpServingLocked(ifname, ipmode);
- break;
- case WifiManager.WIFI_AP_STATE_DISABLING:
- // We can see this state on the way to disabled.
- break;
- case WifiManager.WIFI_AP_STATE_DISABLED:
- case WifiManager.WIFI_AP_STATE_FAILED:
- default:
- disableWifiIpServingLocked(ifname, curState);
- break;
- }
+ switch (curState) {
+ case WifiManager.WIFI_AP_STATE_ENABLING:
+ // We can see this state on the way to both enabled and failure states.
+ break;
+ case WifiManager.WIFI_AP_STATE_ENABLED:
+ enableWifiIpServingLocked(ifname, ipmode);
+ break;
+ case WifiManager.WIFI_AP_STATE_DISABLING:
+ // We can see this state on the way to disabled.
+ break;
+ case WifiManager.WIFI_AP_STATE_DISABLED:
+ case WifiManager.WIFI_AP_STATE_FAILED:
+ default:
+ disableWifiIpServingLocked(ifname, curState);
+ break;
}
}
@@ -1032,32 +1035,30 @@
Log.d(TAG, "WifiP2pAction: P2pInfo: " + p2pInfo + " Group: " + group);
}
- synchronized (Tethering.this.mPublicSync) {
- // if no group is formed, bring it down if needed.
- if (p2pInfo == null || !p2pInfo.groupFormed) {
- disableWifiP2pIpServingLockedIfNeeded(mWifiP2pTetherInterface);
- mWifiP2pTetherInterface = null;
- return;
- }
-
- // If there is a group but the device is not the owner, bail out.
- if (!isGroupOwner(group)) return;
-
- // If already serving from the correct interface, nothing to do.
- if (group.getInterface().equals(mWifiP2pTetherInterface)) return;
-
- // If already serving from another interface, turn it down first.
- if (!TextUtils.isEmpty(mWifiP2pTetherInterface)) {
- mLog.w("P2P tethered interface " + mWifiP2pTetherInterface
- + "is different from current interface "
- + group.getInterface() + ", re-tether it");
- disableWifiP2pIpServingLockedIfNeeded(mWifiP2pTetherInterface);
- }
-
- // Finally bring up serving on the new interface
- mWifiP2pTetherInterface = group.getInterface();
- enableWifiIpServingLocked(mWifiP2pTetherInterface, IFACE_IP_MODE_LOCAL_ONLY);
+ // if no group is formed, bring it down if needed.
+ if (p2pInfo == null || !p2pInfo.groupFormed) {
+ disableWifiP2pIpServingLockedIfNeeded(mWifiP2pTetherInterface);
+ mWifiP2pTetherInterface = null;
+ return;
}
+
+ // If there is a group but the device is not the owner, bail out.
+ if (!isGroupOwner(group)) return;
+
+ // If already serving from the correct interface, nothing to do.
+ if (group.getInterface().equals(mWifiP2pTetherInterface)) return;
+
+ // If already serving from another interface, turn it down first.
+ if (!TextUtils.isEmpty(mWifiP2pTetherInterface)) {
+ mLog.w("P2P tethered interface " + mWifiP2pTetherInterface
+ + "is different from current interface "
+ + group.getInterface() + ", re-tether it");
+ disableWifiP2pIpServingLockedIfNeeded(mWifiP2pTetherInterface);
+ }
+
+ // Finally bring up serving on the new interface
+ mWifiP2pTetherInterface = group.getInterface();
+ enableWifiIpServingLocked(mWifiP2pTetherInterface, IFACE_IP_MODE_LOCAL_ONLY);
}
private void handleUserRestrictionAction() {
@@ -1092,14 +1093,14 @@
@VisibleForTesting
protected static class UserRestrictionActionListener {
private final UserManager mUserMgr;
- private final Tethering mWrapper;
+ private final Tethering mTethering;
private final TetheringNotificationUpdater mNotificationUpdater;
public boolean mDisallowTethering;
- public UserRestrictionActionListener(@NonNull UserManager um, @NonNull Tethering wrapper,
+ public UserRestrictionActionListener(@NonNull UserManager um, @NonNull Tethering tethering,
@NonNull TetheringNotificationUpdater updater) {
mUserMgr = um;
- mWrapper = wrapper;
+ mTethering = tethering;
mNotificationUpdater = updater;
mDisallowTethering = false;
}
@@ -1126,13 +1127,13 @@
return;
}
- if (mWrapper.isTetheringActive()) {
+ if (mTethering.isTetheringActive()) {
// Restricted notification is shown when tethering function is disallowed on
// user's device.
mNotificationUpdater.notifyTetheringDisabledByRestriction();
// Untether from all downstreams since tethering is disallowed.
- mWrapper.untetherAll();
+ mTethering.untetherAll();
}
// TODO(b/148139325): send tetheringSupported on restriction change
}
@@ -1279,86 +1280,51 @@
return hasDownstreamConfiguration && hasUpstreamConfiguration;
}
- // TODO - update callers to use getTetheringConfiguration(),
- // which has only final members.
- String[] getTetherableUsbRegexs() {
- return copy(mConfig.tetherableUsbRegexs);
+ void setUsbTethering(boolean enable, IIntResultListener listener) {
+ mHandler.post(() -> {
+ try {
+ listener.onResult(setUsbTethering(enable));
+ } catch (RemoteException e) { }
+ });
}
- String[] getTetherableWifiRegexs() {
- return copy(mConfig.tetherableWifiRegexs);
- }
-
- String[] getTetherableBluetoothRegexs() {
- return copy(mConfig.tetherableBluetoothRegexs);
- }
-
- int setUsbTethering(boolean enable) {
+ private int setUsbTethering(boolean enable) {
if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")");
UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
if (usbManager == null) {
mLog.e("setUsbTethering: failed to get UsbManager!");
return TETHER_ERROR_SERVICE_UNAVAIL;
}
-
- synchronized (mPublicSync) {
- usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_RNDIS
- : UsbManager.FUNCTION_NONE);
- }
+ usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_RNDIS
+ : UsbManager.FUNCTION_NONE);
return TETHER_ERROR_NO_ERROR;
}
private int setNcmTethering(boolean enable) {
if (VDBG) Log.d(TAG, "setNcmTethering(" + enable + ")");
UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
- synchronized (mPublicSync) {
- usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_NCM
- : UsbManager.FUNCTION_NONE);
- }
+ usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_NCM : UsbManager.FUNCTION_NONE);
return TETHER_ERROR_NO_ERROR;
}
// TODO review API - figure out how to delete these entirely.
String[] getTetheredIfaces() {
ArrayList<String> list = new ArrayList<String>();
- synchronized (mPublicSync) {
- for (int i = 0; i < mTetherStates.size(); i++) {
- TetherState tetherState = mTetherStates.valueAt(i);
- if (tetherState.lastState == IpServer.STATE_TETHERED) {
- list.add(mTetherStates.keyAt(i));
- }
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherState tetherState = mTetherStates.valueAt(i);
+ if (tetherState.lastState == IpServer.STATE_TETHERED) {
+ list.add(mTetherStates.keyAt(i));
}
}
return list.toArray(new String[list.size()]);
}
- String[] getTetherableIfaces() {
+ String[] getTetherableIfacesForTest() {
ArrayList<String> list = new ArrayList<String>();
- synchronized (mPublicSync) {
- for (int i = 0; i < mTetherStates.size(); i++) {
- TetherState tetherState = mTetherStates.valueAt(i);
- if (tetherState.lastState == IpServer.STATE_AVAILABLE) {
- list.add(mTetherStates.keyAt(i));
- }
- }
- }
- return list.toArray(new String[list.size()]);
- }
-
- String[] getTetheredDhcpRanges() {
- // TODO: this is only valid for the old DHCP server. Latest search suggests it is only used
- // by WifiP2pServiceImpl to start dnsmasq: remove/deprecate after migrating callers.
- return mConfig.legacyDhcpRanges;
- }
-
- String[] getErroredIfaces() {
- ArrayList<String> list = new ArrayList<String>();
- synchronized (mPublicSync) {
- for (int i = 0; i < mTetherStates.size(); i++) {
- TetherState tetherState = mTetherStates.valueAt(i);
- if (tetherState.lastError != TETHER_ERROR_NO_ERROR) {
- list.add(mTetherStates.keyAt(i));
- }
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherState tetherState = mTetherStates.valueAt(i);
+ if (tetherState.lastState == IpServer.STATE_AVAILABLE) {
+ list.add(mTetherStates.keyAt(i));
}
}
return list.toArray(new String[list.size()]);
@@ -1370,10 +1336,7 @@
private boolean upstreamWanted() {
if (!mForwardedDownstreams.isEmpty()) return true;
-
- synchronized (mPublicSync) {
- return mWifiTetherRequested;
- }
+ return mWifiTetherRequested;
}
// Needed because the canonical source of upstream truth is just the
@@ -1558,6 +1521,7 @@
? mUpstreamNetworkMonitor.getCurrentPreferredUpstream()
: mUpstreamNetworkMonitor.selectPreferredUpstreamType(
config.preferredUpstreamIfaceTypes);
+
if (ns == null) {
if (tryCell) {
mUpstreamNetworkMonitor.setTryCell(true);
@@ -1565,7 +1529,10 @@
} else {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
}
+ } else if (!isCellular(ns)) {
+ mUpstreamNetworkMonitor.setTryCell(false);
}
+
setUpstreamNetwork(ns);
final Network newUpstream = (ns != null) ? ns.network : null;
if (mTetherUpstream != newUpstream) {
@@ -2073,8 +2040,7 @@
}
private void startTrackDefaultNetwork() {
- mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest(),
- mEntitlementMgr);
+ mUpstreamNetworkMonitor.startTrackDefaultNetwork(mEntitlementMgr);
}
/** Get the latest value of the tethering entitlement check. */
@@ -2241,15 +2207,10 @@
pw.decreaseIndent();
}
- void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
+ void doDump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
// Binder.java closes the resource for us.
- @SuppressWarnings("resource")
- final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
- != PERMISSION_GRANTED) {
- pw.println("Permission Denial: can't dump.");
- return;
- }
+ @SuppressWarnings("resource") final IndentingPrintWriter pw = new IndentingPrintWriter(
+ writer, " ");
if (argsContain(args, "bpf")) {
dumpBpf(pw);
@@ -2270,37 +2231,35 @@
mEntitlementMgr.dump(pw);
pw.decreaseIndent();
- synchronized (mPublicSync) {
- pw.println("Tether state:");
- pw.increaseIndent();
- for (int i = 0; i < mTetherStates.size(); i++) {
- final String iface = mTetherStates.keyAt(i);
- final TetherState tetherState = mTetherStates.valueAt(i);
- pw.print(iface + " - ");
+ pw.println("Tether state:");
+ pw.increaseIndent();
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ final String iface = mTetherStates.keyAt(i);
+ final TetherState tetherState = mTetherStates.valueAt(i);
+ pw.print(iface + " - ");
- switch (tetherState.lastState) {
- case IpServer.STATE_UNAVAILABLE:
- pw.print("UnavailableState");
- break;
- case IpServer.STATE_AVAILABLE:
- pw.print("AvailableState");
- break;
- case IpServer.STATE_TETHERED:
- pw.print("TetheredState");
- break;
- case IpServer.STATE_LOCAL_ONLY:
- pw.print("LocalHotspotState");
- break;
- default:
- pw.print("UnknownState");
- break;
- }
- pw.println(" - lastError = " + tetherState.lastError);
+ switch (tetherState.lastState) {
+ case IpServer.STATE_UNAVAILABLE:
+ pw.print("UnavailableState");
+ break;
+ case IpServer.STATE_AVAILABLE:
+ pw.print("AvailableState");
+ break;
+ case IpServer.STATE_TETHERED:
+ pw.print("TetheredState");
+ break;
+ case IpServer.STATE_LOCAL_ONLY:
+ pw.print("LocalHotspotState");
+ break;
+ default:
+ pw.print("UnknownState");
+ break;
}
- pw.println("Upstream wanted: " + upstreamWanted());
- pw.println("Current upstream interface(s): " + mCurrentUpstreamIfaceSet);
- pw.decreaseIndent();
+ pw.println(" - lastError = " + tetherState.lastError);
}
+ pw.println("Upstream wanted: " + upstreamWanted());
+ pw.println("Current upstream interface(s): " + mCurrentUpstreamIfaceSet);
+ pw.decreaseIndent();
pw.println("Hardware offload:");
pw.increaseIndent();
@@ -2326,6 +2285,40 @@
pw.decreaseIndent();
}
+ void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PERMISSION_GRANTED) {
+ writer.println("Permission Denial: can't dump.");
+ return;
+ }
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ // Don't crash the system if something in doDump throws an exception, but try to propagate
+ // the exception to the caller.
+ AtomicReference<RuntimeException> exceptionRef = new AtomicReference<>();
+ mHandler.post(() -> {
+ try {
+ doDump(fd, writer, args);
+ } catch (RuntimeException e) {
+ exceptionRef.set(e);
+ }
+ latch.countDown();
+ });
+
+ try {
+ if (!latch.await(DUMP_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ writer.println("Dump timeout after " + DUMP_TIMEOUT_MS + "ms");
+ return;
+ }
+ } catch (InterruptedException e) {
+ exceptionRef.compareAndSet(null, new IllegalStateException("Dump interrupted", e));
+ }
+
+ final RuntimeException e = exceptionRef.get();
+ if (e != null) throw e;
+ }
+
private static boolean argsContain(String[] args, String target) {
for (String arg : args) {
if (target.equals(arg)) return true;
@@ -2367,14 +2360,12 @@
// TODO: Move into TetherMainSM.
private void notifyInterfaceStateChange(IpServer who, int state, int error) {
final String iface = who.interfaceName();
- synchronized (mPublicSync) {
- final TetherState tetherState = mTetherStates.get(iface);
- if (tetherState != null && tetherState.ipServer.equals(who)) {
- tetherState.lastState = state;
- tetherState.lastError = error;
- } else {
- if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
- }
+ final TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState != null && tetherState.ipServer.equals(who)) {
+ tetherState.lastState = state;
+ tetherState.lastError = error;
+ } else {
+ if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
}
mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, error));
@@ -2406,14 +2397,12 @@
private void notifyLinkPropertiesChanged(IpServer who, LinkProperties newLp) {
final String iface = who.interfaceName();
final int state;
- synchronized (mPublicSync) {
- final TetherState tetherState = mTetherStates.get(iface);
- if (tetherState != null && tetherState.ipServer.equals(who)) {
- state = tetherState.lastState;
- } else {
- mLog.log("got notification from stale iface " + iface);
- return;
- }
+ final TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState != null && tetherState.ipServer.equals(who)) {
+ state = tetherState.lastState;
+ } else {
+ mLog.log("got notification from stale iface " + iface);
+ return;
}
mLog.log(String.format(
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 413b0cb..2beeeb8 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -58,6 +58,8 @@
private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final String TETHERING_MODULE_NAME = "com.android.tethering";
+
// Default ranges used for the legacy DHCP server.
// USB is 192.168.42.1 and 255.255.255.0
// Wifi is 192.168.43.1 and 255.255.255.0
@@ -473,7 +475,8 @@
@VisibleForTesting
protected boolean isFeatureEnabled(Context ctx, String featureVersionFlag) {
- return DeviceConfigUtils.isFeatureEnabled(ctx, NAMESPACE_CONNECTIVITY, featureVersionFlag);
+ return DeviceConfigUtils.isFeatureEnabled(ctx, NAMESPACE_CONNECTIVITY, featureVersionFlag,
+ TETHERING_MODULE_NAME, false /* defaultEnabled */);
}
private Resources getResources(Context ctx, int subId) {
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 45b9141..7df9475 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -20,7 +20,6 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.INetd;
-import android.net.NetworkRequest;
import android.net.ip.IpServer;
import android.net.util.SharedLog;
import android.os.Handler;
@@ -99,11 +98,6 @@
}
/**
- * Get the NetworkRequest that should be fulfilled by the default network.
- */
- public abstract NetworkRequest getDefaultNetworkRequest();
-
- /**
* Get a reference to the EntitlementManager to be used by tethering.
*/
public EntitlementManager getEntitlementManager(Context ctx, Handler h, SharedLog log,
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index c69dc49..5ab3401 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -35,8 +35,6 @@
import android.net.INetworkStackConnector;
import android.net.ITetheringConnector;
import android.net.ITetheringEventCallback;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
import android.net.NetworkStack;
import android.net.TetheringRequestParcel;
import android.net.dhcp.DhcpServerCallbacks;
@@ -105,9 +103,7 @@
IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
- try {
- listener.onResult(mTethering.tether(iface));
- } catch (RemoteException e) { }
+ mTethering.tether(iface, IpServer.STATE_TETHERED, listener);
}
@Override
@@ -115,9 +111,7 @@
IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
- try {
- listener.onResult(mTethering.untether(iface));
- } catch (RemoteException e) { }
+ mTethering.untether(iface, listener);
}
@Override
@@ -125,9 +119,7 @@
IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
- try {
- listener.onResult(mTethering.setUsbTethering(enable));
- } catch (RemoteException e) { }
+ mTethering.setUsbTethering(enable, listener);
}
@Override
@@ -313,19 +305,6 @@
public TetheringDependencies makeTetheringDependencies() {
return new TetheringDependencies() {
@Override
- public NetworkRequest getDefaultNetworkRequest() {
- // TODO: b/147280869, add a proper system API to replace this.
- final NetworkRequest trackDefaultRequest = new NetworkRequest.Builder()
- .clearCapabilities()
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- .build();
- return trackDefaultRequest;
- }
-
- @Override
public Looper getTetheringLooper() {
final HandlerThread tetherThread = new HandlerThread("android.tethering");
tetherThread.start();
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index f9af777..e615334 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -47,6 +47,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StateMachine;
+import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
+import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import java.util.HashMap;
import java.util.HashSet;
@@ -142,33 +145,28 @@
mWhat = what;
mLocalPrefixes = new HashSet<>();
mIsDefaultCellularUpstream = false;
- }
-
- @VisibleForTesting
- public UpstreamNetworkMonitor(
- ConnectivityManager cm, StateMachine tgt, SharedLog log, int what) {
- this((Context) null, tgt, log, what);
- mCM = cm;
+ mCM = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
}
/**
- * Tracking the system default network. This method should be called when system is ready.
+ * Tracking the system default network. This method should be only called once when system is
+ * ready, and the callback is never unregistered.
*
- * @param defaultNetworkRequest should be the same as ConnectivityService default request
* @param entitle a EntitlementManager object to communicate between EntitlementManager and
* UpstreamNetworkMonitor
*/
- public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest,
- EntitlementManager entitle) {
-
- // defaultNetworkRequest is not really a "request", just a way of tracking the system
- // default network. It's guaranteed not to actually bring up any networks because it's
- // the should be the same request as the ConnectivityService default request, and thus
- // shares fate with it. We can't use registerDefaultNetworkCallback because it will not
- // track the system default network if there is a VPN that applies to our UID.
- if (mDefaultNetworkCallback == null) {
- mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
- cm().requestNetwork(defaultNetworkRequest, mDefaultNetworkCallback, mHandler);
+ public void startTrackDefaultNetwork(EntitlementManager entitle) {
+ if (mDefaultNetworkCallback != null) {
+ Log.wtf(TAG, "default network callback is already registered");
+ return;
+ }
+ ConnectivityManagerShim mCmShim = ConnectivityManagerShimImpl.newInstance(mContext);
+ mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
+ try {
+ mCmShim.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+ } catch (UnsupportedApiLevelException e) {
+ Log.wtf(TAG, "registerSystemDefaultNetworkCallback is not supported");
+ return;
}
if (mEntitlementMgr == null) {
mEntitlementMgr = entitle;
@@ -318,18 +316,6 @@
if (!mIsDefaultCellularUpstream) {
mEntitlementMgr.maybeRunProvisioning();
}
- // If we're on DUN, put our own grab on it.
- registerMobileNetworkRequest();
- break;
- case TYPE_NONE:
- // If we found NONE and mobile upstream is permitted we don't want to do this
- // as we want any previous requests to keep trying to bring up something we can use.
- if (!isCellularUpstreamPermitted()) releaseMobileNetworkRequest();
- break;
- default:
- // If we've found an active upstream connection that's not DUN/HIPRI
- // we should stop any outstanding DUN/HIPRI requests.
- releaseMobileNetworkRequest();
break;
}
@@ -647,7 +633,8 @@
return prefixSet;
}
- private static boolean isCellular(UpstreamNetworkState ns) {
+ /** Check whether upstream is cellular. */
+ static boolean isCellular(UpstreamNetworkState ns) {
return (ns != null) && isCellular(ns.networkCapabilities);
}
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index f63df2c..351b9f4 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -23,6 +23,7 @@
"src/**/*.java",
"src/**/*.kt",
],
+ min_sdk_version: "30",
static_libs: [
"NetworkStackApiStableLib",
"androidx.test.rules",
@@ -44,12 +45,24 @@
}
android_library {
- name: "TetheringIntegrationTestsLib",
+ name: "TetheringIntegrationTestsLatestSdkLib",
+ target_sdk_version: "30",
platform_apis: true,
defaults: ["TetheringIntegrationTestsDefaults"],
visibility: [
- "//cts/tests/tests/tethering",
"//packages/modules/Connectivity/tests/cts/tethering",
+ "//packages/modules/Connectivity/Tethering/tests/mts",
+ ]
+}
+
+android_library {
+ name: "TetheringIntegrationTestsLib",
+ target_sdk_version: "current",
+ platform_apis: true,
+ defaults: ["TetheringIntegrationTestsDefaults"],
+ visibility: [
+ "//packages/modules/Connectivity/tests/cts/tethering",
+ "//packages/modules/Connectivity/Tethering/tests/mts",
]
}
@@ -70,15 +83,18 @@
android_test {
name: "TetheringCoverageTests",
platform_apis: true,
+ min_sdk_version: "30",
+ target_sdk_version: "30",
test_suites: ["device-tests", "mts"],
test_config: "AndroidTest_Coverage.xml",
defaults: ["libnetworkstackutilsjni_deps"],
static_libs: [
+ "modules-utils-native-coverage-listener",
"NetdStaticLibTestsLib",
"NetworkStaticLibTestsLib",
"NetworkStackTestsLib",
- "TetheringTestsLib",
- "TetheringIntegrationTestsLib",
+ "TetheringTestsLatestSdkLib",
+ "TetheringIntegrationTestsLatestSdkLib",
],
jni_libs: [
// For mockito extended
diff --git a/Tethering/tests/integration/AndroidTest_Coverage.xml b/Tethering/tests/integration/AndroidTest_Coverage.xml
index 3def209..33c5b3d 100644
--- a/Tethering/tests/integration/AndroidTest_Coverage.xml
+++ b/Tethering/tests/integration/AndroidTest_Coverage.xml
@@ -8,5 +8,6 @@
<option name="package" value="com.android.networkstack.tethering.tests.coverage" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
+ <option name="device-listeners" value="com.android.modules.utils.testing.NativeCoverageHackInstrumentationListener" />
</test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index d206ea0..de94cba 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -16,10 +16,18 @@
package android.net;
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.TETHER_PRIVILEGED;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -50,6 +58,10 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
import com.android.testutils.HandlerUtils;
import com.android.testutils.TapPacketReader;
@@ -60,6 +72,7 @@
import java.io.FileDescriptor;
import java.net.Inet4Address;
+import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
@@ -109,7 +122,7 @@
// Needed to create a TestNetworkInterface, to call requestTetheredInterface, and to receive
// tethered client callbacks.
mUiAutomation.adoptShellPermissionIdentity(
- MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED);
+ MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED, ACCESS_NETWORK_STATE);
mRunTests = mTm.isTetheringSupported() && mEm != null;
assumeTrue(mRunTests);
@@ -229,6 +242,82 @@
}
+ private static boolean isRouterAdvertisement(byte[] pkt) {
+ if (pkt == null) return false;
+
+ ByteBuffer buf = ByteBuffer.wrap(pkt);
+
+ final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
+ if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
+
+ final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
+ if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return false;
+
+ final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
+ return icmpv6Hdr.type == (short) ICMPV6_ROUTER_ADVERTISEMENT;
+ }
+
+ private static void expectRouterAdvertisement(TapPacketReader reader, String iface,
+ long timeoutMs) {
+ final long deadline = SystemClock.uptimeMillis() + timeoutMs;
+ do {
+ byte[] pkt = reader.popPacket(timeoutMs);
+ if (isRouterAdvertisement(pkt)) return;
+ timeoutMs = deadline - SystemClock.uptimeMillis();
+ } while (timeoutMs > 0);
+ fail("Did not receive router advertisement on " + iface + " after "
+ + timeoutMs + "ms idle");
+ }
+
+ private static void expectLocalOnlyAddresses(String iface) throws Exception {
+ final List<InterfaceAddress> interfaceAddresses =
+ NetworkInterface.getByName(iface).getInterfaceAddresses();
+
+ boolean foundIpv6Ula = false;
+ for (InterfaceAddress ia : interfaceAddresses) {
+ final InetAddress addr = ia.getAddress();
+ if (isIPv6ULA(addr)) {
+ foundIpv6Ula = true;
+ }
+ final int prefixlen = ia.getNetworkPrefixLength();
+ final LinkAddress la = new LinkAddress(addr, prefixlen);
+ if (la.isIpv6() && la.isGlobalPreferred()) {
+ fail("Found global IPv6 address on local-only interface: " + interfaceAddresses);
+ }
+ }
+
+ assertTrue("Did not find IPv6 ULA on local-only interface " + iface,
+ foundIpv6Ula);
+ }
+
+ @Test
+ public void testLocalOnlyTethering() throws Exception {
+ assumeFalse(mEm.isAvailable());
+
+ mEm.setIncludeTestInterfaces(true);
+
+ mTestIface = createTestInterface();
+
+ final String iface = mTetheredInterfaceRequester.getInterface();
+ assertEquals("TetheredInterfaceCallback for unexpected interface",
+ mTestIface.getInterfaceName(), iface);
+
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
+ .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
+ mTetheringEventCallback = enableEthernetTethering(iface, request);
+ mTetheringEventCallback.awaitInterfaceLocalOnly();
+
+ // makePacketReader only works after tethering is started, because until then the interface
+ // does not have an IP address, and unprivileged apps cannot see interfaces without IP
+ // addresses. This shouldn't be flaky because the TAP interface will buffer all packets even
+ // before the reader is started.
+ FileDescriptor fd = mTestIface.getFileDescriptor().getFileDescriptor();
+ mTapPacketReader = makePacketReader(fd, getMTU(mTestIface));
+
+ expectRouterAdvertisement(mTapPacketReader, iface, 2000 /* timeoutMs */);
+ expectLocalOnlyAddresses(iface);
+ }
+
private boolean isAdbOverNetwork() {
// If adb TCP port opened, this test may running by adb over network.
return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1)
@@ -257,10 +346,13 @@
private final TetheringManager mTm;
private final CountDownLatch mTetheringStartedLatch = new CountDownLatch(1);
private final CountDownLatch mTetheringStoppedLatch = new CountDownLatch(1);
+ private final CountDownLatch mLocalOnlyStartedLatch = new CountDownLatch(1);
+ private final CountDownLatch mLocalOnlyStoppedLatch = new CountDownLatch(1);
private final CountDownLatch mClientConnectedLatch = new CountDownLatch(1);
private final String mIface;
private volatile boolean mInterfaceWasTethered = false;
+ private volatile boolean mInterfaceWasLocalOnly = false;
private volatile boolean mUnregistered = false;
private volatile Collection<TetheredClient> mClients = null;
@@ -279,7 +371,6 @@
// Ignore stale callbacks registered by previous test cases.
if (mUnregistered) return;
- final boolean wasTethered = mTetheringStartedLatch.getCount() == 0;
if (!mInterfaceWasTethered && (mIface == null || interfaces.contains(mIface))) {
// This interface is being tethered for the first time.
Log.d(TAG, "Tethering started: " + interfaces);
@@ -291,20 +382,48 @@
}
}
+ @Override
+ public void onLocalOnlyInterfacesChanged(List<String> interfaces) {
+ // Ignore stale callbacks registered by previous test cases.
+ if (mUnregistered) return;
+
+ if (!mInterfaceWasLocalOnly && (mIface == null || interfaces.contains(mIface))) {
+ // This interface is being put into local-only mode for the first time.
+ Log.d(TAG, "Local-only started: " + interfaces);
+ mInterfaceWasLocalOnly = true;
+ mLocalOnlyStartedLatch.countDown();
+ } else if (mInterfaceWasLocalOnly && !interfaces.contains(mIface)) {
+ Log.d(TAG, "Local-only stopped: " + interfaces);
+ mLocalOnlyStoppedLatch.countDown();
+ }
+ }
+
public void awaitInterfaceTethered() throws Exception {
assertTrue("Ethernet not tethered after " + TIMEOUT_MS + "ms",
mTetheringStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
+ public void awaitInterfaceLocalOnly() throws Exception {
+ assertTrue("Ethernet not local-only after " + TIMEOUT_MS + "ms",
+ mLocalOnlyStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
public void awaitInterfaceUntethered() throws Exception {
// Don't block teardown if the interface was never tethered.
// This is racy because the interface might become tethered right after this check, but
// that can only happen in tearDown if startTethering timed out, which likely means
// the test has already failed.
- if (!mInterfaceWasTethered) return;
+ if (!mInterfaceWasTethered && !mInterfaceWasLocalOnly) return;
- assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
- mTetheringStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ if (mInterfaceWasTethered) {
+ assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
+ mTetheringStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } else if (mInterfaceWasLocalOnly) {
+ assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
+ mLocalOnlyStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } else {
+ fail(mIface + " cannot be both tethered and local-only. Update this test class.");
+ }
}
@Override
@@ -347,7 +466,19 @@
};
Log.d(TAG, "Starting Ethernet tethering");
mTm.startTethering(request, mHandler::post /* executor */, startTetheringCallback);
- callback.awaitInterfaceTethered();
+
+ final int connectivityType = request.getConnectivityScope();
+ switch (connectivityType) {
+ case CONNECTIVITY_SCOPE_GLOBAL:
+ callback.awaitInterfaceTethered();
+ break;
+ case CONNECTIVITY_SCOPE_LOCAL:
+ callback.awaitInterfaceLocalOnly();
+ break;
+ default:
+ fail("Unexpected connectivity type requested: " + connectivityType);
+ }
+
return callback;
}
@@ -444,7 +575,6 @@
}
private static final class TetheredInterfaceRequester implements TetheredInterfaceCallback {
- private final CountDownLatch mInterfaceAvailableLatch = new CountDownLatch(1);
private final Handler mHandler;
private final EthernetManager mEm;
diff --git a/Tethering/tests/jarjar-rules.txt b/Tethering/tests/jarjar-rules.txt
index c99ff7f..9cb143e 100644
--- a/Tethering/tests/jarjar-rules.txt
+++ b/Tethering/tests/jarjar-rules.txt
@@ -1,8 +1,8 @@
# Don't jar-jar the entire package because this test use some
# internal classes (like ArrayUtils in com.android.internal.util)
rule com.android.internal.util.BitUtils* com.android.networkstack.tethering.util.BitUtils@1
-rule com.android.internal.util.IndentingPrintWriter.java* com.android.networkstack.tethering.util.IndentingPrintWriter.java@1
-rule com.android.internal.util.IState.java* com.android.networkstack.tethering.util.IState.java@1
+rule com.android.internal.util.IndentingPrintWriter* com.android.networkstack.tethering.util.IndentingPrintWriter@1
+rule com.android.internal.util.IState* com.android.networkstack.tethering.util.IState@1
rule com.android.internal.util.MessageUtils* com.android.networkstack.tethering.util.MessageUtils@1
rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1
rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index edb6356..221771f 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -19,7 +19,10 @@
android_test {
// This tests for functionality that is not required for devices that
// don't use Tethering mainline module.
- name: "MtsTetheringTest",
+ name: "MtsTetheringTestLatestSdk",
+
+ min_sdk_version: "30",
+ target_sdk_version: "30",
libs: [
"android.test.base",
diff --git a/Tethering/tests/mts/AndroidTest.xml b/Tethering/tests/mts/AndroidTest.xml
index 80788df..4edd544 100644
--- a/Tethering/tests/mts/AndroidTest.xml
+++ b/Tethering/tests/mts/AndroidTest.xml
@@ -24,7 +24,7 @@
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="MtsTetheringTest.apk" />
+ <option name="test-file-name" value="MtsTetheringTestLatestSdk.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.tethering.mts" />
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index d469b37..b4b3977 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -36,13 +36,13 @@
"framework-tethering.impl",
],
visibility: [
- "//cts/tests/tests/tethering",
"//packages/modules/Connectivity/tests/cts/tethering",
],
}
java_defaults {
name: "TetheringTestsDefaults",
+ min_sdk_version: "30",
srcs: [
"src/**/*.java",
"src/**/*.kt",
@@ -81,10 +81,10 @@
// unit test code. It is not currently used by the tests themselves because all the build
// configuration needed by the tests is in the TetheringTestsDefaults rule.
android_library {
- name: "TetheringTestsLib",
+ name: "TetheringTestsLatestSdkLib",
defaults: ["TetheringTestsDefaults"],
+ target_sdk_version: "30",
visibility: [
- "//frameworks/base/packages/Tethering/tests/integration",
"//packages/modules/Connectivity/Tethering/tests/integration",
]
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 435cab5..ce69cb3 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -107,6 +107,8 @@
import com.android.networkstack.tethering.Tether4Key;
import com.android.networkstack.tethering.Tether4Value;
import com.android.networkstack.tethering.Tether6Value;
+import com.android.networkstack.tethering.TetherDevKey;
+import com.android.networkstack.tethering.TetherDevValue;
import com.android.networkstack.tethering.TetherDownstream6Key;
import com.android.networkstack.tethering.TetherLimitKey;
import com.android.networkstack.tethering.TetherLimitValue;
@@ -182,6 +184,7 @@
@Mock private BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
@Mock private BpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap;
@Mock private BpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap;
+ @Mock private BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
@Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
@@ -334,6 +337,11 @@
public BpfMap<TetherLimitKey, TetherLimitValue> getBpfLimitMap() {
return mBpfLimitMap;
}
+
+ @Nullable
+ public BpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
+ return mBpfDevMap;
+ }
};
mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps));
diff --git a/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java b/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
index 9968b5f..e5d0b1c 100644
--- a/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
+++ b/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
@@ -15,6 +15,7 @@
*/
package android.net.util;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.system.OsConstants.AF_UNIX;
@@ -78,7 +79,7 @@
}
@Test
- public void testIsTetheringRequestEquals() throws Exception {
+ public void testIsTetheringRequestEquals() {
TetheringRequestParcel request = makeTetheringRequestParcel();
assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, mTetheringRequest));
@@ -104,7 +105,11 @@
request.showProvisioningUi = false;
assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
- MiscAsserts.assertFieldCountEquals(5, TetheringRequestParcel.class);
+ request = makeTetheringRequestParcel();
+ request.connectivityScope = CONNECTIVITY_SCOPE_LOCAL;
+ assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
+
+ MiscAsserts.assertFieldCountEquals(6, TetheringRequestParcel.class);
}
// Writes the specified packet to a filedescriptor, skipping the Ethernet header.
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 233f6db..cc912f4 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -207,6 +207,7 @@
@Mock private BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
@Mock private BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
@Mock private BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
+ @Mock private BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
// Late init since methods must be called by the thread that created this object.
private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
@@ -284,6 +285,11 @@
public BpfMap<TetherLimitKey, TetherLimitValue> getBpfLimitMap() {
return mBpfLimitMap;
}
+
+ @Nullable
+ public BpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
+ return mBpfDevMap;
+ }
});
@Before public void setUp() {
@@ -1368,12 +1374,9 @@
coordinator.tetherOffloadClientAdd(mIpServer, clientInfo);
}
- // TODO: Test the IPv4 and IPv6 exist concurrently.
- // TODO: Test the IPv4 rule delete failed.
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.R)
- public void testSetDataLimitOnRule4Change() throws Exception {
- final BpfCoordinator coordinator = makeBpfCoordinator();
+ private void initBpfCoordinatorForRule4(final BpfCoordinator coordinator) throws Exception {
+ // Needed because addUpstreamIfindexToMap only updates upstream information when polling
+ // was started.
coordinator.startPolling();
// Needed because tetherOffloadRuleRemove of api31.BpfCoordinatorShimImpl only decreases
@@ -1387,6 +1390,15 @@
coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
setUpstreamInformationTo(coordinator);
setDownstreamAndClientInformationTo(coordinator);
+ }
+
+ // TODO: Test the IPv4 and IPv6 exist concurrently.
+ // TODO: Test the IPv4 rule delete failed.
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testSetDataLimitOnRule4Change() throws Exception {
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+ initBpfCoordinatorForRule4(coordinator);
// Applying a data limit to the current upstream does not take any immediate action.
// The data limit could be only set on an upstream which has rules.
@@ -1445,4 +1457,41 @@
verifyTetherOffloadGetAndClearStats(inOrder, UPSTREAM_IFINDEX);
inOrder.verifyNoMoreInteractions();
}
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testAddDevMapRule6() throws Exception {
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+
+ coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
+ final Ipv6ForwardingRule ruleA = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
+ final Ipv6ForwardingRule ruleB = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_B, MAC_B);
+
+ coordinator.tetherOffloadRuleAdd(mIpServer, ruleA);
+ verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
+ eq(new TetherDevValue(UPSTREAM_IFINDEX)));
+ verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX)),
+ eq(new TetherDevValue(DOWNSTREAM_IFINDEX)));
+ clearInvocations(mBpfDevMap);
+
+ coordinator.tetherOffloadRuleAdd(mIpServer, ruleB);
+ verify(mBpfDevMap, never()).updateEntry(any(), any());
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testAddDevMapRule4() throws Exception {
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+ initBpfCoordinatorForRule4(coordinator);
+
+ mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_TCP));
+ verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
+ eq(new TetherDevValue(UPSTREAM_IFINDEX)));
+ verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX)),
+ eq(new TetherDevValue(DOWNSTREAM_IFINDEX)));
+ clearInvocations(mBpfDevMap);
+
+ mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_UDP));
+ verify(mBpfDevMap, never()).updateEntry(any(), any());
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 8cfa7d0..5ae4b43 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -53,6 +53,7 @@
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -203,6 +204,7 @@
doReturn(mPm).when(mContext).getPackageManager();
doReturn(TEST_PACKAGE_NAME).when(mContext).getPackageName();
doReturn(new PackageInfo()).when(mPm).getPackageInfo(anyString(), anyInt());
+ doReturn(new ModuleInfo()).when(mPm).getModuleInfo(anyString(), anyInt());
when(mResources.getStringArray(R.array.config_tether_dhcp_range))
.thenReturn(new String[0]);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index ce52ae2..9bd82f9 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -177,87 +177,65 @@
return offload;
}
- @Test
- public void testNoSettingsValueDefaultDisabledDoesNotStart() throws Exception {
+ @NonNull
+ private OffloadController startOffloadController(boolean expectStart)
+ throws Exception {
setupFunctioningHardwareInterface();
- when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1);
- assertThrows(SettingNotFoundException.class, () ->
- Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
-
final OffloadController offload = makeOffloadController();
offload.start();
final InOrder inOrder = inOrder(mHardware);
inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
- inOrder.verify(mHardware, never()).initOffloadConfig();
- inOrder.verify(mHardware, never()).initOffloadControl(
+ inOrder.verify(mHardware, times(expectStart ? 1 : 0)).initOffloadConfig();
+ inOrder.verify(mHardware, times(expectStart ? 1 : 0)).initOffloadControl(
any(OffloadHardwareInterface.ControlCallback.class));
inOrder.verifyNoMoreInteractions();
+ // Clear counters only instead of whole mock to preserve the mocking setup.
+ clearInvocations(mHardware);
+ return offload;
+ }
+
+ private void stopOffloadController(final OffloadController offload) throws Exception {
+ final InOrder inOrder = inOrder(mHardware);
+ offload.stop();
+ inOrder.verify(mHardware, times(1)).stopOffloadControl();
+ inOrder.verifyNoMoreInteractions();
+ reset(mHardware);
+ }
+
+ @Test
+ public void testNoSettingsValueDefaultDisabledDoesNotStart() throws Exception {
+ when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1);
+ assertThrows(SettingNotFoundException.class, () ->
+ Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
+ startOffloadController(false /*expectStart*/);
}
@Test
public void testNoSettingsValueDefaultEnabledDoesStart() throws Exception {
- setupFunctioningHardwareInterface();
when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0);
assertThrows(SettingNotFoundException.class, () ->
Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
-
- final OffloadController offload = makeOffloadController();
- offload.start();
-
- final InOrder inOrder = inOrder(mHardware);
- inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
- inOrder.verify(mHardware, times(1)).initOffloadConfig();
- inOrder.verify(mHardware, times(1)).initOffloadControl(
- any(OffloadHardwareInterface.ControlCallback.class));
- inOrder.verifyNoMoreInteractions();
+ startOffloadController(true /*expectStart*/);
}
@Test
public void testSettingsAllowsStart() throws Exception {
- setupFunctioningHardwareInterface();
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
-
- final OffloadController offload = makeOffloadController();
- offload.start();
-
- final InOrder inOrder = inOrder(mHardware);
- inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
- inOrder.verify(mHardware, times(1)).initOffloadConfig();
- inOrder.verify(mHardware, times(1)).initOffloadControl(
- any(OffloadHardwareInterface.ControlCallback.class));
- inOrder.verifyNoMoreInteractions();
+ startOffloadController(true /*expectStart*/);
}
@Test
public void testSettingsDisablesStart() throws Exception {
- setupFunctioningHardwareInterface();
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);
-
- final OffloadController offload = makeOffloadController();
- offload.start();
-
- final InOrder inOrder = inOrder(mHardware);
- inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
- inOrder.verify(mHardware, never()).initOffloadConfig();
- inOrder.verify(mHardware, never()).initOffloadControl(anyObject());
- inOrder.verifyNoMoreInteractions();
+ startOffloadController(false /*expectStart*/);
}
@Test
public void testSetUpstreamLinkPropertiesWorking() throws Exception {
- setupFunctioningHardwareInterface();
enableOffload();
-
- final OffloadController offload = makeOffloadController();
- offload.start();
-
- final InOrder inOrder = inOrder(mHardware);
- inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
- inOrder.verify(mHardware, times(1)).initOffloadConfig();
- inOrder.verify(mHardware, times(1)).initOffloadControl(
- any(OffloadHardwareInterface.ControlCallback.class));
- inOrder.verifyNoMoreInteractions();
+ final OffloadController offload =
+ startOffloadController(true /*expectStart*/);
// In reality, the UpstreamNetworkMonitor would have passed down to us
// a covering set of local prefixes representing a minimum essential
@@ -271,6 +249,7 @@
minimumLocalPrefixes.add(new IpPrefix(s));
}
offload.setLocalPrefixes(minimumLocalPrefixes);
+ final InOrder inOrder = inOrder(mHardware);
inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
ArrayList<String> localPrefixes = mStringArrayCaptor.getValue();
assertEquals(4, localPrefixes.size());
@@ -425,11 +404,9 @@
@Test
public void testGetForwardedStats() throws Exception {
- setupFunctioningHardwareInterface();
enableOffload();
-
- final OffloadController offload = makeOffloadController();
- offload.start();
+ final OffloadController offload =
+ startOffloadController(true /*expectStart*/);
final String ethernetIface = "eth1";
final String mobileIface = "rmnet_data0";
@@ -439,7 +416,7 @@
when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(
new ForwardedStats(999, 99999));
- InOrder inOrder = inOrder(mHardware);
+ final InOrder inOrder = inOrder(mHardware);
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(ethernetIface);
@@ -517,11 +494,9 @@
@Test
public void testSetInterfaceQuota() throws Exception {
- setupFunctioningHardwareInterface();
enableOffload();
-
- final OffloadController offload = makeOffloadController();
- offload.start();
+ final OffloadController offload =
+ startOffloadController(true /*expectStart*/);
final String ethernetIface = "eth1";
final String mobileIface = "rmnet_data0";
@@ -581,11 +556,9 @@
@Test
public void testDataLimitCallback() throws Exception {
- setupFunctioningHardwareInterface();
enableOffload();
-
- final OffloadController offload = makeOffloadController();
- offload.start();
+ final OffloadController offload =
+ startOffloadController(true /*expectStart*/);
OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
callback.onStoppedLimitReached();
@@ -594,17 +567,10 @@
@Test
public void testAddRemoveDownstreams() throws Exception {
- setupFunctioningHardwareInterface();
enableOffload();
-
- final OffloadController offload = makeOffloadController();
- offload.start();
-
+ final OffloadController offload =
+ startOffloadController(true /*expectStart*/);
final InOrder inOrder = inOrder(mHardware);
- inOrder.verify(mHardware, times(1)).initOffloadConfig();
- inOrder.verify(mHardware, times(1)).initOffloadControl(
- any(OffloadHardwareInterface.ControlCallback.class));
- inOrder.verifyNoMoreInteractions();
// Tethering makes several calls to setLocalPrefixes() before add/remove
// downstream calls are made. This is not tested here; only the behavior
@@ -668,11 +634,9 @@
@Test
public void testControlCallbackOnStoppedUnsupportedFetchesAllStats() throws Exception {
- setupFunctioningHardwareInterface();
enableOffload();
-
- final OffloadController offload = makeOffloadController();
- offload.start();
+ final OffloadController offload =
+ startOffloadController(true /*expectStart*/);
// Pretend to set a few different upstreams (only the interface name
// matters for this test; we're ignoring IP and route information).
@@ -701,11 +665,9 @@
@Test
public void testControlCallbackOnSupportAvailableFetchesAllStatsAndPushesAllParameters()
throws Exception {
- setupFunctioningHardwareInterface();
enableOffload();
-
- final OffloadController offload = makeOffloadController();
- offload.start();
+ final OffloadController offload =
+ startOffloadController(true /*expectStart*/);
// Pretend to set a few different upstreams (only the interface name
// matters for this test; we're ignoring IP and route information).
@@ -780,11 +742,10 @@
@Test
public void testOnSetAlert() throws Exception {
- setupFunctioningHardwareInterface();
enableOffload();
setOffloadPollInterval(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
- final OffloadController offload = makeOffloadController();
- offload.start();
+ final OffloadController offload =
+ startOffloadController(true /*expectStart*/);
// Initialize with fake eth upstream.
final String ethernetIface = "eth1";
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
index d045bf1..6090213 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
@@ -16,6 +16,10 @@
package com.android.networkstack.tethering;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
@@ -64,12 +68,13 @@
public static final boolean CALLBACKS_FIRST = true;
final Map<NetworkCallback, NetworkRequestInfo> mAllCallbacks = new ArrayMap<>();
+ // This contains the callbacks tracking the system default network, whether it's registered
+ // with registerSystemDefaultNetworkCallback (S+) or with a custom request (R-).
final Map<NetworkCallback, NetworkRequestInfo> mTrackingDefault = new ArrayMap<>();
final Map<NetworkCallback, NetworkRequestInfo> mListening = new ArrayMap<>();
final Map<NetworkCallback, NetworkRequestInfo> mRequested = new ArrayMap<>();
final Map<NetworkCallback, Integer> mLegacyTypeMap = new ArrayMap<>();
- private final NetworkRequest mDefaultRequest;
private final Context mContext;
private int mNetworkId = 100;
@@ -80,13 +85,10 @@
* @param ctx the context to use. Must be a fake or a mock because otherwise the test will
* attempt to send real broadcasts and resulting in permission denials.
* @param svc an IConnectivityManager. Should be a fake or a mock.
- * @param defaultRequest the default NetworkRequest that will be used by Tethering.
*/
- public TestConnectivityManager(Context ctx, IConnectivityManager svc,
- NetworkRequest defaultRequest) {
+ public TestConnectivityManager(Context ctx, IConnectivityManager svc) {
super(ctx, svc);
mContext = ctx;
- mDefaultRequest = defaultRequest;
}
class NetworkRequestInfo {
@@ -181,11 +183,19 @@
makeDefaultNetwork(agent, BROADCAST_FIRST, null /* inBetween */);
}
+ static boolean looksLikeDefaultRequest(NetworkRequest req) {
+ return req.hasCapability(NET_CAPABILITY_INTERNET)
+ && !req.hasCapability(NET_CAPABILITY_DUN)
+ && !req.hasTransport(TRANSPORT_CELLULAR);
+ }
+
@Override
public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
assertFalse(mAllCallbacks.containsKey(cb));
mAllCallbacks.put(cb, new NetworkRequestInfo(req, h));
- if (mDefaultRequest.equals(req)) {
+ // For R- devices, Tethering will invoke this function in 2 cases, one is to request mobile
+ // network, the other is to track system default network.
+ if (looksLikeDefaultRequest(req)) {
assertFalse(mTrackingDefault.containsKey(cb));
mTrackingDefault.put(cb, new NetworkRequestInfo(req, h));
} else {
@@ -203,9 +213,11 @@
public void requestNetwork(NetworkRequest req,
int timeoutMs, int legacyType, Handler h, NetworkCallback cb) {
assertFalse(mAllCallbacks.containsKey(cb));
- mAllCallbacks.put(cb, new NetworkRequestInfo(req, h));
+ NetworkRequest newReq = new NetworkRequest(req.networkCapabilities, legacyType,
+ -1 /** testId */, req.type);
+ mAllCallbacks.put(cb, new NetworkRequestInfo(newReq, h));
assertFalse(mRequested.containsKey(cb));
- mRequested.put(cb, new NetworkRequestInfo(req, h));
+ mRequested.put(cb, new NetworkRequestInfo(newReq, h));
assertFalse(mLegacyTypeMap.containsKey(cb));
if (legacyType != ConnectivityManager.TYPE_NONE) {
mLegacyTypeMap.put(cb, legacyType);
@@ -313,14 +325,26 @@
return matchesLegacyType(networkCapabilities, legacyType);
}
- public void fakeConnect() {
- for (NetworkRequestInfo nri : cm.mRequested.values()) {
- if (matchesLegacyType(nri.request.legacyType)) {
- cm.sendConnectivityAction(legacyType, true /* connected */);
+ private void maybeSendConnectivityBroadcast(boolean connected) {
+ for (Integer requestedLegacyType : cm.mLegacyTypeMap.values()) {
+ if (requestedLegacyType.intValue() == legacyType) {
+ cm.sendConnectivityAction(legacyType, connected /* connected */);
// In practice, a given network can match only one legacy type.
break;
}
}
+ }
+
+ public void fakeConnect() {
+ fakeConnect(BROADCAST_FIRST, null);
+ }
+
+ public void fakeConnect(boolean order, @Nullable Runnable inBetween) {
+ if (order == BROADCAST_FIRST) {
+ maybeSendConnectivityBroadcast(true /* connected */);
+ if (inBetween != null) inBetween.run();
+ }
+
for (NetworkCallback cb : cm.mListening.keySet()) {
final NetworkRequestInfo nri = cm.mListening.get(cb);
nri.handler.post(() -> cb.onAvailable(networkId));
@@ -328,19 +352,32 @@
networkId, copy(networkCapabilities)));
nri.handler.post(() -> cb.onLinkPropertiesChanged(networkId, copy(linkProperties)));
}
+
+ if (order == CALLBACKS_FIRST) {
+ if (inBetween != null) inBetween.run();
+ maybeSendConnectivityBroadcast(true /* connected */);
+ }
// mTrackingDefault will be updated if/when the caller calls makeDefaultNetwork
}
public void fakeDisconnect() {
- for (NetworkRequestInfo nri : cm.mRequested.values()) {
- if (matchesLegacyType(nri.request.legacyType)) {
- cm.sendConnectivityAction(legacyType, false /* connected */);
- break;
- }
+ fakeDisconnect(BROADCAST_FIRST, null);
+ }
+
+ public void fakeDisconnect(boolean order, @Nullable Runnable inBetween) {
+ if (order == BROADCAST_FIRST) {
+ maybeSendConnectivityBroadcast(false /* connected */);
+ if (inBetween != null) inBetween.run();
}
+
for (NetworkCallback cb : cm.mListening.keySet()) {
cb.onLost(networkId);
}
+
+ if (order == CALLBACKS_FIRST) {
+ if (inBetween != null) inBetween.run();
+ maybeSendConnectivityBroadcast(false /* connected */);
+ }
// mTrackingDefault will be updated if/when the caller calls makeDefaultNetwork
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index 1f4e371..a6433a6 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -75,12 +76,14 @@
private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
private static final String PROVISIONING_APP_RESPONSE = "app_response";
private static final String TEST_PACKAGE_NAME = "com.android.tethering.test";
+ private static final String APEX_NAME = "com.android.tethering";
private static final long TEST_PACKAGE_VERSION = 1234L;
@Mock private Context mContext;
@Mock private TelephonyManager mTelephonyManager;
@Mock private Resources mResources;
@Mock private Resources mResourcesForSubId;
@Mock private PackageManager mPackageManager;
+ @Mock private ModuleInfo mMi;
private Context mMockContext;
private boolean mHasTelephonyManager;
private boolean mEnableLegacyDhcpServer;
@@ -143,6 +146,8 @@
final PackageInfo pi = new PackageInfo();
pi.setLongVersionCode(TEST_PACKAGE_VERSION);
doReturn(pi).when(mPackageManager).getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt());
+ doReturn(mMi).when(mPackageManager).getModuleInfo(eq(APEX_NAME), anyInt());
+ doReturn(TEST_PACKAGE_NAME).when(mMi).getPackageName();
when(mResources.getStringArray(R.array.config_tether_dhcp_range)).thenReturn(
new String[0]);
@@ -505,7 +510,7 @@
.thenReturn(false);
setTetherForceUpstreamAutomaticFlagVersion(TEST_PACKAGE_VERSION - 1);
assertTrue(DeviceConfigUtils.isFeatureEnabled(mMockContext, NAMESPACE_CONNECTIVITY,
- TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION));
+ TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, APEX_NAME, false));
assertChooseUpstreamAutomaticallyIs(true);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index 7bba67b..941cd78 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -25,7 +25,10 @@
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -37,6 +40,7 @@
import android.net.ITetheringConnector;
import android.net.ITetheringEventCallback;
import android.net.TetheringRequestParcel;
+import android.net.ip.IpServer;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
@@ -156,11 +160,9 @@
}
private void runTether(final TestTetheringResult result) throws Exception {
- when(mTethering.tether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringSupported();
- verify(mTethering).tether(TEST_IFACE_NAME);
- result.assertResult(TETHER_ERROR_NO_ERROR);
+ verify(mTethering).tether(TEST_IFACE_NAME, IpServer.STATE_TETHERED, result);
}
@Test
@@ -186,12 +188,10 @@
}
private void runUnTether(final TestTetheringResult result) throws Exception {
- when(mTethering.untether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringSupported();
- verify(mTethering).untether(TEST_IFACE_NAME);
- result.assertResult(TETHER_ERROR_NO_ERROR);
+ verify(mTethering).untether(eq(TEST_IFACE_NAME), eq(result));
}
@Test
@@ -217,11 +217,15 @@
}
private void runSetUsbTethering(final TestTetheringResult result) throws Exception {
- when(mTethering.setUsbTethering(true /* enable */)).thenReturn(TETHER_ERROR_NO_ERROR);
+ doAnswer((invocation) -> {
+ final IIntResultListener listener = invocation.getArgument(1);
+ listener.onResult(TETHER_ERROR_NO_ERROR);
+ return null;
+ }).when(mTethering).setUsbTethering(anyBoolean(), any(IIntResultListener.class));
mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringSupported();
- verify(mTethering).setUsbTethering(true /* enable */);
+ verify(mTethering).setUsbTethering(eq(true) /* enable */, any(IIntResultListener.class));
result.assertResult(TETHER_ERROR_NO_ERROR);
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index e042df4..48fc18d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -25,7 +25,8 @@
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.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
@@ -33,9 +34,11 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
@@ -69,7 +72,6 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.argThat;
@@ -93,6 +95,9 @@
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -199,6 +204,7 @@
private static final int IFINDEX_OFFSET = 100;
private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
+ private static final String TEST_DUN_IFNAME = "test_dun0";
private static final String TEST_XLAT_MOBILE_IFNAME = "v4-test_rmnet_data0";
private static final String TEST_USB_IFNAME = "test_rndis0";
private static final String TEST_WIFI_IFNAME = "test_wlan0";
@@ -238,6 +244,8 @@
@Mock private TetheringNotificationUpdater mNotificationUpdater;
@Mock private BpfCoordinator mBpfCoordinator;
@Mock private PackageManager mPackageManager;
+ @Mock private BluetoothAdapter mBluetoothAdapter;
+ @Mock private BluetoothPan mBluetoothPan;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -264,8 +272,6 @@
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private TestConnectivityManager mCm;
- private NetworkRequest mNetworkRequest;
- private NetworkCallback mDefaultNetworkCallback;
private class TestContext extends BroadcastInterceptingContext {
TestContext(Context base) {
@@ -336,12 +342,14 @@
|| ifName.equals(TEST_WLAN_IFNAME)
|| ifName.equals(TEST_WIFI_IFNAME)
|| ifName.equals(TEST_MOBILE_IFNAME)
+ || ifName.equals(TEST_DUN_IFNAME)
|| ifName.equals(TEST_P2P_IFNAME)
|| ifName.equals(TEST_NCM_IFNAME)
- || ifName.equals(TEST_ETH_IFNAME));
+ || ifName.equals(TEST_ETH_IFNAME)
+ || ifName.equals(TEST_BT_IFNAME));
final String[] ifaces = new String[] {
TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_WIFI_IFNAME, TEST_MOBILE_IFNAME,
- TEST_P2P_IFNAME, TEST_NCM_IFNAME, TEST_ETH_IFNAME};
+ TEST_DUN_IFNAME, TEST_P2P_IFNAME, TEST_NCM_IFNAME, TEST_ETH_IFNAME};
return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET,
MacAddress.ALL_ZEROS_ADDRESS);
}
@@ -438,11 +446,6 @@
}
@Override
- public NetworkRequest getDefaultNetworkRequest() {
- return mNetworkRequest;
- }
-
- @Override
public EntitlementManager getEntitlementManager(Context ctx, Handler h, SharedLog log,
Runnable callback) {
mEntitleMgr = spy(super.getEntitlementManager(ctx, h, log, callback));
@@ -478,8 +481,7 @@
@Override
public BluetoothAdapter getBluetoothAdapter() {
- // TODO: add test for bluetooth tethering.
- return null;
+ return mBluetoothAdapter;
}
@Override
@@ -540,8 +542,7 @@
private static NetworkCapabilities buildUpstreamCapabilities(int transport, int... otherCaps) {
// TODO: add NOT_VCN_MANAGED.
final NetworkCapabilities nc = new NetworkCapabilities()
- .addTransportType(transport)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ .addTransportType(transport);
for (int cap : otherCaps) {
nc.addCapability(cap);
}
@@ -552,7 +553,7 @@
boolean withIPv6, boolean with464xlat) {
return new UpstreamNetworkState(
buildUpstreamLinkProperties(TEST_MOBILE_IFNAME, withIPv4, withIPv6, with464xlat),
- buildUpstreamCapabilities(TRANSPORT_CELLULAR),
+ buildUpstreamCapabilities(TRANSPORT_CELLULAR, NET_CAPABILITY_INTERNET),
new Network(CELLULAR_NETID));
}
@@ -576,13 +577,13 @@
return new UpstreamNetworkState(
buildUpstreamLinkProperties(TEST_WIFI_IFNAME, true /* IPv4 */, true /* IPv6 */,
false /* 464xlat */),
- buildUpstreamCapabilities(TRANSPORT_WIFI),
+ buildUpstreamCapabilities(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET),
new Network(WIFI_NETID));
}
private static UpstreamNetworkState buildDunUpstreamState() {
return new UpstreamNetworkState(
- buildUpstreamLinkProperties(TEST_MOBILE_IFNAME, true /* IPv4 */, true /* IPv6 */,
+ buildUpstreamLinkProperties(TEST_DUN_IFNAME, true /* IPv4 */, true /* IPv6 */,
false /* 464xlat */),
buildUpstreamCapabilities(TRANSPORT_CELLULAR, NET_CAPABILITY_DUN),
new Network(DUN_NETID));
@@ -610,7 +611,7 @@
when(mNetd.interfaceGetList())
.thenReturn(new String[] {
TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME,
- TEST_NCM_IFNAME, TEST_ETH_IFNAME});
+ TEST_NCM_IFNAME, TEST_ETH_IFNAME, TEST_BT_IFNAME});
when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
mInterfaceConfiguration = new InterfaceConfigurationParcel();
mInterfaceConfiguration.flags = new String[0];
@@ -635,15 +636,7 @@
mServiceContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(ACTION_TETHER_STATE_CHANGED));
- // TODO: add NOT_VCN_MANAGED here, but more importantly in the production code.
- // TODO: even better, change TetheringDependencies.getDefaultNetworkRequest() to use
- // registerSystemDefaultNetworkCallback() on S and above.
- NetworkCapabilities defaultCaps = new NetworkCapabilities()
- .addCapability(NET_CAPABILITY_INTERNET);
- mNetworkRequest = new NetworkRequest(defaultCaps, TYPE_NONE, 1 /* requestId */,
- NetworkRequest.Type.REQUEST);
- mCm = spy(new TestConnectivityManager(mServiceContext, mock(IConnectivityManager.class),
- mNetworkRequest));
+ mCm = spy(new TestConnectivityManager(mServiceContext, mock(IConnectivityManager.class)));
mTethering = makeTethering();
verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
@@ -674,14 +667,15 @@
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
.thenReturn(new String[] { "test_rndis\\d" });
when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
- .thenReturn(new String[]{ "test_wlan\\d" });
+ .thenReturn(new String[] { "test_wlan\\d" });
when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
- .thenReturn(new String[]{ "test_p2p-p2p\\d-.*" });
+ .thenReturn(new String[] { "test_p2p-p2p\\d-.*" });
when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[0]);
+ .thenReturn(new String[] { "test_pan\\d" });
when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
.thenReturn(new String[] { "test_ncm\\d" });
- when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
+ new int[] { TYPE_WIFI, TYPE_MOBILE_DUN });
when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
}
@@ -696,17 +690,19 @@
}
private TetheringRequestParcel createTetheringRequestParcel(final int type) {
- return createTetheringRequestParcel(type, null, null, false);
+ return createTetheringRequestParcel(type, null, null, false, CONNECTIVITY_SCOPE_GLOBAL);
}
private TetheringRequestParcel createTetheringRequestParcel(final int type,
- final LinkAddress serverAddr, final LinkAddress clientAddr, final boolean exempt) {
+ final LinkAddress serverAddr, final LinkAddress clientAddr, final boolean exempt,
+ final int scope) {
final TetheringRequestParcel request = new TetheringRequestParcel();
request.tetheringType = type;
request.localIPv4Address = serverAddr;
request.staticClientAddress = clientAddr;
request.exemptFromEntitlementCheck = exempt;
request.showProvisioningUi = false;
+ request.connectivityScope = scope;
return request;
}
@@ -780,16 +776,12 @@
}
private void verifyDefaultNetworkRequestFiled() {
- ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class);
- verify(mCm, times(1)).requestNetwork(eq(mNetworkRequest),
- captor.capture(), any(Handler.class));
- mDefaultNetworkCallback = captor.getValue();
- assertNotNull(mDefaultNetworkCallback);
-
+ ArgumentCaptor<NetworkRequest> reqCaptor = ArgumentCaptor.forClass(NetworkRequest.class);
+ verify(mCm, times(1)).requestNetwork(reqCaptor.capture(),
+ any(NetworkCallback.class), any(Handler.class));
+ assertTrue(TestConnectivityManager.looksLikeDefaultRequest(reqCaptor.getValue()));
// The default network request is only ever filed once.
verifyNoMoreInteractions(mCm);
- mUpstreamNetworkMonitor.startTrackDefaultNetwork(mNetworkRequest, mEntitleMgr);
- verifyNoMoreInteractions(mCm);
}
private void verifyInterfaceServingModeStarted(String ifname) throws Exception {
@@ -928,7 +920,7 @@
verifyNoMoreInteractions(mWifiManager);
// Asking for the last error after the per-interface state machine
// has been reaped yields an unknown interface error.
- assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_WLAN_IFNAME));
+ assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastErrorForTest(TEST_WLAN_IFNAME));
}
/**
@@ -1092,15 +1084,13 @@
verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network);
}
- @Test
- public void testAutomaticUpstreamSelection() throws Exception {
+ private void upstreamSelectionTestCommon(final boolean automatic, InOrder inOrder,
+ TestNetworkAgent mobile, TestNetworkAgent wifi) throws Exception {
// Enable automatic upstream selection.
- when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(automatic);
sendConfigurationChanged();
mLooper.dispatchAll();
- InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
-
// Start USB tethering with no current upstream.
prepareUsbTethering();
sendUsbBroadcast(true, true, true, TETHERING_USB);
@@ -1108,23 +1098,31 @@
inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
// Pretend cellular connected and expect the upstream to be set.
- TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
mobile.fakeConnect();
mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
- // Switch upstreams a few times.
- TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
+ // Switch upstream to wifi.
wifi.fakeConnect();
mCm.makeDefaultNetwork(wifi, BROADCAST_FIRST);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId);
+ }
+
+ @Test
+ public void testAutomaticUpstreamSelection() throws Exception {
+ TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
+ TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
+ InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
+ // Enable automatic upstream selection.
+ upstreamSelectionTestCommon(true, inOrder, mobile, wifi);
// This code has historically been racy, so test different orderings of CONNECTIVITY_ACTION
// broadcasts and callbacks, and add mLooper.dispatchAll() calls between the two.
final Runnable doDispatchAll = () -> mLooper.dispatchAll();
+ // Switch upstreams a few times.
mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST, doDispatchAll);
mLooper.dispatchAll();
inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
@@ -1191,6 +1189,138 @@
mLooper.dispatchAll();
}
+ @Test
+ public void testLegacyUpstreamSelection() throws Exception {
+ TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
+ TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
+ InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
+ // Enable legacy upstream selection.
+ upstreamSelectionTestCommon(false, inOrder, mobile, wifi);
+
+ // Wifi disconnecting and the default network switch to mobile, the upstream should also
+ // switch to mobile.
+ wifi.fakeDisconnect();
+ mLooper.dispatchAll();
+ mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST, null);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+
+ wifi.fakeConnect();
+ mLooper.dispatchAll();
+ mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST, null);
+ mLooper.dispatchAll();
+ }
+
+ @Test
+ public void testChooseDunUpstreamByAutomaticMode() throws Exception {
+ // Enable automatic upstream selection.
+ TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
+ TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
+ TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
+ InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
+ chooseDunUpstreamTestCommon(true, inOrder, mobile, wifi, dun);
+
+ // When default network switch to mobile and wifi is connected (may have low signal),
+ // automatic mode would request dun again and choose it as upstream.
+ mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST);
+ mLooper.dispatchAll();
+ ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class);
+ inOrder.verify(mCm).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(), any());
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null);
+ final Runnable doDispatchAll = () -> mLooper.dispatchAll();
+ dun.fakeConnect(CALLBACKS_FIRST, doDispatchAll);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(dun.networkId);
+
+ // Lose and regain upstream again.
+ dun.fakeDisconnect(CALLBACKS_FIRST, doDispatchAll);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null);
+ inOrder.verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
+ dun.fakeConnect(CALLBACKS_FIRST, doDispatchAll);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(dun.networkId);
+ }
+
+ @Test
+ public void testChooseDunUpstreamByLegacyMode() throws Exception {
+ // Enable Legacy upstream selection.
+ TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
+ TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
+ TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
+ InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
+ chooseDunUpstreamTestCommon(false, inOrder, mobile, wifi, dun);
+
+ // Legacy mode would keep use wifi as upstream (because it has higher priority in the
+ // list).
+ mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(any());
+ // BUG: when wifi disconnect, the dun request would not be filed again because wifi is
+ // no longer be default network which do not have CONNECTIVIY_ACTION broadcast.
+ wifi.fakeDisconnect();
+ mLooper.dispatchAll();
+ inOrder.verify(mCm, never()).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(),
+ any());
+
+ // Change the legacy priority list that dun is higher than wifi.
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
+ new int[] { TYPE_MOBILE_DUN, TYPE_WIFI });
+ sendConfigurationChanged();
+ mLooper.dispatchAll();
+
+ // Make wifi as default network. Note: mobile also connected.
+ wifi.fakeConnect();
+ mLooper.dispatchAll();
+ mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
+ mLooper.dispatchAll();
+ // BUG: dun has higher priority than wifi but tethering don't file dun request because
+ // current upstream is wifi.
+ inOrder.verify(mCm, never()).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(),
+ any());
+ }
+
+ private void chooseDunUpstreamTestCommon(final boolean automatic, InOrder inOrder,
+ TestNetworkAgent mobile, TestNetworkAgent wifi, TestNetworkAgent dun) throws Exception {
+ when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(automatic);
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(true);
+ sendConfigurationChanged();
+ mLooper.dispatchAll();
+
+ // Start USB tethering with no current upstream.
+ prepareUsbTethering();
+ sendUsbBroadcast(true, true, true, TETHERING_USB);
+ inOrder.verify(mUpstreamNetworkMonitor).startObserveAllNetworks();
+ inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
+ ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class);
+ inOrder.verify(mCm).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(),
+ captor.capture());
+ final NetworkCallback dunNetworkCallback1 = captor.getValue();
+
+ // Pretend cellular connected and expect the upstream to be set.
+ mobile.fakeConnect();
+ mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(mobile.networkId);
+
+ // Pretend dun connected and expect choose dun as upstream.
+ final Runnable doDispatchAll = () -> mLooper.dispatchAll();
+ dun.fakeConnect(BROADCAST_FIRST, doDispatchAll);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(dun.networkId);
+
+ // When wifi connected, unregister dun request and choose wifi as upstream.
+ wifi.fakeConnect();
+ mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor).setTryCell(false);
+ inOrder.verify(mCm).unregisterNetworkCallback(eq(dunNetworkCallback1));
+ inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId);
+ dun.fakeDisconnect(BROADCAST_FIRST, doDispatchAll);
+ mLooper.dispatchAll();
+ inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(any());
+ }
+
private void runNcmTethering() {
prepareNcmTethering();
sendUsbBroadcast(true, true, true, TETHERING_NCM);
@@ -1323,7 +1453,7 @@
verifyNoMoreInteractions(mWifiManager);
// Asking for the last error after the per-interface state machine
// has been reaped yields an unknown interface error.
- assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_WLAN_IFNAME));
+ assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastErrorForTest(TEST_WLAN_IFNAME));
}
// TODO: Test with and without interfaceStatusChanged().
@@ -1791,7 +1921,7 @@
// There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_LOCAL_ONLY
verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE);
- assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastTetherError(TEST_P2P_IFNAME));
+ assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastErrorForTest(TEST_P2P_IFNAME));
// Emulate externally-visible WifiP2pManager effects, when wifi p2p group
// is being removed.
@@ -1810,7 +1940,7 @@
verifyNoMoreInteractions(mNetd);
// Asking for the last error after the per-interface state machine
// has been reaped yields an unknown interface error.
- assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
+ assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastErrorForTest(TEST_P2P_IFNAME));
}
private void workingWifiP2pGroupClient(
@@ -1840,7 +1970,7 @@
verifyNoMoreInteractions(mNetd);
// Asking for the last error after the per-interface state machine
// has been reaped yields an unknown interface error.
- assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
+ assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastErrorForTest(TEST_P2P_IFNAME));
}
@Test
@@ -1871,7 +2001,7 @@
verify(mNetd, never()).networkAddInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
verify(mNetd, never()).ipfwdEnableForwarding(TETHERING_NAME);
verify(mNetd, never()).tetherStartWithConfiguration(any());
- assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
+ assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastErrorForTest(TEST_P2P_IFNAME));
}
@Test
public void workingWifiP2pGroupOwnerLegacyModeWithIfaceChanged() throws Exception {
@@ -1967,16 +2097,14 @@
final ResultListener thirdResult = new ResultListener(TETHER_ERROR_NO_ERROR);
// Enable USB tethering and check that Tethering starts USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- null, null, false), firstResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), firstResult);
mLooper.dispatchAll();
firstResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
verifyNoMoreInteractions(mUsbManager);
// Enable USB tethering again with the same request and expect no change to USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- null, null, false), secondResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), secondResult);
mLooper.dispatchAll();
secondResult.assertHasResult();
verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -1985,7 +2113,7 @@
// Enable USB tethering with a different request and expect that USB is stopped and
// started.
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false), thirdResult);
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), thirdResult);
mLooper.dispatchAll();
thirdResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -2008,7 +2136,7 @@
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false), null);
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
@@ -2074,7 +2202,8 @@
public void testExemptFromEntitlementCheck() throws Exception {
setupForRequiredProvisioning();
final TetheringRequestParcel wifiNotExemptRequest =
- createTetheringRequestParcel(TETHERING_WIFI, null, null, false);
+ createTetheringRequestParcel(TETHERING_WIFI, null, null, false,
+ CONNECTIVITY_SCOPE_GLOBAL);
mTethering.startTethering(wifiNotExemptRequest, null);
mLooper.dispatchAll();
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
@@ -2087,7 +2216,8 @@
setupForRequiredProvisioning();
final TetheringRequestParcel wifiExemptRequest =
- createTetheringRequestParcel(TETHERING_WIFI, null, null, true);
+ createTetheringRequestParcel(TETHERING_WIFI, null, null, true,
+ CONNECTIVITY_SCOPE_GLOBAL);
mTethering.startTethering(wifiExemptRequest, null);
mLooper.dispatchAll();
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
@@ -2236,10 +2366,10 @@
mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
sendUsbBroadcast(true, true, true, TETHERING_USB);
- assertContains(Arrays.asList(mTethering.getTetherableIfaces()), TEST_USB_IFNAME);
- assertContains(Arrays.asList(mTethering.getTetherableIfaces()), TEST_ETH_IFNAME);
- assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastTetherError(TEST_USB_IFNAME));
- assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastTetherError(TEST_ETH_IFNAME));
+ assertContains(Arrays.asList(mTethering.getTetherableIfacesForTest()), TEST_USB_IFNAME);
+ assertContains(Arrays.asList(mTethering.getTetherableIfacesForTest()), TEST_ETH_IFNAME);
+ assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastErrorForTest(TEST_USB_IFNAME));
+ assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastErrorForTest(TEST_ETH_IFNAME));
}
@Test
@@ -2365,6 +2495,70 @@
return lease;
}
+ @Test
+ public void testBluetoothTethering() throws Exception {
+ final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
+ when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mLooper.dispatchAll();
+ verifySetBluetoothTethering(true);
+ result.assertHasResult();
+
+ mTethering.interfaceAdded(TEST_BT_IFNAME);
+ mLooper.dispatchAll();
+
+ mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
+ mTethering.interfaceStatusChanged(TEST_BT_IFNAME, true);
+ final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.tether(TEST_BT_IFNAME, IpServer.STATE_TETHERED, tetherResult);
+ mLooper.dispatchAll();
+ tetherResult.assertHasResult();
+
+ verify(mNetd).tetherInterfaceAdd(TEST_BT_IFNAME);
+ verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
+ verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME),
+ anyString(), anyString());
+ verify(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
+ verify(mNetd).tetherStartWithConfiguration(any());
+ verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME),
+ anyString(), anyString());
+ verifyNoMoreInteractions(mNetd);
+ reset(mNetd);
+
+ when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+ mTethering.stopTethering(TETHERING_BLUETOOTH);
+ mLooper.dispatchAll();
+ final ResultListener untetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.untether(TEST_BT_IFNAME, untetherResult);
+ mLooper.dispatchAll();
+ untetherResult.assertHasResult();
+ verifySetBluetoothTethering(false);
+
+ verify(mNetd).tetherApplyDnsInterfaces();
+ verify(mNetd).tetherInterfaceRemove(TEST_BT_IFNAME);
+ verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
+ verify(mNetd).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+ verify(mNetd).tetherStop();
+ verify(mNetd).ipfwdDisableForwarding(TETHERING_NAME);
+ verifyNoMoreInteractions(mNetd);
+ }
+
+ private void verifySetBluetoothTethering(final boolean enable) {
+ final ArgumentCaptor<ServiceListener> listenerCaptor =
+ ArgumentCaptor.forClass(ServiceListener.class);
+ verify(mBluetoothAdapter).isEnabled();
+ verify(mBluetoothAdapter).getProfileProxy(eq(mServiceContext), listenerCaptor.capture(),
+ eq(BluetoothProfile.PAN));
+ final ServiceListener listener = listenerCaptor.getValue();
+ when(mBluetoothPan.isTetheringOn()).thenReturn(enable);
+ listener.onServiceConnected(BluetoothProfile.PAN, mBluetoothPan);
+ verify(mBluetoothPan).setBluetoothTethering(enable);
+ verify(mBluetoothPan).isTetheringOn();
+ verify(mBluetoothAdapter).closeProfileProxy(eq(BluetoothProfile.PAN), eq(mBluetoothPan));
+ verifyNoMoreInteractions(mBluetoothAdapter, mBluetoothPan);
+ reset(mBluetoothAdapter, mBluetoothPan);
+ }
+
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
index bc21692..ce4ba85 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -29,10 +29,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -66,6 +66,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -83,10 +84,6 @@
private static final boolean INCLUDES = true;
private static final boolean EXCLUDES = false;
- // Actual contents of the request don't matter for this test. The lack of
- // any specific TRANSPORT_* is sufficient to identify this request.
- private static final NetworkRequest sDefaultRequest = new NetworkRequest.Builder().build();
-
private static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_INTERNET).build();
private static final NetworkCapabilities DUN_CAPABILITIES = new NetworkCapabilities.Builder()
@@ -113,9 +110,10 @@
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
- mCM = spy(new TestConnectivityManager(mContext, mCS, sDefaultRequest));
+ mCM = spy(new TestConnectivityManager(mContext, mCS));
+ when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))).thenReturn(mCM);
mSM = new TestStateMachine(mLooper.getLooper());
- mUNM = new UpstreamNetworkMonitor(mCM, mSM, mLog, EVENT_UNM_UPDATE);
+ mUNM = new UpstreamNetworkMonitor(mContext, mSM, mLog, EVENT_UNM_UPDATE);
}
@After public void tearDown() throws Exception {
@@ -146,7 +144,7 @@
@Test
public void testDefaultNetworkIsTracked() throws Exception {
assertTrue(mCM.hasNoCallbacks());
- mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
+ mUNM.startTrackDefaultNetwork(mEntitleMgr);
mUNM.startObserveAllNetworks();
assertEquals(1, mCM.mTrackingDefault.size());
@@ -159,7 +157,7 @@
public void testListensForAllNetworks() throws Exception {
assertTrue(mCM.mListening.isEmpty());
- mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
+ mUNM.startTrackDefaultNetwork(mEntitleMgr);
mUNM.startObserveAllNetworks();
assertFalse(mCM.mListening.isEmpty());
assertTrue(mCM.isListeningForAll());
@@ -170,9 +168,17 @@
@Test
public void testCallbacksRegistered() {
- mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
+ mUNM.startTrackDefaultNetwork(mEntitleMgr);
+ // Verify the fired default request matches expectation.
+ final ArgumentCaptor<NetworkRequest> requestCaptor =
+ ArgumentCaptor.forClass(NetworkRequest.class);
verify(mCM, times(1)).requestNetwork(
- eq(sDefaultRequest), any(NetworkCallback.class), any(Handler.class));
+ requestCaptor.capture(), any(NetworkCallback.class), any(Handler.class));
+ // For R- devices, Tethering will invoke this function in 2 cases, one is to
+ // request mobile network, the other is to track system default network. Verify
+ // the request is the one tracks default network.
+ assertTrue(TestConnectivityManager.looksLikeDefaultRequest(requestCaptor.getValue()));
+
mUNM.startObserveAllNetworks();
verify(mCM, times(1)).registerNetworkCallback(
any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
@@ -293,7 +299,7 @@
final Collection<Integer> preferredTypes = new ArrayList<>();
preferredTypes.add(TYPE_WIFI);
- mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
+ mUNM.startTrackDefaultNetwork(mEntitleMgr);
mUNM.startObserveAllNetworks();
// There are no networks, so there is nothing to select.
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
@@ -325,16 +331,9 @@
mUNM.setUpstreamConfig(false /* autoUpstream */, false /* dunRequired */);
assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
mUNM.selectPreferredUpstreamType(preferredTypes));
- // Check to see we filed an explicit request.
- assertEquals(1, mCM.mRequested.size());
- NetworkRequest netReq = ((NetworkRequestInfo) mCM.mRequested.values().toArray()[0]).request;
- assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
- assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
// mobile is not permitted, we should not use HIPRI.
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
- assertEquals(0, mCM.mRequested.size());
- // mobile change back to permitted, HIRPI should come back
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
mUNM.selectPreferredUpstreamType(preferredTypes));
@@ -343,7 +342,6 @@
mLooper.dispatchAll();
// WiFi is up, and we should prefer it over cell.
assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
- assertEquals(0, mCM.mRequested.size());
preferredTypes.remove(TYPE_MOBILE_HIPRI);
preferredTypes.add(TYPE_MOBILE_DUN);
@@ -363,15 +361,9 @@
mLooper.dispatchAll();
assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
mUNM.selectPreferredUpstreamType(preferredTypes));
- // Check to see we filed an explicit request.
- assertEquals(1, mCM.mRequested.size());
- netReq = ((NetworkRequestInfo) mCM.mRequested.values().toArray()[0]).request;
- assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
- assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
// mobile is not permitted, we should not use DUN.
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
- assertEquals(0, mCM.mRequested.size());
// mobile change back to permitted, DUN should come back
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
@@ -380,7 +372,7 @@
@Test
public void testGetCurrentPreferredUpstream() throws Exception {
- mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
+ mUNM.startTrackDefaultNetwork(mEntitleMgr);
mUNM.startObserveAllNetworks();
mUNM.setUpstreamConfig(true /* autoUpstream */, false /* dunRequired */);
mUNM.setTryCell(true);
@@ -452,7 +444,7 @@
@Test
public void testLocalPrefixes() throws Exception {
- mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
+ mUNM.startTrackDefaultNetwork(mEntitleMgr);
mUNM.startObserveAllNetworks();
// [0] Test minimum set of local prefixes.
@@ -563,7 +555,7 @@
// Mobile has higher pirority than wifi.
preferredTypes.add(TYPE_MOBILE_HIPRI);
preferredTypes.add(TYPE_WIFI);
- mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
+ mUNM.startTrackDefaultNetwork(mEntitleMgr);
mUNM.startObserveAllNetworks();
// Setup wifi and make wifi as default network.
final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, WIFI_CAPABILITIES);
@@ -647,4 +639,4 @@
expectation, prefixes.contains(new IpPrefix(expectedPrefix)));
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/cts/hostside/TEST_MAPPING b/tests/cts/hostside/TEST_MAPPING
index 02d2d6e..fcec483 100644
--- a/tests/cts/hostside/TEST_MAPPING
+++ b/tests/cts/hostside/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "presubmit": [
+ "presubmit-large": [
{
"name": "CtsHostsideNetworkTests",
"options": [
@@ -15,4 +15,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index 36e2ffe..0715e32 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -17,6 +17,7 @@
package com.android.cts.net.hostside;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getActiveNetworkCapabilities;
@@ -204,9 +205,12 @@
// Mark network as metered.
mMeterednessConfiguration.configureNetworkMeteredness(true);
- // Register callback
+ // Register callback, copy the capabilities from the active network to expect the "original"
+ // network before disconnecting, but null out some fields to prevent over-specified.
registerNetworkCallback(new NetworkRequest.Builder()
- .setCapabilities(networkCapabilities).build(), mTestNetworkCallback);
+ .setCapabilities(networkCapabilities.setTransportInfo(null))
+ .removeCapability(NET_CAPABILITY_NOT_METERED)
+ .setSignalStrength(SIGNAL_STRENGTH_UNSPECIFIED).build(), mTestNetworkCallback);
// Wait for onAvailable() callback to ensure network is available before the test
// and store the default network.
mNetwork = mTestNetworkCallback.expectAvailableCallbackAndGetNetwork();
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 29efb74..532fd86 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -727,8 +727,8 @@
final Handler h = new Handler(Looper.getMainLooper());
runWithShellPermissionIdentity(() -> {
mCM.registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
- mCM.registerDefaultNetworkCallbackAsUid(otherUid, otherUidCallback, h);
- mCM.registerDefaultNetworkCallbackAsUid(Process.myUid(), myUidCallback, h);
+ mCM.registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, h);
+ mCM.registerDefaultNetworkCallbackForUid(Process.myUid(), myUidCallback, h);
}, NETWORK_SETTINGS);
for (TestableNetworkCallback callback :
List.of(systemDefaultCallback, otherUidCallback, myUidCallback)) {
@@ -1149,7 +1149,7 @@
assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
final TransportInfo ti = vpnNc.getTransportInfo();
assertTrue(ti instanceof VpnTransportInfo);
- assertEquals(VpnManager.TYPE_VPN_SERVICE, ((VpnTransportInfo) ti).type);
+ assertEquals(VpnManager.TYPE_VPN_SERVICE, ((VpnTransportInfo) ti).getType());
}
private void assertDefaultProxy(ProxyInfo expected) {
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index 8a5e00f..717ccb1 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -95,7 +95,7 @@
Log.d(TAG, "unregister previous network callback: " + mNetworkCallback);
unregisterNetworkCallback();
}
- Log.d(TAG, "registering network callback");
+ Log.d(TAG, "registering network callback for " + request);
mNetworkCallback = new ConnectivityManager.NetworkCallback() {
@Override
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index a7e2bd7..3b47100 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -36,6 +36,9 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+ <!-- TODO (b/186093901): remove after fixing resource querying -->
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
<!-- This test also uses signature permissions through adopting the shell identity.
The permissions acquired that way include (probably not exhaustive) :
android.permission.MANAGE_TEST_NETWORKS
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index d67eb23..8023aa0 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -28,8 +28,6 @@
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
-import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
@@ -64,6 +62,8 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
+import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
import static com.android.testutils.MiscAsserts.assertThrows;
import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -102,6 +102,7 @@
import android.net.NetworkInfo.State;
import android.net.NetworkRequest;
import android.net.NetworkUtils;
+import android.net.ProxyInfo;
import android.net.SocketKeepalive;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
@@ -131,13 +132,12 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.ArrayUtils;
-import com.android.modules.utils.build.SdkLevel;
import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.NetworkInformationShimImpl;
import com.android.networkstack.apishim.common.ConnectivityManagerShim;
import com.android.testutils.CompatUtil;
import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRuleKt;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.SkipPresubmit;
@@ -313,7 +313,7 @@
mCtsNetUtils.disconnectFromCell();
}
- if (shouldTestSApis()) {
+ if (TestUtils.shouldTestSApis()) {
runWithShellPermissionIdentity(
() -> mCmShim.setRequireVpnForUids(false, mVpnRequiredUidRanges),
NETWORK_SETTINGS);
@@ -606,10 +606,10 @@
final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
final TestNetworkCallback perUidCallback = new TestNetworkCallback();
final Handler h = new Handler(Looper.getMainLooper());
- if (shouldTestSApis()) {
+ if (TestUtils.shouldTestSApis()) {
runWithShellPermissionIdentity(() -> {
mCmShim.registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
- mCmShim.registerDefaultNetworkCallbackAsUid(Process.myUid(), perUidCallback, h);
+ mCmShim.registerDefaultNetworkCallbackForUid(Process.myUid(), perUidCallback, h);
}, NETWORK_SETTINGS);
}
@@ -629,7 +629,7 @@
assertNotNull("Did not receive onAvailable on default network callback",
defaultNetwork);
- if (shouldTestSApis()) {
+ if (TestUtils.shouldTestSApis()) {
assertNotNull("Did not receive onAvailable on system default network callback",
systemDefaultCallback.waitForAvailable());
final Network perUidNetwork = perUidCallback.waitForAvailable();
@@ -643,7 +643,7 @@
} finally {
mCm.unregisterNetworkCallback(callback);
mCm.unregisterNetworkCallback(defaultTrackingCallback);
- if (shouldTestSApis()) {
+ if (TestUtils.shouldTestSApis()) {
mCm.unregisterNetworkCallback(systemDefaultCallback);
mCm.unregisterNetworkCallback(perUidCallback);
}
@@ -1671,7 +1671,7 @@
final Network network = mCtsNetUtils.ensureWifiConnected();
final String ssid = unquoteSSID(mWifiManager.getConnectionInfo().getSSID());
- assertNotNull("Ssid getting from WiifManager is null", ssid);
+ assertNotNull("Ssid getting from WifiManager is null", ssid);
// This package should have no NETWORK_SETTINGS permission. Verify that no ssid is contained
// in the NetworkCapabilities.
verifySsidFromQueriedNetworkCapabilities(network, ssid, false /* hasSsid */);
@@ -1718,9 +1718,13 @@
* Verify background request can only be requested when acquiring
* {@link android.Manifest.permission.NETWORK_SETTINGS}.
*/
+ @AppModeFull(reason = "Instant apps cannot create test networks")
@Test
- @IgnoreUpTo(Build.VERSION_CODES.R)
public void testRequestBackgroundNetwork() {
+ // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
+ // shims, and @IgnoreUpTo does not check that.
+ assumeTrue(TestUtils.shouldTestSApis());
+
// Create a tun interface. Use the returned interface name as the specifier to create
// a test network request.
final TestNetworkManager tnm = runWithShellPermissionIdentity(() ->
@@ -1745,14 +1749,14 @@
final TestableNetworkCallback callback = new TestableNetworkCallback();
final Handler handler = new Handler(Looper.getMainLooper());
assertThrows(SecurityException.class,
- () -> mCmShim.requestBackgroundNetwork(testRequest, handler, callback));
+ () -> mCmShim.requestBackgroundNetwork(testRequest, callback, handler));
Network testNetwork = null;
try {
// Request background test network via Shell identity which has NETWORK_SETTINGS
// permission granted.
runWithShellPermissionIdentity(
- () -> mCmShim.requestBackgroundNetwork(testRequest, handler, callback),
+ () -> mCmShim.requestBackgroundNetwork(testRequest, callback, handler),
new String[] { android.Manifest.permission.NETWORK_SETTINGS });
// Register the test network agent which has no foreground request associated to it.
@@ -1823,10 +1827,10 @@
final DetailedBlockedStatusCallback otherUidCallback = new DetailedBlockedStatusCallback();
final int myUid = Process.myUid();
- final int otherUid = UserHandle.of(5).getUid(Process.FIRST_APPLICATION_UID);
+ final int otherUid = UserHandle.getUid(5, Process.FIRST_APPLICATION_UID);
final Handler handler = new Handler(Looper.getMainLooper());
mCm.registerDefaultNetworkCallback(myUidCallback, handler);
- mCmShim.registerDefaultNetworkCallbackAsUid(otherUid, otherUidCallback, handler);
+ mCmShim.registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, handler);
final Network defaultNetwork = mCm.getActiveNetwork();
final List<DetailedBlockedStatusCallback> allCallbacks =
@@ -1861,8 +1865,10 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.R)
public void testBlockedStatusCallback() {
+ // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
+ // shims, and @IgnoreUpTo does not check that.
+ assumeTrue(TestUtils.shouldTestSApis());
runWithShellPermissionIdentity(() -> doTestBlockedStatusCallback(), NETWORK_SETTINGS);
}
@@ -1893,16 +1899,27 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.R)
public void testLegacyLockdownEnabled() {
+ // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
+ // shims, and @IgnoreUpTo does not check that.
+ assumeTrue(TestUtils.shouldTestSApis());
runWithShellPermissionIdentity(() -> doTestLegacyLockdownEnabled(), NETWORK_SETTINGS);
}
- /**
- * Whether to test S+ APIs. This requires a) that the test be running on an S+ device, and
- * b) that the code be compiled against shims new enough to access these APIs.
- */
- private boolean shouldTestSApis() {
- return SdkLevel.isAtLeastS() && ConstantsShim.VERSION > Build.VERSION_CODES.R;
+ @Test
+ public void testGetCapabilityCarrierName() {
+ assumeTrue(TestUtils.shouldTestSApis());
+ assertEquals("ENTERPRISE", NetworkInformationShimImpl.newInstance()
+ .getCapabilityCarrierName(ConstantsShim.NET_CAPABILITY_ENTERPRISE));
+ assertNull(NetworkInformationShimImpl.newInstance()
+ .getCapabilityCarrierName(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+ }
+
+ @Test
+ public void testSetGlobalProxy() {
+ assumeTrue(TestUtils.shouldTestSApis());
+ // Behavior is verified in gts. Verify exception thrown w/o permission.
+ assertThrows(SecurityException.class, () -> mCm.setGlobalProxy(
+ ProxyInfo.buildDirectProxy("example.com" /* host */, 8080 /* port */)));
}
}
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index 355b496..d08f6e9 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -44,6 +44,11 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.SkipPresubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -52,10 +57,6 @@
import java.net.InetAddress;
import java.util.Arrays;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = "Socket cannot bind in instant app mode")
public class IpSecManagerTest extends IpSecBaseTest {
@@ -692,6 +693,7 @@
}
@Test
+ @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
public void testAesCbcHmacMd5Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -724,6 +726,7 @@
}
@Test
+ @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
public void testAesCbcHmacSha1Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -756,6 +759,7 @@
}
@Test
+ @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
public void testAesCbcHmacSha256Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -788,6 +792,7 @@
}
@Test
+ @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
public void testAesCbcHmacSha384Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -820,6 +825,7 @@
}
@Test
+ @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
public void testAesCbcHmacSha512Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -852,6 +858,7 @@
}
@Test
+ @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
public void testAesGcm64Tcp6() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -884,6 +891,7 @@
}
@Test
+ @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
public void testAesGcm96Tcp6() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -916,6 +924,7 @@
}
@Test
+ @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
public void testAesGcm128Tcp6() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -1110,6 +1119,7 @@
}
@Test
+ @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
public void testCryptTcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, null, null, false, 1, false);
@@ -1117,6 +1127,7 @@
}
@Test
+ @SkipPresubmit(reason = "b/186608065 - kernel 5.10 regression in TrafficStats with ipsec")
public void testAuthTcp6() throws Exception {
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, auth, null, false, 1, false);
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 865a07a..dac2e5c 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -364,7 +364,7 @@
val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
requestNetwork(request, callback)
val agent = createNetworkAgent(context, name)
- agent.setTeardownDelayMs(0)
+ agent.setTeardownDelayMillis(0)
agent.register()
agent.markConnected()
agent.expectCallback<OnNetworkCreated>()
@@ -600,8 +600,7 @@
assertNotNull(vpnNc)
assertEquals(VpnManager.TYPE_VPN_SERVICE,
(vpnNc.transportInfo as VpnTransportInfo).type)
- // TODO: b/183938194 please fix the issue and enable following check.
- // assertEquals(mySessionId, (vpnNc.transportInfo as VpnTransportInfo).sessionId)
+ assertEquals(mySessionId, (vpnNc.transportInfo as VpnTransportInfo).sessionId)
val testAndVpn = intArrayOf(TRANSPORT_TEST, TRANSPORT_VPN)
assertTrue(hasAllTransports(vpnNc, testAndVpn))
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 9906c30..8c35b97 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -28,10 +28,13 @@
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static junit.framework.Assert.fail;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.net.MacAddress;
@@ -324,7 +327,7 @@
// TODO: 1. Refactor test cases with helper method.
// 2. Test capability that does not yet exist.
@Test @IgnoreUpTo(Build.VERSION_CODES.R)
- public void testBypassingVcnForNonInternetRequest() {
+ public void testBypassingVcn() {
// Make an empty request. Verify the NOT_VCN_MANAGED is added.
final NetworkRequest emptyRequest = new NetworkRequest.Builder().build();
assertTrue(emptyRequest.hasCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
@@ -357,12 +360,12 @@
.addCapability(NET_CAPABILITY_NOT_ROAMING).build();
assertTrue(notRoamRequest.hasCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
- // Make a internet request. Verify the NOT_VCN_MANAGED is added.
+ // Make an internet request. Verify the NOT_VCN_MANAGED is added.
final NetworkRequest internetRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_INTERNET).build();
assertTrue(internetRequest.hasCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
- // Make a internet request which explicitly removed NOT_VCN_MANAGED.
+ // Make an internet request which explicitly removed NOT_VCN_MANAGED.
// Verify the NOT_VCN_MANAGED is removed.
final NetworkRequest internetRemoveNotVcnRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
@@ -395,5 +398,54 @@
final NetworkRequest dunRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_DUN).build();
assertTrue(dunRequest.hasCapability(ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+
+ // Make an internet request but with NetworkSpecifier. Verify the NOT_VCN_MANAGED is not
+ // added.
+ final NetworkRequest internetWithSpecifierRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).addCapability(NET_CAPABILITY_INTERNET)
+ .setNetworkSpecifier(makeTestWifiSpecifier()).build();
+ assertFalse(internetWithSpecifierRequest.hasCapability(
+ ConstantsShim.NET_CAPABILITY_NOT_VCN_MANAGED));
+ }
+
+ private void verifyEqualRequestBuilt(NetworkRequest orig) {
+ try {
+ final NetworkRequestShim shim = NetworkRequestShimImpl.newInstance();
+ final NetworkRequest copy = shim.newBuilder(orig).build();
+ assertEquals(orig, copy);
+ } catch (UnsupportedApiLevelException e) {
+ fail("NetworkRequestShim.newBuilder should be supported in this SDK version");
+ }
+ }
+
+ @Test
+ public void testBuildRequestFromExistingRequestWithBuilder() {
+ assumeTrue(TestUtils.shouldTestSApis());
+ final NetworkRequest.Builder builder = new NetworkRequest.Builder();
+
+ final NetworkRequest baseRequest = builder.build();
+ verifyEqualRequestBuilt(baseRequest);
+
+ final NetworkRequest requestCellMms = builder
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_MMS)
+ .setSignalStrength(-99).build();
+ verifyEqualRequestBuilt(requestCellMms);
+
+ final NetworkRequest requestWifi = builder
+ .addTransportType(TRANSPORT_WIFI)
+ .removeTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .removeCapability(NET_CAPABILITY_MMS)
+ .setNetworkSpecifier(makeTestWifiSpecifier())
+ .setSignalStrength(-33).build();
+ verifyEqualRequestBuilt(requestWifi);
+ }
+
+ private WifiNetworkSpecifier makeTestWifiSpecifier() {
+ return new WifiNetworkSpecifier.Builder()
+ .setSsidPattern(new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL))
+ .setBssidPattern(ARBITRARY_ADDRESS, ARBITRARY_ADDRESS)
+ .build();
}
}
diff --git a/tests/cts/net/src/android/net/cts/TestUtils.java b/tests/cts/net/src/android/net/cts/TestUtils.java
new file mode 100644
index 0000000..c1100b1
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/TestUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import android.os.Build;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.networkstack.apishim.ConstantsShim;
+
+/**
+ * Utils class to provide common shared test helper methods or constants that behave differently
+ * depending on the SDK against which they are compiled.
+ */
+public class TestUtils {
+ /**
+ * Whether to test S+ APIs. This requires a) that the test be running on an S+ device, and
+ * b) that the code be compiled against shims new enough to access these APIs.
+ */
+ public static boolean shouldTestSApis() {
+ return SdkLevel.isAtLeastS() && ConstantsShim.VERSION > Build.VERSION_CODES.R;
+ }
+}
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 88172d7..d5a26c4 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.ACCESS_WIFI_STATE;
import static android.Manifest.permission.NETWORK_SETTINGS;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
@@ -87,6 +86,7 @@
private static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 10_000;
private static final int CONNECTIVITY_CHANGE_TIMEOUT_SECS = 30;
+ private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
public static final int HTTP_PORT = 80;
public static final String TEST_HOST = "connectivitycheck.gstatic.com";
public static final String HTTP_REQUEST =
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index fa52e9b..52ce83a 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -16,8 +16,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test {
- name: "CtsTetheringTest",
+java_defaults {
+ name: "CtsTetheringTestDefaults",
defaults: ["cts_defaults"],
libs: [
@@ -30,7 +30,6 @@
static_libs: [
"TetheringCommonTests",
- "TetheringIntegrationTestsLib",
"compatibility-device-util-axt",
"cts-net-utils",
"net-tests-utils",
@@ -47,14 +46,53 @@
// Change to system current when TetheringManager move to bootclass path.
platform_apis: true,
+}
+
+// Tethering CTS tests that target the latest released SDK. These tests can be installed on release
+// devices which has equal or lowner sdk version than target sdk and are useful for qualifying
+// mainline modules on release devices.
+android_test {
+ name: "CtsTetheringTestLatestSdk",
+ defaults: ["CtsTetheringTestDefaults"],
+
+ min_sdk_version: "30",
+ target_sdk_version: "30",
+
+ static_libs: [
+ "TetheringIntegrationTestsLatestSdkLib",
+ ],
+
+ test_suites: [
+ "general-tests",
+ "mts-tethering",
+ ],
+
+ test_config_template: "AndroidTestTemplate.xml",
+
+ // Include both the 32 and 64 bit versions
+ compile_multilib: "both",
+}
+
+// Tethering CTS tests for development and release. These tests always target the platform SDK
+// version, and are subject to all the restrictions appropriate to that version. Before SDK
+// finalization, these tests have a min_sdk_version of 10000, and cannot be installed on release
+// devices.
+android_test {
+ name: "CtsTetheringTest",
+ defaults: ["CtsTetheringTestDefaults"],
+
+ static_libs: [
+ "TetheringIntegrationTestsLib",
+ ],
// Tag this module as a cts test artifact
test_suites: [
"cts",
"general-tests",
- "mts-tethering",
],
+ test_config_template: "AndroidTestTemplate.xml",
+
// Include both the 32 and 64 bit versions
compile_multilib: "both",
}
diff --git a/tests/cts/tethering/AndroidTest.xml b/tests/cts/tethering/AndroidTestTemplate.xml
similarity index 89%
rename from tests/cts/tethering/AndroidTest.xml
rename to tests/cts/tethering/AndroidTestTemplate.xml
index e752e3a..491b004 100644
--- a/tests/cts/tethering/AndroidTest.xml
+++ b/tests/cts/tethering/AndroidTestTemplate.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
+<!-- Copyright (C) 2021 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.
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<configuration description="Config for CTS Tethering test cases">
+<configuration description="Config for {MODULE}">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
@@ -23,7 +23,7 @@
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsTetheringTest.apk" />
+ <option name="test-file-name" value="{MODULE}.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.tethering.cts" />