Snap for 14104676 from 68f55e673a to 25Q4-release
Change-Id: I6c1e6b1d56aad0a5475a0bbd7ca93fc6858a1866
This commit is contained in:
commit
fcc9df03ab
12 changed files with 678 additions and 52 deletions
|
|
@ -299,17 +299,16 @@ DEFINE_BPF_PROG_KVER(schedcls, ingress_accecn_eth, , AID_SYSTEM, 6_1)
|
|||
return TC_ACT_PIPE;
|
||||
}
|
||||
|
||||
uint32_t ce_packets = 1;
|
||||
uint16_t *mss_ptr = bpf_l4s_accecn_mss_map_lookup_elem(&flow_key);
|
||||
if (mss_ptr) {
|
||||
uint16_t mss = *mss_ptr;
|
||||
if (mss != 0xFFFF && mss != 0) {
|
||||
ce_packets = (payload_size / mss) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// update the map if CE is marked
|
||||
if (ip_ecn == 0b11) {
|
||||
uint32_t ce_packets = 1;
|
||||
uint16_t *mss_ptr = bpf_l4s_accecn_mss_map_lookup_elem(&flow_key);
|
||||
if (mss_ptr) {
|
||||
uint16_t mss = *mss_ptr;
|
||||
if (mss != 0xFFFF && mss != 0) {
|
||||
ce_packets = (payload_size / mss) + 1;
|
||||
}
|
||||
}
|
||||
__sync_fetch_and_add(ce_count, ce_packets);
|
||||
}
|
||||
|
||||
|
|
@ -547,17 +546,16 @@ DEFINE_BPF_PROG_KVER(schedcls, ingress_accecn_rawip, , AID_SYSTEM, 6_1)
|
|||
return TC_ACT_PIPE;
|
||||
}
|
||||
|
||||
uint32_t ce_packets = 1;
|
||||
uint16_t *mss_ptr = bpf_l4s_accecn_mss_map_lookup_elem(&flow_key);
|
||||
if (mss_ptr) {
|
||||
uint16_t mss = *mss_ptr;
|
||||
if (mss != 0xFFFF && mss != 0) {
|
||||
ce_packets = (payload_size / mss) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// update the map if CE is marked
|
||||
if (ip_ecn == 0b11) {
|
||||
uint32_t ce_packets = 1;
|
||||
uint16_t *mss_ptr = bpf_l4s_accecn_mss_map_lookup_elem(&flow_key);
|
||||
if (mss_ptr) {
|
||||
uint16_t mss = *mss_ptr;
|
||||
if (mss != 0xFFFF && mss != 0) {
|
||||
ce_packets = (payload_size / mss) + 1;
|
||||
}
|
||||
}
|
||||
__sync_fetch_and_add(ce_count, ce_packets);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ package {
|
|||
|
||||
filegroup {
|
||||
name: "framework-thread-sources",
|
||||
defaults: ["framework-sources-module-defaults"],
|
||||
srcs: [
|
||||
"java/**/*.java",
|
||||
"java/**/*.aidl",
|
||||
|
|
|
|||
|
|
@ -602,17 +602,18 @@ public final class ActiveOperationalDataset implements Parcelable {
|
|||
// SparseArray<byte[]> is not hashable
|
||||
int result = byteArraySparseArrayHashCode(mChannelMask);
|
||||
result = result * 31 + byteArraySparseArrayHashCode(mUnknownTlvs);
|
||||
return result * 31 + deepHashCode(
|
||||
mActiveTimestamp,
|
||||
mNetworkName,
|
||||
mExtendedPanId,
|
||||
mPanId,
|
||||
mChannel,
|
||||
mChannelPage,
|
||||
mPskc,
|
||||
mNetworkKey,
|
||||
mMeshLocalPrefix,
|
||||
mSecurityPolicy);
|
||||
return result * 31
|
||||
+ deepHashCode(
|
||||
mActiveTimestamp,
|
||||
mNetworkName,
|
||||
mExtendedPanId,
|
||||
mPanId,
|
||||
mChannel,
|
||||
mChannelPage,
|
||||
mPskc,
|
||||
mNetworkKey,
|
||||
mMeshLocalPrefix,
|
||||
mSecurityPolicy);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -112,6 +112,10 @@ public final class ThreadNetworkSpecifier extends NetworkSpecifier implements Pa
|
|||
/** @hide */
|
||||
@Override
|
||||
public NetworkSpecifier redact(long redactions) {
|
||||
if (mActiveDataset == null) {
|
||||
return new ThreadNetworkSpecifier.Builder(this).build();
|
||||
}
|
||||
|
||||
var redactedDatasetBuilder = new ActiveOperationalDataset.Builder(mActiveDataset);
|
||||
if ((redactions & REDACT_FOR_THREAD_NETWORK_PRIVILEGED) != 0
|
||||
|| (redactions & REDACT_FOR_ACCESS_FINE_LOCATION) != 0) {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED;
|
|||
import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.annotation.NonNull;
|
||||
|
|
@ -89,7 +90,6 @@ import android.net.Network;
|
|||
import android.net.NetworkAgent;
|
||||
import android.net.NetworkAgentConfig;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkProvider;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.NetworkScore;
|
||||
import android.net.TestNetworkSpecifier;
|
||||
|
|
@ -110,11 +110,13 @@ import android.net.thread.ThreadNetworkController;
|
|||
import android.net.thread.ThreadNetworkController.DeviceRole;
|
||||
import android.net.thread.ThreadNetworkException;
|
||||
import android.net.thread.ThreadNetworkException.ErrorCode;
|
||||
import android.net.thread.ThreadNetworkSpecifier;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.OutcomeReceiver;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
|
|
@ -198,7 +200,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
// particular, the constructor does not run on the handler thread, so it must not touch any of
|
||||
// the non-final fields, nor must it mutate any of the non-final fields inside these objects.
|
||||
|
||||
private final NetworkProvider mNetworkProvider;
|
||||
private final ThreadNetworkFactory mNetworkFactory;
|
||||
private final Supplier<IOtDaemon> mOtDaemonSupplier;
|
||||
private final ConnectivityManager mConnectivityManager;
|
||||
private final RoutingCoordinatorManager mRoutingCoordinatorManager;
|
||||
|
|
@ -233,12 +235,14 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
private InfraLinkState mInfraLinkState;
|
||||
|
||||
@Nullable private ThreadNetworkSpecifier mJoinNetworkSpecifier;
|
||||
|
||||
@VisibleForTesting
|
||||
ThreadNetworkControllerService(
|
||||
Context context,
|
||||
Handler handler,
|
||||
MockableSystemProperties systemProperties,
|
||||
NetworkProvider networkProvider,
|
||||
ThreadNetworkFactory networkFactory,
|
||||
Supplier<IOtDaemon> otDaemonSupplier,
|
||||
ConnectivityManager connectivityManager,
|
||||
RoutingCoordinatorManager routingCoordinatorManager,
|
||||
|
|
@ -253,7 +257,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
mContext = context;
|
||||
mHandler = handler;
|
||||
mSystemProperties = systemProperties;
|
||||
mNetworkProvider = networkProvider;
|
||||
mNetworkFactory = networkFactory;
|
||||
mOtDaemonSupplier = otDaemonSupplier;
|
||||
mConnectivityManager = connectivityManager;
|
||||
mRoutingCoordinatorManager = routingCoordinatorManager;
|
||||
|
|
@ -278,8 +282,6 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
HandlerThread handlerThread = new HandlerThread("ThreadHandlerThread");
|
||||
handlerThread.start();
|
||||
Handler handler = new Handler(handlerThread.getLooper());
|
||||
NetworkProvider networkProvider =
|
||||
new NetworkProvider(context, handlerThread.getLooper(), "ThreadNetworkProvider");
|
||||
Map<Network, LinkProperties> networkToLinkProperties = new HashMap<>();
|
||||
final ConnectivityManager connectivityManager =
|
||||
context.getSystemService(ConnectivityManager.class);
|
||||
|
|
@ -291,7 +293,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
context,
|
||||
handler,
|
||||
new MockableSystemProperties(),
|
||||
networkProvider,
|
||||
ThreadNetworkFactory.newInstance(context, handler.getLooper()),
|
||||
() -> IOtDaemon.Stub.asInterface(CommonConnectivityJni.waitForService("ot_daemon")),
|
||||
connectivityManager,
|
||||
routingCoordinatorManager,
|
||||
|
|
@ -501,7 +503,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to create Thread tunnel interface", e);
|
||||
}
|
||||
mConnectivityManager.registerNetworkProvider(mNetworkProvider);
|
||||
mNetworkFactory.initialize(this);
|
||||
mNetworkFactory.register();
|
||||
mUserRestricted = isThreadUserRestricted();
|
||||
registerUserRestrictionsReceiver();
|
||||
|
||||
|
|
@ -904,31 +907,47 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
mTestNetworkAgent = testNetworkAgent;
|
||||
}
|
||||
|
||||
private NetworkCapabilities computeNetworkCapabilities(
|
||||
@Nullable ActiveOperationalDataset activeDataset) {
|
||||
final var netCapsBuilder =
|
||||
new NetworkCapabilities.Builder()
|
||||
.addTransportType(TRANSPORT_THREAD)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK);
|
||||
if (activeDataset != null) {
|
||||
boolean createPartition =
|
||||
(mJoinNetworkSpecifier != null)
|
||||
? mJoinNetworkSpecifier.shouldCreatePartitionIfNotFound()
|
||||
: false;
|
||||
final var actualSpecifier =
|
||||
new ThreadNetworkSpecifier.Builder()
|
||||
.setActiveOperationalDataset(activeDataset)
|
||||
.setShouldCreatePartitionIfNotFound(createPartition)
|
||||
.build();
|
||||
netCapsBuilder.setNetworkSpecifier(actualSpecifier);
|
||||
}
|
||||
return netCapsBuilder.build();
|
||||
}
|
||||
|
||||
private NetworkAgent newNetworkAgent() {
|
||||
if (mTestNetworkAgent != null) {
|
||||
return mTestNetworkAgent;
|
||||
}
|
||||
|
||||
final var netCapsBuilder =
|
||||
new NetworkCapabilities.Builder()
|
||||
.addTransportType(TRANSPORT_THREAD)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
|
||||
final var scoreBuilder = new NetworkScore.Builder();
|
||||
|
||||
netCapsBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK);
|
||||
scoreBuilder.setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK);
|
||||
|
||||
return new NetworkAgent(
|
||||
mContext,
|
||||
mHandler.getLooper(),
|
||||
LOG.getTag(),
|
||||
netCapsBuilder.build(),
|
||||
computeNetworkCapabilities(mOtDaemonCallbackProxy.mActiveDataset),
|
||||
getTunIfLinkProperties(),
|
||||
newLocalNetworkConfig(),
|
||||
scoreBuilder.build(),
|
||||
new NetworkAgentConfig.Builder().build(),
|
||||
mNetworkProvider) {
|
||||
mNetworkFactory.getProvider()) {
|
||||
|
||||
// TODO(b/374037595): use NetworkFactory to handle dynamic network requests
|
||||
@Override
|
||||
|
|
@ -1235,6 +1254,22 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
};
|
||||
}
|
||||
|
||||
private IOtStatusReceiver newOtStatusReceiver(
|
||||
OutcomeReceiver<Void, ThreadNetworkException> receiver) {
|
||||
return new IOtStatusReceiver.Stub() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
receiver.onResult(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int otError, String message) {
|
||||
receiver.onError(
|
||||
new ThreadNetworkException(otErrorToAndroidError(otError), message));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IOtOutputReceiver newOtOutputReceiver(OutputReceiverWrapper receiver) {
|
||||
return new IOtOutputReceiver.Stub() {
|
||||
@Override
|
||||
|
|
@ -1299,15 +1334,46 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
@NonNull OperationReceiverWrapper receiver) {
|
||||
checkOnHandlerThread();
|
||||
|
||||
mJoinNetworkSpecifier = null;
|
||||
try {
|
||||
// The otDaemon.join() will leave first if this device is currently attached
|
||||
getOtDaemon().join(activeDataset.toThreadTlvs(), newOtStatusReceiver(receiver));
|
||||
getOtDaemon()
|
||||
.join(
|
||||
activeDataset.toThreadTlvs(),
|
||||
true /* createPartitionIfNotFound */,
|
||||
newOtStatusReceiver(receiver));
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
LOG.e("otDaemon.join failed", e);
|
||||
receiver.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
void joinWithSpecifier(
|
||||
ThreadNetworkSpecifier specifier,
|
||||
OutcomeReceiver<Void, ThreadNetworkException> receiver) {
|
||||
mHandler.post(() -> joinWithSpecifierInternal(specifier, receiver));
|
||||
}
|
||||
|
||||
private void joinWithSpecifierInternal(
|
||||
ThreadNetworkSpecifier specifier,
|
||||
OutcomeReceiver<Void, ThreadNetworkException> receiver) {
|
||||
checkOnHandlerThread();
|
||||
|
||||
mJoinNetworkSpecifier = specifier;
|
||||
byte[] dataset = requireNonNull(specifier.getActiveOperationalDataset()).toThreadTlvs();
|
||||
boolean createPartitionIfNotFound = specifier.shouldCreatePartitionIfNotFound();
|
||||
try {
|
||||
getOtDaemon().join(dataset, createPartitionIfNotFound, newOtStatusReceiver(receiver));
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
LOG.e("otDaemon.joinWithSpecifier failed", e);
|
||||
if (e instanceof RemoteException re) {
|
||||
receiver.onError(new ThreadNetworkException(ERROR_INTERNAL_ERROR, re.getMessage()));
|
||||
} else {
|
||||
receiver.onError((ThreadNetworkException) e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleMigration(
|
||||
@NonNull PendingOperationalDataset pendingDataset,
|
||||
|
|
@ -1509,6 +1575,13 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
setInfraLinkState(newInfraLinkStateBuilder().build());
|
||||
}
|
||||
|
||||
private void handleActiveDatasetChanged(@Nullable ActiveOperationalDataset newActiveDataset) {
|
||||
if (mNetworkAgent == null) {
|
||||
return;
|
||||
}
|
||||
mNetworkAgent.sendNetworkCapabilities(computeNetworkCapabilities(newActiveDataset));
|
||||
}
|
||||
|
||||
private void handleThreadInterfaceStateChanged(boolean isUp) {
|
||||
try {
|
||||
mTunIfController.setInterfaceUp(isUp);
|
||||
|
|
@ -1994,6 +2067,14 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
ActiveOperationalDataset activeDataset, long listenerId) {
|
||||
checkOnHandlerThread();
|
||||
boolean hasChange = !Objects.equals(mActiveDataset, activeDataset);
|
||||
if (hasChange) {
|
||||
LOG.i(
|
||||
"Current Active Operational Dataset changed: "
|
||||
+ mActiveDataset
|
||||
+ " -> "
|
||||
+ activeDataset);
|
||||
handleActiveDatasetChanged(activeDataset);
|
||||
}
|
||||
|
||||
for (var callbackEntry : mOpDatasetCallbacks.entrySet()) {
|
||||
if (!hasChange && callbackEntry.getValue().id != listenerId) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* 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.thread;
|
||||
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_THREAD;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.MatchAllNetworkSpecifier;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkFactory;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.thread.IOperationReceiver;
|
||||
import android.net.thread.ThreadNetworkException;
|
||||
import android.net.thread.ThreadNetworkSpecifier;
|
||||
import android.os.Looper;
|
||||
import android.os.OutcomeReceiver;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.net.module.util.SharedLog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
// TODO(b/442787508): replace NetworkFactory subclass with direct usage of NetworkProvider
|
||||
/**
|
||||
* Serves network requests that are initiated from apps via the {@link
|
||||
* ConnectivityManager#requestNetwork} API.
|
||||
*/
|
||||
public final class ThreadNetworkFactory extends NetworkFactory {
|
||||
private static final SharedLog LOG =
|
||||
ThreadNetworkLogger.forSubComponent("ThreadNetworkFactory");
|
||||
private final List<NetworkRequest> mActiveRequests = new ArrayList<>();
|
||||
private @Nullable ThreadNetworkControllerService mControllerService;
|
||||
|
||||
/** Creates a new {@link ThreadNetworkFactory} instance. */
|
||||
public static ThreadNetworkFactory newInstance(Context context, Looper looper) {
|
||||
return new ThreadNetworkFactory(context, looper);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ThreadNetworkFactory(Context context, Looper looper) {
|
||||
super(looper, context, LOG.getTag(), createBaseCapabilities());
|
||||
mControllerService = null;
|
||||
}
|
||||
|
||||
void initialize(ThreadNetworkControllerService controllerService) {
|
||||
mControllerService = controllerService;
|
||||
}
|
||||
|
||||
/** Returns the basic capabilities that all Thread networks initially have. */
|
||||
private static NetworkCapabilities createBaseCapabilities() {
|
||||
return new NetworkCapabilities.Builder()
|
||||
.addTransportType(TRANSPORT_THREAD)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
|
||||
.setLinkUpstreamBandwidthKbps(100)
|
||||
.setLinkDownstreamBandwidthKbps(100)
|
||||
// By default accepts all network requests which have a specifier
|
||||
.setNetworkSpecifier(new MatchAllNetworkSpecifier())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptRequest(@NonNull final NetworkRequest request) {
|
||||
// the filtering is done in {@link #needNetworkFor}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void needNetworkFor(@NonNull final NetworkRequest request) {
|
||||
LOG.i("network request is received for " + request);
|
||||
if (!request.hasTransport(TRANSPORT_THREAD)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.getNetworkSpecifier() instanceof ThreadNetworkSpecifier specifier
|
||||
&& specifier.getActiveOperationalDataset() != null) {
|
||||
mActiveRequests.add(request);
|
||||
join(specifier);
|
||||
} else {
|
||||
// Can do nothing if ThreadNetworkSpecifier or the Dataset is missing
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void join(ThreadNetworkSpecifier specifier) {
|
||||
var receiver =
|
||||
new OutcomeReceiver<Void, ThreadNetworkException>() {
|
||||
@Override
|
||||
public void onError(ThreadNetworkException e) {
|
||||
LOG.e("Failed to join Thread network: " + specifier, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResult(Void v) {
|
||||
LOG.i("Successfully joined Thread network: " + specifier);
|
||||
}
|
||||
};
|
||||
mControllerService.joinWithSpecifier(specifier, receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseNetworkFor(@NonNull final NetworkRequest request) {
|
||||
LOG.i("network release request is received for " + request);
|
||||
if (mActiveRequests.isEmpty()) {
|
||||
LOG.w("Unexpect release of untracked request " + request);
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkRequest lastRequest = mActiveRequests.getLast();
|
||||
mActiveRequests.removeAll(Collections.singletonList(request));
|
||||
|
||||
if (mActiveRequests.isEmpty()) {
|
||||
leave(request);
|
||||
} else if (!Objects.equals(mActiveRequests.getLast(), lastRequest)) {
|
||||
// If the last request is changed, let's re-join the network with the new latest
|
||||
// request
|
||||
join((ThreadNetworkSpecifier) mActiveRequests.getLast().getNetworkSpecifier());
|
||||
}
|
||||
}
|
||||
|
||||
private void leave(NetworkRequest request) {
|
||||
var receiver =
|
||||
new IOperationReceiver.Stub() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LOG.i("Successfully left Thread network for releasing request " + request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String errorMessage) {
|
||||
var ex = new ThreadNetworkException(errorCode, errorMessage);
|
||||
LOG.e(
|
||||
"Failed to leave Thread network for releasing request " + request,
|
||||
ex);
|
||||
}
|
||||
};
|
||||
mControllerService.leave(receiver);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* 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.cts;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.Manifest.permission.THREAD_NETWORK_PRIVILEGED;
|
||||
import static android.net.ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_THREAD;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.net.thread.flags.Flags.FLAG_THREAD_MOBILE_ENABLED;
|
||||
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.ThreadNetworkSpecifier;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
|
||||
import android.net.thread.utils.ThreadNetworkControllerWrapper;
|
||||
import android.platform.test.annotations.RequiresFlagsEnabled;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import com.android.testutils.TestableNetworkCallback;
|
||||
import com.android.testutils.TestableNetworkCallback.Event;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Tests for requesting a Thread network via the {@link ConnectivityManager#requestNetwork} and
|
||||
* {@link ThreadNetworkSpecifier} API.
|
||||
*/
|
||||
@LargeTest
|
||||
@RequiresThreadFeature
|
||||
@RequiresFlagsEnabled(FLAG_THREAD_MOBILE_ENABLED)
|
||||
public final class ThreadNetworkRequestTest {
|
||||
private static final int JOIN_TIMEOUT_MILLIS = 30_000;
|
||||
private static final Duration STOP_TIMEOUT = Duration.ofSeconds(2_000);
|
||||
|
||||
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
|
||||
// Active Timestamp: 1
|
||||
// Channel: 13
|
||||
// Wake-up Channel: 18
|
||||
// Channel Mask: 0x07fff800
|
||||
// Ext PAN ID: 492cc112e634e51b
|
||||
// Mesh Local Prefix: fd2f:357c:dd90:2120::/64
|
||||
// Network Key: 9f55333f52eb25dbfdbe62121b39bee4
|
||||
// Network Name: OpenThread-145a
|
||||
// PAN ID: 0x145a
|
||||
// PSKc: 6d81fe700160ab9f63f50f78ccf0733c
|
||||
// Security Policy: 672 onrc 0
|
||||
private static final byte[] DATASET_TLVS_1 =
|
||||
base16().ignoreCase()
|
||||
.decode(
|
||||
"0e080000000000010000000300000d4a0300001235060004001fff"
|
||||
+ "e00208492cc112e634e51b0708fd2f357cdd90212005109f55333f"
|
||||
+ "52eb25dbfdbe62121b39bee4030f4f70656e5468726561642d3134"
|
||||
+ "35610102145a04106d81fe700160ab9f63f50f78ccf0733c0c0402a0f7f8");
|
||||
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
|
||||
// Active Timestamp: 1
|
||||
// Channel: 24
|
||||
// Wake-up Channel: 19
|
||||
// Channel Mask: 0x07fff800
|
||||
// Ext PAN ID: 779732128edb4591
|
||||
// Mesh Local Prefix: fdbd:3671:821d:18c1::/64
|
||||
// Network Key: 6a643ec02d38ef55b21b26cec46f622d
|
||||
// Network Name: OpenThread-64be
|
||||
// PAN ID: 0x64be
|
||||
// PSKc: 37968aab079fac83fa67b14c9bb7f598
|
||||
// Security Policy: 672 onrc 0
|
||||
private static final byte[] DATASET_TLVS_2 =
|
||||
base16().ignoreCase()
|
||||
.decode(
|
||||
"0e08000000000001000000030000184a0300001335060004001fff"
|
||||
+ "e00208779732128edb45910708fdbd3671821d18c105106a643ec0"
|
||||
+ "2d38ef55b21b26cec46f622d030f4f70656e5468726561642d3634"
|
||||
+ "6265010264be041037968aab079fac83fa67b14c9bb7f5980c0402a0f7f8");
|
||||
|
||||
private static final ActiveOperationalDataset TEST_DATASET_1 =
|
||||
ActiveOperationalDataset.fromThreadTlvs(DATASET_TLVS_1);
|
||||
private static final ActiveOperationalDataset TEST_DATASET_2 =
|
||||
ActiveOperationalDataset.fromThreadTlvs(DATASET_TLVS_2);
|
||||
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
private ConnectivityManager cm;
|
||||
private ThreadNetworkControllerWrapper mController;
|
||||
private List<TestableNetworkCallback> mCallbacks;
|
||||
private Set<String> mGrantedPermissions;
|
||||
|
||||
private void grantPermissions(String... permissions) {
|
||||
for (String permission : permissions) {
|
||||
mGrantedPermissions.add(permission);
|
||||
}
|
||||
String[] allPermissions = new String[mGrantedPermissions.size()];
|
||||
mGrantedPermissions.toArray(allPermissions);
|
||||
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(allPermissions);
|
||||
}
|
||||
|
||||
private static void dropAllPermissions() {
|
||||
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mCallbacks = new ArrayList<>();
|
||||
mGrantedPermissions = new HashSet<String>();
|
||||
cm = mContext.getSystemService(ConnectivityManager.class);
|
||||
mController = ThreadNetworkControllerWrapper.newInstance(mContext);
|
||||
mController.setBorderRouterEnabledAndWait(false);
|
||||
mController.leaveAndWait();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
dropAllPermissions();
|
||||
for (var callback : mCallbacks) {
|
||||
cm.unregisterNetworkCallback(callback);
|
||||
}
|
||||
mController.waitForRole(DEVICE_ROLE_STOPPED, STOP_TIMEOUT);
|
||||
}
|
||||
|
||||
private TestableNetworkCallback newNetworkCallback() {
|
||||
var callback =
|
||||
new TestableNetworkCallback(
|
||||
250L /* timeoutMs */,
|
||||
250L /* noCallbackTimeoutMs */,
|
||||
() -> {} /* waiterFunc */,
|
||||
"ThreadNetworkRequestTest" /* logTag */,
|
||||
FLAG_INCLUDE_LOCATION_INFO);
|
||||
mCallbacks.add(callback);
|
||||
return callback;
|
||||
}
|
||||
|
||||
private NetworkRequest newThreadNetworkRequest(
|
||||
ActiveOperationalDataset dataset, boolean createPartitionIfNotFound) {
|
||||
var specifier =
|
||||
new ThreadNetworkSpecifier.Builder()
|
||||
.setActiveOperationalDataset(dataset)
|
||||
.setShouldCreatePartitionIfNotFound(createPartitionIfNotFound)
|
||||
.build();
|
||||
return new NetworkRequest.Builder()
|
||||
.addTransportType(TRANSPORT_THREAD)
|
||||
.setNetworkSpecifier(specifier)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestNetwork_withoutThreadPrivilegedPermission_failsWithUnavailable() {
|
||||
dropAllPermissions();
|
||||
var request = newThreadNetworkRequest(TEST_DATASET_1, true /* createPartitionIfNotFound */);
|
||||
var callback = newNetworkCallback();
|
||||
|
||||
assertThrows(SecurityException.class, () -> cm.requestNetwork(request, callback));
|
||||
mCallbacks.remove(callback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestNetwork_withCreatePartitionEnabled_succeedsWithAvailable() {
|
||||
grantPermissions(THREAD_NETWORK_PRIVILEGED, ACCESS_FINE_LOCATION);
|
||||
var request = newThreadNetworkRequest(TEST_DATASET_1, true /* createPartitionIfNotFound */);
|
||||
|
||||
var callback = newNetworkCallback();
|
||||
cm.requestNetwork(request, callback);
|
||||
|
||||
callback.eventuallyExpect(Event.AVAILABLE, JOIN_TIMEOUT_MILLIS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestNetwork_multipleRequests_theLastOneWins() {
|
||||
grantPermissions(THREAD_NETWORK_PRIVILEGED, ACCESS_FINE_LOCATION);
|
||||
var request1 =
|
||||
newThreadNetworkRequest(TEST_DATASET_1, true /* createPartitionIfNotFound */);
|
||||
var request2 =
|
||||
newThreadNetworkRequest(TEST_DATASET_2, true /* createPartitionIfNotFound */);
|
||||
|
||||
var callback1 = newNetworkCallback();
|
||||
var callback2 = newNetworkCallback();
|
||||
cm.requestNetwork(request1, callback1);
|
||||
cm.requestNetwork(request2, callback2);
|
||||
|
||||
callback2.eventuallyExpect(Event.AVAILABLE, JOIN_TIMEOUT_MILLIS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unregisterNetworkCallback_threadIsStopped() throws Exception {
|
||||
grantPermissions(THREAD_NETWORK_PRIVILEGED, ACCESS_FINE_LOCATION);
|
||||
var request = newThreadNetworkRequest(TEST_DATASET_1, true /* createPartitionIfNotFound */);
|
||||
var callback = newNetworkCallback();
|
||||
cm.requestNetwork(request, callback);
|
||||
callback.eventuallyExpect(Event.AVAILABLE, JOIN_TIMEOUT_MILLIS);
|
||||
|
||||
cm.unregisterNetworkCallback(callback);
|
||||
mCallbacks.remove(callback);
|
||||
|
||||
dropAllPermissions();
|
||||
mController.waitForRole(DEVICE_ROLE_STOPPED, STOP_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
package="android.net.thread.unittests">
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.NETWORK_FACTORY" />
|
||||
|
||||
<application android:debuggable="true">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
|
|
|
|||
|
|
@ -179,8 +179,11 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
private static final String TEST_VENDOR_NAME = "test vendor";
|
||||
private static final String TEST_MODEL_NAME = "test model";
|
||||
private static final LinkAddress TEST_NAT64_CIDR = new LinkAddress("192.168.255.0/24");
|
||||
private static final int THREAD_NETWORK_PROVIDER_ID = 1;
|
||||
|
||||
@Mock private MockableSystemProperties mMockSystemProperties;
|
||||
@Mock private ThreadNetworkFactory mMockNetworkFactory;
|
||||
@Mock private NetworkProvider mMockNetworkProvider;
|
||||
@Mock private ConnectivityManager mMockConnectivityManager;
|
||||
@Mock private RoutingCoordinatorManager mMockRoutingCoordinatorManager;
|
||||
@Mock private NetworkAgent mMockNetworkAgent;
|
||||
|
|
@ -233,8 +236,12 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
|
||||
mTestLooper = new TestLooper();
|
||||
final Handler handler = new Handler(mTestLooper.getLooper());
|
||||
NetworkProvider networkProvider =
|
||||
new NetworkProvider(mContext, mTestLooper.getLooper(), "ThreadNetworkProvider");
|
||||
|
||||
when(mMockNetworkFactory.getProvider()).thenReturn(mMockNetworkProvider);
|
||||
doNothing().when(mMockNetworkFactory).register();
|
||||
|
||||
when(mMockConnectivityManager.registerNetworkProvider(any(NetworkProvider.class)))
|
||||
.thenReturn(THREAD_NETWORK_PROVIDER_ID);
|
||||
|
||||
when(mMockRoutingCoordinatorManager.requestDownstreamAddress(any()))
|
||||
.thenReturn(TEST_NAT64_CIDR);
|
||||
|
|
@ -272,7 +279,7 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
mContext,
|
||||
handler,
|
||||
mMockSystemProperties,
|
||||
networkProvider,
|
||||
mMockNetworkFactory,
|
||||
() -> mFakeOtDaemon,
|
||||
mMockConnectivityManager,
|
||||
mMockRoutingCoordinatorManager,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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.thread;
|
||||
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.ThreadNetworkSpecifier;
|
||||
import android.os.test.TestLooper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** Unit tests for {@link ThreadNetworkFactory}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public final class ThreadNetworkFactoryTest {
|
||||
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
|
||||
// Active Timestamp: 1
|
||||
// Channel: 19
|
||||
// Channel Mask: 0x07FFF800
|
||||
// Ext PAN ID: ACC214689BC40BDF
|
||||
// Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
|
||||
// Network Key: F26B3153760F519A63BAFDDFFC80D2AF
|
||||
// Network Name: OpenThread-d9a0
|
||||
// PAN ID: 0xD9A0
|
||||
// PSKc: A245479C836D551B9CA557F7B9D351B4
|
||||
// Security Policy: 672 onrcb
|
||||
private static final byte[] DATASET_TLVS =
|
||||
base16().decode(
|
||||
"0E080000000000010000000300001335060004001FFFE002"
|
||||
+ "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
|
||||
+ "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
|
||||
+ "642D643961300102D9A00410A245479C836D551B9CA557F7"
|
||||
+ "B9D351B40C0402A0FFF8");
|
||||
private static final ActiveOperationalDataset ACTIVE_DATASET =
|
||||
ActiveOperationalDataset.fromThreadTlvs(DATASET_TLVS);
|
||||
|
||||
private Context mContext;
|
||||
private TestLooper mTestLooper;
|
||||
@Mock private ThreadNetworkControllerService mMockControllerService;
|
||||
|
||||
private ThreadNetworkFactory mFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mContext = ApplicationProvider.getApplicationContext();
|
||||
mTestLooper = new TestLooper();
|
||||
mFactory = new ThreadNetworkFactory(mContext, mTestLooper.getLooper());
|
||||
mFactory.initialize(mMockControllerService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void needNetworkFor_noThreadTransport_doNothing() {
|
||||
NetworkRequest request =
|
||||
new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
.build();
|
||||
|
||||
mFactory.needNetworkFor(request);
|
||||
|
||||
verifyNoInteractions(mMockControllerService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void needNetworkFor_threadTransportAndNoSpecifier_doNothing() {
|
||||
NetworkRequest request =
|
||||
new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.build();
|
||||
|
||||
mFactory.needNetworkFor(request);
|
||||
|
||||
verifyNoInteractions(mMockControllerService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void needNetworkFor_threadTransportAndSpecifier_joinIsCalled() {
|
||||
ThreadNetworkSpecifier specifier =
|
||||
new ThreadNetworkSpecifier.Builder()
|
||||
.setActiveOperationalDataset(ACTIVE_DATASET)
|
||||
.build();
|
||||
NetworkRequest request =
|
||||
new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.setNetworkSpecifier(specifier)
|
||||
.build();
|
||||
|
||||
mFactory.needNetworkFor(request);
|
||||
|
||||
verify(mMockControllerService).joinWithSpecifier(eq(specifier), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void needNetworkFor_specifierWithNoDataset_doNothing() {
|
||||
NetworkRequest request =
|
||||
new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.setNetworkSpecifier(new ThreadNetworkSpecifier.Builder().build())
|
||||
.build();
|
||||
|
||||
mFactory.needNetworkFor(request);
|
||||
|
||||
verifyNoInteractions(mMockControllerService);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,8 +17,8 @@ package android.net.thread.utils;
|
|||
|
||||
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
|
||||
|
||||
import static com.android.testutils.TestableNetworkCallback.Event.LINK_PROPERTIES_CHANGED;
|
||||
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
||||
import static com.android.testutils.TestableNetworkCallback.Event.LINK_PROPERTIES_CHANGED;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ public final class ThreadNetworkControllerWrapper {
|
|||
try {
|
||||
return future.get(timeout.toSeconds(), SECONDS);
|
||||
} finally {
|
||||
mController.unregisterStateCallback(callback);
|
||||
runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -230,6 +230,12 @@ public final class ThreadNetworkControllerWrapper {
|
|||
future.get(CONFIG_TIMEOUT.toSeconds(), SECONDS);
|
||||
}
|
||||
|
||||
public void setBorderRouterEnabledAndWait(boolean enabled) throws Exception {
|
||||
final ThreadConfiguration config =
|
||||
new ThreadConfiguration.Builder().setBorderRouterEnabled(enabled).build();
|
||||
setConfigurationAndWait(config);
|
||||
}
|
||||
|
||||
public void setNat64EnabledAndWait(boolean enabled) throws Exception {
|
||||
final ThreadConfiguration config = getConfiguration();
|
||||
final ThreadConfiguration newConfig =
|
||||
|
|
|
|||
Loading…
Reference in a new issue