Merge "[Thread] add private API for disabling border router" into main
This commit is contained in:
commit
a2f666e08a
11 changed files with 329 additions and 52 deletions
|
|
@ -44,3 +44,12 @@ flag {
|
|||
description: "Controls whether the setConfiguration API of NAT64 feature is enabled"
|
||||
bug: "368456504"
|
||||
}
|
||||
|
||||
flag {
|
||||
name: "thread_mobile_enabled"
|
||||
is_exported: true
|
||||
is_fixed_read_only: true
|
||||
namespace: "thread_network"
|
||||
description: "Controls whether Thread support for mobile devices is enabled"
|
||||
bug: "368867060"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,18 +44,38 @@ import java.util.Objects;
|
|||
@FlaggedApi(Flags.FLAG_CONFIGURATION_ENABLED)
|
||||
@SystemApi
|
||||
public final class ThreadConfiguration implements Parcelable {
|
||||
private final boolean mBorderRouterEnabled;
|
||||
private final boolean mNat64Enabled;
|
||||
private final boolean mDhcpv6PdEnabled;
|
||||
|
||||
private ThreadConfiguration(Builder builder) {
|
||||
this(builder.mNat64Enabled, builder.mDhcpv6PdEnabled);
|
||||
this(builder.mBorderRouterEnabled, builder.mNat64Enabled, builder.mDhcpv6PdEnabled);
|
||||
}
|
||||
|
||||
private ThreadConfiguration(boolean nat64Enabled, boolean dhcpv6PdEnabled) {
|
||||
private ThreadConfiguration(
|
||||
boolean borderRouterEnabled, boolean nat64Enabled, boolean dhcpv6PdEnabled) {
|
||||
this.mBorderRouterEnabled = borderRouterEnabled;
|
||||
this.mNat64Enabled = nat64Enabled;
|
||||
this.mDhcpv6PdEnabled = dhcpv6PdEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this device is operating as a Thread Border Router.
|
||||
*
|
||||
* <p>A Thread Border Router works on both Thread and infrastructure networks. For example, it
|
||||
* can route packets between Thread and infrastructure networks (e.g. Wi-Fi or Ethernet), makes
|
||||
* devices in both networks discoverable to each other, and accepts connections from external
|
||||
* commissioner.
|
||||
*
|
||||
* <p>Note it costs significantly more power to operate as a Border Router, so this is typically
|
||||
* only enabled for wired Android devices (e.g. TV or display).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public boolean isBorderRouterEnabled() {
|
||||
return mBorderRouterEnabled;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if NAT64 is enabled. */
|
||||
public boolean isNat64Enabled() {
|
||||
return mNat64Enabled;
|
||||
|
|
@ -78,22 +98,24 @@ public final class ThreadConfiguration implements Parcelable {
|
|||
return false;
|
||||
} else {
|
||||
ThreadConfiguration otherConfig = (ThreadConfiguration) other;
|
||||
return mNat64Enabled == otherConfig.mNat64Enabled
|
||||
return mBorderRouterEnabled == otherConfig.mBorderRouterEnabled
|
||||
&& mNat64Enabled == otherConfig.mNat64Enabled
|
||||
&& mDhcpv6PdEnabled == otherConfig.mDhcpv6PdEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mNat64Enabled, mDhcpv6PdEnabled);
|
||||
return Objects.hash(mBorderRouterEnabled, mNat64Enabled, mDhcpv6PdEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('{');
|
||||
sb.append("Nat64Enabled=").append(mNat64Enabled);
|
||||
sb.append(", Dhcpv6PdEnabled=").append(mDhcpv6PdEnabled);
|
||||
sb.append("borderRouterEnabled=").append(mBorderRouterEnabled);
|
||||
sb.append(", nat64Enabled=").append(mNat64Enabled);
|
||||
sb.append(", dhcpv6PdEnabled=").append(mDhcpv6PdEnabled);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
|
@ -105,6 +127,7 @@ public final class ThreadConfiguration implements Parcelable {
|
|||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeBoolean(mBorderRouterEnabled);
|
||||
dest.writeBoolean(mNat64Enabled);
|
||||
dest.writeBoolean(mDhcpv6PdEnabled);
|
||||
}
|
||||
|
|
@ -114,6 +137,7 @@ public final class ThreadConfiguration implements Parcelable {
|
|||
@Override
|
||||
public ThreadConfiguration createFromParcel(Parcel in) {
|
||||
ThreadConfiguration.Builder builder = new ThreadConfiguration.Builder();
|
||||
builder.setBorderRouterEnabled(in.readBoolean());
|
||||
builder.setNat64Enabled(in.readBoolean());
|
||||
builder.setDhcpv6PdEnabled(in.readBoolean());
|
||||
return builder.build();
|
||||
|
|
@ -133,6 +157,10 @@ public final class ThreadConfiguration implements Parcelable {
|
|||
@FlaggedApi(Flags.FLAG_SET_NAT64_CONFIGURATION_ENABLED)
|
||||
@SystemApi
|
||||
public static final class Builder {
|
||||
// Thread in Android V is default to a Border Router device, so the default value here needs
|
||||
// to be {@code true} to be compatible.
|
||||
private boolean mBorderRouterEnabled = true;
|
||||
|
||||
private boolean mNat64Enabled = false;
|
||||
private boolean mDhcpv6PdEnabled = false;
|
||||
|
||||
|
|
@ -156,10 +184,25 @@ public final class ThreadConfiguration implements Parcelable {
|
|||
public Builder(@NonNull ThreadConfiguration config) {
|
||||
Objects.requireNonNull(config);
|
||||
|
||||
mBorderRouterEnabled = config.mBorderRouterEnabled;
|
||||
mNat64Enabled = config.mNat64Enabled;
|
||||
mDhcpv6PdEnabled = config.mDhcpv6PdEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables this device as a Border Router.
|
||||
*
|
||||
* <p>Defaults to {@code true} if this method is not called.
|
||||
*
|
||||
* @see ThreadConfiguration#isBorderRouterEnabled
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setBorderRouterEnabled(boolean enabled) {
|
||||
this.mBorderRouterEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables NAT64 for the device.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
|||
import static android.net.MulticastRoutingConfig.CONFIG_FORWARD_NONE;
|
||||
import static android.net.MulticastRoutingConfig.FORWARD_SELECTED;
|
||||
import static android.net.MulticastRoutingConfig.FORWARD_WITH_MIN_SCOPE;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_THREAD;
|
||||
import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
|
||||
import static android.net.thread.ActiveOperationalDataset.LENGTH_EXTENDED_PAN_ID;
|
||||
import static android.net.thread.ActiveOperationalDataset.LENGTH_MESH_LOCAL_PREFIX_BITS;
|
||||
|
|
@ -122,8 +123,8 @@ import android.util.SparseArray;
|
|||
|
||||
import com.android.connectivity.resources.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.net.module.util.RoutingCoordinatorManager;
|
||||
import com.android.net.module.util.IIpv4PrefixRequest;
|
||||
import com.android.net.module.util.RoutingCoordinatorManager;
|
||||
import com.android.net.module.util.SharedLog;
|
||||
import com.android.server.ServiceManagerWrapper;
|
||||
import com.android.server.connectivity.ConnectivityResources;
|
||||
|
|
@ -151,7 +152,6 @@ import java.time.Clock;
|
|||
import java.time.DateTimeException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -222,6 +222,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
private NetworkRequest mUpstreamNetworkRequest;
|
||||
private UpstreamNetworkCallback mUpstreamNetworkCallback;
|
||||
private TestNetworkSpecifier mUpstreamTestNetworkSpecifier;
|
||||
private ThreadNetworkCallback mThreadNetworkCallback;
|
||||
private final Map<Network, LinkProperties> mNetworkToLinkProperties;
|
||||
private final ThreadPersistentSettings mPersistentSettings;
|
||||
private final UserManager mUserManager;
|
||||
|
|
@ -315,14 +316,6 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
.build();
|
||||
}
|
||||
|
||||
private LocalNetworkConfig newLocalNetworkConfig() {
|
||||
return new LocalNetworkConfig.Builder()
|
||||
.setUpstreamMulticastRoutingConfig(mUpstreamMulticastRoutingConfig)
|
||||
.setDownstreamMulticastRoutingConfig(mDownstreamMulticastRoutingConfig)
|
||||
.setUpstreamSelector(mUpstreamNetworkRequest)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void maybeInitializeOtDaemon() {
|
||||
if (!shouldEnableThread()) {
|
||||
return;
|
||||
|
|
@ -476,10 +469,16 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
throw new IllegalStateException("Failed to create Thread tunnel interface", e);
|
||||
}
|
||||
mConnectivityManager.registerNetworkProvider(mNetworkProvider);
|
||||
requestUpstreamNetwork();
|
||||
registerThreadNetworkCallback();
|
||||
mUserRestricted = isThreadUserRestricted();
|
||||
registerUserRestrictionsReceiver();
|
||||
|
||||
if (isBorderRouterMode()) {
|
||||
requestUpstreamNetwork();
|
||||
registerThreadNetworkCallback();
|
||||
} else {
|
||||
cancelRequestUpstreamNetwork();
|
||||
unregisterThreadNetworkCallback();
|
||||
}
|
||||
maybeInitializeOtDaemon();
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +587,20 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
LOG.i("Set Thread configuration: " + configuration);
|
||||
|
||||
final boolean changed = mPersistentSettings.putConfiguration(configuration);
|
||||
|
||||
if (changed) {
|
||||
if (isBorderRouterMode()) {
|
||||
requestUpstreamNetwork();
|
||||
registerThreadNetworkCallback();
|
||||
} else {
|
||||
cancelRequestUpstreamNetwork();
|
||||
unregisterThreadNetworkCallback();
|
||||
disableBorderRouting();
|
||||
}
|
||||
}
|
||||
|
||||
receiver.onSuccess();
|
||||
|
||||
if (changed) {
|
||||
for (IConfigurationReceiver configReceiver : mConfigurationReceivers.keySet()) {
|
||||
try {
|
||||
|
|
@ -598,6 +610,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
getOtDaemon()
|
||||
.setConfiguration(
|
||||
|
|
@ -612,11 +625,17 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
private static OtDaemonConfiguration newOtDaemonConfig(
|
||||
@NonNull ThreadConfiguration threadConfig) {
|
||||
return new OtDaemonConfiguration.Builder()
|
||||
.setBorderRouterEnabled(threadConfig.isBorderRouterEnabled())
|
||||
.setNat64Enabled(threadConfig.isNat64Enabled())
|
||||
.setDhcpv6PdEnabled(threadConfig.isDhcpv6PdEnabled())
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Returns {@code true} if this device is operating as a border router. */
|
||||
private boolean isBorderRouterMode() {
|
||||
return mPersistentSettings.getConfiguration().isBorderRouterEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerConfigurationCallback(@NonNull IConfigurationReceiver callback) {
|
||||
enforceAllPermissionsGranted(permission.THREAD_NETWORK_PRIVILEGED);
|
||||
|
|
@ -723,7 +742,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
private void requestUpstreamNetwork() {
|
||||
if (mUpstreamNetworkCallback != null) {
|
||||
throw new AssertionError("The upstream network request is already there.");
|
||||
return;
|
||||
}
|
||||
mUpstreamNetworkCallback = new UpstreamNetworkCallback();
|
||||
mConnectivityManager.registerNetworkCallback(
|
||||
|
|
@ -732,7 +751,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
private void cancelRequestUpstreamNetwork() {
|
||||
if (mUpstreamNetworkCallback == null) {
|
||||
throw new AssertionError("The upstream network request null.");
|
||||
return;
|
||||
}
|
||||
mNetworkToLinkProperties.clear();
|
||||
mConnectivityManager.unregisterNetworkCallback(mUpstreamNetworkCallback);
|
||||
|
|
@ -812,16 +831,28 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
}
|
||||
|
||||
private void registerThreadNetworkCallback() {
|
||||
mConnectivityManager.registerNetworkCallback(
|
||||
if (mThreadNetworkCallback != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mThreadNetworkCallback = new ThreadNetworkCallback();
|
||||
NetworkRequest request =
|
||||
new NetworkRequest.Builder()
|
||||
// clearCapabilities() is needed to remove forbidden capabilities and UID
|
||||
// requirement.
|
||||
.clearCapabilities()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.addTransportType(TRANSPORT_THREAD)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
|
||||
.build(),
|
||||
new ThreadNetworkCallback(),
|
||||
mHandler);
|
||||
.build();
|
||||
mConnectivityManager.registerNetworkCallback(request, mThreadNetworkCallback, mHandler);
|
||||
}
|
||||
|
||||
private void unregisterThreadNetworkCallback() {
|
||||
if (mThreadNetworkCallback == null) {
|
||||
return;
|
||||
}
|
||||
mConnectivityManager.unregisterNetworkCallback(mThreadNetworkCallback);
|
||||
mThreadNetworkCallback = null;
|
||||
}
|
||||
|
||||
/** Injects a {@link NetworkAgent} for testing. */
|
||||
|
|
@ -835,27 +866,46 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
return mTestNetworkAgent;
|
||||
}
|
||||
|
||||
final NetworkCapabilities netCaps =
|
||||
final var netCapsBuilder =
|
||||
new NetworkCapabilities.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK)
|
||||
.addTransportType(TRANSPORT_THREAD)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
|
||||
.build();
|
||||
final NetworkScore score =
|
||||
new NetworkScore.Builder()
|
||||
.setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK)
|
||||
.build();
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
|
||||
final var scoreBuilder = new NetworkScore.Builder();
|
||||
|
||||
if (isBorderRouterMode()) {
|
||||
netCapsBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK);
|
||||
scoreBuilder.setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_LOCAL_NETWORK);
|
||||
}
|
||||
|
||||
return new NetworkAgent(
|
||||
mContext,
|
||||
mHandler.getLooper(),
|
||||
LOG.getTag(),
|
||||
netCaps,
|
||||
netCapsBuilder.build(),
|
||||
getTunIfLinkProperties(),
|
||||
newLocalNetworkConfig(),
|
||||
score,
|
||||
isBorderRouterMode() ? newLocalNetworkConfig() : null,
|
||||
scoreBuilder.build(),
|
||||
new NetworkAgentConfig.Builder().build(),
|
||||
mNetworkProvider) {};
|
||||
mNetworkProvider) {
|
||||
|
||||
// TODO(b/374037595): use NetworkFactory to handle dynamic network requests
|
||||
@Override
|
||||
public void onNetworkUnwanted() {
|
||||
LOG.i("Thread network is unwanted by ConnectivityService");
|
||||
if (!isBorderRouterMode()) {
|
||||
leave(false /* eraseDataset */, new LoggingOperationReceiver("leave"));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private LocalNetworkConfig newLocalNetworkConfig() {
|
||||
return new LocalNetworkConfig.Builder()
|
||||
.setUpstreamMulticastRoutingConfig(mUpstreamMulticastRoutingConfig)
|
||||
.setDownstreamMulticastRoutingConfig(mDownstreamMulticastRoutingConfig)
|
||||
.setUpstreamSelector(mUpstreamNetworkRequest)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void registerThreadNetwork() {
|
||||
|
|
@ -901,6 +951,12 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
long lifetimeMillis, OperationReceiverWrapper receiver) {
|
||||
checkOnHandlerThread();
|
||||
|
||||
if (!isBorderRouterMode()) {
|
||||
receiver.onError(
|
||||
ERROR_FAILED_PRECONDITION, "This device is not configured a Border Router");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
getOtDaemon().activateEphemeralKeyMode(lifetimeMillis, newOtStatusReceiver(receiver));
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
|
|
@ -920,6 +976,12 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
private void deactivateEphemeralKeyModeInternal(OperationReceiverWrapper receiver) {
|
||||
checkOnHandlerThread();
|
||||
|
||||
if (!isBorderRouterMode()) {
|
||||
receiver.onError(
|
||||
ERROR_FAILED_PRECONDITION, "This device is not configured a Border Router");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
getOtDaemon().deactivateEphemeralKeyMode(newOtStatusReceiver(receiver));
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
|
|
@ -1233,16 +1295,20 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
@Override
|
||||
public void leave(@NonNull IOperationReceiver receiver) {
|
||||
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
|
||||
|
||||
mHandler.post(() -> leaveInternal(new OperationReceiverWrapper(receiver)));
|
||||
leave(true /* eraseDataset */, receiver);
|
||||
}
|
||||
|
||||
private void leaveInternal(@NonNull OperationReceiverWrapper receiver) {
|
||||
private void leave(boolean eraseDataset, @NonNull IOperationReceiver receiver) {
|
||||
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
|
||||
|
||||
mHandler.post(() -> leaveInternal(eraseDataset, new OperationReceiverWrapper(receiver)));
|
||||
}
|
||||
|
||||
private void leaveInternal(boolean eraseDataset, @NonNull OperationReceiverWrapper receiver) {
|
||||
checkOnHandlerThread();
|
||||
|
||||
try {
|
||||
getOtDaemon().leave(newOtStatusReceiver(receiver));
|
||||
getOtDaemon().leave(eraseDataset, newOtStatusReceiver(receiver));
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
LOG.e("otDaemon.leave failed", e);
|
||||
receiver.onError(e);
|
||||
|
|
@ -1388,6 +1454,11 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
}
|
||||
}
|
||||
|
||||
private void disableBorderRouting() {
|
||||
LOG.i("Disabling border routing");
|
||||
setInfraLinkState(newInfraLinkStateBuilder().build());
|
||||
}
|
||||
|
||||
private void handleThreadInterfaceStateChanged(boolean isUp) {
|
||||
try {
|
||||
mTunIfController.setInterfaceUp(isUp);
|
||||
|
|
@ -1571,6 +1642,25 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
}
|
||||
}
|
||||
|
||||
/** An implementation of {@link IOperationReceiver} that simply logs the operation result. */
|
||||
private static class LoggingOperationReceiver extends IOperationReceiver.Stub {
|
||||
private final String mOperation;
|
||||
|
||||
LoggingOperationReceiver(String operation) {
|
||||
mOperation = operation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
LOG.i("The operation " + mOperation + " succeeded");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String errorMessage) {
|
||||
LOG.w("The operation " + mOperation + " failed: " + errorCode + " " + errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LoggingOtStatusReceiver extends IOtStatusReceiver.Stub {
|
||||
private final String mAction;
|
||||
|
||||
|
|
|
|||
|
|
@ -137,6 +137,8 @@ public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
return setThreadEnabled(true);
|
||||
case "disable":
|
||||
return setThreadEnabled(false);
|
||||
case "config":
|
||||
return handleConfigCommand();
|
||||
case "join":
|
||||
return join();
|
||||
case "leave":
|
||||
|
|
@ -149,8 +151,6 @@ public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
return forceCountryCode();
|
||||
case "get-country-code":
|
||||
return getCountryCode();
|
||||
case "config":
|
||||
return handleConfigCommand();
|
||||
case "ot-ctl":
|
||||
return handleOtCtlCommand();
|
||||
default:
|
||||
|
|
@ -321,6 +321,7 @@ public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
final ThreadConfiguration.Builder newConfigBuilder =
|
||||
new ThreadConfiguration.Builder(oldConfig);
|
||||
switch (name) {
|
||||
case "br" -> newConfigBuilder.setBorderRouterEnabled(argEnabledOrDisabled(value));
|
||||
case "nat64" -> newConfigBuilder.setNat64Enabled(argEnabledOrDisabled(value));
|
||||
case "pd" -> newConfigBuilder.setDhcpv6PdEnabled(argEnabledOrDisabled(value));
|
||||
default -> throw new IllegalArgumentException("Invalid config name: " + name);
|
||||
|
|
|
|||
|
|
@ -77,6 +77,13 @@ public class ThreadPersistentSettings {
|
|||
/** Stores the Thread country code, null if no country code is stored. */
|
||||
public static final Key<String> THREAD_COUNTRY_CODE = new Key<>("thread_country_code", null);
|
||||
|
||||
/**
|
||||
* Saves the boolean flag for border router being enabled. The value defaults to {@code true} if
|
||||
* this config is missing.
|
||||
*/
|
||||
private static final Key<Boolean> CONFIG_BORDER_ROUTER_ENABLED =
|
||||
new Key<>("config_border_router_enabled", true);
|
||||
|
||||
/** Stores the Thread NAT64 feature toggle state, true for enabled and false for disabled. */
|
||||
private static final Key<Boolean> CONFIG_NAT64_ENABLED =
|
||||
new Key<>("config_nat64_enabled", false);
|
||||
|
|
@ -197,6 +204,7 @@ public class ThreadPersistentSettings {
|
|||
if (getConfiguration().equals(configuration)) {
|
||||
return false;
|
||||
}
|
||||
putObject(CONFIG_BORDER_ROUTER_ENABLED.key, configuration.isBorderRouterEnabled());
|
||||
putObject(CONFIG_NAT64_ENABLED.key, configuration.isNat64Enabled());
|
||||
putObject(CONFIG_DHCP6_PD_ENABLED.key, configuration.isDhcpv6PdEnabled());
|
||||
writeToStoreFile();
|
||||
|
|
@ -206,6 +214,7 @@ public class ThreadPersistentSettings {
|
|||
/** Retrieve the {@link ThreadConfiguration} from the persistent settings. */
|
||||
public ThreadConfiguration getConfiguration() {
|
||||
return new ThreadConfiguration.Builder()
|
||||
.setBorderRouterEnabled(get(CONFIG_BORDER_ROUTER_ENABLED))
|
||||
.setNat64Enabled(get(CONFIG_NAT64_ENABLED))
|
||||
.setDhcpv6PdEnabled(get(CONFIG_DHCP6_PD_ENABLED))
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import java.util.Collection;
|
|||
public final class ThreadConfigurationTest {
|
||||
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
|
||||
|
||||
public final boolean mIsBorderRouterEnabled;
|
||||
public final boolean mIsNat64Enabled;
|
||||
public final boolean mIsDhcpv6PdEnabled;
|
||||
|
||||
|
|
@ -48,14 +49,16 @@ public final class ThreadConfigurationTest {
|
|||
public static Collection configArguments() {
|
||||
return Arrays.asList(
|
||||
new Object[][] {
|
||||
{false, false}, // All disabled
|
||||
{true, false}, // NAT64 enabled
|
||||
{false, true}, // DHCP6-PD enabled
|
||||
{true, true}, // All enabled
|
||||
{false, false, false}, // All disabled
|
||||
{false, true, false}, // NAT64 enabled
|
||||
{false, false, true}, // DHCP6-PD enabled
|
||||
{true, true, true}, // All enabled
|
||||
});
|
||||
}
|
||||
|
||||
public ThreadConfigurationTest(boolean isNat64Enabled, boolean isDhcpv6PdEnabled) {
|
||||
public ThreadConfigurationTest(
|
||||
boolean isBorderRouterEnabled, boolean isNat64Enabled, boolean isDhcpv6PdEnabled) {
|
||||
mIsBorderRouterEnabled = isBorderRouterEnabled;
|
||||
mIsNat64Enabled = isNat64Enabled;
|
||||
mIsDhcpv6PdEnabled = isDhcpv6PdEnabled;
|
||||
}
|
||||
|
|
@ -64,6 +67,7 @@ public final class ThreadConfigurationTest {
|
|||
public void parcelable_parcelingIsLossLess() {
|
||||
ThreadConfiguration config =
|
||||
new ThreadConfiguration.Builder()
|
||||
.setBorderRouterEnabled(mIsBorderRouterEnabled)
|
||||
.setNat64Enabled(mIsNat64Enabled)
|
||||
.setDhcpv6PdEnabled(mIsDhcpv6PdEnabled)
|
||||
.build();
|
||||
|
|
@ -74,10 +78,12 @@ public final class ThreadConfigurationTest {
|
|||
public void builder_correctValuesAreSet() {
|
||||
ThreadConfiguration config =
|
||||
new ThreadConfiguration.Builder()
|
||||
.setBorderRouterEnabled(mIsBorderRouterEnabled)
|
||||
.setNat64Enabled(mIsNat64Enabled)
|
||||
.setDhcpv6PdEnabled(mIsDhcpv6PdEnabled)
|
||||
.build();
|
||||
|
||||
assertThat(config.isBorderRouterEnabled()).isEqualTo(mIsBorderRouterEnabled);
|
||||
assertThat(config.isNat64Enabled()).isEqualTo(mIsNat64Enabled);
|
||||
assertThat(config.isDhcpv6PdEnabled()).isEqualTo(mIsDhcpv6PdEnabled);
|
||||
}
|
||||
|
|
@ -86,6 +92,7 @@ public final class ThreadConfigurationTest {
|
|||
public void builderConstructor_configsAreEqual() {
|
||||
ThreadConfiguration config1 =
|
||||
new ThreadConfiguration.Builder()
|
||||
.setBorderRouterEnabled(mIsBorderRouterEnabled)
|
||||
.setNat64Enabled(mIsNat64Enabled)
|
||||
.setDhcpv6PdEnabled(mIsDhcpv6PdEnabled)
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -920,6 +920,27 @@ public class ThreadNetworkControllerTest {
|
|||
assertThat(threadException.getErrorCode()).isEqualTo(ERROR_BUSY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void activateEphemeralKeyMode_notBorderRouter_failsWithFailedPrecondition()
|
||||
throws Exception {
|
||||
setConfigurationAndWait(
|
||||
mController,
|
||||
new ThreadConfiguration.Builder().setBorderRouterEnabled(false).build());
|
||||
grantPermissions(THREAD_NETWORK_PRIVILEGED);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
mController.activateEphemeralKeyMode(
|
||||
Duration.ofSeconds(1), mExecutor, newOutcomeReceiver(future));
|
||||
|
||||
var thrown =
|
||||
assertThrows(
|
||||
ExecutionException.class,
|
||||
() -> future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS));
|
||||
var threadException = (ThreadNetworkException) thrown.getCause();
|
||||
assertThat(threadException.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void deactivateEphemeralKeyMode_withoutPrivilegedPermission_throwsSecurityException()
|
||||
|
|
@ -931,6 +952,26 @@ public class ThreadNetworkControllerTest {
|
|||
() -> mController.deactivateEphemeralKeyMode(mExecutor, v -> {}));
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void deactivateEphemeralKeyMode_notBorderRouter_failsWithFailedPrecondition()
|
||||
throws Exception {
|
||||
setConfigurationAndWait(
|
||||
mController,
|
||||
new ThreadConfiguration.Builder().setBorderRouterEnabled(false).build());
|
||||
grantPermissions(THREAD_NETWORK_PRIVILEGED);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
mController.deactivateEphemeralKeyMode(mExecutor, newOutcomeReceiver(future));
|
||||
|
||||
var thrown =
|
||||
assertThrows(
|
||||
ExecutionException.class,
|
||||
() -> future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS));
|
||||
var threadException = (ThreadNetworkException) thrown.getCause();
|
||||
assertThat(threadException.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
|
||||
public void subscribeEpskcState_permissionsGranted_returnsCurrentState() throws Exception {
|
||||
|
|
@ -1151,9 +1192,15 @@ public class ThreadNetworkControllerTest {
|
|||
CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
|
||||
ConfigurationListener listener = new ConfigurationListener(mController);
|
||||
ThreadConfiguration config1 =
|
||||
new ThreadConfiguration.Builder().setNat64Enabled(true).build();
|
||||
new ThreadConfiguration.Builder()
|
||||
.setBorderRouterEnabled(true)
|
||||
.setNat64Enabled(true)
|
||||
.build();
|
||||
ThreadConfiguration config2 =
|
||||
new ThreadConfiguration.Builder().setNat64Enabled(false).build();
|
||||
new ThreadConfiguration.Builder()
|
||||
.setBorderRouterEnabled(false)
|
||||
.setNat64Enabled(false)
|
||||
.build();
|
||||
|
||||
try {
|
||||
runAsShell(
|
||||
|
|
|
|||
|
|
@ -16,10 +16,13 @@
|
|||
|
||||
package android.net.thread;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED;
|
||||
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.DEFAULT_CONFIG;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
|
||||
import static android.net.thread.utils.IntegrationTestUtils.getPrefixesFromNetData;
|
||||
|
|
@ -30,17 +33,23 @@ import static android.net.thread.utils.IntegrationTestUtils.waitFor;
|
|||
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;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import android.content.Context;
|
||||
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;
|
||||
import android.net.thread.utils.FullThreadDevice;
|
||||
import android.net.thread.utils.OtDaemonController;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule;
|
||||
|
|
@ -66,6 +75,7 @@ import java.net.InetAddress;
|
|||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
|
|
@ -83,6 +93,8 @@ public class ThreadIntegrationTest {
|
|||
// The maximum time for changes to be propagated to netdata.
|
||||
private static final Duration NET_DATA_UPDATE_TIMEOUT = Duration.ofSeconds(1);
|
||||
|
||||
private static final Duration NETWORK_CALLBACK_TIMEOUT = Duration.ofSeconds(10);
|
||||
|
||||
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
|
||||
private static final byte[] DEFAULT_DATASET_TLVS =
|
||||
base16().decode(
|
||||
|
|
@ -93,6 +105,8 @@ public class ThreadIntegrationTest {
|
|||
+ "B9D351B40C0402A0FFF8");
|
||||
private static final ActiveOperationalDataset DEFAULT_DATASET =
|
||||
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
|
||||
private static final ThreadConfiguration DEFAULT_CONFIG =
|
||||
new ThreadConfiguration.Builder().build();
|
||||
|
||||
private static final Inet6Address GROUP_ADDR_ALL_ROUTERS =
|
||||
(Inet6Address) InetAddresses.parseNumericAddress("ff02::2");
|
||||
|
|
@ -126,6 +140,7 @@ public class ThreadIntegrationTest {
|
|||
public void tearDown() throws Exception {
|
||||
mController.setTestNetworkAsUpstreamAndWait(null);
|
||||
mController.leaveAndWait();
|
||||
mController.setConfigurationAndWait(DEFAULT_CONFIG);
|
||||
|
||||
mFtd.destroy();
|
||||
mExecutor.shutdownNow();
|
||||
|
|
@ -327,6 +342,44 @@ public class ThreadIntegrationTest {
|
|||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setConfiguration_disableBorderRouter_noBrfunctionsEnabled() throws Exception {
|
||||
NetworkRequest request =
|
||||
new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.build();
|
||||
startFtdLeader(mFtd, DEFAULT_DATASET);
|
||||
|
||||
mController.setConfigurationAndWait(
|
||||
new ThreadConfiguration.Builder().setBorderRouterEnabled(false).build());
|
||||
mController.joinAndWait(DEFAULT_DATASET);
|
||||
NetworkCapabilities caps = registerNetworkCallbackAndWait(request);
|
||||
|
||||
assertThat(caps.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)).isFalse();
|
||||
assertThat(mOtCtl.getBorderRoutingState()).ignoringCase().isEqualTo("disabled");
|
||||
assertThat(mOtCtl.getSrpServerState()).ignoringCase().isNotEqualTo("disabled");
|
||||
// TODO: b/376217403 - enables / disables Border Agent at runtime
|
||||
}
|
||||
|
||||
private NetworkCapabilities registerNetworkCallbackAndWait(NetworkRequest request)
|
||||
throws Exception {
|
||||
CompletableFuture<Network> networkFuture = new CompletableFuture<>();
|
||||
ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
|
||||
ConnectivityManager.NetworkCallback callback =
|
||||
new ConnectivityManager.NetworkCallback() {
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
networkFuture.complete(network);
|
||||
}
|
||||
};
|
||||
|
||||
runAsShell(ACCESS_NETWORK_STATE, () -> cm.registerNetworkCallback(request, callback));
|
||||
|
||||
assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT.getSeconds(), SECONDS)).isNotNull();
|
||||
return runAsShell(
|
||||
ACCESS_NETWORK_STATE, () -> cm.getNetworkCapabilities(networkFuture.get()));
|
||||
}
|
||||
|
||||
// TODO (b/323300829): add more tests for integration with linux platform and
|
||||
// ConnectivityService
|
||||
|
||||
|
|
@ -341,6 +394,14 @@ public class ThreadIntegrationTest {
|
|||
ftd.waitForStateAnyOf(List.of("router", "child"), Duration.ofSeconds(8));
|
||||
}
|
||||
|
||||
/** Starts a Thread FTD device as a leader. */
|
||||
private void startFtdLeader(FullThreadDevice ftd, ActiveOperationalDataset activeDataset)
|
||||
throws Exception {
|
||||
ftd.factoryReset();
|
||||
ftd.joinNetwork(activeDataset);
|
||||
ftd.waitForStateAnyOf(List.of("leader"), Duration.ofSeconds(8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a UDP echo server and replies to the first UDP message.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ public class ThreadNetworkShellCommandTest {
|
|||
|
||||
final String result = runThreadCommand("config");
|
||||
|
||||
assertThat(result).contains("Nat64Enabled=true");
|
||||
assertThat(result).contains("nat64Enabled=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -54,6 +54,16 @@ public final class OtDaemonController {
|
|||
SystemClock.sleep(500);
|
||||
}
|
||||
|
||||
/** Returns the output string of the "ot-ctl br state" command. */
|
||||
public String getBorderRoutingState() {
|
||||
return executeCommandAndParse("br state").getFirst();
|
||||
}
|
||||
|
||||
/** Returns the output string of the "ot-ctl srp server state" command. */
|
||||
public String getSrpServerState() {
|
||||
return executeCommandAndParse("srp server state").getFirst();
|
||||
}
|
||||
|
||||
/** Returns the list of IPv6 addresses on ot-daemon. */
|
||||
public List<Inet6Address> getAddresses() {
|
||||
return executeCommandAndParse("ipaddr").stream()
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ public class ThreadNetworkShellCommandTest {
|
|||
runShellCommand("config");
|
||||
|
||||
verify(mErrorWriter, never()).println();
|
||||
verify(mOutputWriter, times(1)).println(contains("Nat64Enabled=true"));
|
||||
verify(mOutputWriter, times(1)).println(contains("nat64Enabled=true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Reference in a new issue