Merge changes from topic "revert-3535182-EBVRAQTHFS" into main
* changes:
Revert "Return the registry in registerNetworkAgent."
Revert "Unify registration and NM assignment in NAI"
Revert "Add a queue system server-side for NetworkAgent"
diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
index 9e4e4a1..60ca885 100644
--- a/OWNERS_core_networking_xts
+++ b/OWNERS_core_networking_xts
@@ -10,3 +10,5 @@
# In addition to cherry-picks, flaky test fixes and no-op refactors, also for
# NsdManager tests
reminv@google.com #{LAST_RESORT_SUGGESTION}
+# Only for APF firmware tests (to verify correct behaviour of the wifi APF interpreter)
+yuyanghuang@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 2878f79..531489d 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -47,15 +47,6 @@
// as the above target may have different "enabled" values
// depending on the branch
-apex_defaults {
- name: "CronetInTetheringApexDefaults",
- jni_libs: [
- "cronet_aml_components_cronet_android_cronet",
- "//external/cronet/third_party/boringssl:libcrypto",
- "//external/cronet/third_party/boringssl:libssl",
- ],
-}
-
apex {
name: "com.android.tethering",
defaults: [
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 0a66f01..2f9c3bc 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -37,6 +37,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -1258,6 +1259,44 @@
return sj.toString();
}
+ @SuppressLint("UnflaggedApi")
+ private static boolean supportsInterfaceName(int tetheringType) {
+ // TODO: Check the interface name for TETHERING_WIFI and TETHERING_WIFI_P2P once
+ // they're actually used.
+ // Suppress lint for TETHERING_VIRTUAL since this method is only used internally.
+ return tetheringType == TETHERING_VIRTUAL;
+ }
+
+ private static boolean supportsConcurrentConnectivityScopes(int tetheringType) {
+ // Currently, only WIFI supports simultaneous local and global connectivity.
+ // This can't happen for REQUEST_TYPE_EXPLICIT requests, because
+ // TetheringRequest.Builder will not allow building an explicit TetheringRequest
+ // with TETHERING_WIFI and CONNECTIVITY_SCOPE_LOCAL, but when local-only hotspot
+ // is running, there is a REQUEST_TYPE_IMPLICIT request in the serving request list.
+ return tetheringType == TETHERING_WIFI;
+ }
+
+ /**
+ * Returns true if the other TetheringRequest "fuzzy" matches this one. This is used
+ * internally to match tracked requests with external requests from API calls, and to reject
+ * additional requests that the link layer has no capacity for.
+ * @hide
+ */
+ public boolean fuzzyMatches(final TetheringRequest other) {
+ if (other == null) return false;
+ final int type = getTetheringType();
+ if (type != other.getTetheringType()) return false;
+ if (supportsInterfaceName(type)
+ && !TextUtils.equals(getInterfaceName(), other.getInterfaceName())) {
+ return false;
+ }
+ if (supportsConcurrentConnectivityScopes(type)
+ && getConnectivityScope() != other.getConnectivityScope()) {
+ return false;
+ }
+ return true;
+ }
+
/**
* @hide
*/
@@ -1279,6 +1318,8 @@
public boolean equalsIgnoreUidPackage(TetheringRequest otherRequest) {
TetheringRequestParcel parcel = getParcel();
TetheringRequestParcel otherParcel = otherRequest.getParcel();
+ // Note: Changes here should also be reflected in fuzzyMatches(TetheringRequest) when
+ // appropriate.
return parcel.requestType == otherParcel.requestType
&& parcel.tetheringType == otherParcel.tetheringType
&& Objects.equals(parcel.localIPv4Address, otherParcel.localIPv4Address)
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index d0ba431..70a3442 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -415,12 +415,6 @@
return mIpv4PrefixRequest;
}
- /** The TetheringRequest the IpServer started with. */
- @Nullable
- public TetheringRequest getTetheringRequest() {
- return mTetheringRequest;
- }
-
/**
* Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
* thread.
diff --git a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
index 3ebe4f7..9c61716 100644
--- a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
+++ b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
@@ -19,6 +19,8 @@
import static com.android.networkstack.tethering.util.TetheringUtils.createPlaceholderRequest;
import android.net.TetheringManager.TetheringRequest;
+import android.net.ip.IpServer;
+import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -28,6 +30,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Helper class to keep track of tethering requests.
@@ -36,29 +39,17 @@
* layer to start.
* 2) When the link layer is up, use {@link #getOrCreatePendingRequest(int)} to get a request to
* start IP serving with.
- * 3) Remove all pending requests with {@link #removeAllPendingRequests(int)}.
+ * 3) Remove pending request with {@link #removePendingRequest(TetheringRequest)}.
* Note: This class is not thread-safe.
- * TODO: Add the pending IIntResultListeners at the same time as the pending requests, and
- * call them when we get the tether result.
- * TODO: Add support for multiple Bluetooth requests before the PAN service connects instead of
- * using a separate mPendingPanRequestListeners.
- * TODO: Add support for fuzzy-matched requests.
*/
public class RequestTracker {
private static final String TAG = RequestTracker.class.getSimpleName();
- private class PendingRequest {
- @NonNull
- private final TetheringRequest mTetheringRequest;
+ @NonNull
+ private final boolean mUseFuzzyMatching;
- private PendingRequest(@NonNull TetheringRequest tetheringRequest) {
- mTetheringRequest = tetheringRequest;
- }
-
- @NonNull
- TetheringRequest getTetheringRequest() {
- return mTetheringRequest;
- }
+ public RequestTracker(boolean useFuzzyMatching) {
+ mUseFuzzyMatching = useFuzzyMatching;
}
public enum AddResult {
@@ -69,24 +60,51 @@
/**
* Failure indicating that the request could not be added due to a request of the same type
* with conflicting parameters already pending. If so, we must stop tethering for the
- * pending request before trying to add the result again.
+ * pending request before trying to add the result again. Only returned on V-.
*/
- FAILURE_CONFLICTING_PENDING_REQUEST
+ FAILURE_DUPLICATE_REQUEST_RESTART,
+ /**
+ * Failure indicating that the request could not be added due to a fuzzy-matched request
+ * already pending or serving. Only returned on B+.
+ */
+ FAILURE_DUPLICATE_REQUEST_ERROR,
}
/**
- * List of pending requests added by {@link #addPendingRequest(TetheringRequest)}. There can be
- * only one per type, since we remove every request of the same type when we add a request.
+ * List of pending requests added by {@link #addPendingRequest(TetheringRequest)}
+ * There can be only one per type, since we remove every request of the
+ * same type when we add a request.
*/
- private final List<PendingRequest> mPendingRequests = new ArrayList<>();
+ private final List<TetheringRequest> mPendingRequests = new ArrayList<>();
+ /**
+ * List of serving requests added by
+ * {@link #promoteRequestToServing(IpServer, TetheringRequest)}.
+ */
+ private final Map<IpServer, TetheringRequest> mServingRequests = new ArrayMap<>();
@VisibleForTesting
List<TetheringRequest> getPendingTetheringRequests() {
- List<TetheringRequest> requests = new ArrayList<>();
- for (PendingRequest pendingRequest : mPendingRequests) {
- requests.add(pendingRequest.getTetheringRequest());
+ return new ArrayList<>(mPendingRequests);
+ }
+
+ /**
+ * Adds a pending request or fails with FAILURE_CONFLICTING_REQUEST_FAIL if the request
+ * fuzzy-matches an existing request (either pending or serving).
+ */
+ public AddResult addPendingRequestFuzzyMatched(@NonNull final TetheringRequest newRequest) {
+ List<TetheringRequest> existingRequests = new ArrayList<>();
+ existingRequests.addAll(mServingRequests.values());
+ existingRequests.addAll(mPendingRequests);
+ for (TetheringRequest request : existingRequests) {
+ if (request.fuzzyMatches(newRequest)) {
+ Log.i(TAG, "Cannot add pending request due to existing fuzzy-matched "
+ + "request: " + request);
+ return AddResult.FAILURE_DUPLICATE_REQUEST_ERROR;
+ }
}
- return requests;
+
+ mPendingRequests.add(newRequest);
+ return AddResult.SUCCESS;
}
/**
@@ -95,9 +113,12 @@
* layer comes up. The result of the add operation will be returned as an AddResult code.
*/
public AddResult addPendingRequest(@NonNull final TetheringRequest newRequest) {
+ if (mUseFuzzyMatching) {
+ return addPendingRequestFuzzyMatched(newRequest);
+ }
+
// Check the existing requests to see if it is OK to add the new request.
- for (PendingRequest request : mPendingRequests) {
- TetheringRequest existingRequest = request.getTetheringRequest();
+ for (TetheringRequest existingRequest : mPendingRequests) {
if (existingRequest.getTetheringType() != newRequest.getTetheringType()) {
continue;
}
@@ -105,7 +126,7 @@
// Can't add request if there's a request of the same type with different
// parameters.
if (!existingRequest.equalsIgnoreUidPackage(newRequest)) {
- return AddResult.FAILURE_CONFLICTING_PENDING_REQUEST;
+ return AddResult.FAILURE_DUPLICATE_REQUEST_RESTART;
}
}
@@ -113,7 +134,7 @@
// conflicting parameters above, so these would have been equivalent anyway (except for
// UID).
removeAllPendingRequests(newRequest.getTetheringType());
- mPendingRequests.add(new PendingRequest(newRequest));
+ mPendingRequests.add(newRequest);
return AddResult.SUCCESS;
}
@@ -145,10 +166,8 @@
*/
@Nullable
public TetheringRequest getNextPendingRequest(int type) {
- for (PendingRequest pendingRequest : mPendingRequests) {
- TetheringRequest tetheringRequest =
- pendingRequest.getTetheringRequest();
- if (tetheringRequest.getTetheringType() == type) return tetheringRequest;
+ for (TetheringRequest request : mPendingRequests) {
+ if (request.getTetheringType() == type) return request;
}
return null;
}
@@ -158,7 +177,83 @@
*
* @param type Tethering type
*/
- public void removeAllPendingRequests(int type) {
- mPendingRequests.removeIf(r -> r.getTetheringRequest().getTetheringType() == type);
+ public void removeAllPendingRequests(final int type) {
+ mPendingRequests.removeIf(r -> r.getTetheringType() == type);
+ }
+
+ /**
+ * Removes a specific pending request.
+ *
+ * Note: For V-, this will be the same as removeAllPendingRequests to align with historical
+ * behavior.
+ *
+ * @param request Request to be removed
+ */
+ public void removePendingRequest(@NonNull TetheringRequest request) {
+ if (!mUseFuzzyMatching) {
+ // Remove all requests of the same type to match the historical behavior.
+ removeAllPendingRequests(request.getTetheringType());
+ return;
+ }
+
+ mPendingRequests.removeIf(r -> r.equals(request));
+ }
+
+ /**
+ * Removes a tethering request from the pending list and promotes it to serving with the
+ * IpServer that is using it.
+ * Note: If mUseFuzzyMatching is false, then the request will be removed from the pending list,
+ * but it will not be added to serving list.
+ */
+ public void promoteRequestToServing(@NonNull final IpServer ipServer,
+ @NonNull final TetheringRequest tetheringRequest) {
+ removePendingRequest(tetheringRequest);
+ if (!mUseFuzzyMatching) return;
+ mServingRequests.put(ipServer, tetheringRequest);
+ }
+
+
+ /**
+ * Returns the serving request tied to the given IpServer, or null if there is none.
+ * Note: If mUseFuzzyMatching is false, then this will always return null.
+ */
+ @Nullable
+ public TetheringRequest getServingRequest(@NonNull final IpServer ipServer) {
+ return mServingRequests.get(ipServer);
+ }
+
+ /**
+ * Removes the serving request tied to the given IpServer.
+ * Note: If mUseFuzzyMatching is false, then this is a no-op since serving requests are unused
+ * for that configuration.
+ */
+ public void removeServingRequest(@NonNull final IpServer ipServer) {
+ mServingRequests.remove(ipServer);
+ }
+
+ /**
+ * Removes all serving requests of the given tethering type.
+ *
+ * @param type Tethering type
+ */
+ public void removeAllServingRequests(final int type) {
+ mServingRequests.entrySet().removeIf(e -> e.getValue().getTetheringType() == type);
+ }
+
+ /**
+ * Returns an existing (pending or serving) request that fuzzy matches the given request.
+ * Optionally specify matchUid to only return requests with the same uid.
+ */
+ public TetheringRequest findFuzzyMatchedRequest(
+ @NonNull final TetheringRequest tetheringRequest, boolean matchUid) {
+ List<TetheringRequest> allRequests = new ArrayList<>();
+ allRequests.addAll(getPendingTetheringRequests());
+ allRequests.addAll(mServingRequests.values());
+ for (TetheringRequest request : allRequests) {
+ if (!request.fuzzyMatches(tetheringRequest)) continue;
+ if (matchUid && tetheringRequest.getUid() != request.getUid()) continue;
+ return request;
+ }
+ return null;
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 1a26658..c7ae353 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -43,6 +43,7 @@
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHERING_WIGIG;
import static android.net.TetheringManager.TETHER_ERROR_BLUETOOTH_SERVICE_PENDING;
+import static android.net.TetheringManager.TETHER_ERROR_DUPLICATE_REQUEST;
import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
@@ -67,6 +68,8 @@
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.networkstack.tethering.RequestTracker.AddResult.FAILURE_DUPLICATE_REQUEST_ERROR;
+import static com.android.networkstack.tethering.RequestTracker.AddResult.FAILURE_DUPLICATE_REQUEST_RESTART;
import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS;
import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
import static com.android.networkstack.tethering.UpstreamNetworkMonitor.isCellular;
@@ -145,7 +148,6 @@
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.modules.utils.build.SdkLevel;
-import com.android.net.flags.Flags;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.HandlerUtils;
@@ -309,7 +311,7 @@
mLooper = mDeps.makeTetheringLooper();
mNotificationUpdater = mDeps.makeNotificationUpdater(mContext, mLooper);
mTetheringMetrics = mDeps.makeTetheringMetrics(mContext);
- mRequestTracker = new RequestTracker();
+ mRequestTracker = new RequestTracker(isTetheringWithSoftApConfigEnabled());
mTetherStates = new ArrayMap<>();
mConnectedClientsTracker = new ConnectedClientsTracker();
@@ -461,7 +463,7 @@
}
boolean isTetheringWithSoftApConfigEnabled() {
- return SdkLevel.isAtLeastB() && Flags.tetheringWithSoftApConfig();
+ return mDeps.isTetheringWithSoftApConfigEnabled();
}
/**
@@ -704,9 +706,16 @@
RequestTracker.AddResult result = mRequestTracker.addPendingRequest(request);
// If tethering is already pending with a conflicting request, stop tethering before
// starting.
- if (result == RequestTracker.AddResult.FAILURE_CONFLICTING_PENDING_REQUEST) {
+ if (result == FAILURE_DUPLICATE_REQUEST_RESTART) {
stopTetheringInternal(type); // Also removes the request from the tracker.
mRequestTracker.addPendingRequest(request);
+ } else if (result == FAILURE_DUPLICATE_REQUEST_ERROR) {
+ // Reject any fuzzy matched request.
+ // TODO: Add a CTS test to verify back-to-back start/stop calls succeed. This must
+ // be for a non-Wifi type, since Wifi will reject the start calls if it hasn't
+ // brought down the SoftAP yet.
+ sendTetherResult(listener, TETHER_ERROR_DUPLICATE_REQUEST);
+ return;
}
if (request.isExemptFromEntitlementCheck()) {
@@ -726,39 +735,28 @@
});
}
- private boolean isTetheringTypePendingOrServing(final int type) {
- if (mRequestTracker.getNextPendingRequest(type) != null) return true;
- for (TetherState state : mTetherStates.values()) {
- // TODO: isCurrentlyServing only starts returning true once the IpServer has processed
- // the CMD_TETHER_REQUESTED. Ensure that we consider the request to be serving even when
- // that has not happened yet.
- if (state.isCurrentlyServing() && state.ipServer.interfaceType() == type) return true;
- }
- return false;
- }
-
void stopTetheringRequest(@NonNull final TetheringRequest request,
@NonNull final IIntResultListener listener) {
if (!isTetheringWithSoftApConfigEnabled()) return;
+ final boolean hasNetworkSettings = hasCallingPermission(NETWORK_SETTINGS);
mHandler.post(() -> {
- final int type = request.getTetheringType();
- if (isTetheringTypePendingOrServing(type)) {
+ if (mRequestTracker.findFuzzyMatchedRequest(request, !hasNetworkSettings) != null) {
+ final int type = request.getTetheringType();
stopTetheringInternal(type);
- try {
- listener.onResult(TETHER_ERROR_NO_ERROR);
- } catch (RemoteException ignored) { }
+ // TODO: We should send the success result after the waiting for tethering to
+ // actually stop.
+ sendTetherResult(listener, TETHER_ERROR_NO_ERROR);
return;
}
// Request doesn't match any active requests, ignore.
- try {
- listener.onResult(TETHER_ERROR_UNKNOWN_REQUEST);
- } catch (RemoteException ignored) { }
+ sendTetherResult(listener, TETHER_ERROR_UNKNOWN_REQUEST);
});
}
void stopTetheringInternal(int type) {
mRequestTracker.removeAllPendingRequests(type);
+ mRequestTracker.removeAllServingRequests(type);
// Using a placeholder here is ok since none of the disable APIs use the request for
// anything. We simply need the tethering type to know which link layer to poke for removal.
@@ -802,21 +800,25 @@
// The result of Bluetooth tethering will be sent after the pan service connects.
if (result == TETHER_ERROR_BLUETOOTH_SERVICE_PENDING) return;
- sendTetherResult(listener, result, type);
+ sendTetherResultAndRemoveOnError(request, listener, result);
}
- private void sendTetherResult(final IIntResultListener listener, final int result,
- final int type) {
+ private void sendTetherResult(final IIntResultListener listener, final int result) {
if (listener != null) {
try {
listener.onResult(result);
- } catch (RemoteException e) { }
+ } catch (RemoteException e) {
+ }
}
+ }
- // If changing tethering fail, remove corresponding request
- // no matter who trigger the start/stop.
+ private void sendTetherResultAndRemoveOnError(TetheringRequest request,
+ final IIntResultListener listener, final int result) {
+ sendTetherResult(listener, result);
+
if (result != TETHER_ERROR_NO_ERROR) {
- mRequestTracker.removeAllPendingRequests(type);
+ mRequestTracker.removePendingRequest(request);
+ final int type = request.getTetheringType();
mTetheringMetrics.updateErrorCode(type, result);
mTetheringMetrics.sendReport(type);
}
@@ -862,8 +864,7 @@
// the service just to stop tethering since tethering is not started. Just remove
// any pending request to enable tethering, and notify them that they have failed.
if (mPendingPanRequestListener != null) {
- sendTetherResult(mPendingPanRequestListener, TETHER_ERROR_SERVICE_UNAVAIL,
- TETHERING_BLUETOOTH);
+ sendTetherResult(mPendingPanRequestListener, TETHER_ERROR_SERVICE_UNAVAIL);
}
mPendingPanRequestListener = null;
return TETHER_ERROR_NO_ERROR;
@@ -904,7 +905,10 @@
if (mPendingPanRequestListener != null) {
final int result = setBluetoothTetheringSettings(mBluetoothPan,
true /* enable */);
- sendTetherResult(mPendingPanRequestListener, result, TETHERING_BLUETOOTH);
+ sendTetherResultAndRemoveOnError(
+ mRequestTracker.getOrCreatePendingRequest(TETHERING_BLUETOOTH),
+ mPendingPanRequestListener,
+ result);
}
mPendingPanRequestListener = null;
});
@@ -918,8 +922,10 @@
mIsConnected = false;
if (mPendingPanRequestListener != null) {
- sendTetherResult(mPendingPanRequestListener, TETHER_ERROR_SERVICE_UNAVAIL,
- TETHERING_BLUETOOTH);
+ sendTetherResultAndRemoveOnError(
+ mRequestTracker.getOrCreatePendingRequest(TETHERING_BLUETOOTH),
+ mPendingPanRequestListener,
+ TETHER_ERROR_SERVICE_UNAVAIL);
}
mPendingPanRequestListener = null;
mBluetoothIfaceRequest = null;
@@ -1093,9 +1099,7 @@
final int type = ifaceNameToType(iface);
if (type == TETHERING_INVALID) {
Log.e(TAG, "Ignoring call to legacy tether for unknown iface " + iface);
- try {
- listener.onResult(TETHER_ERROR_UNKNOWN_IFACE);
- } catch (RemoteException e) { }
+ sendTetherResult(listener, TETHER_ERROR_UNKNOWN_IFACE);
}
TetheringRequest request = mRequestTracker.getNextPendingRequest(type);
@@ -1132,9 +1136,7 @@
// Do nothing
break;
}
- try {
- listener.onResult(result);
- } catch (RemoteException e) { }
+ sendTetherResult(listener, result);
}
/**
@@ -1167,11 +1169,9 @@
Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring");
return TETHER_ERROR_UNAVAIL_IFACE;
}
+ mRequestTracker.promoteRequestToServing(tetherState.ipServer, request);
// NOTE: If a CMD_TETHER_REQUESTED message is already in the IpServer'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.
- mRequestTracker.removeAllPendingRequests(request.getTetheringType());
tetherState.ipServer.enable(request);
if (request.getRequestType() == REQUEST_TYPE_PLACEHOLDER) {
TerribleErrorLog.logTerribleError(TetheringStatsLog::write,
@@ -1191,10 +1191,7 @@
return;
}
mHandler.post(() -> {
- try {
- listener.onResult(legacyUntetherInternal(iface));
- } catch (RemoteException e) {
- }
+ sendTetherResult(listener, legacyUntetherInternal(iface));
});
}
@@ -1209,7 +1206,7 @@
Log.e(TAG, "Tried to untether an inactive iface :" + iface + ", ignoring");
return TETHER_ERROR_UNAVAIL_IFACE;
}
- tetherState.ipServer.unwanted();
+ ensureIpServerUnwanted(tetherState.ipServer);
return TETHER_ERROR_NO_ERROR;
}
@@ -1289,7 +1286,12 @@
final TetherState tetherState = mTetherStates.valueAt(i);
final int type = tetherState.ipServer.interfaceType();
final String iface = mTetherStates.keyAt(i);
- final TetheringRequest request = tetherState.ipServer.getTetheringRequest();
+ // Note: serving requests are only populated on B+. B+ also uses the sync state
+ // machine by default. This ensures that the serving request is (correctly) populated
+ // after the IpServer enters the available state and before it enters the serving
+ // state.
+ final TetheringRequest request =
+ mRequestTracker.getServingRequest(tetherState.ipServer);
final boolean includeSoftApConfig = request != null && cookie != null
&& (cookie.uid == request.getUid() || cookie.hasSystemPrivilege);
final TetheringInterface tetheringIface = new TetheringInterface(type, iface,
@@ -1611,7 +1613,7 @@
private void disableWifiIpServingCommon(int tetheringType, String ifname) {
if (!TextUtils.isEmpty(ifname) && mTetherStates.containsKey(ifname)) {
- mTetherStates.get(ifname).ipServer.unwanted();
+ ensureIpServerUnwanted(mTetherStates.get(ifname).ipServer);
return;
}
@@ -1628,7 +1630,7 @@
for (int i = 0; i < mTetherStates.size(); i++) {
final IpServer ipServer = mTetherStates.valueAt(i).ipServer;
if (ipServer.interfaceType() == tetheringType) {
- ipServer.unwanted();
+ ensureIpServerUnwanted(ipServer);
return;
}
}
@@ -1775,9 +1777,7 @@
void setUsbTethering(boolean enable, IIntResultListener listener) {
mHandler.post(() -> {
- try {
- listener.onResult(setUsbTethering(enable));
- } catch (RemoteException e) { }
+ sendTetherResult(listener, setUsbTethering(enable));
});
}
@@ -2904,6 +2904,9 @@
tetherState.lastState = state;
tetherState.lastError = lastError;
} else {
+ // Note: Even if an IpServer exists for this iface, it may be different from "who"
+ // if a new IpServer fills the gap before the IpServer.STATE_UNAVAILABLE transition.
+ // TODO: remove this comment once the sync state machine is enabled everywhere.
if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
}
@@ -2918,7 +2921,19 @@
int which;
switch (state) {
case IpServer.STATE_UNAVAILABLE:
+ which = TetherMainSM.EVENT_IFACE_SERVING_STATE_INACTIVE;
+ break;
case IpServer.STATE_AVAILABLE:
+ if (lastError != TETHER_ERROR_NO_ERROR) {
+ // IpServer transitioned from an enabled state (STATE_TETHERED or
+ // STATE_LOCAL_ONLY) back to STATE_AVAILABLE due to an error, so make sure
+ // we remove the serving request from RequestTracker.
+ // TODO: don't continue to use IpServers after they have hit an error, and
+ // instead move them to STATE_UNAVAILABLE. This code can then
+ // unconditionally remove the serving request whenever the IpServer enters
+ // STATE_UNAVAILABLE.
+ mRequestTracker.removeServingRequest(who);
+ }
which = TetherMainSM.EVENT_IFACE_SERVING_STATE_INACTIVE;
break;
case IpServer.STATE_TETHERED:
@@ -3014,11 +3029,18 @@
final TetherState tetherState = mTetherStates.get(iface);
if (tetherState == null) return;
+ mRequestTracker.removeServingRequest(tetherState.ipServer);
tetherState.ipServer.stop();
mLog.i("removing IpServer for: " + iface);
mTetherStates.remove(iface);
}
+ private void ensureIpServerUnwanted(final IpServer ipServer) {
+ mLog.i("unrequesting IpServer: " + ipServer);
+ mRequestTracker.removeServingRequest(ipServer);
+ ipServer.unwanted();
+ }
+
private static String[] copy(String[] strarray) {
return Arrays.copyOf(strarray, strarray.length);
}
@@ -3026,9 +3048,7 @@
void setPreferTestNetworks(final boolean prefer, IIntResultListener listener) {
mHandler.post(() -> {
mUpstreamNetworkMonitor.setPreferTestNetworks(prefer);
- try {
- listener.onResult(TETHER_ERROR_NO_ERROR);
- } catch (RemoteException e) { }
+ sendTetherResult(listener, TETHER_ERROR_NO_ERROR);
});
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index bd35cf2..00a7f09 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -37,7 +37,6 @@
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
-import com.android.net.flags.Flags;
import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.RoutingCoordinatorService;
import com.android.net.module.util.SharedLog;
@@ -211,9 +210,9 @@
}
/**
- * Wrapper for tethering_with_soft_ap_config feature flag.
+ * Returns true if the tethering with soft ap config feature is enabled.
*/
public boolean isTetheringWithSoftApConfigEnabled() {
- return SdkLevel.isAtLeastB() && Flags.tetheringWithSoftApConfig();
+ return SdkLevel.isAtLeastB();
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index b553f46..f501a50 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -173,7 +173,21 @@
@Override
public void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
boolean showEntitlementUi, String callerPkg, String callingAttributionTag) {
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, receiver)) return;
+ // Wrap the app-provided ResultReceiver in an IIntResultListener in order to call
+ // checkAndNotifyCommonError with it.
+ IIntResultListener listener = new IIntResultListener() {
+ @Override
+ public void onResult(int i) {
+ receiver.send(i, null);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException("asBinder unexpectedly called on"
+ + " internal-only listener");
+ }
+ };
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
mTethering.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
}
@@ -277,27 +291,6 @@
return false;
}
- private boolean checkAndNotifyCommonError(final String callerPkg,
- final String callingAttributionTag, final ResultReceiver receiver) {
- if (!checkPackageNameMatchesUid(getBinderCallingUid(), callerPkg)) {
- Log.e(TAG, "Package name " + callerPkg + " does not match UID "
- + getBinderCallingUid());
- receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
- return true;
- }
- if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
- false /* onlyAllowPrivileged */)) {
- receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
- return true;
- }
- if (!mTethering.isTetheringSupported() || !mTethering.isTetheringAllowed()) {
- receiver.send(TETHER_ERROR_UNSUPPORTED, null);
- return true;
- }
-
- return false;
- }
-
private boolean hasNetworkSettingsPermission() {
return checkCallingOrSelfPermission(NETWORK_SETTINGS);
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java
index e00e9f0..086f2d2 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java
@@ -18,19 +18,22 @@
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static com.android.networkstack.tethering.util.TetheringUtils.createPlaceholderRequest;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+
import android.net.TetheringManager.TetheringRequest;
+import android.net.ip.IpServer;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.networkstack.tethering.RequestTracker.AddResult;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,13 +42,10 @@
public class RequestTrackerTest {
private RequestTracker mRequestTracker;
- @Before
- public void setUp() {
- mRequestTracker = new RequestTracker();
- }
-
@Test
public void testNoRequestsAdded_noPendingRequests() {
+ mRequestTracker = new RequestTracker(false);
+
assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
.isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
@@ -53,6 +53,7 @@
@Test
public void testAddRequest_successResultAndBecomesNextPending() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
final AddResult result = mRequestTracker.addPendingRequest(request);
@@ -64,6 +65,7 @@
@Test
public void testAddRequest_equalRequestExists_successResultAndBecomesNextPending() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
mRequestTracker.addPendingRequest(request);
@@ -77,6 +79,7 @@
@Test
public void testAddRequest_equalButDifferentUidRequest_successResultAndBecomesNextPending() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
request.setUid(1000);
request.setPackageName("package");
@@ -94,7 +97,8 @@
}
@Test
- public void testAddConflictingRequest_returnsFailureConflictingPendingRequest() {
+ public void testAddRequest_conflictingPendingRequest_returnsFailureConflictingRequestRestart() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
final TetheringRequest conflictingRequest = new TetheringRequest.Builder(TETHERING_WIFI)
.setExemptFromEntitlementCheck(true).build();
@@ -102,13 +106,139 @@
final AddResult result = mRequestTracker.addPendingRequest(conflictingRequest);
- assertThat(result).isEqualTo(AddResult.FAILURE_CONFLICTING_PENDING_REQUEST);
+ assertThat(result).isEqualTo(AddResult.FAILURE_DUPLICATE_REQUEST_RESTART);
assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
}
@Test
+ public void testAddRequest_noExistingRequestsFuzzyMatching_returnsSuccess() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+
+ final AddResult result = mRequestTracker.addPendingRequest(request);
+
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ }
+
+ @Test
+ public void testAddRequest_conflictingPendingRequestFuzzyMatching_returnsFailure() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ final TetheringRequest conflictingRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setExemptFromEntitlementCheck(true).build();
+ mRequestTracker.addPendingRequest(request);
+
+ final AddResult result = mRequestTracker.addPendingRequest(conflictingRequest);
+
+ assertThat(result).isEqualTo(AddResult.FAILURE_DUPLICATE_REQUEST_ERROR);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ }
+
+ @Test
+ public void testAddRequest_conflictingServingRequestFuzzyMatching_returnsFailure() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ final TetheringRequest conflictingRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setExemptFromEntitlementCheck(true).build();
+ mRequestTracker.promoteRequestToServing(mock(IpServer.class), request);
+
+ final AddResult result = mRequestTracker.addPendingRequest(conflictingRequest);
+
+ assertThat(result).isEqualTo(AddResult.FAILURE_DUPLICATE_REQUEST_ERROR);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
+ }
+
+ @Test
+ public void testAddRequest_nonMatchingPendingRequestFuzzyMatching_returnsSuccess() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_VIRTUAL).build();
+ final TetheringRequest nonFuzzyMatched = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+ .setInterfaceName("iface")
+ .build();
+ mRequestTracker.addPendingRequest(request);
+
+ final AddResult result = mRequestTracker.addPendingRequest(nonFuzzyMatched);
+
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ // Next request is still the first, but verify RequestTracker contains the second request by
+ // seeing if it rejects anything matching the second request
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_VIRTUAL)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_VIRTUAL))
+ .isEqualTo(request);
+ assertThat(mRequestTracker.addPendingRequestFuzzyMatched(nonFuzzyMatched))
+ .isEqualTo(AddResult.FAILURE_DUPLICATE_REQUEST_ERROR);
+ }
+
+ @Test
+ public void testAddRequest_nonMatchingServingRequestFuzzyMatching_returnsSuccess() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_VIRTUAL).build();
+ final TetheringRequest nonFuzzyMatched = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+ .setInterfaceName("iface")
+ .build();
+ mRequestTracker.promoteRequestToServing(mock(IpServer.class), request);
+
+ final AddResult result = mRequestTracker.addPendingRequest(nonFuzzyMatched);
+
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_VIRTUAL))
+ .isEqualTo(nonFuzzyMatched);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_VIRTUAL))
+ .isEqualTo(nonFuzzyMatched);
+ }
+
+ @Test
+ public void testRemovePendingRequest_removesAllPendingRequestsOfType() {
+ mRequestTracker = new RequestTracker(false);
+ final TetheringRequest request1 = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ request1.setUid(1000);
+ request1.setPackageName("package");
+ mRequestTracker.addPendingRequest(request1);
+ final TetheringRequest request2 = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ request2.setUid(2000);
+ request2.setPackageName("package2");
+
+ mRequestTracker.removePendingRequest(request2);
+
+ // Verify request1 isn't pending even though we tried to remove a different request
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
+ }
+
+ @Test
+ public void testRemovePendingRequest_fuzzyMatching_onlyTheEqualRequestIsRemoved() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request1 = new TetheringRequest.Builder(TETHERING_VIRTUAL).build();
+ final TetheringRequest request2 = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+ .setInterfaceName("iface")
+ .build();
+ mRequestTracker.addPendingRequest(request1);
+ mRequestTracker.addPendingRequest(request2);
+
+ mRequestTracker.removePendingRequest(request2);
+
+ // Verify request1 is still pending.
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_VIRTUAL)).isEqualTo(request1);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_VIRTUAL))
+ .isEqualTo(request1);
+ assertThat(mRequestTracker.addPendingRequestFuzzyMatched(request1))
+ .isEqualTo(AddResult.FAILURE_DUPLICATE_REQUEST_ERROR);
+ // Verify we've removed request2 by checking if it can be added back without
+ // FAILURE_CONFLICTING_REQUEST_FAIL.
+ assertThat(mRequestTracker.addPendingRequestFuzzyMatched(request2))
+ .isEqualTo(AddResult.SUCCESS);
+ }
+
+ @Test
public void testRemoveAllPendingRequests_noPendingRequestsLeft() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest firstRequest = new TetheringRequest.Builder(TETHERING_WIFI).build();
firstRequest.setUid(1000);
firstRequest.setPackageName("package");
@@ -127,6 +257,7 @@
@Test
public void testRemoveAllPendingRequests_differentTypeExists_doesNotRemoveDifferentType() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest differentType = new TetheringRequest.Builder(TETHERING_USB).build();
mRequestTracker.addPendingRequest(differentType);
@@ -136,4 +267,61 @@
assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_USB))
.isEqualTo(differentType);
}
+
+ @Test
+ public void testPromoteRequestToServing_requestIsntPendingAnymore() {
+ mRequestTracker = new RequestTracker(false);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ mRequestTracker.addPendingRequest(request);
+
+ mRequestTracker.promoteRequestToServing(mock(IpServer.class), request);
+
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
+ }
+
+ @Test
+ public void testPromoteRequestToServing_fuzzyMatching_requestIsntPendingAnymore() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ mRequestTracker.addPendingRequest(request);
+
+ mRequestTracker.promoteRequestToServing(mock(IpServer.class), request);
+
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
+ }
+
+ @Test
+ public void testRemoveServingRequest_fuzzyMatching_requestCanBeAddedAgain() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ mRequestTracker.addPendingRequest(request);
+ IpServer ipServer = mock(IpServer.class);
+ mRequestTracker.promoteRequestToServing(ipServer, request);
+
+ mRequestTracker.removeServingRequest(ipServer);
+
+ AddResult result = mRequestTracker.addPendingRequest(request);
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ }
+
+ @Test
+ public void testRemoveAllServingRequests_fuzzyMatching_requestCanBeAddedAgain() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ mRequestTracker.addPendingRequest(request);
+ mRequestTracker.promoteRequestToServing(mock(IpServer.class), request);
+
+ mRequestTracker.removeAllServingRequests(TETHERING_WIFI);
+
+ AddResult result = mRequestTracker.addPendingRequest(request);
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ }
}
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 2a22c6d..1083ef9 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -50,15 +50,18 @@
import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_DUPLICATE_REQUEST;
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_REQUEST;
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -308,8 +311,7 @@
@Mock private TetheringMetrics mTetheringMetrics;
@Mock private PrivateAddressCoordinator.Dependencies mPrivateAddressCoordinatorDependencies;
- private final MockIpServerDependencies mIpServerDependencies =
- spy(new MockIpServerDependencies());
+ private MockIpServerDependencies mIpServerDependencies;
private final MockTetheringDependencies mTetheringDependencies =
new MockTetheringDependencies();
@@ -340,6 +342,7 @@
private TestConnectivityManager mCm;
private boolean mForceEthernetServiceUnavailable = false;
private int mBinderCallingUid = TEST_CALLER_UID;
+ private boolean mTetheringWithSoftApConfigEnabled = SdkLevel.isAtLeastB();
private class TestContext extends BroadcastInterceptingContext {
TestContext(Context base) {
@@ -396,6 +399,9 @@
}
public class MockIpServerDependencies extends IpServer.Dependencies {
+
+ private int mOnDhcpServerCreatedResult = STATUS_SUCCESS;
+
@Override
public DadProxy getDadProxy(
Handler handler, InterfaceParams ifParams) {
@@ -437,7 +443,7 @@
DhcpServerCallbacks cb) {
new Thread(() -> {
try {
- cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer);
+ cb.onDhcpServerCreated(mOnDhcpServerCreatedResult, mDhcpServer);
} catch (RemoteException e) {
fail(e.getMessage());
}
@@ -448,6 +454,10 @@
IpNeighborMonitor.NeighborEventConsumer c) {
return mIpNeighborMonitor;
}
+
+ public void setOnDhcpServerCreatedResult(final int result) {
+ mOnDhcpServerCreatedResult = result;
+ }
}
public class MockTetheringDependencies extends TetheringDependencies {
@@ -573,6 +583,11 @@
public int getBinderCallingUid() {
return mBinderCallingUid;
}
+
+ @Override
+ public boolean isTetheringWithSoftApConfigEnabled() {
+ return mTetheringWithSoftApConfigEnabled;
+ }
}
private static LinkProperties buildUpstreamLinkProperties(String interfaceName,
@@ -708,6 +723,9 @@
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
+ mIpServerDependencies = spy(new MockIpServerDependencies());
+ when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
+ mTetheringWithSoftApConfigEnabled = SdkLevel.isAtLeastB();
}
// In order to interact with syncSM from the test, tethering must be created in test thread.
@@ -1944,7 +1962,6 @@
@Test
public void failingWifiTetheringLegacyApBroadcast() throws Exception {
initTetheringOnTestThread();
- when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
@@ -1981,7 +1998,6 @@
@Test
public void workingWifiTetheringEnrichedApBroadcast() throws Exception {
initTetheringOnTestThread();
- when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
@@ -2030,7 +2046,6 @@
@Test
public void failureEnablingIpForwarding() throws Exception {
initTetheringOnTestThread();
- when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
// Emulate pressing the WiFi tethering button.
@@ -2080,9 +2095,9 @@
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
verify(mTetheringMetrics, times(0)).maybeUpdateUpstreamType(any());
- verify(mTetheringMetrics, times(2)).updateErrorCode(eq(TETHERING_WIFI),
+ verify(mTetheringMetrics, times(1)).updateErrorCode(eq(TETHERING_WIFI),
eq(TETHER_ERROR_INTERNAL_ERROR));
- verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_WIFI));
+ verify(mTetheringMetrics, times(1)).sendReport(eq(TETHERING_WIFI));
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
@@ -2374,7 +2389,6 @@
// 2. Enable wifi tethering.
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
initTetheringUpstream(upstreamState);
- when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
@@ -2425,7 +2439,7 @@
@Test
public void testSoftApConfigInTetheringEventCallback() throws Exception {
- assumeTrue(SdkLevel.isAtLeastV());
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
when(mContext.checkCallingOrSelfPermission(NETWORK_SETTINGS))
.thenReturn(PERMISSION_DENIED);
when(mContext.checkCallingOrSelfPermission(NETWORK_STACK))
@@ -2477,19 +2491,16 @@
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
initTetheringUpstream(upstreamState);
- when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
// Enable wifi tethering
mBinderCallingUid = TEST_CALLER_UID;
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, null);
+ mLooper.dispatchAll();
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
mLooper.dispatchAll();
- if (SdkLevel.isAtLeastB()) {
- // Starting in B, ignore the interfaceStatusChanged
- callback.assertNoStateChangeCallback();
- }
+ // Netd "up" event should not trigger a state change callback in B+.
+ callback.assertNoStateChangeCallback();
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
- mLooper.dispatchAll();
// Verify we see Available -> Tethered states
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
callback.pollTetherStatesChanged().availableList);
@@ -2507,20 +2518,15 @@
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED);
// Disable wifi tethering
- mLooper.dispatchAll();
mTethering.stopTethering(TETHERING_WIFI);
- sendWifiApStateChanged(WIFI_AP_STATE_DISABLED);
- if (isAtLeastT()) {
- // After T, tethering doesn't support WIFI_AP_STATE_DISABLED with null interface name.
- callback.assertNoStateChangeCallback();
- sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
+ mLooper.dispatchAll();
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
IFACE_IP_MODE_TETHERED);
- }
- assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
+ assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
callback.pollTetherStatesChanged().availableList);
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
differentCallback.pollTetherStatesChanged().availableList);
- assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
+ assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
settingsCallback.pollTetherStatesChanged().availableList);
mLooper.dispatchAll();
callback.expectUpstreamChanged(NULL_NETWORK);
@@ -2529,6 +2535,320 @@
}
@Test
+ public void testFuzzyMatchedWifiCannotBeAdded() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringInterface wifiIfaceWithoutConfig = new TetheringInterface(
+ TETHERING_WIFI, TEST_WLAN_IFNAME, null);
+ final TetheringInterface wifiIfaceWithConfig = new TetheringInterface(
+ TETHERING_WIFI, TEST_WLAN_IFNAME, softApConfig);
+
+ // Register callback before running any tethering.
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ callback.expectTetheredClientChanged(Collections.emptyList());
+ callback.expectUpstreamChanged(NULL_NETWORK);
+ callback.expectConfigurationChanged(
+ mTethering.getTetheringConfiguration().toStableParcelable());
+ assertTetherStatesNotNullButEmpty(callback.pollTetherStatesChanged());
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+ UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
+ initTetheringUpstream(upstreamState);
+
+ // Start wifi tethering but don't trigger the link layer event yet.
+ mBinderCallingUid = TEST_CALLER_UID;
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ tetheringRequest.setUid(TEST_CALLER_UID);
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+
+ // Try starting wifi tethering with various fuzzy-matching requests and verify we get
+ // TETHER_ERROR_DUPLICATE_REQUEST.
+
+ // Different static IP addresses
+ final TetheringRequest differentIpAddr = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig)
+ .setStaticIpv4Addresses(new LinkAddress("192.168.0.123/24"),
+ new LinkAddress("192.168.0.42/24"))
+ .build();
+ differentIpAddr.setUid(TEST_CALLER_UID);
+ ResultListener differentIpAddrListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(differentIpAddr, TEST_CALLER_PKG, differentIpAddrListener);
+ mLooper.dispatchAll();
+ verify(mWifiManager, times(1)).startTetheredHotspot(any());
+ verify(mWifiManager, never()).stopSoftAp();
+ differentIpAddrListener.assertHasResult();
+
+ // Different UID
+ final TetheringRequest differentUid = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ differentUid.setUid(TEST_CALLER_UID + 1);
+ ResultListener differentUidListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(differentUid, TEST_CALLER_PKG, differentUidListener);
+ mLooper.dispatchAll();
+ differentUidListener.assertHasResult();
+ verify(mWifiManager, times(1)).startTetheredHotspot(any());
+ verify(mWifiManager, never()).stopSoftAp();
+
+ // Mock the link layer event to start IP serving and verify we still get
+ // TETHER_ERROR_DUPLICATE_REQUEST even though the request is no longer pending and is
+ // already serving.
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
+ callback.pollTetherStatesChanged().availableList);
+ assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
+ callback.pollTetherStatesChanged().tetheredList);
+ callback.expectUpstreamChanged(upstreamState.network);
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED);
+ differentIpAddrListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ differentUidListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(differentIpAddr, TEST_CALLER_PKG, differentIpAddrListener);
+ mTethering.startTethering(differentUid, TEST_CALLER_PKG, differentUidListener);
+ mLooper.dispatchAll();
+ differentIpAddrListener.assertHasResult();
+ differentUidListener.assertHasResult();
+ verify(mWifiManager, times(1)).startTetheredHotspot(any());
+ verify(mWifiManager, never()).stopSoftAp();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCanBeAddedAfterIpServerStopped() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+
+ // Start wifi tethering and mock the ap state change.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
+ ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener);
+ mLooper.dispatchAll();
+ failureListener.assertHasResult();
+
+ // Trigger Netd callback to stop the IpServer
+ mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, false);
+
+ // We should be able to request the same Wifi again
+ ResultListener successListener2 = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
+ mLooper.dispatchAll();
+ successListener2.assertHasResult();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCanBeAddedAfterIpServerUnwanted() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+
+ // Start wifi tethering and mock the ap state change.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
+ ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener);
+ mLooper.dispatchAll();
+ failureListener.assertHasResult();
+
+ // Trigger wifi ap state change to tell IpServer it's unwanted.
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ // We should be able to request the same Wifi again
+ ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
+ mLooper.dispatchAll();
+ successListener2.assertHasResult();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCanBeAddedAfterIpServerError() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+
+ // Set up the DHCP server to fail creation.
+ mIpServerDependencies.setOnDhcpServerCreatedResult(STATUS_UNKNOWN_ERROR);
+
+ // Start wifi tethering and mock the ap state change.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ // We should be able to request the same Wifi again since the DHCP server transitioned the
+ // IpServer back to InitialState.
+ ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
+ mLooper.dispatchAll();
+ successListener2.assertHasResult();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCanBeAddedAfterStoppingPendingRequest() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+
+ // Start wifi tethering but keep the request pending by not sending the ap state change.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+
+ // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
+ ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener);
+ mLooper.dispatchAll();
+ failureListener.assertHasResult();
+
+ // Stop Wifi tethering.
+ mTethering.stopTethering(TETHERING_WIFI);
+
+ // We should be able to request the same Wifi again
+ ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
+ mLooper.dispatchAll();
+ successListener2.assertHasResult();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCanBeAddedAfterStoppingServingRequest() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+
+ // Start wifi tethering and mock the ap state change.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
+ ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener);
+ mLooper.dispatchAll();
+ failureListener.assertHasResult();
+
+ // Stop Wifi tethering.
+ mTethering.stopTethering(TETHERING_WIFI);
+
+ // We should be able to request the same Wifi again
+ ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
+ mLooper.dispatchAll();
+ successListener2.assertHasResult();
+ }
+
+ @Test
+ public void testStopTetheringWithMatchingRequest() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ when(mContext.checkCallingOrSelfPermission(NETWORK_SETTINGS)).thenReturn(PERMISSION_DENIED);
+ initTetheringOnTestThread();
+ UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
+ initTetheringUpstream(upstreamState);
+
+ // Enable wifi tethering.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+ .setSsid("SoftApConfig")
+ .build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ tetheringRequest.setUid(TEST_CALLER_UID);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, null);
+ mLooper.dispatchAll();
+
+ // Stop tethering with non-matching config. Should fail with TETHER_ERROR_UNKNOWN_REQUEST.
+ SoftApConfiguration softApConfig2 = new SoftApConfiguration.Builder()
+ .setSsid("SoftApConfig2")
+ .build();
+ IIntResultListener differentConfigListener = mock(IIntResultListener.class);
+ mTethering.stopTetheringRequest(new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig2).build(), differentConfigListener);
+ mLooper.dispatchAll();
+ verify(differentConfigListener).onResult(eq(TETHER_ERROR_UNKNOWN_REQUEST));
+ verify(mWifiManager, never()).stopSoftAp();
+
+ // Stop tethering with non-matching UID. Should fail with TETHER_ERROR_UNKNOWN_REQUEST.
+ final TetheringRequest nonMatchingUid = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ IIntResultListener nonMatchingUidListener = mock(IIntResultListener.class);
+ nonMatchingUid.setUid(TEST_CALLER_UID_2);
+ mTethering.stopTetheringRequest(nonMatchingUid, nonMatchingUidListener);
+ mLooper.dispatchAll();
+ verify(nonMatchingUidListener).onResult(eq(TETHER_ERROR_UNKNOWN_REQUEST));
+ verify(mWifiManager, never()).stopSoftAp();
+
+ // Stop tethering with matching request. Should succeed now.
+ IIntResultListener matchingListener = mock(IIntResultListener.class);
+ mTethering.stopTetheringRequest(tetheringRequest, matchingListener);
+ mLooper.dispatchAll();
+ verify(matchingListener).onResult(eq(TETHER_ERROR_NO_ERROR));
+ verify(mWifiManager).stopSoftAp();
+ }
+
+ @Test
+ public void testStopTetheringWithSettingsPermission() throws Exception {
+ assumeTrue(mTetheringDependencies.isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+ UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
+ initTetheringUpstream(upstreamState);
+
+ // Enable wifi tethering.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+ .setSsid("SoftApConfig")
+ .build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ tetheringRequest.setUid(TEST_CALLER_UID);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, null);
+ mLooper.dispatchAll();
+
+ // Stop tethering with non-matching UID and Settings permission. Should succeed.
+ final TetheringRequest nonMatchingUid = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ IIntResultListener nonMatchingUidListener = mock(IIntResultListener.class);
+ nonMatchingUid.setUid(TEST_CALLER_UID_2);
+ when(mContext.checkCallingOrSelfPermission(NETWORK_SETTINGS))
+ .thenReturn(PERMISSION_GRANTED);
+ mTethering.stopTetheringRequest(nonMatchingUid, nonMatchingUidListener);
+ mLooper.dispatchAll();
+ verify(nonMatchingUidListener).onResult(eq(TETHER_ERROR_NO_ERROR));
+ verify(mWifiManager).stopSoftAp();
+ }
+
+ @Test
public void testReportFailCallbackIfOffloadNotSupported() throws Exception {
initTetheringOnTestThread();
final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
@@ -2794,7 +3114,8 @@
}
@Test
- public void testMultipleStartTethering() throws Exception {
+ public void testMultipleStartTetheringLegacy() throws Exception {
+ mTetheringWithSoftApConfigEnabled = false;
initTetheringOnTestThread();
final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24");
final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24");
@@ -2891,7 +3212,6 @@
final int clientAddrParceled = 0xc0a8002a;
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
- when(mWifiManager.startTetheredHotspot(any())).thenReturn(true);
mTethering.startTethering(createTetheringRequest(TETHERING_WIFI,
serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL, null),
TEST_CALLER_PKG, null);
@@ -3055,26 +3375,38 @@
mLooper.dispatchAll();
verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI);
reset(mEntitleMgr);
+ }
- // If one app enables tethering without provisioning check first, then another app enables
- // tethering of the same type but does not disable the provisioning check.
+ @Test
+ public void testNonExemptRequestAddedAfterExemptRequestOfSameType() throws Exception {
+ // Note: When fuzzy-matching is enabled, it is not possible yet to have two concurrent
+ // requests of the same type that are subject to carrier entitlement due to fuzzy-matching.
+ mTetheringWithSoftApConfigEnabled = false;
+ initTetheringOnTestThread();
setupForRequiredProvisioning();
+ final TetheringRequest wifiExemptRequest =
+ createTetheringRequest(TETHERING_WIFI, null, null, true,
+ CONNECTIVITY_SCOPE_GLOBAL, null);
mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
assertTrue(mEntitleMgr.isCellularUpstreamPermitted());
reset(mEntitleMgr);
+
setupForRequiredProvisioning();
+ final TetheringRequest wifiNotExemptRequest =
+ createTetheringRequest(TETHERING_WIFI, null, null, false,
+ CONNECTIVITY_SCOPE_GLOBAL, null);
mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
+ verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI);
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
assertFalse(mEntitleMgr.isCellularUpstreamPermitted());
mTethering.stopTethering(TETHERING_WIFI);
mLooper.dispatchAll();
- verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI);
- reset(mEntitleMgr);
+ verify(mEntitleMgr, times(2)).stopProvisioningIfNeeded(TETHERING_WIFI);
}
private void setupForRequiredProvisioning() {
@@ -3575,6 +3907,7 @@
@Test
public void testStartBluetoothTetheringFailsWhenTheresAnExistingRequestWaitingForPanService()
throws Exception {
+ mTetheringWithSoftApConfigEnabled = false;
initTetheringOnTestThread();
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
@@ -3873,6 +4206,21 @@
}
@Test
+ public void testFailStartTetheredHotspotWithoutRequest() throws Exception {
+ mTetheringWithSoftApConfigEnabled = false;
+ initTetheringOnTestThread();
+ when(mWifiManager.startTetheredHotspot(null)).thenReturn(false);
+
+ ResultListener result = new ResultListener(TETHER_ERROR_INTERNAL_ERROR);
+ mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG, result);
+ mLooper.dispatchAll();
+ verify(mWifiManager).startTetheredHotspot(null);
+ verifyNoMoreInteractions(mWifiManager);
+ result.assertHasResult();
+ assertTrue(mTethering.getPendingTetheringRequests().isEmpty());
+ }
+
+ @Test
public void testWifiTetheringWhenP2pActive() throws Exception {
initTetheringOnTestThread();
// Enable wifi P2P.
@@ -3885,7 +4233,6 @@
verify(mUpstreamNetworkMonitor, never()).setTryCell(true);
assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastErrorForTest(TEST_P2P_IFNAME));
- when(mWifiManager.startTetheredHotspot(any())).thenReturn(true);
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(createTetheringRequest(TETHERING_WIFI), TEST_CALLER_PKG,
null);
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
index fb42c03..41b58fa 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -33,8 +33,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Collection;
import java.util.Optional;
/** Helper class to download certificate transparency log files. */
@@ -48,28 +47,21 @@
private final DownloadHelper mDownloadHelper;
private final SignatureVerifier mSignatureVerifier;
private final CertificateTransparencyLogger mLogger;
-
- private final List<CompatibilityVersion> mCompatVersions = new ArrayList<>();
+ private final Collection<CompatibilityVersion> mCompatVersions;
CertificateTransparencyDownloader(
Context context,
DataStore dataStore,
DownloadHelper downloadHelper,
SignatureVerifier signatureVerifier,
- CertificateTransparencyLogger logger) {
+ CertificateTransparencyLogger logger,
+ Collection<CompatibilityVersion> compatVersions) {
mContext = context;
mSignatureVerifier = signatureVerifier;
mDataStore = dataStore;
mDownloadHelper = downloadHelper;
mLogger = logger;
- }
-
- void addCompatibilityVersion(CompatibilityVersion compatVersion) {
- mCompatVersions.add(compatVersion);
- }
-
- void clearCompatibilityVersions() {
- mCompatVersions.clear();
+ mCompatVersions = compatVersions;
}
long startPublicKeyDownload() {
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
index f1b9a4f..286f326 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
@@ -28,6 +28,8 @@
import android.os.SystemClock;
import android.util.Log;
+import java.util.Collection;
+
/** Implementation of the Certificate Transparency job */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public class CertificateTransparencyJob extends BroadcastReceiver {
@@ -37,8 +39,8 @@
private final Context mContext;
private final DataStore mDataStore;
private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
- private final CompatibilityVersion mCompatVersion;
private final SignatureVerifier mSignatureVerifier;
+ private final Collection<CompatibilityVersion> mCompatVersions;
private final AlarmManager mAlarmManager;
private final PendingIntent mPendingIntent;
@@ -50,13 +52,13 @@
Context context,
DataStore dataStore,
CertificateTransparencyDownloader certificateTransparencyDownloader,
- CompatibilityVersion compatVersion,
- SignatureVerifier signatureVerifier) {
+ SignatureVerifier signatureVerifier,
+ Collection<CompatibilityVersion> compatVersions) {
mContext = context;
mDataStore = dataStore;
mCertificateTransparencyDownloader = certificateTransparencyDownloader;
- mCompatVersion = compatVersion;
mSignatureVerifier = signatureVerifier;
+ mCompatVersions = compatVersions;
mAlarmManager = context.getSystemService(AlarmManager.class);
mPendingIntent =
@@ -99,7 +101,9 @@
}
mDependenciesReady = false;
- mCompatVersion.delete();
+ for (CompatibilityVersion compatVersion : mCompatVersions) {
+ compatVersion.delete();
+ }
if (Config.DEBUG) {
Log.d(TAG, "CertificateTransparencyJob canceled.");
@@ -129,7 +133,6 @@
private void startDependencies() {
mDataStore.load();
- mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
mSignatureVerifier.loadAllowedKeys();
mContext.registerReceiver(
mCertificateTransparencyDownloader,
@@ -144,7 +147,6 @@
private void stopDependencies() {
mContext.unregisterReceiver(mCertificateTransparencyDownloader);
mSignatureVerifier.clearAllowedKeys();
- mCertificateTransparencyDownloader.clearCompatibilityVersions();
mDataStore.delete();
if (Config.DEBUG) {
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
index 2e910b2..5e530c7 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -30,6 +30,8 @@
import com.android.server.SystemService;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.concurrent.Executors;
/** Implementation of the Certificate Transparency service. */
@@ -51,8 +53,18 @@
/** Creates a new {@link CertificateTransparencyService} object. */
public CertificateTransparencyService(Context context) {
DataStore dataStore = new DataStore(Config.PREFERENCES_FILE);
-
SignatureVerifier signatureVerifier = new SignatureVerifier(context);
+ Collection<CompatibilityVersion> compatVersions =
+ Arrays.asList(
+ new CompatibilityVersion(
+ Config.COMPATIBILITY_VERSION_V1,
+ Config.URL_SIGNATURE_V1,
+ Config.URL_LOG_LIST_V1),
+ new CompatibilityVersion(
+ Config.COMPATIBILITY_VERSION_V2,
+ Config.URL_SIGNATURE_V2,
+ Config.URL_LOG_LIST_V2));
+
mCertificateTransparencyJob =
new CertificateTransparencyJob(
context,
@@ -62,13 +74,10 @@
dataStore,
new DownloadHelper(context),
signatureVerifier,
- new CertificateTransparencyLoggerImpl(dataStore)),
- new CompatibilityVersion(
- Config.COMPATIBILITY_VERSION,
- Config.URL_SIGNATURE,
- Config.URL_LOG_LIST,
- Config.CT_ROOT_DIRECTORY_PATH),
- signatureVerifier);
+ new CertificateTransparencyLoggerImpl(dataStore),
+ compatVersions),
+ signatureVerifier,
+ compatVersions);
}
/**
diff --git a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
index e8a6e64..0a91963 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
@@ -23,6 +23,8 @@
import android.system.Os;
import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
import com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState;
import org.json.JSONException;
@@ -40,6 +42,8 @@
private static final String TAG = "CompatibilityVersion";
+ private static File sRootDirectory = new File(Config.CT_ROOT_DIRECTORY_PATH);
+
static final String LOGS_DIR_PREFIX = "logs-";
static final String LOGS_LIST_FILE_NAME = "log_list.json";
static final String CURRENT_LOGS_DIR_SYMLINK_NAME = "current";
@@ -48,23 +52,21 @@
private final String mMetadataUrl;
private final String mContentUrl;
- private final File mRootDirectory;
private final File mVersionDirectory;
private final File mCurrentLogsDirSymlink;
CompatibilityVersion(
- String compatVersion, String metadataUrl, String contentUrl, File rootDirectory) {
+ String compatVersion, String metadataUrl, String contentUrl) {
mCompatVersion = compatVersion;
mMetadataUrl = metadataUrl;
mContentUrl = contentUrl;
- mRootDirectory = rootDirectory;
- mVersionDirectory = new File(rootDirectory, compatVersion);
+ mVersionDirectory = new File(sRootDirectory, compatVersion);
mCurrentLogsDirSymlink = new File(mVersionDirectory, CURRENT_LOGS_DIR_SYMLINK_NAME);
}
- CompatibilityVersion(
- String compatVersion, String metadataUrl, String contentUrl, String rootDirectoryPath) {
- this(compatVersion, metadataUrl, contentUrl, new File(rootDirectoryPath));
+ @VisibleForTesting
+ static void setRootDirectoryForTesting(File rootDirectory) {
+ sRootDirectory = rootDirectory;
}
/**
@@ -75,8 +77,8 @@
* @return true if the log list was installed successfully, false otherwise.
* @throws IOException if the list cannot be saved in the CT directory.
*/
- LogListUpdateStatus install(
- InputStream newContent, LogListUpdateStatus.Builder statusBuilder) throws IOException {
+ LogListUpdateStatus install(InputStream newContent, LogListUpdateStatus.Builder statusBuilder)
+ throws IOException {
String content = new String(newContent.readAllBytes(), UTF_8);
try {
JSONObject contentJson = new JSONObject(content);
@@ -98,7 +100,7 @@
// there's a bunch of steps. We create a new directory with the logs and then do
// an atomic update of the current symlink to point to the new directory.
// 1. Ensure the path to the root and version directories exist and are readable.
- DirectoryUtils.makeDir(mRootDirectory);
+ DirectoryUtils.makeDir(sRootDirectory);
DirectoryUtils.makeDir(mVersionDirectory);
File newLogsDir = new File(mVersionDirectory, LOGS_DIR_PREFIX + version);
diff --git a/networksecurity/service/src/com/android/server/net/ct/Config.java b/networksecurity/service/src/com/android/server/net/ct/Config.java
index 5fdba09..72b715a 100644
--- a/networksecurity/service/src/com/android/server/net/ct/Config.java
+++ b/networksecurity/service/src/com/android/server/net/ct/Config.java
@@ -33,18 +33,14 @@
private static final String PREFERENCES_FILE_NAME = "ct.preferences";
static final File PREFERENCES_FILE = new File(DEVICE_PROTECTED_DATA_DIR, PREFERENCES_FILE_NAME);
- // CT directory
+ // CT paths
static final String CT_ROOT_DIRECTORY_PATH = "/data/misc/keychain/ct/";
- static final String COMPATIBILITY_VERSION = "v1";
+ static final String URL_PREFIX = "https://www.gstatic.com/android/certificate_transparency/";
// Phenotype flags
static final String NAMESPACE_NETWORK_SECURITY = "network_security";
private static final String FLAGS_PREFIX = "CertificateTransparencyLogList__";
static final String FLAG_SERVICE_ENABLED = FLAGS_PREFIX + "service_enabled";
- static final String FLAG_CONTENT_URL = FLAGS_PREFIX + "content_url";
- static final String FLAG_METADATA_URL = FLAGS_PREFIX + "metadata_url";
- static final String FLAG_VERSION = FLAGS_PREFIX + "version";
- static final String FLAG_PUBLIC_KEY = FLAGS_PREFIX + "public_key";
// properties
static final String VERSION = "version";
@@ -53,9 +49,18 @@
static final String PUBLIC_KEY_DOWNLOAD_ID = "public_key_download_id";
static final String LOG_LIST_UPDATE_FAILURE_COUNT = "log_list_update_failure_count";
- // URLs
- static final String URL_PREFIX = "https://www.gstatic.com/android/certificate_transparency/";
- static final String URL_LOG_LIST = URL_PREFIX + "log_list.json";
- static final String URL_SIGNATURE = URL_PREFIX + "log_list.sig";
+ // Public Key URLs
static final String URL_PUBLIC_KEY = URL_PREFIX + "log_list.pub";
+
+ // Compatibility Version v1
+ static final String COMPATIBILITY_VERSION_V1 = "v1";
+ static final String URL_PREFIX_V1 = URL_PREFIX;
+ static final String URL_LOG_LIST_V1 = URL_PREFIX_V1 + "log_list.json";
+ static final String URL_SIGNATURE_V1 = URL_PREFIX_V1 + "log_list.sig";
+
+ // Compatibility Version v2
+ static final String COMPATIBILITY_VERSION_V2 = "v2";
+ static final String URL_PREFIX_V2 = URL_PREFIX + COMPATIBILITY_VERSION_V2 + "/";
+ static final String URL_LOG_LIST_V2 = URL_PREFIX_V2 + "log_list.json";
+ static final String URL_SIGNATURE_V2 = URL_PREFIX_V2 + "log_list.sig";
}
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
index 22dc6ab..956bad5 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
@@ -60,6 +60,7 @@
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
+import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
@@ -94,24 +95,25 @@
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mDataStore = new DataStore(File.createTempFile("datastore-test", ".properties"));
mSignatureVerifier = new SignatureVerifier(mContext);
+
+ CompatibilityVersion.setRootDirectoryForTesting(mContext.getFilesDir());
+ mCompatVersion =
+ new CompatibilityVersion(
+ /* compatVersion= */ "v666",
+ Config.URL_SIGNATURE_V1,
+ Config.URL_LOG_LIST_V1);
mCertificateTransparencyDownloader =
new CertificateTransparencyDownloader(
mContext,
mDataStore,
new DownloadHelper(mDownloadManager),
mSignatureVerifier,
- mLogger);
- mCompatVersion =
- new CompatibilityVersion(
- /* compatVersion= */ "v666",
- Config.URL_SIGNATURE,
- Config.URL_LOG_LIST,
- mContext.getFilesDir());
+ mLogger,
+ Arrays.asList(mCompatVersion));
prepareDownloadManager();
mSignatureVerifier.addAllowedKey(mPublicKey);
mDataStore.load();
- mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
}
@After
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java
index 2b8b3cd..0d15183 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java
@@ -27,6 +27,7 @@
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -47,9 +48,16 @@
private final File mTestDir =
InstrumentationRegistry.getInstrumentation().getContext().getFilesDir();
- private final CompatibilityVersion mCompatVersion =
- new CompatibilityVersion(
- TEST_VERSION, Config.URL_SIGNATURE, Config.URL_LOG_LIST, mTestDir);
+
+ private CompatibilityVersion mCompatVersion;
+
+ @Before
+ public void setUp() {
+ CompatibilityVersion.setRootDirectoryForTesting(mTestDir);
+ mCompatVersion =
+ new CompatibilityVersion(
+ TEST_VERSION, Config.URL_SIGNATURE_V1, Config.URL_LOG_LIST_V1);
+ }
@After
public void tearDown() {
@@ -111,9 +119,7 @@
JSONObject logList = makeLogList(version, "i_am_content");
try (InputStream inputStream = asStream(logList)) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
@@ -142,9 +148,7 @@
@Test
public void testCompatibilityVersion_deleteSuccessfully() throws Exception {
try (InputStream inputStream = asStream(makeLogList(/* version= */ "123"))) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
@@ -156,9 +160,7 @@
@Test
public void testCompatibilityVersion_invalidLogList() throws Exception {
try (InputStream inputStream = new ByteArrayInputStream(("not_a_valid_list".getBytes()))) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(LogListUpdateStatus.builder().setState(LOG_LIST_INVALID).build());
}
@@ -179,9 +181,7 @@
JSONObject newLogList = makeLogList(existingVersion, "i_am_the_real_content");
try (InputStream inputStream = asStream(newLogList)) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
@@ -193,16 +193,12 @@
String existingVersion = "666";
JSONObject existingLogList = makeLogList(existingVersion, "i_was_installed_successfully");
try (InputStream inputStream = asStream(existingLogList)) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
try (InputStream inputStream = asStream(makeLogList(existingVersion, "i_am_ignored"))) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(
LogListUpdateStatus.builder()
.setState(VERSION_ALREADY_EXISTS)
diff --git a/service-t/Android.bp b/service-t/Android.bp
index ab38c7a..81378f5 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -109,13 +109,15 @@
":service-mdns-droidstubs",
],
exclude_srcs: [
+ "src/com/android/server/connectivity/mdns/internal/MdnsRealtimeScheduler.java",
"src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java",
- "src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java",
"src/com/android/server/connectivity/mdns/MdnsAdvertiser.java",
"src/com/android/server/connectivity/mdns/MdnsAnnouncer.java",
"src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java",
"src/com/android/server/connectivity/mdns/MdnsProber.java",
"src/com/android/server/connectivity/mdns/MdnsRecordRepository.java",
+ "src/com/android/server/connectivity/mdns/SchedulerFactory.java",
+ "src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java",
],
static_libs: [
"net-utils-device-common-mdns-standalone-build-test",
@@ -132,7 +134,10 @@
droidstubs {
name: "service-mdns-droidstubs",
- srcs: ["src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java"],
+ srcs: [
+ "src/com/android/server/connectivity/mdns/SchedulerFactory.java",
+ "src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java",
+ ],
libs: [
"net-utils-device-common-mdns-standalone-build-test",
"service-connectivity-tiramisu-pre-jarjar",
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 555549c..4af8b0e 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1942,7 +1942,7 @@
mContext, MdnsFeatureFlags.NSD_QUERY_WITH_KNOWN_ANSWER))
.setAvoidAdvertisingEmptyTxtRecords(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS))
- .setIsCachedServicesRemovalEnabled(mDeps.isFeatureEnabled(
+ .setIsCachedServicesRemovalEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_CACHED_SERVICES_REMOVAL))
.setCachedServicesRetentionTime(mDeps.getDeviceConfigPropertyInt(
MdnsFeatureFlags.NSD_CACHED_SERVICES_RETENTION_TIME,
diff --git a/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java b/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java
index 21af1a1..99354f8 100644
--- a/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java
+++ b/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java
@@ -49,7 +49,13 @@
@NonNull
private final ArrayList<Pair<Runnable, Long>> mPendingTasks = new ArrayList<>();
- DiscoveryExecutor(@Nullable Looper defaultLooper) {
+ @GuardedBy("mPendingTasks")
+ @Nullable
+ Scheduler mScheduler;
+ @NonNull private final MdnsFeatureFlags mMdnsFeatureFlags;
+
+ DiscoveryExecutor(@Nullable Looper defaultLooper, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
+ mMdnsFeatureFlags = mdnsFeatureFlags;
if (defaultLooper != null) {
this.mHandlerThread = null;
synchronized (mPendingTasks) {
@@ -62,7 +68,7 @@
synchronized (mPendingTasks) {
mHandler = new Handler(getLooper());
for (Pair<Runnable, Long> pendingTask : mPendingTasks) {
- mHandler.postDelayed(pendingTask.first, pendingTask.second);
+ executeDelayed(pendingTask.first, pendingTask.second);
}
mPendingTasks.clear();
}
@@ -95,15 +101,33 @@
/** Execute the given function after the specified amount of time elapses. */
public void executeDelayed(Runnable function, long delayMillis) {
final Handler handler;
+ final Scheduler scheduler;
synchronized (mPendingTasks) {
if (this.mHandler == null) {
mPendingTasks.add(Pair.create(function, delayMillis));
return;
} else {
handler = this.mHandler;
+ if (mMdnsFeatureFlags.mIsAccurateDelayCallbackEnabled
+ && this.mScheduler == null) {
+ this.mScheduler = SchedulerFactory.createScheduler(mHandler);
+ }
+ scheduler = this.mScheduler;
}
}
- handler.postDelayed(function, delayMillis);
+ if (scheduler != null) {
+ if (delayMillis == 0L) {
+ handler.post(function);
+ return;
+ }
+ if (HandlerUtils.isRunningOnHandlerThread(handler)) {
+ scheduler.postDelayed(function, delayMillis);
+ } else {
+ handler.post(() -> scheduler.postDelayed(function, delayMillis));
+ }
+ } else {
+ handler.postDelayed(function, delayMillis);
+ }
}
/** Shutdown the thread if necessary. */
@@ -111,6 +135,11 @@
if (this.mHandlerThread != null) {
this.mHandlerThread.quitSafely();
}
+ synchronized (mPendingTasks) {
+ if (mScheduler != null) {
+ mScheduler.close();
+ }
+ }
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 33bcb70..8cd3662 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -125,7 +125,7 @@
this.sharedLog = sharedLog;
this.perSocketServiceTypeClients = new PerSocketServiceTypeClients();
this.mdnsFeatureFlags = mdnsFeatureFlags;
- this.discoveryExecutor = new DiscoveryExecutor(socketClient.getLooper());
+ this.discoveryExecutor = new DiscoveryExecutor(socketClient.getLooper(), mdnsFeatureFlags);
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index 1cf5e4d..11a374d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -316,7 +316,7 @@
mIsAggressiveQueryModeEnabled = false;
mIsQueryWithKnownAnswerEnabled = false;
mAvoidAdvertisingEmptyTxtRecords = true; // Default enabled.
- mIsCachedServicesRemovalEnabled = false;
+ mIsCachedServicesRemovalEnabled = true; // Default enabled.
mCachedServicesRetentionTime = DEFAULT_CACHED_SERVICES_RETENTION_TIME_MILLISECONDS;
mIsAccurateDelayCallbackEnabled = false;
mIsShortHostnamesEnabled = true; // Default enabled.
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 56d4b9a..95f4fff 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -37,7 +37,6 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.DnsUtils;
-import com.android.net.module.util.RealtimeScheduler;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -96,9 +95,9 @@
private final boolean removeServiceAfterTtlExpires =
MdnsConfigs.removeServiceAfterTtlExpires();
private final Clock clock;
- // Use RealtimeScheduler for query scheduling, which allows for more accurate sending of
+ // Use MdnsRealtimeScheduler for query scheduling, which allows for more accurate sending of
// queries.
- @Nullable private final RealtimeScheduler realtimeScheduler;
+ @Nullable private final Scheduler scheduler;
@Nullable private MdnsSearchOptions searchOptions;
@@ -193,7 +192,7 @@
sharedLog.log(String.format("Query sent with transactionId: %d. "
+ "Next run: sessionId: %d, in %d ms",
sentResult.transactionId, args.sessionId, timeToNextTaskMs));
- if (realtimeScheduler != null) {
+ if (scheduler != null) {
setDelayedTask(args, timeToNextTaskMs);
} else {
dependencies.sendMessageDelayed(
@@ -264,11 +263,11 @@
}
/**
- * @see RealtimeScheduler
+ * @see Scheduler
*/
@Nullable
- public RealtimeScheduler createRealtimeScheduler(@NonNull Handler handler) {
- return new RealtimeScheduler(handler);
+ public Scheduler createScheduler(@NonNull Handler handler) {
+ return SchedulerFactory.createScheduler(handler);
}
}
@@ -317,8 +316,8 @@
this.mdnsQueryScheduler = new MdnsQueryScheduler();
this.cacheKey = new MdnsServiceCache.CacheKey(serviceType, socketKey);
this.featureFlags = featureFlags;
- this.realtimeScheduler = featureFlags.isAccurateDelayCallbackEnabled()
- ? dependencies.createRealtimeScheduler(handler) : null;
+ this.scheduler = featureFlags.isAccurateDelayCallbackEnabled()
+ ? dependencies.createScheduler(handler) : null;
}
/**
@@ -328,8 +327,8 @@
removeScheduledTask();
mdnsQueryScheduler.cancelScheduledRun();
serviceCache.unregisterServiceExpiredCallback(cacheKey);
- if (realtimeScheduler != null) {
- realtimeScheduler.close();
+ if (scheduler != null) {
+ scheduler.close();
}
}
@@ -339,8 +338,8 @@
}
private void setDelayedTask(ScheduledQueryTaskArgs args, long timeToNextTaskMs) {
- realtimeScheduler.removeDelayedMessage(EVENT_START_QUERYTASK);
- realtimeScheduler.sendDelayedMessage(
+ scheduler.removeDelayedMessage(EVENT_START_QUERYTASK);
+ scheduler.sendDelayedMessage(
handler.obtainMessage(EVENT_START_QUERYTASK, args), timeToNextTaskMs);
}
@@ -404,7 +403,7 @@
final long timeToNextTaskMs = calculateTimeToNextTask(args, now);
sharedLog.log(String.format("Schedule a query. Next run: sessionId: %d, in %d ms",
args.sessionId, timeToNextTaskMs));
- if (realtimeScheduler != null) {
+ if (scheduler != null) {
setDelayedTask(args, timeToNextTaskMs);
} else {
dependencies.sendMessageDelayed(
@@ -451,8 +450,8 @@
}
private void removeScheduledTask() {
- if (realtimeScheduler != null) {
- realtimeScheduler.removeDelayedMessage(EVENT_START_QUERYTASK);
+ if (scheduler != null) {
+ scheduler.removeDelayedMessage(EVENT_START_QUERYTASK);
} else {
dependencies.removeMessages(handler, EVENT_START_QUERYTASK);
}
@@ -541,8 +540,8 @@
}
}
}
- final boolean hasScheduledTask = realtimeScheduler != null
- ? realtimeScheduler.hasDelayedMessage(EVENT_START_QUERYTASK)
+ final boolean hasScheduledTask = scheduler != null
+ ? scheduler.hasDelayedMessage(EVENT_START_QUERYTASK)
: dependencies.hasMessages(handler, EVENT_START_QUERYTASK);
if (hasScheduledTask) {
final long now = clock.elapsedRealtime();
@@ -556,7 +555,7 @@
final long timeToNextTaskMs = calculateTimeToNextTask(args, now);
sharedLog.log(String.format("Reschedule a query. Next run: sessionId: %d, in %d ms",
args.sessionId, timeToNextTaskMs));
- if (realtimeScheduler != null) {
+ if (scheduler != null) {
setDelayedTask(args, timeToNextTaskMs);
} else {
dependencies.sendMessageDelayed(
diff --git a/service-t/src/com/android/server/connectivity/mdns/Scheduler.java b/service-t/src/com/android/server/connectivity/mdns/Scheduler.java
new file mode 100644
index 0000000..85a8e76
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/Scheduler.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.os.Message;
+
+import androidx.annotation.NonNull;
+
+/**
+ * The interface for scheduler.
+ */
+public interface Scheduler {
+ /**
+ * Set a message to be sent after a specified delay.
+ */
+ boolean sendDelayedMessage(@NonNull Message message, long delayMs);
+
+ /**
+ * Remove a scheduled message.
+ */
+ void removeDelayedMessage(int what);
+
+ /**
+ * Check if there is a scheduled message.
+ */
+ boolean hasDelayedMessage(int what);
+
+ /**
+ * Set a runnable to be executed after a specified delay.
+ */
+ boolean postDelayed(@NonNull Runnable runnable, long delayMs);
+
+ /**
+ * Close this object.
+ */
+ void close();
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/SchedulerFactory.java b/service-t/src/com/android/server/connectivity/mdns/SchedulerFactory.java
new file mode 100644
index 0000000..1cc9a6b
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/SchedulerFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import com.android.server.connectivity.mdns.internal.MdnsRealtimeScheduler;
+
+/**
+ * The factory class for creating a scheduler.
+ */
+public class SchedulerFactory {
+
+ /**
+ * Creates an realtime delay callback.
+ */
+ public static Scheduler createScheduler(@NonNull Handler handler) {
+ return new MdnsRealtimeScheduler(handler);
+ }
+
+ private SchedulerFactory() {
+ }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/internal/MdnsRealtimeScheduler.java b/service-t/src/com/android/server/connectivity/mdns/internal/MdnsRealtimeScheduler.java
new file mode 100644
index 0000000..eff7085
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/internal/MdnsRealtimeScheduler.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns.internal;
+
+import android.os.Handler;
+import android.os.Message;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.RealtimeScheduler;
+import com.android.server.connectivity.mdns.Scheduler;
+
+/**
+ * The delay callback for delivering scheduled tasks accurately.
+ */
+public class MdnsRealtimeScheduler extends RealtimeScheduler implements
+ Scheduler {
+ private static final String TAG = MdnsRealtimeScheduler.class.getSimpleName();
+
+ public MdnsRealtimeScheduler(@NonNull Handler handler) {
+ super(handler);
+ }
+
+ public boolean sendDelayedMessage(@NonNull Message message, long delayMs) {
+ return super.sendDelayedMessage(message, delayMs);
+ }
+
+ public void removeDelayedMessage(int what) {
+ super.removeDelayedMessage(what);
+ }
+
+ public boolean hasDelayedMessage(int what) {
+ return super.hasDelayedMessage(what);
+ }
+
+ public boolean postDelayed(@NonNull Runnable runnable, long delayMs) {
+ return super.postDelayed(runnable, delayMs);
+ }
+
+ public void close() {
+ super.close();
+ }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index 5f66f47..5ff708d 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -21,7 +21,6 @@
import static android.net.NetworkStats.UID_ALL;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.net.NetworkStats;
import android.net.UnderlyingNetworkInfo;
@@ -31,6 +30,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.BpfNetMaps;
+import com.android.server.connectivity.InterfaceTracker;
import java.io.IOException;
import java.net.ProtocolException;
@@ -108,7 +108,7 @@
/** Create a new {@link BpfNetMaps}. */
public BpfNetMaps createBpfNetMaps(@NonNull Context ctx) {
- return new BpfNetMaps(ctx);
+ return new BpfNetMaps(ctx, new InterfaceTracker(ctx));
}
}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 36c0cf9..25c0617 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -49,6 +49,8 @@
import static com.android.server.connectivity.NetworkPermissions.TRAFFIC_PERMISSION_UNINSTALLED;
import static com.android.server.connectivity.NetworkPermissions.TRAFFIC_PERMISSION_UPDATE_DEVICE_STATS;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.StatsManager;
import android.content.Context;
import android.net.BpfNetMapsUtils;
@@ -85,12 +87,14 @@
import com.android.net.module.util.bpf.IngressDiscardKey;
import com.android.net.module.util.bpf.IngressDiscardValue;
import com.android.net.module.util.bpf.LocalNetAccessKey;
+import com.android.server.connectivity.InterfaceTracker;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
@@ -142,6 +146,7 @@
Pair.create(TRAFFIC_PERMISSION_INTERNET, "PERMISSION_INTERNET"),
Pair.create(TRAFFIC_PERMISSION_UPDATE_DEVICE_STATS, "PERMISSION_UPDATE_DEVICE_STATS")
);
+ private final InterfaceTracker mInterfaceTracker;
/**
* Set configurationMap for test.
@@ -423,23 +428,27 @@
/** Constructor used after T that doesn't need to use netd anymore. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
- public BpfNetMaps(final Context context) {
- this(context, null);
+ public BpfNetMaps(final Context context, @NonNull final InterfaceTracker interfaceTracker) {
+ this(context, null, interfaceTracker);
if (!SdkLevel.isAtLeastT()) throw new IllegalArgumentException("BpfNetMaps need to use netd before T");
}
- public BpfNetMaps(final Context context, final INetd netd) {
- this(context, netd, new Dependencies());
+ public BpfNetMaps(final Context context, final INetd netd, @NonNull final InterfaceTracker
+ interfaceTracker) {
+ this(context, netd, new Dependencies(), interfaceTracker);
}
@VisibleForTesting
- public BpfNetMaps(final Context context, final INetd netd, final Dependencies deps) {
+ public BpfNetMaps(final Context context, final INetd netd, final Dependencies deps,
+ @NonNull final InterfaceTracker interfaceTracker) {
+ Objects.requireNonNull(interfaceTracker);
if (SdkLevel.isAtLeastT()) {
ensureInitialized(context);
}
mNetd = netd;
mDeps = deps;
+ mInterfaceTracker = interfaceTracker;
}
private void maybeThrow(final int err, final String msg) {
@@ -902,7 +911,7 @@
* @param isAllowed is the local network call allowed or blocked.
*/
@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
- public void addLocalNetAccess(final int lpmBitlen, final String iface,
+ public void addLocalNetAccess(final int lpmBitlen, @Nullable final String iface,
final InetAddress address, final int protocol, final int remotePort,
final boolean isAllowed) {
throwIfPre25Q2("addLocalNetAccess is not available on pre-B devices");
@@ -910,7 +919,7 @@
if (iface == null) {
ifIndex = 0;
} else {
- ifIndex = mDeps.getIfIndex(iface);
+ ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
}
if (ifIndex == 0) {
Log.e(TAG, "Failed to get if index, skip addLocalNetAccess for " + address
@@ -937,14 +946,14 @@
* @param remotePort src/dst port for ingress/egress
*/
@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
- public void removeLocalNetAccess(final int lpmBitlen, final String iface,
+ public void removeLocalNetAccess(final int lpmBitlen, @Nullable final String iface,
final InetAddress address, final int protocol, final int remotePort) {
throwIfPre25Q2("removeLocalNetAccess is not available on pre-B devices");
final int ifIndex;
if (iface == null) {
ifIndex = 0;
} else {
- ifIndex = mDeps.getIfIndex(iface);
+ ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
}
if (ifIndex == 0) {
Log.e(TAG, "Failed to get if index, skip removeLocalNetAccess for " + address
@@ -973,14 +982,14 @@
* is not local network or if configuration is allowed like local dns servers.
*/
@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
- public boolean getLocalNetAccess(final int lpmBitlen, final String iface,
+ public boolean getLocalNetAccess(final int lpmBitlen, @Nullable final String iface,
final InetAddress address, final int protocol, final int remotePort) {
throwIfPre25Q2("getLocalNetAccess is not available on pre-B devices");
final int ifIndex;
if (iface == null) {
ifIndex = 0;
} else {
- ifIndex = mDeps.getIfIndex(iface);
+ ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
}
if (ifIndex == 0) {
Log.e(TAG, "Failed to get if index, returning default from getLocalNetAccess for "
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index b2e49e7..3ce3f02 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -365,6 +365,7 @@
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.DscpPolicyTracker;
import com.android.server.connectivity.FullScore;
+import com.android.server.connectivity.InterfaceTracker;
import com.android.server.connectivity.InvalidTagException;
import com.android.server.connectivity.KeepaliveResourceUtil;
import com.android.server.connectivity.KeepaliveTracker;
@@ -577,6 +578,7 @@
private final NetworkStatsManager mStatsManager;
private final NetworkPolicyManager mPolicyManager;
private final BpfNetMaps mBpfNetMaps;
+ private final InterfaceTracker mInterfaceTracker;
/**
* TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple
@@ -1662,8 +1664,17 @@
* @param netd a netd binder
* @return BpfNetMaps implementation.
*/
- public BpfNetMaps getBpfNetMaps(Context context, INetd netd) {
- return new BpfNetMaps(context, netd);
+ public BpfNetMaps getBpfNetMaps(Context context, INetd netd,
+ InterfaceTracker interfaceTracker) {
+ return new BpfNetMaps(context, netd, interfaceTracker);
+ }
+
+ /**
+ * Get the InterfaceTracker implementation to use in ConnectivityService.
+ * @return InterfaceTracker implementation.
+ */
+ public InterfaceTracker getInterfaceTracker(Context context) {
+ return new InterfaceTracker(context);
}
/**
@@ -1886,7 +1897,8 @@
mWakeUpMask = mask;
mNetd = netd;
- mBpfNetMaps = mDeps.getBpfNetMaps(mContext, netd);
+ mInterfaceTracker = mDeps.getInterfaceTracker(mContext);
+ mBpfNetMaps = mDeps.getBpfNetMaps(mContext, netd, mInterfaceTracker);
mHandlerThread = mDeps.makeHandlerThread("ConnectivityServiceThread");
mPermissionMonitorDeps = mPermDeps;
mPermissionMonitor =
@@ -9605,8 +9617,6 @@
updateIngressToVpnAddressFiltering(newLp, oldLp, networkAgent);
- updateLocalNetworkAddresses(newLp, oldLp);
-
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -9769,16 +9779,23 @@
wakeupModifyInterface(iface, nai, true);
mDeps.reportNetworkInterfaceForTransports(mContext, iface,
nai.networkCapabilities.getTransportTypes());
+ mInterfaceTracker.addInterface(iface);
} catch (Exception e) {
logw("Exception adding interface: " + e);
}
}
}
+
+ // The local network addresses needs to be updated before interfaces are removed because
+ // modifying bpf map local_net_access requires mapping interface name to index.
+ updateLocalNetworkAddresses(newLp, oldLp);
+
for (final String iface : interfaceDiff.removed) {
try {
if (DBG) log("Removing iface " + iface + " from network " + netId);
wakeupModifyInterface(iface, nai, false);
mRoutingCoordinatorService.removeInterfaceFromNetwork(netId, iface);
+ mInterfaceTracker.removeInterface(iface);
} catch (Exception e) {
loge("Exception removing interface: " + e);
}
diff --git a/service/src/com/android/server/connectivity/InterfaceTracker.java b/service/src/com/android/server/connectivity/InterfaceTracker.java
new file mode 100644
index 0000000..0b4abeb
--- /dev/null
+++ b/service/src/com/android/server/connectivity/InterfaceTracker.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.system.Os;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.BpfNetMaps;
+
+import java.util.Map;
+
+/**
+ * InterfaceTracker is responsible for providing interface mapping and tracking.
+ * @hide
+ */
+public class InterfaceTracker {
+ static {
+ if (BpfNetMaps.isAtLeast25Q2()) {
+ System.loadLibrary("service-connectivity");
+ }
+ }
+ private static final String TAG = "InterfaceTracker";
+ private final Dependencies mDeps;
+ private final Map<String, Integer> mInterfaceMap;
+
+ public InterfaceTracker(final Context context) {
+ this(context, new Dependencies());
+ }
+
+ @VisibleForTesting
+ public InterfaceTracker(final Context context, final Dependencies deps) {
+ this.mInterfaceMap = new ArrayMap<>();
+ this.mDeps = deps;
+ }
+
+ /**
+ * To add interface to tracking
+ * @param interfaceName name of interface added.
+ */
+ public void addInterface(@Nullable final String interfaceName) {
+ final int interfaceIndex;
+ if (interfaceName == null) {
+ interfaceIndex = 0;
+ } else {
+ interfaceIndex = mDeps.getIfIndex(interfaceName);
+ }
+ if (interfaceIndex == 0) {
+ Log.e(TAG, "Failed to get interface index for " + interfaceName);
+ return;
+ }
+ synchronized (mInterfaceMap) {
+ mInterfaceMap.put(interfaceName, interfaceIndex);
+ }
+ }
+
+ /**
+ * To remove interface from tracking
+ * @param interfaceName name of interface removed.
+ * @return true if the value was present and now removed.
+ */
+ public boolean removeInterface(@Nullable final String interfaceName) {
+ if (interfaceName == null) return false;
+ synchronized (mInterfaceMap) {
+ return mInterfaceMap.remove(interfaceName) != null;
+ }
+ }
+
+ /**
+ * Get interface index from interface name.
+ * @param interfaceName name of interface
+ * @return interface index for given interface name or 0 if interface is not found.
+ */
+ public int getInterfaceIndex(@Nullable final String interfaceName) {
+ final int interfaceIndex;
+ if (interfaceName != null) {
+ synchronized (mInterfaceMap) {
+ interfaceIndex = mInterfaceMap.getOrDefault(interfaceName, 0);
+ }
+ } else {
+ interfaceIndex = 0;
+ }
+ return interfaceIndex;
+ }
+
+ /**
+ * Dependencies of InterfaceTracker, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * Get interface index.
+ */
+ public int getIfIndex(final String ifName) {
+ return Os.if_nametoindex(ifName);
+ }
+
+ /**
+ * Get interface name
+ */
+ public String getIfName(final int ifIndex) {
+ return Os.if_indextoname(ifIndex);
+ }
+
+ }
+}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index b064723..abfc447 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -427,11 +427,7 @@
srcs: [
"device/com/android/net/module/util/FdEventsReader.java",
"device/com/android/net/module/util/HandlerUtils.java",
- "device/com/android/net/module/util/JniUtil.java",
- "device/com/android/net/module/util/RealtimeScheduler.java",
"device/com/android/net/module/util/SharedLog.java",
- "device/com/android/net/module/util/ServiceConnectivityJni.java",
- "device/com/android/net/module/util/TimerFdUtils.java",
"framework/com/android/net/module/util/ByteUtils.java",
"framework/com/android/net/module/util/CollectionUtils.java",
"framework/com/android/net/module/util/DnsUtils.java",
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index c19a124..5d49fa3 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -22,7 +22,6 @@
import static com.android.net.module.util.netlink.NetlinkConstants.IFF_UP;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETLINK;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
-import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST_ACK;
import android.net.MacAddress;
@@ -307,7 +306,7 @@
}
return RtNetlinkLinkMessage.build(
- new StructNlMsgHdr(0, RTM_GETLINK, NLM_F_REQUEST, sequenceNumber),
+ new StructNlMsgHdr(0, RTM_GETLINK, NLM_F_REQUEST_ACK, sequenceNumber),
new StructIfinfoMsg((short) AF_UNSPEC, (short) 0, interfaceIndex, 0, 0),
DEFAULT_MTU, null, null);
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
index 13710b1..08cab03 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
@@ -290,7 +290,7 @@
@Test
public void testCreateGetLinkMessage() {
final String expectedHexBytes =
- "20000000120001006824000000000000" // struct nlmsghdr
+ "20000000120005006824000000000000" // struct nlmsghdr
+ "00000000080000000000000000000000"; // struct ifinfomsg
final String interfaceName = "wlan0";
final int interfaceIndex = 8;
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index 49ffad6..c2ad18e 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -18,6 +18,7 @@
from mobly.controllers import android_device
from mobly.controllers.android_device_lib.adb import AdbError
from net_tests_utils.host.python import adb_utils, assert_utils
+import functools
class PatternNotFoundException(Exception):
@@ -400,6 +401,20 @@
f" {expected_version}",
)
+def at_least_B():
+ def decorator(test_function):
+ @functools.wraps(test_function)
+ def wrapper(self, *args, **kwargs):
+ asserts.abort_class_if(
+ (not hasattr(self, 'client')) or (not hasattr(self.client, 'isAtLeastB')),
+ "client device is not B+"
+ )
+
+ asserts.abort_class_if(not self.client.isAtLeastB(), "not B+")
+ return test_function(self, *args, **kwargs)
+ return wrapper
+ return decorator
+
class AdbOutputHandler:
def __init__(self, ad, cmd):
self._ad = ad
diff --git a/staticlibs/testutils/host/python/multi_devices_test_base.py b/staticlibs/testutils/host/python/multi_devices_test_base.py
index 677329a..72bac0c 100644
--- a/staticlibs/testutils/host/python/multi_devices_test_base.py
+++ b/staticlibs/testutils/host/python/multi_devices_test_base.py
@@ -53,3 +53,4 @@
raise_on_exception=True,
)
self.client = self.clientDevice.connectivity_multi_devices_snippet
+ self.server = self.serverDevice.connectivity_multi_devices_snippet
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index c730b86..00fb934 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -29,6 +29,7 @@
libs: [
"absl-py",
"mobly",
+ "scapy",
"net-tests-utils-host-python-common",
],
test_suites: [
diff --git a/tests/cts/multidevices/apfv6_test.py b/tests/cts/multidevices/apfv6_test.py
index fc732d2..61f1bfc 100644
--- a/tests/cts/multidevices/apfv6_test.py
+++ b/tests/cts/multidevices/apfv6_test.py
@@ -13,6 +13,8 @@
# limitations under the License.
from mobly import asserts
+from scapy.layers.inet import IP, ICMP
+from scapy.layers.l2 import Ether
from net_tests_utils.host.python import apf_test_base, apf_utils, adb_utils, assert_utils, packet_utils
APFV6_VERSION = 6000
@@ -82,3 +84,18 @@
self.send_packet_and_expect_reply_received(
arp_request, "DROPPED_ARP_REQUEST_REPLIED", arp_reply
)
+
+ @apf_utils.at_least_B()
+ def test_ipv4_icmp_echo_request_offload(self):
+ eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
+ ip = IP(src=self.server_ipv4_addresses[0], dst=self.client_ipv4_addresses[0])
+ icmp = ICMP(id=1, seq=123)
+ echo_request = bytes(eth/ip/icmp/b"hello").hex()
+
+ eth = Ether(src=self.client_mac_address, dst=self.server_mac_address)
+ ip = IP(src=self.client_ipv4_addresses[0], dst=self.server_ipv4_addresses[0])
+ icmp = ICMP(type=0, id=1, seq=123)
+ expected_echo_reply = bytes(eth/ip/icmp/b"hello").hex()
+ self.send_packet_and_expect_reply_received(
+ echo_request, "DROPPED_IPV4_PING_REQUEST_REPLIED", expected_echo_reply
+ )
\ No newline at end of file
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index 252052e..e1c6bf1 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -33,6 +33,8 @@
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiSsid
+import android.os.Build.VERSION.CODENAME
+import android.os.Build.VERSION.SDK_INT
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.PropertyUtil
import com.android.modules.utils.build.SdkLevel
@@ -62,6 +64,17 @@
cbHelper.unregisterAll()
}
+ private fun isAtLeastPreReleaseCodename(codeName: String): Boolean {
+ // Special case "REL", which means the build is not a pre-release build.
+ if ("REL".equals(CODENAME)) {
+ return false
+ }
+
+ // Otherwise lexically compare them. Return true if the build codename is equal to or
+ // greater than the requested codename.
+ return CODENAME.compareTo(codeName) >= 0
+ }
+
@Rpc(description = "Check whether the device has wifi feature.")
fun hasWifiFeature() = pm.hasSystemFeature(FEATURE_WIFI)
@@ -77,6 +90,11 @@
@Rpc(description = "Return whether the Sdk level is at least V.")
fun isAtLeastV() = SdkLevel.isAtLeastV()
+ @Rpc(description = "Check whether the device is at least B.")
+ fun isAtLeastB(): Boolean {
+ return SDK_INT >= 36 || (SDK_INT == 35 && isAtLeastPreReleaseCodename("Baklava"))
+ }
+
@Rpc(description = "Return the API level that the VSR requirement must be fulfilled.")
fun getVsrApiLevel() = PropertyUtil.getVsrApiLevel()
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index e645f67..7d6a213 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -32,6 +32,7 @@
import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_DUPLICATE_REQUEST;
import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -85,7 +86,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.build.SdkLevel;
-import com.android.net.flags.Flags;
import com.android.testutils.ParcelUtils;
import com.android.testutils.com.android.testutils.CarrierConfigRule;
@@ -234,6 +234,30 @@
}
+ @Test
+ public void testStartTetheringDuplicateRequestRejected() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastB());
+ final TestTetheringEventCallback tetherEventCallback =
+ mCtsTetheringUtils.registerTetheringEventCallback();
+ try {
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ tetherEventCallback.expectNoTetheringActive();
+
+ final String[] wifiRegexs = mTM.getTetherableWifiRegexs();
+ mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
+ mTetherChangeReceiver.expectTethering(true /* active */, wifiRegexs);
+
+ final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+ runAsShell(TETHER_PRIVILEGED, () -> {
+ mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
+ c -> c.run() /* executor */, startTetheringCallback);
+ startTetheringCallback.expectTetheringFailed(TETHER_ERROR_DUPLICATE_REQUEST);
+ });
+ } finally {
+ mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+ }
+ }
+
private SoftApConfiguration createSoftApConfiguration(@NonNull String ssid) {
SoftApConfiguration config;
if (SdkLevel.isAtLeastT()) {
@@ -380,17 +404,20 @@
tetherEventCallback.assumeWifiTetheringSupported(mContext);
tetherEventCallback.expectNoTetheringActive();
- SoftApConfiguration softApConfig = createSoftApConfiguration("SSID");
+ SoftApConfiguration softApConfig = SdkLevel.isAtLeastB()
+ ? createSoftApConfiguration("SSID") : null;
final TetheringInterface tetheredIface =
mCtsTetheringUtils.startWifiTethering(tetherEventCallback, softApConfig);
assertNotNull(tetheredIface);
- assertEquals(softApConfig, tetheredIface.getSoftApConfiguration());
- final String wifiTetheringIface = tetheredIface.getInterface();
+ if (SdkLevel.isAtLeastB()) {
+ assertEquals(softApConfig, tetheredIface.getSoftApConfiguration());
+ }
mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
if (!SdkLevel.isAtLeastB()) {
+ final String wifiTetheringIface = tetheredIface.getInterface();
try {
final int ret = runAsShell(TETHER_PRIVILEGED,
() -> mTM.tether(wifiTetheringIface));
@@ -455,31 +482,75 @@
}
@Test
- public void testStopTetheringRequest() throws Exception {
- assumeTrue(isTetheringWithSoftApConfigEnabled());
+ public void testStopTetheringRequestNoMatchFailure() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastB());
+ final TestTetheringEventCallback tetherEventCallback =
+ mCtsTetheringUtils.registerTetheringEventCallback();
+ try {
+ final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+ mTM.startTethering(new TetheringRequest.Builder(TETHERING_VIRTUAL).build(),
+ c -> c.run(), startTetheringCallback);
+
+ // Stopping a non-matching request should have no effect
+ TetheringRequest nonMatchingRequest = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+ .setInterfaceName("iface")
+ .build();
+ mCtsTetheringUtils.stopTethering(nonMatchingRequest, false /* success */);
+ } finally {
+ mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+ }
+ }
+
+ @Test
+ public void testStopTetheringRequestMatchSuccess() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastB());
final TestTetheringEventCallback tetherEventCallback =
mCtsTetheringUtils.registerTetheringEventCallback();
try {
tetherEventCallback.assumeWifiTetheringSupported(mContext);
- // stopTethering without any tethering active should fail.
- TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
- mCtsTetheringUtils.stopTethering(request, false /* expectSuccess */);
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+ .setWifiSsid(WifiSsid.fromBytes("This is one config"
+ .getBytes(StandardCharsets.UTF_8))).build();
+ mCtsTetheringUtils.startWifiTethering(tetherEventCallback, softApConfig);
- // Start wifi tethering
- mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
-
- // stopTethering should succeed now that there's a request.
- mCtsTetheringUtils.stopTethering(request, true /* expectSuccess */);
+ // Stopping the active request should stop tethering.
+ TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig)
+ .build();
+ mCtsTetheringUtils.stopTethering(request, true /* success */);
tetherEventCallback.expectNoTetheringActive();
} finally {
- mCtsTetheringUtils.stopAllTethering();
mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
}
}
- private boolean isTetheringWithSoftApConfigEnabled() {
- return SdkLevel.isAtLeastB() && Flags.tetheringWithSoftApConfig();
+ @Test
+ public void testStopTetheringRequestFuzzyMatchSuccess() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastB());
+ final TestTetheringEventCallback tetherEventCallback =
+ mCtsTetheringUtils.registerTetheringEventCallback();
+ try {
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
+
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+ .setWifiSsid(WifiSsid.fromBytes("This is one config"
+ .getBytes(StandardCharsets.UTF_8))).build();
+ mCtsTetheringUtils.startWifiTethering(tetherEventCallback, softApConfig);
+
+ // Stopping with a fuzzy matching request should stop tethering.
+ final LinkAddress localAddr = new LinkAddress("192.168.24.5/24");
+ final LinkAddress clientAddr = new LinkAddress("192.168.24.100/24");
+ TetheringRequest fuzzyMatchingRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig)
+ .setShouldShowEntitlementUi(true)
+ .setStaticIpv4Addresses(localAddr, clientAddr)
+ .build();
+ mCtsTetheringUtils.stopTethering(fuzzyMatchingRequest, true /* success */);
+ tetherEventCallback.expectNoTetheringActive();
+ } finally {
+ mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+ }
}
@Test
@@ -492,7 +563,7 @@
startTetheringCallback.expectTetheringFailed(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
// WRITE_SETTINGS not sufficient
- if (isTetheringWithSoftApConfigEnabled()) {
+ if (SdkLevel.isAtLeastB()) {
runAsShell(WRITE_SETTINGS, () -> {
mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
c -> c.run() /* executor */, startTetheringCallback);
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 437eb81..de39215 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -55,11 +55,11 @@
import com.android.networkstack.apishim.TelephonyManagerShimImpl
import com.android.server.BpfNetMaps
import com.android.server.ConnectivityService
-import com.android.server.L2capNetworkProvider
import com.android.server.NetworkAgentWrapper
import com.android.server.TestNetIdManager
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
import com.android.server.connectivity.ConnectivityResources
+import com.android.server.connectivity.InterfaceTracker
import com.android.server.connectivity.MockableSystemProperties
import com.android.server.connectivity.MultinetworkPolicyTracker
import com.android.server.connectivity.PermissionMonitor
@@ -114,6 +114,8 @@
@Mock
private lateinit var netd: INetd
@Mock
+ private lateinit var interfaceTracker: InterfaceTracker
+ @Mock
private lateinit var dnsResolver: IDnsResolver
@Mock
private lateinit var systemConfigManager: SystemConfigManager
@@ -140,11 +142,15 @@
private val realContext get() = InstrumentationRegistry.getInstrumentation().context
private val httpProbeUrl get() =
- realContext.getResources().getString(com.android.server.net.integrationtests.R.string
- .config_captive_portal_http_url)
+ realContext.getResources().getString(
+ com.android.server.net.integrationtests.R.string
+ .config_captive_portal_http_url
+ )
private val httpsProbeUrl get() =
- realContext.getResources().getString(com.android.server.net.integrationtests.R.string
- .config_captive_portal_https_url)
+ realContext.getResources().getString(
+ com.android.server.net.integrationtests.R.string
+ .config_captive_portal_https_url
+ )
private class InstrumentationServiceConnection : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
@@ -166,12 +172,19 @@
fun setUpClass() {
val intent = Intent(realContext, NetworkStackInstrumentationService::class.java)
intent.action = INetworkStackInstrumentation::class.qualifiedName
- assertTrue(realContext.bindService(intent, InstrumentationServiceConnection(),
- BIND_AUTO_CREATE or BIND_IMPORTANT),
- "Error binding to instrumentation service")
- assertTrue(bindingCondition.block(SERVICE_BIND_TIMEOUT_MS),
+ assertTrue(
+ realContext.bindService(
+ intent,
+ InstrumentationServiceConnection(),
+ BIND_AUTO_CREATE or BIND_IMPORTANT
+ ),
+ "Error binding to instrumentation service"
+ )
+ assertTrue(
+ bindingCondition.block(SERVICE_BIND_TIMEOUT_MS),
"Timed out binding to instrumentation service " +
- "after $SERVICE_BIND_TIMEOUT_MS ms")
+ "after $SERVICE_BIND_TIMEOUT_MS ms"
+ )
}
}
@@ -201,7 +214,8 @@
// We don't test the actual notification value strings, so just return an empty array.
// It doesn't matter what the values are as long as it's not null.
doReturn(emptyArray<String>()).`when`(resources).getStringArray(
- R.array.network_switch_type_name)
+ R.array.network_switch_type_name
+ )
doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi)
doReturn(R.array.config_networkSupportedKeepaliveCount).`when`(resources)
.getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any())
@@ -223,7 +237,13 @@
}
private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService(
- context, dnsResolver, log, netd, deps, PermissionMonitorDependencies())
+ context,
+ dnsResolver,
+ log,
+ netd,
+ deps,
+ PermissionMonitorDependencies()
+ )
private inner class TestDependencies : ConnectivityService.Dependencies() {
override fun getNetworkStack() = networkStackClient
@@ -231,7 +251,11 @@
mock(ProxyTracker::class.java)
override fun getSystemProperties() = mock(MockableSystemProperties::class.java)
override fun makeNetIdManager() = TestNetIdManager()
- override fun getBpfNetMaps(context: Context?, netd: INetd?) = mock(BpfNetMaps::class.java)
+ override fun getBpfNetMaps(
+ context: Context?,
+ netd: INetd?,
+ interfaceTracker: InterfaceTracker?
+ ) = mock(BpfNetMaps::class.java)
.also {
doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
}
@@ -241,13 +265,17 @@
c: Context,
h: Handler,
r: Runnable
- ) = MultinetworkPolicyTracker(c, h, r,
+ ) = MultinetworkPolicyTracker(
+ c,
+ h,
+ r,
object : MultinetworkPolicyTracker.Dependencies() {
override fun getResourcesForActiveSubId(
connResources: ConnectivityResources,
activeSubId: Int
) = resources
- })
+ }
+ )
override fun makeHandlerThread(tag: String): HandlerThread =
super.makeHandlerThread(tag).also { handlerThreads.add(it) }
@@ -259,13 +287,18 @@
listener: BiConsumer<Int, Int>,
handler: Handler
): CarrierPrivilegeAuthenticator {
- return CarrierPrivilegeAuthenticator(context,
+ return CarrierPrivilegeAuthenticator(
+ context,
object : CarrierPrivilegeAuthenticator.Dependencies() {
override fun makeHandlerThread(): HandlerThread =
super.makeHandlerThread().also { handlerThreads.add(it) }
},
- tm, TelephonyManagerShimImpl.newInstance(tm),
- requestRestrictedWifiEnabled, listener, handler)
+ tm,
+ TelephonyManagerShimImpl.newInstance(tm),
+ requestRestrictedWifiEnabled,
+ listener,
+ handler
+ )
}
override fun makeSatelliteAccessController(
@@ -273,7 +306,8 @@
updateSatellitePreferredUid: Consumer<MutableSet<Int>>?,
connectivityServiceInternalHandler: Handler
): SatelliteAccessController? = mock(
- SatelliteAccessController::class.java)
+ SatelliteAccessController::class.java
+ )
override fun makeL2capNetworkProvider(context: Context) = null
}
@@ -305,8 +339,12 @@
nsInstrumentation.addHttpResponse(HttpResponse(httpProbeUrl, responseCode = 204))
nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204))
- val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), null /* ncTemplate */,
- context)
+ val na = NetworkAgentWrapper(
+ TRANSPORT_CELLULAR,
+ LinkProperties(),
+ null /* ncTemplate */,
+ context
+ )
networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
na.addCapability(NET_CAPABILITY_INTERNET)
@@ -344,7 +382,8 @@
| "user-portal-url": "https://login.capport.android.com",
| "venue-info-url": "https://venueinfo.capport.android.com"
|}
- """.trimMargin()))
+ """.trimMargin()
+ ))
// Tests will fail if a non-mocked query is received: mock the HTTPS probe, but not the
// HTTP probe as it should not be sent.
@@ -398,8 +437,10 @@
BpfUtils.BPF_CGROUP_INET_EGRESS,
BpfUtils.BPF_CGROUP_INET_SOCK_CREATE
).forEach {
- val ret = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd connectivity bpf-get-cgroup-program-id $it").trim()
+ val ret = SystemUtil.runShellCommand(
+ InstrumentationRegistry.getInstrumentation(),
+ "cmd connectivity bpf-get-cgroup-program-id $it"
+ ).trim()
assertTrue(Integer.parseInt(ret) > 0, "Unexpected output $ret for type $it")
}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index caf1765..1d2e8b0 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -109,6 +109,7 @@
import com.android.net.module.util.bpf.IngressDiscardKey;
import com.android.net.module.util.bpf.IngressDiscardValue;
import com.android.net.module.util.bpf.LocalNetAccessKey;
+import com.android.server.connectivity.InterfaceTracker;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -170,6 +171,8 @@
@Mock INetd mNetd;
@Mock BpfNetMaps.Dependencies mDeps;
@Mock Context mContext;
+
+ @Mock InterfaceTracker mInterfaceTracker;
private final IBpfMap<S32, U32> mConfigurationMap = new TestBpfMap<>(S32.class, U32.class);
private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap =
new TestBpfMap<>(S32.class, UidOwnerValue.class);
@@ -188,6 +191,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME);
+ doReturn(TEST_IF_INDEX).when(mInterfaceTracker).getInterfaceIndex(TEST_IF_NAME);
doReturn(TEST_IF_NAME).when(mDeps).getIfName(TEST_IF_INDEX);
doReturn(0).when(mDeps).synchronizeKernelRCU();
BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
@@ -202,7 +206,7 @@
BpfNetMaps.setDataSaverEnabledMapForTest(mDataSaverEnabledMap);
mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED));
BpfNetMaps.setIngressDiscardMapForTest(mIngressDiscardMap);
- mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps);
+ mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps, mInterfaceTracker);
}
@Test
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index c4944b6..c28a0f8 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -413,6 +413,7 @@
import com.android.server.connectivity.ClatCoordinator;
import com.android.server.connectivity.ConnectivityFlags;
import com.android.server.connectivity.ConnectivityResources;
+import com.android.server.connectivity.InterfaceTracker;
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.MultinetworkPolicyTracker;
import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies;
@@ -2262,7 +2263,8 @@
}
@Override
- public BpfNetMaps getBpfNetMaps(Context context, INetd netd) {
+ public BpfNetMaps getBpfNetMaps(Context context, INetd netd,
+ InterfaceTracker interfaceTracker) {
return mBpfNetMaps;
}
diff --git a/tests/unit/java/com/android/server/connectivity/InterfaceTrackerTest.java b/tests/unit/java/com/android/server/connectivity/InterfaceTrackerTest.java
new file mode 100644
index 0000000..8a9ada0
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/InterfaceTrackerTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.ConnectivityModuleTest;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@ConnectivityModuleTest
+public class InterfaceTrackerTest {
+ private static final String TAG = "InterfaceTrackerTest";
+ private static final String TEST_IF_NAME = "wlan10";
+ private static final String TEST_INCORRECT_IF_NAME = "wlan20";
+ private static final int TEST_IF_INDEX = 7;
+
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
+ private InterfaceTracker mInterfaceTracker;
+
+ @Mock Context mContext;
+ @Mock InterfaceTracker.Dependencies mDeps;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME);
+ mInterfaceTracker = new InterfaceTracker(mContext, mDeps);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddingInterface_InterfaceNameIndexMappingAdded() {
+ mInterfaceTracker.addInterface(TEST_IF_NAME);
+ assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddingNullInterface_InterfaceNameIndexMappingNotAdded() {
+ mInterfaceTracker.addInterface(null);
+ assertEquals(0, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddingIncorrectInterface_InterfaceNameIndexMappingNotAdded() {
+ mInterfaceTracker.addInterface(TEST_INCORRECT_IF_NAME);
+
+ assertEquals(0, mInterfaceTracker.getInterfaceIndex(TEST_INCORRECT_IF_NAME));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemovingInterface_InterfaceNameIndexMappingRemoved() {
+ mInterfaceTracker.addInterface(TEST_IF_NAME);
+ assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ mInterfaceTracker.removeInterface(TEST_IF_NAME);
+ assertEquals(0, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemovingNullInterface_InterfaceNameIndexMappingNotRemoved() {
+ mInterfaceTracker.addInterface(TEST_IF_NAME);
+ mInterfaceTracker.removeInterface(null);
+ assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemovingIncorrectInterface_InterfaceNameIndexMappingNotRemoved() {
+ mInterfaceTracker.addInterface(TEST_IF_NAME);
+ mInterfaceTracker.removeInterface(TEST_INCORRECT_IF_NAME);
+ assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ }
+
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt
index 51539a0..67fb428 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt
@@ -17,6 +17,7 @@
package com.android.server.connectivity.mdns
import android.os.Build
+import android.os.Handler
import android.os.HandlerThread
import android.testing.TestableLooper
import com.android.testutils.DevSdkIgnoreRule
@@ -36,6 +37,8 @@
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class DiscoveryExecutorTest {
private val thread = HandlerThread(DiscoveryExecutorTest::class.simpleName).apply { start() }
+ private val handler by lazy { Handler(thread.looper) }
+ private val testableLooper by lazy { TestableLooper(thread.looper) }
@After
fun tearDown() {
@@ -45,8 +48,10 @@
@Test
fun testCheckAndRunOnHandlerThread() {
- val testableLooper = TestableLooper(thread.looper)
- val executor = DiscoveryExecutor(testableLooper.looper)
+ val executor = DiscoveryExecutor(
+ testableLooper.looper,
+ MdnsFeatureFlags.newBuilder().build()
+ )
try {
val future = CompletableFuture<Boolean>()
executor.checkAndRunOnHandlerThread { future.complete(true) }
@@ -58,17 +63,17 @@
// Create a DiscoveryExecutor with the null defaultLooper and verify the task can execute
// normally.
- val executor2 = DiscoveryExecutor(null /* defaultLooper */)
+ val executor2 = DiscoveryExecutor(
+ null /* defaultLooper */,
+ MdnsFeatureFlags.newBuilder().build()
+ )
val future2 = CompletableFuture<Boolean>()
executor2.checkAndRunOnHandlerThread { future2.complete(true) }
assertTrue(future2.get(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS))
executor2.shutDown()
}
- @Test
- fun testExecute() {
- val testableLooper = TestableLooper(thread.looper)
- val executor = DiscoveryExecutor(testableLooper.looper)
+ private fun verifyExecute(executor: DiscoveryExecutor) {
try {
val future = CompletableFuture<Boolean>()
executor.execute { future.complete(true) }
@@ -81,9 +86,27 @@
}
@Test
+ fun testExecute() {
+ verifyExecute(DiscoveryExecutor(
+ testableLooper.looper,
+ MdnsFeatureFlags.newBuilder().build()
+ ))
+ }
+
+ @Test
+ fun testExecute_RealtimeScheduler() {
+ verifyExecute(DiscoveryExecutor(
+ testableLooper.looper,
+ MdnsFeatureFlags.newBuilder().setIsAccurateDelayCallbackEnabled(true).build()
+ ))
+ }
+
+ @Test
fun testExecuteDelayed() {
- val testableLooper = TestableLooper(thread.looper)
- val executor = DiscoveryExecutor(testableLooper.looper)
+ val executor = DiscoveryExecutor(
+ testableLooper.looper,
+ MdnsFeatureFlags.newBuilder().build()
+ )
try {
// Verify the executeDelayed method
val future = CompletableFuture<Boolean>()
@@ -107,4 +130,21 @@
testableLooper.destroy()
}
}
+
+ @Test
+ fun testExecuteDelayed_RealtimeScheduler() {
+ val executor = DiscoveryExecutor(
+ thread.looper,
+ MdnsFeatureFlags.newBuilder().setIsAccurateDelayCallbackEnabled(true).build()
+ )
+ try {
+ // Verify the executeDelayed method
+ val future = CompletableFuture<Boolean>()
+ // Schedule a task with 50ms delay
+ executor.executeDelayed({ future.complete(true) }, 50L)
+ assertTrue(future.get(500L, TimeUnit.MILLISECONDS))
+ } finally {
+ testableLooper.destroy()
+ }
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index dad03e0..b9c0d2f 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -59,7 +59,6 @@
import android.text.TextUtils;
import com.android.net.module.util.CollectionUtils;
-import com.android.net.module.util.RealtimeScheduler;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -129,7 +128,7 @@
@Mock
private MdnsServiceTypeClient.Dependencies mockDeps;
@Mock
- private RealtimeScheduler mockRealtimeScheduler;
+ private Scheduler mockScheduler;
@Captor
private ArgumentCaptor<MdnsServiceInfo> serviceInfoCaptor;
@@ -250,14 +249,14 @@
doAnswer(inv -> {
realHandler = (Handler) inv.getArguments()[0];
- return mockRealtimeScheduler;
- }).when(mockDeps).createRealtimeScheduler(any(Handler.class));
+ return mockScheduler;
+ }).when(mockDeps).createScheduler(any(Handler.class));
doAnswer(inv -> {
message = (Message) inv.getArguments()[0];
latestDelayMs = (long) inv.getArguments()[1];
return null;
- }).when(mockRealtimeScheduler).sendDelayedMessage(any(), anyLong());
+ }).when(mockScheduler).sendDelayedMessage(any(), anyLong());
client = makeMdnsServiceTypeClient(featureFlags);
}
@@ -2137,7 +2136,7 @@
.setNumOfQueriesBeforeBackoff(numOfQueriesBeforeBackoff)
.build();
startSendAndReceive(mockListenerOne, searchOptions);
- verify(mockRealtimeScheduler, times(1)).removeDelayedMessage(EVENT_START_QUERYTASK);
+ verify(mockScheduler, times(1)).removeDelayedMessage(EVENT_START_QUERYTASK);
// Verify that the first query has been sent.
verifyAndSendQuery(0 /* index */, 0 /* timeInMs */, true /* expectsUnicastResponse */,
@@ -2159,13 +2158,13 @@
// 0.8 * smallestRemainingTtl is larger than time to next run.
long currentTime = TEST_TTL / 2 + TEST_ELAPSED_REALTIME;
doReturn(currentTime).when(mockDecoderClock).elapsedRealtime();
- doReturn(true).when(mockRealtimeScheduler).hasDelayedMessage(EVENT_START_QUERYTASK);
+ doReturn(true).when(mockScheduler).hasDelayedMessage(EVENT_START_QUERYTASK);
processResponse(createResponse(
"service-instance-1", "192.0.2.123", 5353,
SERVICE_TYPE_LABELS,
Collections.emptyMap(), TEST_TTL), socketKey);
// Verify that the message removal occurred.
- verify(mockRealtimeScheduler, times(6)).removeDelayedMessage(EVENT_START_QUERYTASK);
+ verify(mockScheduler, times(6)).removeDelayedMessage(EVENT_START_QUERYTASK);
assertNotNull(message);
verifyAndSendQuery(3 /* index */, (long) (TEST_TTL / 2 * 0.8) /* timeInMs */,
true /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
@@ -2174,7 +2173,7 @@
// Stop sending packets.
stopSendAndReceive(mockListenerOne);
- verify(mockRealtimeScheduler, times(8)).removeDelayedMessage(EVENT_START_QUERYTASK);
+ verify(mockScheduler, times(8)).removeDelayedMessage(EVENT_START_QUERYTASK);
}
@Test
@@ -2184,12 +2183,12 @@
// Start query
startSendAndReceive(mockListenerOne, MdnsSearchOptions.newBuilder().build());
- verify(mockRealtimeScheduler, times(1)).removeDelayedMessage(EVENT_START_QUERYTASK);
+ verify(mockScheduler, times(1)).removeDelayedMessage(EVENT_START_QUERYTASK);
// Stop query and verify the close() method has been called.
stopSendAndReceive(mockListenerOne);
- verify(mockRealtimeScheduler, times(2)).removeDelayedMessage(EVENT_START_QUERYTASK);
- verify(mockRealtimeScheduler).close();
+ verify(mockScheduler, times(2)).removeDelayedMessage(EVENT_START_QUERYTASK);
+ verify(mockScheduler).close();
}
private static MdnsServiceInfo matchServiceName(String name) {
@@ -2247,8 +2246,7 @@
.sendMessage(any(Handler.class), any(Message.class));
// Verify the task has been scheduled.
if (useAccurateDelayCallback) {
- verify(mockRealtimeScheduler, times(scheduledCount))
- .sendDelayedMessage(any(), anyLong());
+ verify(mockScheduler, times(scheduledCount)).sendDelayedMessage(any(), anyLong());
} else {
verify(mockDeps, times(scheduledCount))
.sendMessageDelayed(any(Handler.class), any(Message.class), anyLong());
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index 557bfd6..d7e781e 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -68,6 +68,7 @@
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
import com.android.server.connectivity.ClatCoordinator
import com.android.server.connectivity.ConnectivityFlags
+import com.android.server.connectivity.InterfaceTracker
import com.android.server.connectivity.MulticastRoutingCoordinatorService
import com.android.server.connectivity.MultinetworkPolicyTracker
import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies
@@ -193,6 +194,7 @@
val connResources = makeMockConnResources(sysResources, packageManager)
val netd = mock<INetd>()
+ val interfaceTracker = mock<InterfaceTracker>()
val bpfNetMaps = mock<BpfNetMaps>().also {
doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
}
@@ -279,7 +281,11 @@
inner class CSDeps : ConnectivityService.Dependencies() {
override fun getResources(ctx: Context) = connResources
- override fun getBpfNetMaps(context: Context, netd: INetd) = this@CSTest.bpfNetMaps
+ override fun getBpfNetMaps(
+ context: Context,
+ netd: INetd,
+ interfaceTracker: InterfaceTracker
+ ) = this@CSTest.bpfNetMaps
override fun getClatCoordinator(netd: INetd?) = this@CSTest.clatCoordinator
override fun getNetworkStack() = this@CSTest.networkStack
diff --git a/thread/TEST_MAPPING b/thread/TEST_MAPPING
index 40842f1..0c38f93 100644
--- a/thread/TEST_MAPPING
+++ b/thread/TEST_MAPPING
@@ -16,6 +16,9 @@
},
{
"name": "ThreadNetworkTrelDisabledTests"
+ },
+ {
+ "name": "ThreadBorderRouterIntegrationTests"
}
]
}
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index 520a434..2f2a5d1 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -326,12 +326,13 @@
private static LinkAddress newLinkAddress(
Ipv6AddressInfo addressInfo, boolean hasActiveOmrAddress) {
// Mesh-local addresses and OMR address have the same scope, to distinguish them we set
- // mesh-local addresses as deprecated when there is an active OMR address.
+ // mesh-local addresses as deprecated when there is an active OMR address. If OMR address
+ // is missing, only ML-EID in mesh-local addresses will be set preferred.
// For OMR address and link-local address we only use the value isPreferred set by
// ot-daemon.
boolean isPreferred = addressInfo.isPreferred;
- if (addressInfo.isMeshLocal && hasActiveOmrAddress) {
- isPreferred = false;
+ if (addressInfo.isMeshLocal) {
+ isPreferred = (!hasActiveOmrAddress && addressInfo.isMeshLocalEid);
}
final long deprecationTimeMillis =
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
index 5ba76b8..5be8f49 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
@@ -19,12 +19,10 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkManager;
import android.os.Build;
@@ -41,8 +39,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.List;
-
/** Tests for {@link ThreadNetworkManager}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
diff --git a/thread/tests/integration/Android.bp b/thread/tests/integration/Android.bp
index 798a51e..8a72017 100644
--- a/thread/tests/integration/Android.bp
+++ b/thread/tests/integration/Android.bp
@@ -64,6 +64,26 @@
}
android_test {
+ name: "ThreadBorderRouterIntegrationTests",
+ platform_apis: true,
+ manifest: "AndroidManifest.xml",
+ test_config: "AndroidTestBorderRouter.xml",
+ defaults: [
+ "framework-connectivity-test-defaults",
+ "ThreadNetworkIntegrationTestsDefaults",
+ ],
+ test_suites: [
+ "mts-tethering",
+ "general-tests",
+ ],
+ srcs: [
+ "borderrouter/**/*.java",
+ "borderrouter/**/*.kt",
+ ],
+ compile_multilib: "both",
+}
+
+android_test {
name: "ThreadNetworkTrelDisabledTests",
platform_apis: true,
manifest: "AndroidManifest.xml",
diff --git a/thread/tests/integration/AndroidTestBorderRouter.xml b/thread/tests/integration/AndroidTestBorderRouter.xml
new file mode 100644
index 0000000..644e839
--- /dev/null
+++ b/thread/tests/integration/AndroidTestBorderRouter.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 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.
+ -->
+
+<configuration description="Config for Thread Border Router integration tests">
+ <option name="test-tag" value="ThreadBorderRouterIntegrationTests" />
+ <option name="test-suite-tag" value="apct" />
+
+ <!--
+ Only run tests if the device under test is SDK version 34 (Android 14) or above.
+ -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.Sdk34ModuleController" />
+
+ <!-- Run tests in MTS only if the Tethering Mainline module is installed. -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.tethering" />
+ </object>
+
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+ <!-- Install test -->
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="ThreadBorderRouterIntegrationTests.apk" />
+ <option name="check-min-sdk" value="true" />
+ <option name="cleanup-apks" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.thread.tests.integration" />
+ </test>
+
+ <!-- Enable TREL for integration tests -->
+ <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer">
+ <option name="flag-value"
+ value="thread_network/TrelFeature__enabled=true"/>
+ </target_preparer>
+</configuration>
diff --git a/thread/tests/integration/borderrouter/src/android/net/thread/borderrouter/BorderRouterIntegrationTest.java b/thread/tests/integration/borderrouter/src/android/net/thread/borderrouter/BorderRouterIntegrationTest.java
new file mode 100644
index 0000000..292079f
--- /dev/null
+++ b/thread/tests/integration/borderrouter/src/android/net/thread/borderrouter/BorderRouterIntegrationTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2025 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.thread;
+
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED;
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
+import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.getIpv6Addresses;
+import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
+import static android.net.thread.utils.IntegrationTestUtils.getPrefixesFromNetData;
+import static android.net.thread.utils.IntegrationTestUtils.getThreadNetwork;
+import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
+import static android.os.SystemClock.elapsedRealtime;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkProperties;
+import android.net.thread.utils.FullThreadDevice;
+import android.net.thread.utils.OtDaemonController;
+import android.net.thread.utils.TapTestNetworkTracker;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
+import android.net.thread.utils.ThreadNetworkControllerWrapper;
+import android.net.thread.utils.ThreadStateListener;
+import android.os.HandlerThread;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.FluentIterable;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** Tests for E2E Border Router integration with ot-daemon, ConnectivityService, etc.. */
+@LargeTest
+@RequiresThreadFeature
+@RunWith(AndroidJUnit4.class)
+public class BorderRouterIntegrationTest {
+ // The maximum time for changes to be propagated to netdata.
+ private static final Duration NET_DATA_UPDATE_TIMEOUT = Duration.ofSeconds(1);
+
+ // The maximum time for OT addresses to be propagated to the TUN interface "thread-wpan"
+ private static final Duration TUN_ADDR_UPDATE_TIMEOUT = Duration.ofSeconds(1);
+
+ // The maximum time for changes in netdata to be propagated to link properties.
+ private static final Duration LINK_PROPERTIES_UPDATE_TIMEOUT = Duration.ofSeconds(1);
+
+ // The duration between attached and OMR address shows up on thread-wpan
+ private static final Duration OMR_LINK_ADDR_TIMEOUT = Duration.ofSeconds(30);
+
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
+ private static final byte[] DEFAULT_DATASET_TLVS =
+ base16().decode(
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8");
+ private static final ActiveOperationalDataset DEFAULT_DATASET =
+ ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+
+ private static final Inet6Address GROUP_ADDR_ALL_ROUTERS =
+ (Inet6Address) InetAddresses.parseNumericAddress("ff02::2");
+
+ private static final String TEST_NO_SLAAC_PREFIX = "9101:dead:beef:cafe::/64";
+ private static final InetAddress TEST_NO_SLAAC_PREFIX_ADDRESS =
+ InetAddresses.parseNumericAddress("9101:dead:beef:cafe::");
+
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ private ExecutorService mExecutor;
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final ThreadNetworkControllerWrapper mController =
+ ThreadNetworkControllerWrapper.newInstance(mContext);
+ private OtDaemonController mOtCtl;
+ private FullThreadDevice mFtd;
+ private HandlerThread mHandlerThread;
+ private TapTestNetworkTracker mTestNetworkTracker;
+
+ @Before
+ public void setUp() throws Exception {
+ mExecutor = Executors.newSingleThreadExecutor();
+ mFtd = new FullThreadDevice(10 /* nodeId */);
+ mOtCtl = new OtDaemonController();
+ mController.setEnabledAndWait(true);
+ mController.setConfigurationAndWait(
+ new ThreadConfiguration.Builder().setBorderRouterEnabled(true).build());
+ mController.leaveAndWait();
+
+ mHandlerThread = new HandlerThread("ThreadIntegrationTest");
+ mHandlerThread.start();
+
+ mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper());
+ assertThat(mTestNetworkTracker).isNotNull();
+ mController.setTestNetworkAsUpstreamAndWait(mTestNetworkTracker.getInterfaceName());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ ThreadStateListener.stopAllListeners();
+
+ if (mTestNetworkTracker != null) {
+ mTestNetworkTracker.tearDown();
+ }
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ mHandlerThread.join();
+ }
+ mController.setTestNetworkAsUpstreamAndWait(null);
+ mController.leaveAndWait();
+
+ mFtd.destroy();
+ mExecutor.shutdownNow();
+ }
+
+ @Test
+ public void otDaemonRestart_JoinedNetworkAndStopped_autoRejoinedAndTunIfStateConsistent()
+ throws Exception {
+ mController.joinAndWait(DEFAULT_DATASET);
+
+ runShellCommand("stop ot-daemon");
+
+ mController.waitForRole(DEVICE_ROLE_DETACHED, CALLBACK_TIMEOUT);
+ mController.waitForRole(DEVICE_ROLE_LEADER, RESTART_JOIN_TIMEOUT);
+ assertThat(mOtCtl.isInterfaceUp()).isTrue();
+ assertThat(runShellCommand("ifconfig thread-wpan")).contains("UP POINTOPOINT RUNNING");
+ }
+
+ @Test
+ public void joinNetwork_tunInterfaceJoinsAllRouterMulticastGroup() throws Exception {
+ mController.joinAndWait(DEFAULT_DATASET);
+
+ waitFor(
+ () -> isInMulticastGroup("thread-wpan", GROUP_ADDR_ALL_ROUTERS),
+ TUN_ADDR_UPDATE_TIMEOUT);
+ }
+
+ @Test
+ public void joinNetwork_allMlAddrAreNotPreferredAndOmrIsPreferred() throws Exception {
+ mController.setTestNetworkAsUpstreamAndWait(mTestNetworkTracker.getInterfaceName());
+ mController.joinAndWait(DEFAULT_DATASET);
+ waitFor(
+ () -> getIpv6Addresses("thread-wpan").contains(mOtCtl.getOmrAddress()),
+ OMR_LINK_ADDR_TIMEOUT);
+
+ IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix();
+ var linkAddrs = FluentIterable.from(getIpv6LinkAddresses("thread-wpan"));
+ var meshLocalAddrs = linkAddrs.filter(addr -> meshLocalPrefix.contains(addr.getAddress()));
+ assertThat(meshLocalAddrs).isNotEmpty();
+ assertThat(meshLocalAddrs.allMatch(addr -> !addr.isPreferred())).isTrue();
+ assertThat(meshLocalAddrs.allMatch(addr -> addr.getDeprecationTime() <= elapsedRealtime()))
+ .isTrue();
+ var omrAddrs = linkAddrs.filter(addr -> addr.getAddress().equals(mOtCtl.getOmrAddress()));
+ assertThat(omrAddrs).hasSize(1);
+ assertThat(omrAddrs.get(0).isPreferred()).isTrue();
+ assertThat(omrAddrs.get(0).getDeprecationTime() > elapsedRealtime()).isTrue();
+ }
+
+ @Test
+ @RequiresSimulationThreadDevice
+ public void edPingsMeshLocalAddresses_oneReplyPerRequest() throws Exception {
+ mController.joinAndWait(DEFAULT_DATASET);
+ startFtdChild(mFtd, DEFAULT_DATASET);
+ List<Inet6Address> meshLocalAddresses = mOtCtl.getMeshLocalAddresses();
+
+ for (Inet6Address address : meshLocalAddresses) {
+ assertWithMessage(
+ "There may be duplicated replies of ping request to "
+ + address.getHostAddress())
+ .that(mFtd.ping(address, 2))
+ .isEqualTo(2);
+ }
+ }
+
+ @Test
+ public void addPrefixToNetData_routeIsAddedToTunInterface() throws Exception {
+ mController.joinAndWait(DEFAULT_DATASET);
+
+ // Ftd child doesn't have the ability to add a prefix, so let BR itself add a prefix.
+ mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
+ mOtCtl.executeCommand("netdata register");
+ waitFor(
+ () -> {
+ String netData = mOtCtl.executeCommand("netdata show");
+ return getPrefixesFromNetData(netData).contains(TEST_NO_SLAAC_PREFIX);
+ },
+ NET_DATA_UPDATE_TIMEOUT);
+
+ assertRouteAddedOrRemovedInLinkProperties(true /* isAdded */, TEST_NO_SLAAC_PREFIX_ADDRESS);
+ }
+
+ @Test
+ public void removePrefixFromNetData_routeIsRemovedFromTunInterface() throws Exception {
+ mController.joinAndWait(DEFAULT_DATASET);
+ mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
+ mOtCtl.executeCommand("netdata register");
+
+ mOtCtl.executeCommand("prefix remove " + TEST_NO_SLAAC_PREFIX);
+ mOtCtl.executeCommand("netdata register");
+ waitFor(
+ () -> {
+ String netData = mOtCtl.executeCommand("netdata show");
+ return !getPrefixesFromNetData(netData).contains(TEST_NO_SLAAC_PREFIX);
+ },
+ NET_DATA_UPDATE_TIMEOUT);
+
+ assertRouteAddedOrRemovedInLinkProperties(
+ false /* isAdded */, TEST_NO_SLAAC_PREFIX_ADDRESS);
+ }
+
+ @Test
+ public void toggleThreadNetwork_routeFromPreviousNetDataIsRemoved() throws Exception {
+ mController.joinAndWait(DEFAULT_DATASET);
+ mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
+ mOtCtl.executeCommand("netdata register");
+
+ mController.leaveAndWait();
+ mController.joinAndWait(DEFAULT_DATASET);
+
+ assertRouteAddedOrRemovedInLinkProperties(
+ false /* isAdded */, TEST_NO_SLAAC_PREFIX_ADDRESS);
+ }
+
+ private void startFtdChild(FullThreadDevice ftd, ActiveOperationalDataset activeDataset)
+ throws Exception {
+ ftd.factoryReset();
+ ftd.joinNetwork(activeDataset);
+ ftd.waitForStateAnyOf(List.of("router", "child"), Duration.ofSeconds(8));
+ }
+
+ private void assertRouteAddedOrRemovedInLinkProperties(boolean isAdded, InetAddress addr)
+ throws Exception {
+ ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+
+ waitFor(
+ () -> {
+ try {
+ LinkProperties lp =
+ cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT));
+ return lp != null
+ && isAdded
+ == lp.getRoutes().stream().anyMatch(r -> r.matches(addr));
+ } catch (Exception e) {
+ return false;
+ }
+ },
+ LINK_PROPERTIES_UPDATE_TIMEOUT);
+ }
+}
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/borderrouter/src/android/net/thread/borderrouter/BorderRoutingTest.java
similarity index 99%
rename from thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
rename to thread/tests/integration/borderrouter/src/android/net/thread/borderrouter/BorderRoutingTest.java
index 40f0089..1d210c6 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/borderrouter/src/android/net/thread/borderrouter/BorderRoutingTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.net.thread;
+package android.net.thread.borderrouter;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
@@ -92,10 +92,10 @@
import java.util.function.Predicate;
/** Integration test cases for Thread Border Routing feature. */
-@RunWith(AndroidJUnit4.class)
+@LargeTest
@RequiresThreadFeature
@RequiresSimulationThreadDevice
-@LargeTest
+@RunWith(AndroidJUnit4.class)
public class BorderRoutingTest {
private static final String TAG = BorderRoutingTest.class.getSimpleName();
private static final int NUM_FTD = 2;
diff --git a/thread/tests/integration/src/android/net/thread/InternetAccessTest.kt b/thread/tests/integration/borderrouter/src/android/net/thread/borderrouter/InternetAccessTest.kt
similarity index 99%
rename from thread/tests/integration/src/android/net/thread/InternetAccessTest.kt
rename to thread/tests/integration/borderrouter/src/android/net/thread/borderrouter/InternetAccessTest.kt
index 46d4708..ad98305 100644
--- a/thread/tests/integration/src/android/net/thread/InternetAccessTest.kt
+++ b/thread/tests/integration/borderrouter/src/android/net/thread/borderrouter/InternetAccessTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.net.thread
+package android.net.thread.borderrouter
import android.content.Context
import android.net.DnsResolver.CLASS_IN
@@ -60,10 +60,10 @@
import org.junit.runner.RunWith
/** Integration test cases for Thread Internet Access features. */
+@LargeTest
@RunWith(AndroidJUnit4::class)
@RequiresThreadFeature
@RequiresSimulationThreadDevice
-@LargeTest
class InternetAccessTest {
companion object {
private val TAG = BorderRoutingTest::class.java.simpleName
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index 195f6d2..b608c5d 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -21,17 +21,14 @@
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
-import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.getIpv6Addresses;
import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
-import static android.net.thread.utils.IntegrationTestUtils.getPrefixesFromNetData;
-import static android.net.thread.utils.IntegrationTestUtils.getThreadNetwork;
-import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static android.net.thread.utils.ThreadNetworkControllerWrapper.JOIN_TIMEOUT;
+import static android.os.SystemClock.elapsedRealtime;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
-import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.io.BaseEncoding.base16;
@@ -46,8 +43,6 @@
import android.net.ConnectivityManager;
import android.net.InetAddresses;
import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
@@ -62,13 +57,15 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.FluentIterable;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -77,7 +74,6 @@
import java.net.InetAddress;
import java.time.Duration;
import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -86,7 +82,7 @@
/** Tests for E2E Android Thread integration with ot-daemon, ConnectivityService, etc.. */
@LargeTest
@RequiresThreadFeature
-@RunWith(Parameterized.class)
+@RunWith(AndroidJUnit4.class)
public class ThreadIntegrationTest {
// The byte[] buffer size for UDP tests
private static final int UDP_BUFFER_SIZE = 1024;
@@ -94,14 +90,11 @@
// The maximum time for OT addresses to be propagated to the TUN interface "thread-wpan"
private static final Duration TUN_ADDR_UPDATE_TIMEOUT = Duration.ofSeconds(1);
- // The maximum time for changes to be propagated to netdata.
- private static final Duration NET_DATA_UPDATE_TIMEOUT = Duration.ofSeconds(1);
-
- // The maximum time for changes in netdata to be propagated to link properties.
- private static final Duration LINK_PROPERTIES_UPDATE_TIMEOUT = Duration.ofSeconds(1);
-
private static final Duration NETWORK_CALLBACK_TIMEOUT = Duration.ofSeconds(10);
+ // The duration between attached and addresses show up on thread-wpan
+ private static final Duration LINK_ADDR_TIMEOUT = Duration.ofSeconds(2);
+
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
private static final byte[] DEFAULT_DATASET_TLVS =
base16().decode(
@@ -113,9 +106,6 @@
private static final ActiveOperationalDataset DEFAULT_DATASET =
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
- private static final Inet6Address GROUP_ADDR_ALL_ROUTERS =
- (Inet6Address) InetAddresses.parseNumericAddress("ff02::2");
-
private static final String TEST_NO_SLAAC_PREFIX = "9101:dead:beef:cafe::/64";
private static final InetAddress TEST_NO_SLAAC_PREFIX_ADDRESS =
InetAddresses.parseNumericAddress("9101:dead:beef:cafe::");
@@ -129,30 +119,15 @@
private OtDaemonController mOtCtl;
private FullThreadDevice mFtd;
- public final boolean mIsBorderRouterEnabled;
- private final ThreadConfiguration mConfig;
-
- @Parameterized.Parameters
- public static Collection configArguments() {
- return Arrays.asList(new Object[][] {{false}, {true}});
- }
-
- public ThreadIntegrationTest(boolean isBorderRouterEnabled) {
- mIsBorderRouterEnabled = isBorderRouterEnabled;
- mConfig =
- new ThreadConfiguration.Builder()
- .setBorderRouterEnabled(isBorderRouterEnabled)
- .build();
- }
-
@Before
public void setUp() throws Exception {
mExecutor = Executors.newSingleThreadExecutor();
+ mFtd = new FullThreadDevice(10 /* nodeId */);
mOtCtl = new OtDaemonController();
mController.setEnabledAndWait(true);
- mController.setConfigurationAndWait(mConfig);
+ mController.setConfigurationAndWait(
+ new ThreadConfiguration.Builder().setBorderRouterEnabled(false).build());
mController.leaveAndWait();
- mFtd = new FullThreadDevice(10 /* nodeId */);
}
@After
@@ -178,20 +153,6 @@
}
@Test
- public void otDaemonRestart_JoinedNetworkAndStopped_autoRejoinedAndTunIfStateConsistent()
- throws Exception {
- assumeTrue(mController.getConfiguration().isBorderRouterEnabled());
- mController.joinAndWait(DEFAULT_DATASET);
-
- runShellCommand("stop ot-daemon");
-
- mController.waitForRole(DEVICE_ROLE_DETACHED, CALLBACK_TIMEOUT);
- mController.waitForRole(DEVICE_ROLE_LEADER, RESTART_JOIN_TIMEOUT);
- assertThat(mOtCtl.isInterfaceUp()).isTrue();
- assertThat(runShellCommand("ifconfig thread-wpan")).contains("UP POINTOPOINT RUNNING");
- }
-
- @Test
public void otDaemonFactoryReset_deviceRoleIsStopped() throws Exception {
mController.joinAndWait(DEFAULT_DATASET);
@@ -265,30 +226,25 @@
}
@Test
- public void joinNetworkWithBrDisabled_meshLocalAddressesArePreferred() throws Exception {
- // When BR feature is disabled, there is no OMR address, so the mesh-local addresses are
- // expected to be preferred.
- mOtCtl.executeCommand("br disable");
+ public void joinNetwork_onlyMlEidIsPreferred() throws Exception {
mController.joinAndWait(DEFAULT_DATASET);
+ waitFor(
+ () -> getIpv6Addresses("thread-wpan").contains(mOtCtl.getMlEid()),
+ LINK_ADDR_TIMEOUT);
IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix();
- List<LinkAddress> linkAddresses = getIpv6LinkAddresses("thread-wpan");
- for (LinkAddress address : linkAddresses) {
- if (meshLocalPrefix.contains(address.getAddress())) {
- assertThat(address.getDeprecationTime())
- .isGreaterThan(SystemClock.elapsedRealtime());
- assertThat(address.isPreferred()).isTrue();
- }
- }
-
- mOtCtl.executeCommand("br enable");
- }
-
- @Test
- public void joinNetwork_tunInterfaceJoinsAllRouterMulticastGroup() throws Exception {
- mController.joinAndWait(DEFAULT_DATASET);
-
- assertTunInterfaceMemberOfGroup(GROUP_ADDR_ALL_ROUTERS);
+ var linkAddrs = FluentIterable.from(getIpv6LinkAddresses("thread-wpan"));
+ var meshLocalAddrs = linkAddrs.filter(addr -> meshLocalPrefix.contains(addr.getAddress()));
+ var mlEidAddrs = meshLocalAddrs.filter(addr -> addr.getAddress().equals(mOtCtl.getMlEid()));
+ var nonMlEidAddrs = meshLocalAddrs.filter(addr -> !mlEidAddrs.contains(addr));
+ assertThat(mlEidAddrs).hasSize(1);
+ assertThat(mlEidAddrs.allMatch(addr -> addr.isPreferred())).isTrue();
+ assertThat(mlEidAddrs.allMatch(addr -> addr.getDeprecationTime() > elapsedRealtime()))
+ .isTrue();
+ assertThat(nonMlEidAddrs).isNotEmpty();
+ assertThat(nonMlEidAddrs.allMatch(addr -> !addr.isPreferred())).isTrue();
+ assertThat(nonMlEidAddrs.allMatch(addr -> addr.getDeprecationTime() <= elapsedRealtime()))
+ .isTrue();
}
@Test
@@ -322,55 +278,6 @@
}
@Test
- public void addPrefixToNetData_routeIsAddedToTunInterface() throws Exception {
- mController.joinAndWait(DEFAULT_DATASET);
-
- // Ftd child doesn't have the ability to add a prefix, so let BR itself add a prefix.
- mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
- mOtCtl.executeCommand("netdata register");
- waitFor(
- () -> {
- String netData = mOtCtl.executeCommand("netdata show");
- return getPrefixesFromNetData(netData).contains(TEST_NO_SLAAC_PREFIX);
- },
- NET_DATA_UPDATE_TIMEOUT);
-
- assertRouteAddedOrRemovedInLinkProperties(true /* isAdded */, TEST_NO_SLAAC_PREFIX_ADDRESS);
- }
-
- @Test
- public void removePrefixFromNetData_routeIsRemovedFromTunInterface() throws Exception {
- mController.joinAndWait(DEFAULT_DATASET);
- mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
- mOtCtl.executeCommand("netdata register");
-
- mOtCtl.executeCommand("prefix remove " + TEST_NO_SLAAC_PREFIX);
- mOtCtl.executeCommand("netdata register");
- waitFor(
- () -> {
- String netData = mOtCtl.executeCommand("netdata show");
- return !getPrefixesFromNetData(netData).contains(TEST_NO_SLAAC_PREFIX);
- },
- NET_DATA_UPDATE_TIMEOUT);
-
- assertRouteAddedOrRemovedInLinkProperties(
- false /* isAdded */, TEST_NO_SLAAC_PREFIX_ADDRESS);
- }
-
- @Test
- public void toggleThreadNetwork_routeFromPreviousNetDataIsRemoved() throws Exception {
- mController.joinAndWait(DEFAULT_DATASET);
- mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
- mOtCtl.executeCommand("netdata register");
-
- mController.leaveAndWait();
- mController.joinAndWait(DEFAULT_DATASET);
-
- assertRouteAddedOrRemovedInLinkProperties(
- false /* isAdded */, TEST_NO_SLAAC_PREFIX_ADDRESS);
- }
-
- @Test
@RequiresSimulationThreadDevice
public void setConfiguration_disableBorderRouter_borderRoutingDisabled() throws Exception {
startFtdLeader(mFtd, DEFAULT_DATASET);
@@ -445,27 +352,4 @@
throw new IllegalStateException(e);
}
}
-
- private void assertTunInterfaceMemberOfGroup(Inet6Address address) throws Exception {
- waitFor(() -> isInMulticastGroup(TUN_IF_NAME, address), TUN_ADDR_UPDATE_TIMEOUT);
- }
-
- private void assertRouteAddedOrRemovedInLinkProperties(boolean isAdded, InetAddress addr)
- throws Exception {
- ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
-
- waitFor(
- () -> {
- try {
- LinkProperties lp =
- cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT));
- return lp != null
- && isAdded
- == lp.getRoutes().stream().anyMatch(r -> r.matches(addr));
- } catch (Exception e) {
- return false;
- }
- },
- LINK_PROPERTIES_UPDATE_TIMEOUT);
- }
}
diff --git a/thread/tests/utils/Android.bp b/thread/tests/utils/Android.bp
index 726ec9d..7990752 100644
--- a/thread/tests/utils/Android.bp
+++ b/thread/tests/utils/Android.bp
@@ -31,6 +31,7 @@
],
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
],
defaults: [
"framework-connectivity-test-defaults",
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/utils/src/android/net/thread/utils/FullThreadDevice.java
similarity index 100%
rename from thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
rename to thread/tests/utils/src/android/net/thread/utils/FullThreadDevice.java
diff --git a/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java b/thread/tests/utils/src/android/net/thread/utils/InfraNetworkDevice.java
similarity index 100%
rename from thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
rename to thread/tests/utils/src/android/net/thread/utils/InfraNetworkDevice.java
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt b/thread/tests/utils/src/android/net/thread/utils/IntegrationTestUtils.kt
similarity index 98%
rename from thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
rename to thread/tests/utils/src/android/net/thread/utils/IntegrationTestUtils.kt
index f41e903..77d0955 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
+++ b/thread/tests/utils/src/android/net/thread/utils/IntegrationTestUtils.kt
@@ -455,9 +455,8 @@
fun isInMulticastGroup(interfaceName: String, address: Inet6Address): Boolean {
val cmd = "ip -6 maddr show dev $interfaceName"
val output: String = runShellCommandOrThrow(cmd)
- val addressStr = address.hostAddress
for (line in output.split("\\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
- if (line.contains(addressStr)) {
+ if (line.contains(address.hostAddress!!)) {
return true
}
}
@@ -479,6 +478,12 @@
return addresses
}
+ /** Returns the list of [InetAddress] of the given network. */
+ @JvmStatic
+ fun getIpv6Addresses(interfaceName: String): List<InetAddress> {
+ return getIpv6LinkAddresses(interfaceName).map { it.address }
+ }
+
/** Return the first discovered service of `serviceType`. */
@JvmStatic
@Throws(Exception::class)
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/utils/src/android/net/thread/utils/OtDaemonController.java
similarity index 88%
rename from thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
rename to thread/tests/utils/src/android/net/thread/utils/OtDaemonController.java
index 272685f..d35b94e 100644
--- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
+++ b/thread/tests/utils/src/android/net/thread/utils/OtDaemonController.java
@@ -24,9 +24,11 @@
import com.android.compatibility.common.util.SystemUtil;
import java.net.Inet6Address;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Wrapper of the "/system/bin/ot-ctl" which can be used to send CLI commands to ot-daemon to
@@ -72,6 +74,25 @@
.toList();
}
+ /** Returns the OMR address of this device or {@code null} if it doesn't exist. */
+ @Nullable
+ public Inet6Address getOmrAddress() {
+ List<Inet6Address> allAddresses = new ArrayList<>(getAddresses());
+ allAddresses.removeAll(getMeshLocalAddresses());
+
+ List<Inet6Address> omrAddresses =
+ allAddresses.stream()
+ .filter(addr -> !addr.isLinkLocalAddress())
+ .collect(Collectors.toList());
+ if (omrAddresses.isEmpty()) {
+ return null;
+ } else if (omrAddresses.size() > 1) {
+ throw new IllegalStateException();
+ }
+
+ return omrAddresses.getFirst();
+ }
+
/** Returns {@code true} if the Thread interface is up. */
public boolean isInterfaceUp() {
String output = executeCommand("ifconfig");
diff --git a/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java b/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java
index b586a19..9a1a05b 100644
--- a/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java
+++ b/thread/tests/utils/src/android/net/thread/utils/TapTestNetworkTracker.java
@@ -100,27 +100,17 @@
private void setUpTestNetwork() throws Exception {
mInterface = mContext.getSystemService(TestNetworkManager.class).createTapInterface();
- mConnectivityManager.requestNetwork(newNetworkRequest(), mNetworkCallback);
+ mConnectivityManager.requestNetwork(
+ TestableNetworkAgent.Companion.makeNetworkRequestForInterface(
+ mInterface.getInterfaceName()),
+ mNetworkCallback);
LinkProperties lp = new LinkProperties();
lp.setInterfaceName(getInterfaceName());
mAgent =
- new TestableNetworkAgent(
- mContext,
- mLooper,
- newNetworkCapabilities(),
- lp,
- new NetworkAgentConfig.Builder().build());
- mNetwork = mAgent.register();
- mAgent.markConnected();
+ TestableNetworkAgent.Companion.createOnInterface(
+ mContext, mLooper, mInterface.getInterfaceName(), TIMEOUT.toMillis());
- PollingCheck.check(
- "No usable address on interface",
- TIMEOUT.toMillis(),
- () -> hasUsableAddress(mNetwork, getInterfaceName()));
-
- lp.setLinkAddresses(makeLinkAddresses());
- mAgent.sendLinkProperties(lp);
mNetworkCallback.eventuallyExpect(
LINK_PROPERTIES_CHANGED,
TIMEOUT.toMillis(),
@@ -133,59 +123,4 @@
mInterface.getFileDescriptor().close();
mAgent.waitForIdle(TIMEOUT.toMillis());
}
-
- private NetworkRequest newNetworkRequest() {
- return new NetworkRequest.Builder()
- .removeCapability(NET_CAPABILITY_TRUSTED)
- .addTransportType(TRANSPORT_TEST)
- .setNetworkSpecifier(new TestNetworkSpecifier(getInterfaceName()))
- .build();
- }
-
- private NetworkCapabilities newNetworkCapabilities() {
- return new NetworkCapabilities()
- .removeCapability(NET_CAPABILITY_TRUSTED)
- .addTransportType(TRANSPORT_TEST)
- .setNetworkSpecifier(new TestNetworkSpecifier(getInterfaceName()));
- }
-
- private List<LinkAddress> makeLinkAddresses() {
- List<LinkAddress> linkAddresses = new ArrayList<>();
- List<InterfaceAddress> interfaceAddresses = Collections.emptyList();
-
- try {
- interfaceAddresses =
- NetworkInterface.getByName(getInterfaceName()).getInterfaceAddresses();
- } catch (SocketException ignored) {
- // Ignore failures when getting the addresses.
- }
-
- for (InterfaceAddress address : interfaceAddresses) {
- linkAddresses.add(
- new LinkAddress(address.getAddress(), address.getNetworkPrefixLength()));
- }
-
- return linkAddresses;
- }
-
- private static boolean hasUsableAddress(Network network, String interfaceName) {
- try {
- if (NetworkInterface.getByName(interfaceName).getInterfaceAddresses().isEmpty()) {
- return false;
- }
- } catch (SocketException e) {
- return false;
- }
- // Check if the link-local address can be used. Address flags are not available without
- // elevated permissions, so check that bindSocket works.
- try {
- FileDescriptor sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
- network.bindSocket(sock);
- Os.connect(sock, parseNumericAddress("ff02::fb%" + interfaceName), 12345);
- Os.close(sock);
- } catch (ErrnoException | IOException e) {
- return false;
- }
- return true;
- }
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/TestDnsServer.kt b/thread/tests/utils/src/android/net/thread/utils/TestDnsServer.kt
similarity index 100%
rename from thread/tests/integration/src/android/net/thread/utils/TestDnsServer.kt
rename to thread/tests/utils/src/android/net/thread/utils/TestDnsServer.kt
diff --git a/thread/tests/integration/src/android/net/thread/utils/TestTunNetworkUtils.kt b/thread/tests/utils/src/android/net/thread/utils/TestTunNetworkUtils.kt
similarity index 100%
rename from thread/tests/integration/src/android/net/thread/utils/TestTunNetworkUtils.kt
rename to thread/tests/utils/src/android/net/thread/utils/TestTunNetworkUtils.kt
diff --git a/thread/tests/integration/src/android/net/thread/utils/TestUdpEchoServer.kt b/thread/tests/utils/src/android/net/thread/utils/TestUdpEchoServer.kt
similarity index 100%
rename from thread/tests/integration/src/android/net/thread/utils/TestUdpEchoServer.kt
rename to thread/tests/utils/src/android/net/thread/utils/TestUdpEchoServer.kt
diff --git a/thread/tests/integration/src/android/net/thread/utils/TestUdpServer.kt b/thread/tests/utils/src/android/net/thread/utils/TestUdpServer.kt
similarity index 100%
rename from thread/tests/integration/src/android/net/thread/utils/TestUdpServer.kt
rename to thread/tests/utils/src/android/net/thread/utils/TestUdpServer.kt
diff --git a/thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java b/thread/tests/utils/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java
similarity index 100%
rename from thread/tests/integration/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java
rename to thread/tests/utils/src/android/net/thread/utils/ThreadNetworkControllerWrapper.java