Merge "Enhance ApfIntegrationTest to test multiple packet transmission" into main
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index e81cbf0..21f36e8 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.IBinder;
@@ -657,6 +658,13 @@
}
}
+ private void unsupportedAfterV() {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ throw new UnsupportedOperationException("Not supported after SDK version "
+ + Build.VERSION_CODES.VANILLA_ICE_CREAM);
+ }
+ }
+
/**
* Attempt to tether the named interface. This will setup a dhcp server
* on the interface, forward and NAT IP v4 packets and forward DNS requests
@@ -666,8 +674,10 @@
* access will of course fail until an upstream network interface becomes
* active.
*
- * @deprecated The only usages is PanService. It uses this for legacy reasons
- * and will migrate away as soon as possible.
+ * @deprecated Legacy tethering API. Callers should instead use
+ * {@link #startTethering(int, Executor, StartTetheringCallback)}.
+ * On SDK versions after {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this will
+ * throw an UnsupportedOperationException.
*
* @param iface the interface name to tether.
* @return error a {@code TETHER_ERROR} value indicating success or failure type
@@ -677,6 +687,8 @@
@Deprecated
@SystemApi(client = MODULE_LIBRARIES)
public int tether(@NonNull final String iface) {
+ unsupportedAfterV();
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "tether caller:" + callerPkg);
final RequestDispatcher dispatcher = new RequestDispatcher();
@@ -700,14 +712,18 @@
/**
* Stop tethering the named interface.
*
- * @deprecated The only usages is PanService. It uses this for legacy reasons
- * and will migrate away as soon as possible.
+ * @deprecated Legacy tethering API. Callers should instead use
+ * {@link #stopTethering(int)}.
+ * On SDK versions after {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this will
+ * throw an UnsupportedOperationException.
*
* {@hide}
*/
@Deprecated
@SystemApi(client = MODULE_LIBRARIES)
public int untether(@NonNull final String iface) {
+ unsupportedAfterV();
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "untether caller:" + callerPkg);
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 4f07f58..40b1ec0 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -108,6 +108,7 @@
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -600,13 +601,40 @@
// This method needs to exist because TETHERING_BLUETOOTH before Android T and TETHERING_WIGIG
// can't use enableIpServing.
private void processInterfaceStateChange(final String iface, boolean enabled) {
+ final int type = ifaceNameToType(iface);
// Do not listen to USB interface state changes or USB interface add/removes. USB tethering
// is driven only by USB_ACTION broadcasts.
- final int type = ifaceNameToType(iface);
if (type == TETHERING_USB || type == TETHERING_NCM) return;
+ // On T+, BLUETOOTH uses enableIpServing.
if (type == TETHERING_BLUETOOTH && SdkLevel.isAtLeastT()) return;
+ // Cannot happen: on S+, tetherableWigigRegexps is always empty.
+ if (type == TETHERING_WIGIG && SdkLevel.isAtLeastS()) return;
+
+ // After V, disallow this legacy codepath from starting tethering of any type:
+ // everything must call ensureIpServerStarted directly.
+ //
+ // Don't touch the teardown path for now. It's more complicated because:
+ // - ensureIpServerStarted and ensureIpServerStopped act on different
+ // tethering types.
+ // - Depending on the type, ensureIpServerStopped is either called twice (once
+ // on interface down and once on interface removed) or just once (on
+ // interface removed).
+ //
+ // Note that this only affects WIFI and WIFI_P2P. The other types are either
+ // ignored above, or ignored by ensureIpServerStarted. Note that even for WIFI
+ // and WIFI_P2P, this code should not ever run in normal use, because the
+ // hotspot and p2p code do not call tether(). It's possible that this could
+ // happen in the field due to unforeseen OEM modifications. If it does happen,
+ // a terrible error is logged in tether().
+ // TODO: fix the teardown path to stop depending on interface state notifications.
+ // These are not necessary since most/all link layers have their own teardown
+ // notifications, and can race with those notifications.
+ if (enabled && Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ return;
+ }
+
if (enabled) {
ensureIpServerStarted(iface);
} else {
@@ -999,7 +1027,27 @@
return TETHER_ERROR_NO_ERROR;
}
+ /**
+ * Legacy tether API that starts tethering with CONNECTIVITY_SCOPE_GLOBAL on the given iface.
+ *
+ * This API relies on the IpServer having been started for the interface by
+ * processInterfaceStateChanged beforehand, which is only possible for
+ * - WIGIG Pre-S
+ * - BLUETOOTH Pre-T
+ * - WIFI
+ * - WIFI_P2P.
+ * Note that WIFI and WIFI_P2P already start tethering on their respective ifaces via
+ * WIFI_(AP/P2P_STATE_CHANGED broadcasts, which makes this API redundant for those types unless
+ * those broadcasts are disabled by OEM.
+ */
void tether(String iface, int requestedState, final IIntResultListener listener) {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ // After V, the TetheringManager and ConnectivityManager tether and untether methods
+ // throw UnsupportedOperationException, so this cannot happen in normal use. Ensure
+ // that this code cannot run even if callers use raw binder calls or other
+ // unsupported methods.
+ return;
+ }
mHandler.post(() -> {
switch (ifaceNameToType(iface)) {
case TETHERING_WIFI:
@@ -1051,6 +1099,13 @@
}
void untether(String iface, final IIntResultListener listener) {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ // After V, the TetheringManager and ConnectivityManager tether and untether methods
+ // throw UnsupportedOperationException, so this cannot happen in normal use. Ensure
+ // that this code cannot run even if callers use raw binder calls or other
+ // unsupported methods.
+ return;
+ }
mHandler.post(() -> {
try {
listener.onResult(untether(iface));
diff --git a/bpf/headers/include/bpf_helpers.h b/bpf/headers/include/bpf_helpers.h
index b994a9f..0bd3421 100644
--- a/bpf/headers/include/bpf_helpers.h
+++ b/bpf/headers/include/bpf_helpers.h
@@ -62,8 +62,8 @@
// Android Mainline BpfLoader when running on Android V (sdk=35)
#define BPFLOADER_MAINLINE_V_VERSION (BPFLOADER_MAINLINE_U_QPR3_VERSION + 1u)
-// Android Mainline BpfLoader when running on Android W (sdk=36)
-#define BPFLOADER_MAINLINE_W_VERSION (BPFLOADER_MAINLINE_V_VERSION + 1u)
+// Android Mainline BpfLoader when running on Android 25Q2 (sdk=36)
+#define BPFLOADER_MAINLINE_25Q2_VERSION (BPFLOADER_MAINLINE_V_VERSION + 1u)
/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
* before #include "bpf_helpers.h" to change which bpfloaders will
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index ce144a7..038786c 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -1409,17 +1409,15 @@
//
// Also note that 'android_get_device_api_level()' is what the
// //system/core/init/apex_init_util.cpp
- // apex init .XXrc parsing code uses for XX filtering.
- //
- // That code has a hack to bump <35 to 35 (to force aosp/main to parse .35rc),
- // but could (should?) perhaps be adjusted to match this.
- const int effective_api_level = android_get_device_api_level() + (int)unreleased;
- const bool isAtLeastT = (effective_api_level >= __ANDROID_API_T__);
- const bool isAtLeastU = (effective_api_level >= __ANDROID_API_U__);
- const bool isAtLeastV = (effective_api_level >= __ANDROID_API_V__);
- const bool isAtLeastW = (effective_api_level > __ANDROID_API_V__); // TODO: switch to W
+ // apex init .XXrc parsing code uses for XX filtering, and that code
+ // (now) similarly uses __ANDROID_API_FUTURE__ for non 'REL' codenames.
+ const int api_level = unreleased ? __ANDROID_API_FUTURE__ : android_get_device_api_level();
+ const bool isAtLeastT = (api_level >= __ANDROID_API_T__);
+ const bool isAtLeastU = (api_level >= __ANDROID_API_U__);
+ const bool isAtLeastV = (api_level >= __ANDROID_API_V__);
+ const bool isAtLeast25Q2 = (api_level > __ANDROID_API_V__); // TODO: fix >
- const int first_api_level = GetIntProperty("ro.board.first_api_level", effective_api_level);
+ const int first_api_level = GetIntProperty("ro.board.first_api_level", api_level);
// last in U QPR2 beta1
const bool has_platform_bpfloader_rc = exists("/system/etc/init/bpfloader.rc");
@@ -1432,10 +1430,10 @@
if (isAtLeastU) ++bpfloader_ver; // [44] BPFLOADER_MAINLINE_U_VERSION
if (runningAsRoot) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_U_QPR3_VERSION
if (isAtLeastV) ++bpfloader_ver; // [46] BPFLOADER_MAINLINE_V_VERSION
- if (isAtLeastW) ++bpfloader_ver; // [47] BPFLOADER_MAINLINE_W_VERSION
+ if (isAtLeast25Q2) ++bpfloader_ver; // [47] BPFLOADER_MAINLINE_25Q2_VERSION
ALOGI("NetBpfLoad v0.%u (%s) api:%d/%d kver:%07x (%s) uid:%d rc:%d%d",
- bpfloader_ver, argv[0], android_get_device_api_level(), effective_api_level,
+ bpfloader_ver, argv[0], android_get_device_api_level(), api_level,
kernelVersion(), describeArch(), getuid(),
has_platform_bpfloader_rc, has_platform_netbpfload_rc);
@@ -1475,6 +1473,13 @@
return 1;
}
+ // 25Q2 bumps the kernel requirement up to 5.4
+ // see also: //system/netd/tests/kernel_test.cpp TestKernel54
+ if (isAtLeast25Q2 && !isAtLeastKernelVersion(5, 4, 0)) {
+ ALOGE("Android 25Q2 requires kernel 5.4.");
+ return 1;
+ }
+
// Technically already required by U, but only enforce on V+
// see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
if (isAtLeastV && isKernel32Bit() && isAtLeastKernelVersion(5, 16, 0)) {
@@ -1498,13 +1503,13 @@
bool bad = false;
if (!isLtsKernel()) {
- ALOGW("Android V only supports LTS kernels.");
+ ALOGW("Android V+ only supports LTS kernels.");
bad = true;
}
#define REQUIRE(maj, min, sub) \
if (isKernelVersion(maj, min) && !isAtLeastKernelVersion(maj, min, sub)) { \
- ALOGW("Android V requires %d.%d kernel to be %d.%d.%d+.", maj, min, maj, min, sub); \
+ ALOGW("Android V+ requires %d.%d kernel to be %d.%d.%d+.", maj, min, maj, min, sub); \
bad = true; \
}
diff --git a/bpf/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp
index 340acda..bcd0cba 100644
--- a/bpf/netd/BpfHandler.cpp
+++ b/bpf/netd/BpfHandler.cpp
@@ -70,6 +70,13 @@
return netdutils::status::ok;
}
+// Checks if the device is running on release version of Android 25Q2 or newer.
+static bool isAtLeast25Q2() {
+ return android_get_device_api_level() >= 36 ||
+ (android_get_device_api_level() == 35 &&
+ modules::sdklevel::detail::IsAtLeastPreReleaseCodename("Baklava"));
+}
+
static Status initPrograms(const char* cg2_path) {
if (!cg2_path) return Status("cg2_path is NULL");
@@ -91,6 +98,16 @@
return Status("U+ platform with cg2_path != /sys/fs/cgroup is unsupported");
}
+ // V bumps the kernel requirement up to 4.19
+ if (modules::sdklevel::IsAtLeastV() && !bpf::isAtLeastKernelVersion(4, 19, 0)) {
+ return Status("V+ platform with kernel version < 4.19.0 is unsupported");
+ }
+
+ // 25Q2 bumps the kernel requirement up to 5.4
+ if (isAtLeast25Q2() && !bpf::isAtLeastKernelVersion(5, 4, 0)) {
+ return Status("25Q2+ platform with kernel version < 5.4.0 is unsupported");
+ }
+
unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
if (!cg_fd.ok()) {
const int err = errno;
diff --git a/framework/api/current.txt b/framework/api/current.txt
index a9d1569..323c533 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -244,7 +244,7 @@
field public static final int HEADER_COMPRESSION_6LOWPAN = 2; // 0x2
field public static final int HEADER_COMPRESSION_ANY = 0; // 0x0
field public static final int HEADER_COMPRESSION_NONE = 1; // 0x1
- field public static final int PSM_ANY = -1; // 0xffffffff
+ field public static final int PSM_ANY = 0; // 0x0
field public static final int ROLE_ANY = 0; // 0x0
field public static final int ROLE_CLIENT = 1; // 0x1
field public static final int ROLE_SERVER = 2; // 0x2
@@ -254,8 +254,8 @@
ctor public L2capNetworkSpecifier.Builder();
method @NonNull public android.net.L2capNetworkSpecifier build();
method @NonNull public android.net.L2capNetworkSpecifier.Builder setHeaderCompression(int);
- method @NonNull public android.net.L2capNetworkSpecifier.Builder setPsm(int);
- method @NonNull public android.net.L2capNetworkSpecifier.Builder setRemoteAddress(@NonNull android.net.MacAddress);
+ method @NonNull public android.net.L2capNetworkSpecifier.Builder setPsm(@IntRange(from=0, to=255) int);
+ method @NonNull public android.net.L2capNetworkSpecifier.Builder setRemoteAddress(@Nullable android.net.MacAddress);
method @NonNull public android.net.L2capNetworkSpecifier.Builder setRole(int);
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 9016d13..5d99b74 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -3065,7 +3065,8 @@
* <p>WARNING: New clients should not use this function. The only usages should be in PanService
* and WifiStateMachine which need direct access. All other clients should use
* {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning
- * logic.</p>
+ * logic. On SDK versions after {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this will throw
+ * an UnsupportedOperationException.</p>
*
* @param iface the interface name to tether.
* @return error a {@code TETHER_ERROR} value indicating success or failure type
@@ -3090,7 +3091,8 @@
* <p>WARNING: New clients should not use this function. The only usages should be in PanService
* and WifiStateMachine which need direct access. All other clients should use
* {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning
- * logic.</p>
+ * logic. On SDK versions after {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this will throw
+ * an UnsupportedOperationException.</p>
*
* @param iface the interface name to untether.
* @return error a {@code TETHER_ERROR} value indicating success or failure type
diff --git a/framework/src/android/net/L2capNetworkSpecifier.java b/framework/src/android/net/L2capNetworkSpecifier.java
index c7067f6..cfc9ed9 100644
--- a/framework/src/android/net/L2capNetworkSpecifier.java
+++ b/framework/src/android/net/L2capNetworkSpecifier.java
@@ -18,8 +18,10 @@
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,34 +32,46 @@
import java.util.Objects;
/**
- * A {@link NetworkSpecifier} used to identify an L2CAP network.
+ * A {@link NetworkSpecifier} used to identify an L2CAP network over BLE.
*
* An L2CAP network is not symmetrical, meaning there exists both a server (Bluetooth peripheral)
- * and a client (Bluetooth central) node. This specifier contains information required to request or
- * reserve an L2CAP network.
+ * and a client (Bluetooth central) node. This specifier contains the information required to
+ * request a client L2CAP network using {@link ConnectivityManager#requestNetwork} while specifying
+ * the remote MAC address, and Protocol/Service Multiplexer (PSM). It can also contain information
+ * allocated by the system when reserving a server network using {@link
+ * ConnectivityManager#reserveNetwork} such as the Protocol/Service Multiplexer (PSM). In both
+ * cases, the header compression option must be specified.
*
- * An L2CAP server network allocates a PSM to be advertised to the client. Therefore, the server
- * network must always be reserved using {@link ConnectivityManager#reserveNetwork}. The subsequent
- * {@link ConnectivityManager.NetworkCallback#onReserved(NetworkCapabilities)} includes information
- * (i.e. the PSM) for the server to advertise to the client.
- * Under the hood, an L2CAP server network is represented by a {@link
- * android.bluetooth.BluetoothServerSocket} which can, in theory, accept many connections. However,
- * before Android 15 Bluetooth APIs do not expose the channel ID, so these connections are
- * indistinguishable. In practice, this means that network matching semantics in {@link
- * ConnectivityService} will tear down all but the first connection.
+ * An L2CAP server network allocates a Protocol/Service Multiplexer (PSM) to be advertised to the
+ * client. A new server network must always be reserved using {@code
+ * ConnectivityManager#reserveNetwork}. The subsequent {@link
+ * ConnectivityManager.NetworkCallback#onReserved(NetworkCapabilities)} callback includes an {@code
+ * L2CapNetworkSpecifier}. The {@link getPsm()} method will return the Protocol/Service Multiplexer
+ * (PSM) of the reserved network so that the server can advertise it to the client and the client
+ * can connect.
+ * An L2CAP server network is backed by a {@link android.bluetooth.BluetoothServerSocket} which can,
+ * in theory, accept many connections. However, before SDK version {@link
+ * Build.VERSION_CODES.VANILLA_ICE_CREAM} Bluetooth APIs do not expose the channel ID, so these
+ * connections are indistinguishable. In practice, this means that the network matching semantics in
+ * ConnectivityService will tear down all but the first connection.
*
- * The L2cap client network can be connected using {@link ConnectivityManager#requestNetwork}
- * including passing in the relevant information (i.e. PSM and destination MAC address) using the
- * {@link L2capNetworkSpecifier}.
- *
+ * When the connection between client and server completes, a {@link Network} whose capabilities
+ * satisfy this {@code L2capNetworkSpecifier} will connect and the usual callbacks, such as {@link
+ * NetworkCallback#onAvailable}, will be called on the callback object passed to {@code
+ * ConnectivityManager#reserveNetwork} or {@code ConnectivityManager#requestNetwork}.
*/
@FlaggedApi(Flags.FLAG_IPV6_OVER_BLE)
public final class L2capNetworkSpecifier extends NetworkSpecifier implements Parcelable {
- /** Accept any role. */
+ /**
+ * Match any role.
+ *
+ * This role is only meaningful in {@link NetworkRequest}s. Specifiers for actual L2CAP
+ * networks never have this role set.
+ */
public static final int ROLE_ANY = 0;
- /** Specifier describes a client network. */
+ /** Specifier describes a client network, i.e., the device is the Bluetooth central. */
public static final int ROLE_CLIENT = 1;
- /** Specifier describes a server network. */
+ /** Specifier describes a server network, i.e., the device is the Bluetooth peripheral. */
public static final int ROLE_SERVER = 2;
/** @hide */
@@ -72,7 +86,12 @@
@Role
private final int mRole;
- /** Accept any form of header compression. */
+ /**
+ * Accept any form of header compression.
+ *
+ * This option is only meaningful in {@link NetworkRequest}s. Specifiers for actual L2CAP
+ * networks never have this option set.
+ */
public static final int HEADER_COMPRESSION_ANY = 0;
/** Do not compress packets on this network. */
public static final int HEADER_COMPRESSION_NONE = 1;
@@ -91,16 +110,19 @@
@HeaderCompression
private final int mHeaderCompression;
- /**
- * The MAC address of the remote.
- */
+ /** The MAC address of the remote. */
@Nullable
private final MacAddress mRemoteAddress;
- /** Match any PSM. */
- public static final int PSM_ANY = -1;
+ /**
+ * Match any Protocol/Service Multiplexer (PSM).
+ *
+ * This PSM value is only meaningful in {@link NetworkRequest}s. Specifiers for actual L2CAP
+ * networks never have this value set.
+ */
+ public static final int PSM_ANY = 0;
- /** The Bluetooth L2CAP Protocol Service Multiplexer (PSM). */
+ /** The Bluetooth L2CAP Protocol/Service Multiplexer (PSM). */
private final int mPsm;
private L2capNetworkSpecifier(Parcel in) {
@@ -131,12 +153,19 @@
return mHeaderCompression;
}
- /** Returns the remote MAC address for this network to connect to. */
+ /**
+ * Returns the remote MAC address for this network to connect to.
+ *
+ * The remote address is only meaningful for networks that have ROLE_CLIENT.
+ *
+ * When receiving this {@link L2capNetworkSpecifier} from Connectivity APIs such as a {@link
+ * ConnectivityManager.NetworkCallback}, the MAC address is redacted.
+ */
public @Nullable MacAddress getRemoteAddress() {
return mRemoteAddress;
}
- /** Returns the PSM for this network to connect to. */
+ /** Returns the Protocol/Service Multiplexer (PSM) for this network to connect to. */
public int getPsm() {
return mPsm;
}
@@ -144,9 +173,9 @@
/** A builder class for L2capNetworkSpecifier. */
public static final class Builder {
@Role
- private int mRole;
+ private int mRole = ROLE_ANY;
@HeaderCompression
- private int mHeaderCompression;
+ private int mHeaderCompression = HEADER_COMPRESSION_ANY;
@Nullable
private MacAddress mRemoteAddress;
private int mPsm = PSM_ANY;
@@ -154,6 +183,8 @@
/**
* Set the role to use for this network.
*
+ * If not set, defaults to {@link ROLE_ANY}.
+ *
* @param role the role to use.
*/
@NonNull
@@ -165,6 +196,10 @@
/**
* Set the header compression mechanism to use for this network.
*
+ * If not set, defaults to {@link HEADER_COMPRESSION_ANY}. This option must be specified
+ * (i.e. must not be set to {@link HEADER_COMPRESSION_ANY}) when requesting or reserving a
+ * new network.
+ *
* @param headerCompression the header compression mechanism to use.
*/
@NonNull
@@ -176,26 +211,28 @@
/**
* Set the remote address for the client to connect to.
*
- * Only valid for client networks. A null MacAddress matches *any* MacAddress.
+ * Only valid for client networks. If not set, the specifier matches any MAC address.
*
- * @param remoteAddress the MAC address to connect to.
+ * @param remoteAddress the MAC address to connect to, or null to match any MAC address.
*/
@NonNull
- public Builder setRemoteAddress(@NonNull MacAddress remoteAddress) {
- Objects.requireNonNull(remoteAddress);
+ public Builder setRemoteAddress(@Nullable MacAddress remoteAddress) {
mRemoteAddress = remoteAddress;
return this;
}
/**
- * Set the PSM for the client to connect to.
+ * Set the Protocol/Service Multiplexer (PSM) for the client to connect to.
*
- * Can only be configured on client networks.
+ * If not set, defaults to {@link PSM_ANY}.
*
- * @param psm the Protocol Service Multiplexer (PSM) to connect to.
+ * @param psm the Protocol/Service Multiplexer (PSM) to connect to.
*/
@NonNull
- public Builder setPsm(int psm) {
+ public Builder setPsm(@IntRange(from = 0, to = 255) int psm) {
+ if (psm < 0 /* PSM_ANY */ || psm > 0xFF) {
+ throw new IllegalArgumentException("PSM must be PSM_ANY or within range [1, 255]");
+ }
mPsm = psm;
return this;
}
@@ -203,7 +240,10 @@
/** Create the L2capNetworkSpecifier object. */
@NonNull
public L2capNetworkSpecifier build() {
- // TODO: throw an exception for combinations that cannot be supported.
+ if (mRole == ROLE_SERVER && mRemoteAddress != null) {
+ throw new IllegalArgumentException(
+ "Specifying a remote address is not valid for server role.");
+ }
return new L2capNetworkSpecifier(mRole, mHeaderCompression, mRemoteAddress, mPsm);
}
}
@@ -211,21 +251,41 @@
/** @hide */
@Override
public boolean canBeSatisfiedBy(NetworkSpecifier other) {
- // TODO: implement matching semantics.
- return false;
+ if (!(other instanceof L2capNetworkSpecifier)) return false;
+ final L2capNetworkSpecifier rhs = (L2capNetworkSpecifier) other;
+
+ // A network / offer cannot be ROLE_ANY, but it is added for consistency.
+ if (mRole != rhs.mRole && mRole != ROLE_ANY && rhs.mRole != ROLE_ANY) {
+ return false;
+ }
+
+ if (mHeaderCompression != rhs.mHeaderCompression
+ && mHeaderCompression != HEADER_COMPRESSION_ANY
+ && rhs.mHeaderCompression != HEADER_COMPRESSION_ANY) {
+ return false;
+ }
+
+ if (!Objects.equals(mRemoteAddress, rhs.mRemoteAddress)
+ && mRemoteAddress != null && rhs.mRemoteAddress != null) {
+ return false;
+ }
+
+ if (mPsm != rhs.mPsm && mPsm != PSM_ANY && rhs.mPsm != PSM_ANY) {
+ return false;
+ }
+ return true;
}
/** @hide */
@Override
@Nullable
public NetworkSpecifier redact() {
- // Redact the remote MAC address and the PSM (for non-server roles).
final NetworkSpecifier redactedSpecifier = new Builder()
.setRole(mRole)
.setHeaderCompression(mHeaderCompression)
- // TODO: consider not redacting the specifier in onReserved, so the redaction can be
- // more strict (i.e. the PSM could always be redacted).
- .setPsm(mRole == ROLE_SERVER ? mPsm : PSM_ANY)
+ // The remote address is redacted.
+ .setRemoteAddress(null)
+ .setPsm(mPsm)
.build();
return redactedSpecifier;
}
@@ -248,6 +308,41 @@
&& mPsm == rhs.mPsm;
}
+ /** @hide */
+ @Override
+ public String toString() {
+ final String role;
+ switch (mRole) {
+ case ROLE_CLIENT:
+ role = "ROLE_CLIENT";
+ break;
+ case ROLE_SERVER:
+ role = "ROLE_SERVER";
+ break;
+ default:
+ role = "ROLE_ANY";
+ break;
+ }
+
+ final String headerCompression;
+ switch (mHeaderCompression) {
+ case HEADER_COMPRESSION_NONE:
+ headerCompression = "HEADER_COMPRESSION_NONE";
+ break;
+ case HEADER_COMPRESSION_6LOWPAN:
+ headerCompression = "HEADER_COMPRESSION_6LOWPAN";
+ break;
+ default:
+ headerCompression = "HEADER_COMPRESSION_ANY";
+ break;
+ }
+
+ final String psm = (mPsm == PSM_ANY) ? "PSM_ANY" : String.valueOf(mPsm);
+
+ return String.format("L2capNetworkSpecifier(%s, %s, RemoteAddress=%s, PSM=%s)",
+ role, headerCompression, Objects.toString(mRemoteAddress), psm);
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/networksecurity/TEST_MAPPING b/networksecurity/TEST_MAPPING
index 20ecbce..f9238c3 100644
--- a/networksecurity/TEST_MAPPING
+++ b/networksecurity/TEST_MAPPING
@@ -1,5 +1,14 @@
{
- "postsubmit": [
+ "presubmit": [
+ {
+ "name": "CtsNetSecConfigCertificateTransparencyTestCases"
+ },
+ {
+ "name": "CtsNetSecConfigCertificateTransparencyDefaultTestCases"
+ },
+ {
+ "name": "NetSecConfigCertificateTransparencySctLogListTestCases"
+ },
{
"name": "NetworkSecurityUnitTests"
}
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 764300c..ce14fc6 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -29,6 +29,7 @@
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
+import android.provider.DeviceConfig;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -231,25 +232,17 @@
}
boolean success = false;
- boolean failureLogged = false;
+ int failureReason = -1;
try {
success = mSignatureVerifier.verify(contentUri, metadataUri);
} catch (MissingPublicKeyException e) {
if (updateFailureCount()) {
- failureLogged = true;
- mLogger.logCTLogListUpdateFailedEvent(
- CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND,
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
+ failureReason = CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND;
}
} catch (InvalidKeyException e) {
if (updateFailureCount()) {
- failureLogged = true;
- mLogger.logCTLogListUpdateFailedEvent(
- CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
- mDataStore.getPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
+ failureReason = CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION;
}
} catch (IOException | GeneralSecurityException e) {
Log.e(TAG, "Could not verify new log list", e);
@@ -259,9 +252,13 @@
Log.w(TAG, "Log list did not pass verification");
// Avoid logging failure twice
- if (!failureLogged && updateFailureCount()) {
+ if (failureReason == -1 && updateFailureCount()) {
+ failureReason = CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION;
+ }
+
+ if (failureReason != -1) {
mLogger.logCTLogListUpdateFailedEvent(
- CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
+ failureReason,
mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0));
}
@@ -324,7 +321,12 @@
mDataStore.setPropertyInt(Config.LOG_LIST_UPDATE_FAILURE_COUNT, new_failure_count);
mDataStore.store();
- boolean shouldReport = new_failure_count >= Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD;
+ int threshold = DeviceConfig.getInt(
+ Config.NAMESPACE_NETWORK_SECURITY,
+ Config.FLAG_LOG_FAILURE_THRESHOLD,
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+
+ boolean shouldReport = new_failure_count >= threshold;
if (shouldReport) {
Log.d(TAG, "Log list update failure count exceeds threshold: " + new_failure_count);
}
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 fdeb746..9d60163 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
@@ -46,6 +46,7 @@
private final String mMetadataUrl;
private final String mContentUrl;
+ private final File mRootDirectory;
private final File mVersionDirectory;
private final File mCurrentLogsDirSymlink;
@@ -54,6 +55,7 @@
mCompatVersion = compatVersion;
mMetadataUrl = metadataUrl;
mContentUrl = contentUrl;
+ mRootDirectory = rootDirectory;
mVersionDirectory = new File(rootDirectory, compatVersion);
mCurrentLogsDirSymlink = new File(mVersionDirectory, CURRENT_LOGS_DIR_SYMLINK_NAME);
}
@@ -86,7 +88,8 @@
// To support atomically replacing the old configuration directory with the new
// 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 directory exists and is readable.
+ // 1. Ensure the path to the root and version directories exist and are readable.
+ DirectoryUtils.makeDir(mRootDirectory);
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 592bc4e..bc4efab 100644
--- a/networksecurity/service/src/com/android/server/net/ct/Config.java
+++ b/networksecurity/service/src/com/android/server/net/ct/Config.java
@@ -45,6 +45,7 @@
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";
+ static final String FLAG_LOG_FAILURE_THRESHOLD = FLAGS_PREFIX + "log_list_failure_threshold";
// properties
static final String VERSION = "version";
@@ -60,5 +61,5 @@
static final String URL_PUBLIC_KEY = URL_PREFIX + "log_list.pub";
// Threshold amounts
- static final int LOG_LIST_UPDATE_FAILURE_THRESHOLD = 10;
+ static final int DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD = 10;
}
diff --git a/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java b/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java
index 54e277a..ba42a82 100644
--- a/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java
+++ b/networksecurity/service/src/com/android/server/net/ct/DirectoryUtils.java
@@ -25,7 +25,7 @@
class DirectoryUtils {
static void makeDir(File dir) throws IOException {
- dir.mkdirs();
+ dir.mkdir();
if (!dir.isDirectory()) {
throw new IOException("Unable to make directory " + dir.getCanonicalPath());
}
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 34f8dd1..2f57fc9 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
@@ -203,7 +203,8 @@
mCertificateTransparencyDownloader.startPublicKeyDownload();
// Set the failure count to just below the threshold
mDataStore.setPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT,
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
mCertificateTransparencyDownloader.onReceive(
mContext,
@@ -212,11 +213,11 @@
assertThat(
mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ .isEqualTo(Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
verify(mLogger, times(1))
.logCTLogListUpdateFailedEventWithDownloadStatus(
DownloadManager.ERROR_INSUFFICIENT_SPACE,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
@@ -273,7 +274,8 @@
mCertificateTransparencyDownloader.startMetadataDownload();
// Set the failure count to just below the threshold
mDataStore.setPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT,
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
mCertificateTransparencyDownloader.onReceive(
mContext,
@@ -283,11 +285,11 @@
assertThat(
mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ .isEqualTo(Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
verify(mLogger, times(1))
.logCTLogListUpdateFailedEventWithDownloadStatus(
DownloadManager.ERROR_INSUFFICIENT_SPACE,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
@@ -350,7 +352,8 @@
mCertificateTransparencyDownloader.startContentDownload(mCompatVersion);
// Set the failure count to just below the threshold
mDataStore.setPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT,
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
mCertificateTransparencyDownloader.onReceive(
mContext,
@@ -360,11 +363,11 @@
assertThat(
mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ .isEqualTo(Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
verify(mLogger, times(1))
.logCTLogListUpdateFailedEventWithDownloadStatus(
DownloadManager.ERROR_INSUFFICIENT_SPACE,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
@@ -415,7 +418,8 @@
mCertificateTransparencyDownloader.startMetadataDownload();
// Set the failure count to just below the threshold
mDataStore.setPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT,
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
// Set the public key to be missing
mSignatureVerifier.resetPublicKey();
@@ -427,11 +431,11 @@
assertThat(
mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ .isEqualTo(Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
verify(mLogger, times(1))
.logCTLogListUpdateFailedEvent(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_NOT_FOUND,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
verify(mLogger, never())
.logCTLogListUpdateFailedEvent(
eq(
@@ -453,7 +457,8 @@
// Set the failure count to just below the threshold
mDataStore.setPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT,
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
// Act
mCertificateTransparencyDownloader.startMetadataDownload();
@@ -466,7 +471,7 @@
assertThat(
mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ .isEqualTo(Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
verify(mLogger, never())
.logCTLogListUpdateFailedEvent(
eq(
@@ -475,7 +480,7 @@
verify(mLogger, times(1))
.logCTLogListUpdateFailedEvent(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
@@ -492,7 +497,8 @@
// Set the failure count to just below the threshold
mDataStore.setPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT,
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
// Act
mCertificateTransparencyDownloader.startMetadataDownload();
@@ -505,7 +511,7 @@
assertThat(
mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ .isEqualTo(Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
verify(mLogger, never())
.logCTLogListUpdateFailedEvent(
eq(
@@ -514,7 +520,7 @@
verify(mLogger, times(1))
.logCTLogListUpdateFailedEvent(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_SIGNATURE_VERIFICATION,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
@@ -560,7 +566,8 @@
mSignatureVerifier.setPublicKey(mPublicKey);
// Set the failure count to just below the threshold
mDataStore.setPropertyInt(
- Config.LOG_LIST_UPDATE_FAILURE_COUNT, Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
+ Config.LOG_LIST_UPDATE_FAILURE_COUNT,
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD - 1);
mCertificateTransparencyDownloader.startMetadataDownload();
mCertificateTransparencyDownloader.onReceive(
@@ -571,11 +578,11 @@
assertThat(
mDataStore.getPropertyInt(
Config.LOG_LIST_UPDATE_FAILURE_COUNT, /* defaultValue= */ 0))
- .isEqualTo(Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ .isEqualTo(Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
verify(mLogger, times(1))
.logCTLogListUpdateFailedEvent(
CERTIFICATE_TRANSPARENCY_LOG_LIST_UPDATE_FAILED__FAILURE_REASON__FAILURE_VERSION_ALREADY_EXISTS,
- Config.LOG_LIST_UPDATE_FAILURE_THRESHOLD);
+ Config.DEFAULT_LOG_LIST_UPDATE_FAILURE_THRESHOLD);
}
@Test
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
index 9add6df..1d43d38 100644
--- a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
@@ -140,6 +140,7 @@
}
impl Platform for JavaPlatform {
+ #[allow(clippy::unit_arg)]
fn send_request(
&mut self,
connection_id: i32,
diff --git a/remoteauth/service/jni/src/unique_jvm.rs b/remoteauth/service/jni/src/unique_jvm.rs
index 46cc361..ddbb16f 100644
--- a/remoteauth/service/jni/src/unique_jvm.rs
+++ b/remoteauth/service/jni/src/unique_jvm.rs
@@ -41,6 +41,7 @@
Ok(())
}
/// Gets a 'static reference to the unique JavaVM. Returns None if set_once() was never called.
+#[allow(static_mut_refs)]
pub(crate) fn get_static_ref() -> Option<&'static Arc<JavaVM>> {
// Safety: follows [this pattern](https://doc.rust-lang.org/std/sync/struct.Once.html).
// Modification to static mut is nested inside call_once.
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 4a9410e..eedf427 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -51,7 +51,6 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.NetdUtils;
-import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.ip.NetlinkMonitor;
import com.android.net.module.util.netlink.NetlinkConstants;
@@ -624,7 +623,7 @@
nc = mNetworkCapabilities.get(hwAddress);
if (nc == null) {
final boolean isTestIface = iface.matches(TEST_IFACE_REGEXP);
- nc = createDefaultNetworkCapabilities(isTestIface);
+ nc = createDefaultNetworkCapabilities(isTestIface, /* overrideTransport */ null);
}
}
@@ -739,9 +738,13 @@
*/
private void parseEthernetConfig(String configString) {
final EthernetTrackerConfig config = createEthernetTrackerConfig(configString);
- NetworkCapabilities nc = createNetworkCapabilities(
- !TextUtils.isEmpty(config.mCapabilities) /* clear default capabilities */,
- config.mCapabilities, config.mTransport).build();
+ NetworkCapabilities nc;
+ if (TextUtils.isEmpty(config.mCapabilities)) {
+ boolean isTestIface = config.mIface.matches(TEST_IFACE_REGEXP);
+ nc = createDefaultNetworkCapabilities(isTestIface, config.mTransport);
+ } else {
+ nc = createNetworkCapabilities(config.mCapabilities, config.mTransport).build();
+ }
mNetworkCapabilities.put(config.mIface, nc);
if (null != config.mIpConfig) {
@@ -756,15 +759,16 @@
return new EthernetTrackerConfig(configString.split(";", /* limit of tokens */ 4));
}
- private static NetworkCapabilities createDefaultNetworkCapabilities(boolean isTestIface) {
- NetworkCapabilities.Builder builder = createNetworkCapabilities(
- false /* clear default capabilities */, null, null)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
- .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);
+ private static NetworkCapabilities createDefaultNetworkCapabilities(
+ boolean isTestIface, @Nullable String overrideTransport) {
+ NetworkCapabilities.Builder builder =
+ createNetworkCapabilities(/* commaSeparatedCapabilities */ null, overrideTransport)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .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);
if (isTestIface) {
builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST);
@@ -778,7 +782,6 @@
/**
* Parses a static list of network capabilities
*
- * @param clearDefaultCapabilities Indicates whether or not to clear any default capabilities
* @param commaSeparatedCapabilities A comma separated string list of integer encoded
* NetworkCapability.NET_CAPABILITY_* values
* @param overrideTransport A string representing a single integer encoded override transport
@@ -788,12 +791,12 @@
*/
@VisibleForTesting
static NetworkCapabilities.Builder createNetworkCapabilities(
- boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities,
- @Nullable String overrideTransport) {
+ @Nullable String commaSeparatedCapabilities, @Nullable String overrideTransport) {
- final NetworkCapabilities.Builder builder = clearDefaultCapabilities
- ? NetworkCapabilities.Builder.withoutDefaultCapabilities()
- : new NetworkCapabilities.Builder();
+ final NetworkCapabilities.Builder builder =
+ TextUtils.isEmpty(commaSeparatedCapabilities)
+ ? new NetworkCapabilities.Builder()
+ : NetworkCapabilities.Builder.withoutDefaultCapabilities();
// Determine the transport type. If someone has tried to define an override transport then
// attempt to add it. Since we can only have one override, all errors with it will
diff --git a/service/Android.bp b/service/Android.bp
index 2659ebf..c4e2ef0 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -113,7 +113,6 @@
":services.connectivity-netstats-jni-sources",
"jni/com_android_server_connectivity_ClatCoordinator.cpp",
"jni/com_android_server_ServiceManagerWrapper.cpp",
- "jni/com_android_server_TestNetworkService.cpp",
"jni/onload.cpp",
],
header_libs: [
@@ -125,7 +124,7 @@
"libmodules-utils-build",
"libnetjniutils",
"libnet_utils_device_common_bpfjni",
- "libnet_utils_device_common_timerfdjni",
+ "libserviceconnectivityjni",
"netd_aidl_interface-lateststable-ndk",
],
shared_libs: [
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index 3627157..d1d9e52 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -79,4 +79,10 @@
to the devices, or reaching to Thread end devices doesn't require border routing to work.
-->
<bool name="config_thread_srp_server_wait_for_border_routing_enabled">true</bool>
+
+ <!-- Whether this border router will automatically join the previous connected network after
+ device reboots. Setting this value to false can allow the user to control the lifecycle of
+ the Thread border router state on this device.
+ -->
+ <bool name="config_thread_border_router_auto_join_enabled">true</bool>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 121cbc4..7ac86aa 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -53,6 +53,7 @@
<item type="string" name="config_thread_model_name" />
<item type="array" name="config_thread_mdns_vendor_specific_txts" />
<item type="bool" name="config_thread_srp_server_wait_for_border_routing_enabled" />
+ <item type="bool" name="config_thread_border_router_auto_join_enabled" />
</policy>
</overlayable>
</resources>
diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp
deleted file mode 100644
index 08d31a3..0000000
--- a/service/jni/com_android_server_TestNetworkService.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-#define LOG_NDEBUG 0
-
-#define LOG_TAG "TestNetworkServiceJni"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/if.h>
-#include <linux/if_tun.h>
-#include <linux/ipv6_route.h>
-#include <linux/route.h>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <log/log.h>
-
-#include "jni.h"
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
-#include <bpf/KernelUtils.h>
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedUtfChars.h>
-
-#ifndef IFF_NO_CARRIER
-#define IFF_NO_CARRIER 0x0040
-#endif
-
-namespace android {
-
-//------------------------------------------------------------------------------
-
-static void throwException(JNIEnv* env, int error, const char* action, const char* iface) {
- const std::string& msg = "Error: " + std::string(action) + " " + std::string(iface) + ": "
- + std::string(strerror(error));
- jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
-}
-
-// enable or disable carrier on tun / tap interface.
-static void setTunTapCarrierEnabledImpl(JNIEnv* env, const char* iface, int tunFd, bool enabled) {
- uint32_t carrierOn = enabled;
- if (ioctl(tunFd, TUNSETCARRIER, &carrierOn)) {
- throwException(env, errno, "set carrier", iface);
- }
-}
-
-static int createTunTapImpl(JNIEnv* env, bool isTun, bool hasCarrier, bool setIffMulticast,
- const char* iface) {
- base::unique_fd tun(open("/dev/tun", O_RDWR | O_NONBLOCK));
- ifreq ifr{};
-
- // Allocate interface.
- ifr.ifr_flags = (isTun ? IFF_TUN : IFF_TAP) | IFF_NO_PI;
- if (!hasCarrier) {
- // Using IFF_NO_CARRIER is supported starting in kernel version >= 6.0
- // Up until then, unsupported flags are ignored.
- if (!bpf::isAtLeastKernelVersion(6, 0, 0)) {
- throwException(env, EOPNOTSUPP, "IFF_NO_CARRIER not supported", ifr.ifr_name);
- return -1;
- }
- ifr.ifr_flags |= IFF_NO_CARRIER;
- }
- strlcpy(ifr.ifr_name, iface, IFNAMSIZ);
- if (ioctl(tun.get(), TUNSETIFF, &ifr)) {
- throwException(env, errno, "allocating", ifr.ifr_name);
- return -1;
- }
-
- // Mark some TAP interfaces as supporting multicast
- if (setIffMulticast && !isTun) {
- base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
- ifr.ifr_flags = IFF_MULTICAST;
-
- if (ioctl(inet6CtrlSock.get(), SIOCSIFFLAGS, &ifr)) {
- throwException(env, errno, "set IFF_MULTICAST", ifr.ifr_name);
- return -1;
- }
- }
-
- return tun.release();
-}
-
-static void bringUpInterfaceImpl(JNIEnv* env, const char* iface) {
- // Activate interface using an unconnected datagram socket.
- base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
-
- ifreq ifr{};
- strlcpy(ifr.ifr_name, iface, IFNAMSIZ);
- if (ioctl(inet6CtrlSock.get(), SIOCGIFFLAGS, &ifr)) {
- throwException(env, errno, "read flags", iface);
- return;
- }
- ifr.ifr_flags |= IFF_UP;
- if (ioctl(inet6CtrlSock.get(), SIOCSIFFLAGS, &ifr)) {
- throwException(env, errno, "set IFF_UP", iface);
- return;
- }
-}
-
-//------------------------------------------------------------------------------
-
-
-
-static void setTunTapCarrierEnabled(JNIEnv* env, jclass /* clazz */, jstring
- jIface, jint tunFd, jboolean enabled) {
- ScopedUtfChars iface(env, jIface);
- if (!iface.c_str()) {
- jniThrowNullPointerException(env, "iface");
- return;
- }
- setTunTapCarrierEnabledImpl(env, iface.c_str(), tunFd, enabled);
-}
-
-static jint createTunTap(JNIEnv* env, jclass /* clazz */, jboolean isTun,
- jboolean hasCarrier, jboolean setIffMulticast, jstring jIface) {
- ScopedUtfChars iface(env, jIface);
- if (!iface.c_str()) {
- jniThrowNullPointerException(env, "iface");
- return -1;
- }
-
- return createTunTapImpl(env, isTun, hasCarrier, setIffMulticast, iface.c_str());
-}
-
-static void bringUpInterface(JNIEnv* env, jclass /* clazz */, jstring jIface) {
- ScopedUtfChars iface(env, jIface);
- if (!iface.c_str()) {
- jniThrowNullPointerException(env, "iface");
- return;
- }
- bringUpInterfaceImpl(env, iface.c_str());
-}
-
-//------------------------------------------------------------------------------
-
-static const JNINativeMethod gMethods[] = {
- {"nativeSetTunTapCarrierEnabled", "(Ljava/lang/String;IZ)V", (void*)setTunTapCarrierEnabled},
- {"nativeCreateTunTap", "(ZZZLjava/lang/String;)I", (void*)createTunTap},
- {"nativeBringUpInterface", "(Ljava/lang/String;)V", (void*)bringUpInterface},
-};
-
-int register_com_android_server_TestNetworkService(JNIEnv* env) {
- return jniRegisterNativeMethods(env,
- "android/net/connectivity/com/android/server/TestNetworkService", gMethods,
- NELEM(gMethods));
-}
-
-}; // namespace android
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index 8e01260..f87470d 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -21,12 +21,11 @@
namespace android {
-int register_com_android_server_TestNetworkService(JNIEnv* env);
int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env);
int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
int register_android_server_net_NetworkStatsService(JNIEnv* env);
int register_com_android_server_ServiceManagerWrapper(JNIEnv* env);
-int register_com_android_net_module_util_TimerFdUtils(JNIEnv *env,
+int register_com_android_net_module_util_ServiceConnectivityJni(JNIEnv *env,
char const *class_name);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -36,10 +35,6 @@
return JNI_ERR;
}
- if (register_com_android_server_TestNetworkService(env) < 0) {
- return JNI_ERR;
- }
-
if (register_com_android_server_ServiceManagerWrapper(env) < 0) {
return JNI_ERR;
}
@@ -58,9 +53,9 @@
}
}
- if (register_com_android_net_module_util_TimerFdUtils(
+ if (register_com_android_net_module_util_ServiceConnectivityJni(
env, "android/net/connectivity/com/android/net/module/util/"
- "TimerFdUtils") < 0) {
+ "ServiceConnectivityJni") < 0) {
return JNI_ERR;
}
diff --git a/service/src/com/android/server/L2capNetworkProvider.java b/service/src/com/android/server/L2capNetworkProvider.java
new file mode 100644
index 0000000..c5ec9ee
--- /dev/null
+++ b/service/src/com/android/server/L2capNetworkProvider.java
@@ -0,0 +1,244 @@
+/*
+ * 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;
+
+import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN;
+import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_ANY;
+import static android.net.L2capNetworkSpecifier.PSM_ANY;
+import static android.net.L2capNetworkSpecifier.ROLE_SERVER;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.RES_ID_MATCH_ALL_RESERVATIONS;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.L2capNetworkSpecifier;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkProvider.NetworkOfferCallback;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.NetworkSpecifier;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Map;
+
+
+public class L2capNetworkProvider {
+ private static final String TAG = L2capNetworkProvider.class.getSimpleName();
+ private final Dependencies mDeps;
+ private final Context mContext;
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
+ private final NetworkProvider mProvider;
+ private final BlanketReservationOffer mBlanketOffer;
+ private final Map<Integer, ReservedServerOffer> mReservedServerOffers = new ArrayMap<>();
+
+ /**
+ * The blanket reservation offer is used to create an L2CAP server network, i.e. a network
+ * based on a BluetoothServerSocket.
+ *
+ * Note that NetworkCapabilities matching semantics will cause onNetworkNeeded to be called for
+ * requests that do not have a NetworkSpecifier set.
+ */
+ private class BlanketReservationOffer implements NetworkOfferCallback {
+ // TODO: ensure that once the incoming request is satisfied, the blanket offer does not get
+ // unneeded. This means the blanket offer must always outscore the reserved offer. This
+ // might require setting the blanket offer as setTransportPrimary().
+ public static final NetworkScore SCORE = new NetworkScore.Builder().build();
+ // Note the missing NET_CAPABILITY_NOT_RESTRICTED marking the network as restricted.
+ public static final NetworkCapabilities CAPABILITIES;
+ static {
+ final L2capNetworkSpecifier l2capNetworkSpecifier = new L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .build();
+ NetworkCapabilities caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ // TODO: consider removing NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED.
+ .addCapability(NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .addCapability(NET_CAPABILITY_NOT_ROAMING)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addCapability(NET_CAPABILITY_NOT_VPN)
+ .setNetworkSpecifier(l2capNetworkSpecifier)
+ .build();
+ caps.setReservationId(RES_ID_MATCH_ALL_RESERVATIONS);
+ CAPABILITIES = caps;
+ }
+
+ // TODO: consider moving this into L2capNetworkSpecifier as #isValidServerReservation().
+ private boolean isValidL2capSpecifier(@Nullable NetworkSpecifier spec) {
+ if (spec == null) return false;
+ // If spec is not null, L2capNetworkSpecifier#canBeSatisfiedBy() guarantees the
+ // specifier is of type L2capNetworkSpecifier.
+ final L2capNetworkSpecifier l2capSpec = (L2capNetworkSpecifier) spec;
+
+ // The ROLE_SERVER offer can be satisfied by a ROLE_ANY request.
+ if (l2capSpec.getRole() != ROLE_SERVER) return false;
+
+ // HEADER_COMPRESSION_ANY is never valid in a request.
+ if (l2capSpec.getHeaderCompression() == HEADER_COMPRESSION_ANY) return false;
+
+ // remoteAddr must be null for ROLE_SERVER requests.
+ if (l2capSpec.getRemoteAddress() != null) return false;
+
+ // reservation must allocate a PSM, so only PSM_ANY can be passed.
+ if (l2capSpec.getPsm() != PSM_ANY) return false;
+
+ return true;
+ }
+
+ @Override
+ public void onNetworkNeeded(NetworkRequest request) {
+ Log.d(TAG, "New reservation request: " + request);
+ if (!isValidL2capSpecifier(request.getNetworkSpecifier())) {
+ Log.w(TAG, "Ignoring invalid reservation request: " + request);
+ return;
+ }
+
+ final NetworkCapabilities reservationCaps = request.networkCapabilities;
+ final ReservedServerOffer reservedOffer = new ReservedServerOffer(reservationCaps);
+
+ final NetworkCapabilities reservedCaps = reservedOffer.getReservedCapabilities();
+ mProvider.registerNetworkOffer(SCORE, reservedCaps, mHandler::post, reservedOffer);
+ mReservedServerOffers.put(request.requestId, reservedOffer);
+ }
+
+ @Override
+ public void onNetworkUnneeded(NetworkRequest request) {
+ if (!mReservedServerOffers.containsKey(request.requestId)) {
+ return;
+ }
+
+ final ReservedServerOffer reservedOffer = mReservedServerOffers.get(request.requestId);
+ // Note that the reserved offer gets torn down when the reservation goes away, even if
+ // there are lingering requests.
+ reservedOffer.tearDown();
+ mProvider.unregisterNetworkOffer(reservedOffer);
+ }
+ }
+
+ private class ReservedServerOffer implements NetworkOfferCallback {
+ private final boolean mUseHeaderCompression;
+ private final int mPsm;
+ private final NetworkCapabilities mReservedCapabilities;
+
+ public ReservedServerOffer(NetworkCapabilities reservationCaps) {
+ // getNetworkSpecifier() is guaranteed to return a non-null L2capNetworkSpecifier.
+ final L2capNetworkSpecifier reservationSpec =
+ (L2capNetworkSpecifier) reservationCaps.getNetworkSpecifier();
+ mUseHeaderCompression =
+ reservationSpec.getHeaderCompression() == HEADER_COMPRESSION_6LOWPAN;
+
+ // TODO: open BluetoothServerSocket and allocate a PSM.
+ mPsm = 0x80;
+
+ final L2capNetworkSpecifier reservedSpec = new L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(reservationSpec.getHeaderCompression())
+ .setPsm(mPsm)
+ .build();
+ mReservedCapabilities = new NetworkCapabilities.Builder(reservationCaps)
+ .setNetworkSpecifier(reservedSpec)
+ .build();
+ }
+
+ public NetworkCapabilities getReservedCapabilities() {
+ return mReservedCapabilities;
+ }
+
+ @Override
+ public void onNetworkNeeded(NetworkRequest request) {
+ // TODO: implement
+ }
+
+ @Override
+ public void onNetworkUnneeded(NetworkRequest request) {
+ // TODO: implement
+ }
+
+ /**
+ * Called when the reservation goes away and the reserved offer must be torn down.
+ *
+ * This method can be called multiple times.
+ */
+ public void tearDown() {
+ // TODO: implement.
+ // This method can be called multiple times.
+ }
+ }
+
+ @VisibleForTesting
+ public static class Dependencies {
+ /** Get NetworkProvider */
+ public NetworkProvider getNetworkProvider(Context context, Looper looper) {
+ return new NetworkProvider(context, looper, TAG);
+ }
+
+ /** Get the HandlerThread for L2capNetworkProvider to run on */
+ public HandlerThread getHandlerThread() {
+ final HandlerThread thread = new HandlerThread("L2capNetworkProviderThread");
+ thread.start();
+ return thread;
+ }
+ }
+
+ public L2capNetworkProvider(Context context) {
+ this(new Dependencies(), context);
+ }
+
+ @VisibleForTesting
+ public L2capNetworkProvider(Dependencies deps, Context context) {
+ mDeps = deps;
+ mContext = context;
+ mHandlerThread = mDeps.getHandlerThread();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mProvider = mDeps.getNetworkProvider(context, mHandlerThread.getLooper());
+ mBlanketOffer = new BlanketReservationOffer();
+ }
+
+ /**
+ * Start L2capNetworkProvider.
+ *
+ * Called on CS Handler thread.
+ */
+ public void start() {
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm.hasSystemFeature(FEATURE_BLUETOOTH_LE)) {
+ mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(mProvider);
+ mProvider.registerNetworkOffer(BlanketReservationOffer.SCORE,
+ BlanketReservationOffer.CAPABILITIES, mHandler::post, mBlanketOffer);
+ }
+ }
+}
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index 4d39d7d..96f4e20 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -48,6 +48,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.ServiceConnectivityJni;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -75,15 +76,6 @@
@NonNull private final ConnectivityManager mCm;
@NonNull private final NetworkProvider mNetworkProvider;
- // Native method stubs
- private static native int nativeCreateTunTap(boolean isTun, boolean hasCarrier,
- boolean setIffMulticast, @NonNull String iface);
-
- private static native void nativeSetTunTapCarrierEnabled(@NonNull String iface, int tunFd,
- boolean enabled);
-
- private static native void nativeBringUpInterface(String iface);
-
@VisibleForTesting
protected TestNetworkService(@NonNull Context context) {
mHandlerThread = new HandlerThread("TestNetworkServiceThread");
@@ -143,7 +135,8 @@
// flags atomically.
final boolean setIffMulticast = bringUp;
ParcelFileDescriptor tunIntf = ParcelFileDescriptor.adoptFd(
- nativeCreateTunTap(isTun, hasCarrier, setIffMulticast, interfaceName));
+ ServiceConnectivityJni.createTunTap(
+ isTun, hasCarrier, setIffMulticast, interfaceName));
// Disable DAD and remove router_solicitation_delay before assigning link addresses.
if (disableIpv6ProvisioningDelay) {
@@ -160,7 +153,7 @@
}
if (bringUp) {
- nativeBringUpInterface(interfaceName);
+ ServiceConnectivityJni.bringUpInterface(interfaceName);
}
return new TestNetworkInterface(tunIntf, interfaceName);
@@ -403,11 +396,11 @@
@Override
public void setCarrierEnabled(@NonNull TestNetworkInterface iface, boolean enabled) {
enforceTestNetworkPermissions(mContext);
- nativeSetTunTapCarrierEnabled(iface.getInterfaceName(), iface.getFileDescriptor().getFd(),
- enabled);
+ ServiceConnectivityJni.setTunTapCarrierEnabled(iface.getInterfaceName(),
+ iface.getFileDescriptor().getFd(), enabled);
// Explicitly close fd after use to prevent StrictMode from complaining.
// Also, explicitly referencing iface guarantees that the object is not garbage collected
- // before nativeSetTunTapCarrierEnabled() executes.
+ // before setTunTapCarrierEnabled() executes.
try {
iface.getFileDescriptor().close();
} catch (IOException e) {
diff --git a/staticlibs/device/com/android/net/module/util/RealtimeScheduler.java b/staticlibs/device/com/android/net/module/util/RealtimeScheduler.java
index bc3b3a5..a556ccc 100644
--- a/staticlibs/device/com/android/net/module/util/RealtimeScheduler.java
+++ b/staticlibs/device/com/android/net/module/util/RealtimeScheduler.java
@@ -25,6 +25,8 @@
import android.os.MessageQueue;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.CloseGuard;
import android.util.Log;
@@ -265,6 +267,26 @@
}
private void handleExpiration() {
+ // The data from the FileDescriptor must be read after the timer expires. Otherwise,
+ // expiration callbacks will continue to be sent, notifying of unread data. The content(the
+ // number of expirations) can be ignored, as the callback is the only item of interest.
+ // Refer to https://man7.org/linux/man-pages/man2/timerfd_create.2.html
+ // read(2)
+ // If the timer has already expired one or more times since
+ // its settings were last modified using timerfd_settime(),
+ // or since the last successful read(2), then the buffer
+ // given to read(2) returns an unsigned 8-byte integer
+ // (uint64_t) containing the number of expirations that have
+ // occurred. (The returned value is in host byte order—that
+ // is, the native byte order for integers on the host
+ // machine.)
+ final byte[] readBuffer = new byte[8];
+ try {
+ Os.read(mParcelFileDescriptor.getFileDescriptor(), readBuffer, 0, readBuffer.length);
+ } catch (IOException | ErrnoException exception) {
+ Log.wtf(TAG, "Read FileDescriptor failed. ", exception);
+ }
+
long currentTimeMs = SystemClock.elapsedRealtime();
while (!mTaskQueue.isEmpty()) {
final Task task = mTaskQueue.peek();
@@ -276,7 +298,6 @@
mTaskQueue.poll();
}
-
if (!mTaskQueue.isEmpty()) {
// Using currentTimeMs ensures that the calculated expiration time
// is always positive.
@@ -286,10 +307,6 @@
Log.wtf(TAG, "Failed to set expiration time");
mTaskQueue.clear();
}
- } else {
- // We have to clean up the timer if no tasks are left. Otherwise, the timer will keep
- // being triggered.
- TimerFdUtils.setExpirationTime(mFdInt, 0);
}
}
diff --git a/staticlibs/device/com/android/net/module/util/ServiceConnectivityJni.java b/staticlibs/device/com/android/net/module/util/ServiceConnectivityJni.java
new file mode 100644
index 0000000..4a5dd4f
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/ServiceConnectivityJni.java
@@ -0,0 +1,63 @@
+/*
+ * 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.net.module.util;
+
+import android.annotation.NonNull;
+
+import java.io.IOException;
+
+/**
+ * Contains JNI functions for use in service-connectivity
+ */
+public class ServiceConnectivityJni {
+ static {
+ final String libName = JniUtil.getJniLibraryName(ServiceConnectivityJni.class.getPackage());
+ if (libName.equals("android_net_connectivity_com_android_net_module_util_jni")) {
+ // This library is part of service-connectivity.jar when in the system server,
+ // so libservice-connectivity.so is the library to load.
+ System.loadLibrary("service-connectivity");
+ } else {
+ System.loadLibrary(libName);
+ }
+ }
+
+ /**
+ * Create a timerfd.
+ *
+ * @throws IOException if the timerfd creation is failed.
+ */
+ public static native int createTimerFd() throws IOException;
+
+ /**
+ * Set given time to the timerfd.
+ *
+ * @param timeMs target time
+ * @throws IOException if setting expiration time is failed.
+ */
+ public static native void setTimerFdTime(int fd, long timeMs) throws IOException;
+
+ /** Create tun/tap interface */
+ public static native int createTunTap(boolean isTun, boolean hasCarrier,
+ boolean setIffMulticast, @NonNull String iface);
+
+ /** Enable carrier on tun/tap interface */
+ public static native void setTunTapCarrierEnabled(@NonNull String iface, int tunFd,
+ boolean enabled);
+
+ /** Bring up tun/tap interface */
+ public static native void bringUpInterface(String iface);
+}
diff --git a/staticlibs/device/com/android/net/module/util/TimerFdUtils.java b/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
index f0de142..10bc595 100644
--- a/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
+++ b/staticlibs/device/com/android/net/module/util/TimerFdUtils.java
@@ -25,40 +25,14 @@
* Contains mostly timerfd functionality.
*/
public class TimerFdUtils {
- static {
- final String jniLibName = JniUtil.getJniLibraryName(TimerFdUtils.class.getPackage());
- if (jniLibName.equals("android_net_connectivity_com_android_net_module_util_jni")) {
- // This library is part of service-connectivity.jar when in the system server,
- // so libservice-connectivity.so is the library to load.
- System.loadLibrary("service-connectivity");
- } else {
- System.loadLibrary(jniLibName);
- }
- }
-
private static final String TAG = TimerFdUtils.class.getSimpleName();
/**
- * Create a timerfd.
- *
- * @throws IOException if the timerfd creation is failed.
- */
- private static native int createTimerFd() throws IOException;
-
- /**
- * Set given time to the timerfd.
- *
- * @param timeMs target time
- * @throws IOException if setting expiration time is failed.
- */
- private static native void setTime(int fd, long timeMs) throws IOException;
-
- /**
* Create a timerfd
*/
static int createTimerFileDescriptor() {
try {
- return createTimerFd();
+ return ServiceConnectivityJni.createTimerFd();
} catch (IOException e) {
Log.e(TAG, "createTimerFd failed", e);
return -1;
@@ -70,7 +44,7 @@
*/
static boolean setExpirationTime(int fd, long expirationTimeMs) {
try {
- setTime(fd, expirationTimeMs);
+ ServiceConnectivityJni.setTimerFdTime(fd, expirationTimeMs);
} catch (IOException e) {
Log.e(TAG, "setExpirationTime failed", e);
return false;
diff --git a/staticlibs/native/timerfdutils/Android.bp b/staticlibs/native/serviceconnectivityjni/Android.bp
similarity index 86%
rename from staticlibs/native/timerfdutils/Android.bp
rename to staticlibs/native/serviceconnectivityjni/Android.bp
index 939a2d2..18246dd 100644
--- a/staticlibs/native/timerfdutils/Android.bp
+++ b/staticlibs/native/serviceconnectivityjni/Android.bp
@@ -18,17 +18,20 @@
}
cc_library_static {
- name: "libnet_utils_device_common_timerfdjni",
+ name: "libserviceconnectivityjni",
srcs: [
- "com_android_net_module_util_TimerFdUtils.cpp",
+ "com_android_net_module_util_ServiceConnectivityJni.cpp",
],
header_libs: [
+ "bpf_headers",
"jni_headers",
+ "libbase_headers",
],
shared_libs: [
"liblog",
"libnativehelper_compat_libc++",
],
+ stl: "libc++_static",
cflags: [
"-Wall",
"-Werror",
diff --git a/staticlibs/native/serviceconnectivityjni/com_android_net_module_util_ServiceConnectivityJni.cpp b/staticlibs/native/serviceconnectivityjni/com_android_net_module_util_ServiceConnectivityJni.cpp
new file mode 100644
index 0000000..8767589
--- /dev/null
+++ b/staticlibs/native/serviceconnectivityjni/com_android_net_module_util_ServiceConnectivityJni.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <jni.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <linux/ipv6_route.h>
+#include <linux/route.h>
+#include <netinet/in.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/timerfd.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <android-base/unique_fd.h>
+#include <bpf/KernelUtils.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/scoped_utf_chars.h>
+
+#define MSEC_PER_SEC 1000
+#define NSEC_PER_MSEC 1000000
+
+#ifndef IFF_NO_CARRIER
+#define IFF_NO_CARRIER 0x0040
+#endif
+
+namespace android {
+
+static jint createTimerFd(JNIEnv *env, jclass clazz) {
+ int tfd;
+ // For safety, the file descriptor should have O_NONBLOCK(TFD_NONBLOCK) set
+ // using fcntl during creation. This ensures that, in the worst-case scenario,
+ // an EAGAIN error is returned when reading.
+ tfd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK);
+ if (tfd == -1) {
+ jniThrowErrnoException(env, "createTimerFd", tfd);
+ }
+ return tfd;
+}
+
+static void setTimerFdTime(JNIEnv *env, jclass clazz, jint tfd,
+ jlong milliseconds) {
+ struct itimerspec new_value;
+ new_value.it_value.tv_sec = milliseconds / MSEC_PER_SEC;
+ new_value.it_value.tv_nsec = (milliseconds % MSEC_PER_SEC) * NSEC_PER_MSEC;
+ // Set the interval time to 0 because it's designed for repeated timer
+ // expirations after the initial expiration, which doesn't fit the current
+ // usage.
+ new_value.it_interval.tv_sec = 0;
+ new_value.it_interval.tv_nsec = 0;
+
+ int ret = timerfd_settime(tfd, 0, &new_value, NULL);
+ if (ret == -1) {
+ jniThrowErrnoException(env, "setTimerFdTime", ret);
+ }
+}
+
+static void throwException(JNIEnv *env, int error, const char *action,
+ const char *iface) {
+ const std::string &msg = "Error: " + std::string(action) + " " +
+ std::string(iface) + ": " +
+ std::string(strerror(error));
+ jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
+}
+
+// enable or disable carrier on tun / tap interface.
+static void setTunTapCarrierEnabledImpl(JNIEnv *env, const char *iface,
+ int tunFd, bool enabled) {
+ uint32_t carrierOn = enabled;
+ if (ioctl(tunFd, TUNSETCARRIER, &carrierOn)) {
+ throwException(env, errno, "set carrier", iface);
+ }
+}
+
+static int createTunTapImpl(JNIEnv *env, bool isTun, bool hasCarrier,
+ bool setIffMulticast, const char *iface) {
+ base::unique_fd tun(open("/dev/tun", O_RDWR | O_NONBLOCK));
+ ifreq ifr{};
+
+ // Allocate interface.
+ ifr.ifr_flags = (isTun ? IFF_TUN : IFF_TAP) | IFF_NO_PI;
+ if (!hasCarrier) {
+ // Using IFF_NO_CARRIER is supported starting in kernel version >= 6.0
+ // Up until then, unsupported flags are ignored.
+ if (!bpf::isAtLeastKernelVersion(6, 0, 0)) {
+ throwException(env, EOPNOTSUPP, "IFF_NO_CARRIER not supported",
+ ifr.ifr_name);
+ return -1;
+ }
+ ifr.ifr_flags |= IFF_NO_CARRIER;
+ }
+ strlcpy(ifr.ifr_name, iface, IFNAMSIZ);
+ if (ioctl(tun.get(), TUNSETIFF, &ifr)) {
+ throwException(env, errno, "allocating", ifr.ifr_name);
+ return -1;
+ }
+
+ // Mark some TAP interfaces as supporting multicast
+ if (setIffMulticast && !isTun) {
+ base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
+ ifr.ifr_flags = IFF_MULTICAST;
+
+ if (ioctl(inet6CtrlSock.get(), SIOCSIFFLAGS, &ifr)) {
+ throwException(env, errno, "set IFF_MULTICAST", ifr.ifr_name);
+ return -1;
+ }
+ }
+
+ return tun.release();
+}
+
+static void bringUpInterfaceImpl(JNIEnv *env, const char *iface) {
+ // Activate interface using an unconnected datagram socket.
+ base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
+
+ ifreq ifr{};
+ strlcpy(ifr.ifr_name, iface, IFNAMSIZ);
+ if (ioctl(inet6CtrlSock.get(), SIOCGIFFLAGS, &ifr)) {
+ throwException(env, errno, "read flags", iface);
+ return;
+ }
+ ifr.ifr_flags |= IFF_UP;
+ if (ioctl(inet6CtrlSock.get(), SIOCSIFFLAGS, &ifr)) {
+ throwException(env, errno, "set IFF_UP", iface);
+ return;
+ }
+}
+
+//------------------------------------------------------------------------------
+
+static void setTunTapCarrierEnabled(JNIEnv *env, jclass /* clazz */,
+ jstring jIface, jint tunFd,
+ jboolean enabled) {
+ ScopedUtfChars iface(env, jIface);
+ if (!iface.c_str()) {
+ jniThrowNullPointerException(env, "iface");
+ return;
+ }
+ setTunTapCarrierEnabledImpl(env, iface.c_str(), tunFd, enabled);
+}
+
+static jint createTunTap(JNIEnv *env, jclass /* clazz */, jboolean isTun,
+ jboolean hasCarrier, jboolean setIffMulticast,
+ jstring jIface) {
+ ScopedUtfChars iface(env, jIface);
+ if (!iface.c_str()) {
+ jniThrowNullPointerException(env, "iface");
+ return -1;
+ }
+
+ return createTunTapImpl(env, isTun, hasCarrier, setIffMulticast,
+ iface.c_str());
+}
+
+static void bringUpInterface(JNIEnv *env, jclass /* clazz */, jstring jIface) {
+ ScopedUtfChars iface(env, jIface);
+ if (!iface.c_str()) {
+ jniThrowNullPointerException(env, "iface");
+ return;
+ }
+ bringUpInterfaceImpl(env, iface.c_str());
+}
+
+//------------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"createTimerFd", "()I", (void *)createTimerFd},
+ {"setTimerFdTime", "(IJ)V", (void *)setTimerFdTime},
+ {"setTunTapCarrierEnabled", "(Ljava/lang/String;IZ)V",
+ (void *)setTunTapCarrierEnabled},
+ {"createTunTap", "(ZZZLjava/lang/String;)I", (void *)createTunTap},
+ {"bringUpInterface", "(Ljava/lang/String;)V", (void *)bringUpInterface},
+};
+
+int register_com_android_net_module_util_ServiceConnectivityJni(
+ JNIEnv *env, char const *class_name) {
+ return jniRegisterNativeMethods(env, class_name, gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/staticlibs/native/timerfdutils/com_android_net_module_util_TimerFdUtils.cpp b/staticlibs/native/timerfdutils/com_android_net_module_util_TimerFdUtils.cpp
deleted file mode 100644
index c4c960d..0000000
--- a/staticlibs/native/timerfdutils/com_android_net_module_util_TimerFdUtils.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.
- */
-
-#include <errno.h>
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/scoped_utf_chars.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/epoll.h>
-#include <sys/timerfd.h>
-#include <time.h>
-#include <unistd.h>
-
-#define MSEC_PER_SEC 1000
-#define NSEC_PER_MSEC 1000000
-
-namespace android {
-
-static jint
-com_android_net_module_util_TimerFdUtils_createTimerFd(JNIEnv *env,
- jclass clazz) {
- int tfd;
- tfd = timerfd_create(CLOCK_BOOTTIME, 0);
- if (tfd == -1) {
- jniThrowErrnoException(env, "createTimerFd", tfd);
- }
- return tfd;
-}
-
-static void
-com_android_net_module_util_TimerFdUtils_setTime(JNIEnv *env, jclass clazz,
- jint tfd, jlong milliseconds) {
- struct itimerspec new_value;
- new_value.it_value.tv_sec = milliseconds / MSEC_PER_SEC;
- new_value.it_value.tv_nsec = (milliseconds % MSEC_PER_SEC) * NSEC_PER_MSEC;
- // Set the interval time to 0 because it's designed for repeated timer expirations after the
- // initial expiration, which doesn't fit the current usage.
- new_value.it_interval.tv_sec = 0;
- new_value.it_interval.tv_nsec = 0;
-
- int ret = timerfd_settime(tfd, 0, &new_value, NULL);
- if (ret == -1) {
- jniThrowErrnoException(env, "setTime", ret);
- }
-}
-
-/*
- * JNI registration.
- */
-static const JNINativeMethod gMethods[] = {
- /* name, signature, funcPtr */
- {"createTimerFd", "()I",
- (void *)com_android_net_module_util_TimerFdUtils_createTimerFd},
- {"setTime", "(IJ)V",
- (void *)com_android_net_module_util_TimerFdUtils_setTime},
-};
-
-int register_com_android_net_module_util_TimerFdUtils(JNIEnv *env,
- char const *class_name) {
- return jniRegisterNativeMethods(env, class_name, gMethods, NELEM(gMethods));
-}
-
-}; // namespace android
diff --git a/staticlibs/tests/unit/jni/Android.bp b/staticlibs/tests/unit/jni/Android.bp
index e456471..c444159 100644
--- a/staticlibs/tests/unit/jni/Android.bp
+++ b/staticlibs/tests/unit/jni/Android.bp
@@ -30,7 +30,7 @@
"com_android_net_moduletests_util/onload.cpp",
],
static_libs: [
- "libnet_utils_device_common_timerfdjni",
+ "libserviceconnectivityjni",
],
shared_libs: [
"liblog",
diff --git a/staticlibs/tests/unit/jni/com_android_net_moduletests_util/onload.cpp b/staticlibs/tests/unit/jni/com_android_net_moduletests_util/onload.cpp
index a035540..af4810f 100644
--- a/staticlibs/tests/unit/jni/com_android_net_moduletests_util/onload.cpp
+++ b/staticlibs/tests/unit/jni/com_android_net_moduletests_util/onload.cpp
@@ -22,7 +22,7 @@
namespace android {
-int register_com_android_net_module_util_TimerFdUtils(JNIEnv *env,
+int register_com_android_net_module_util_ServiceConnectivityJni(JNIEnv *env,
char const *class_name);
extern "C" jint JNI_OnLoad(JavaVM *vm, void *) {
@@ -32,8 +32,8 @@
return JNI_ERR;
}
- if (register_com_android_net_module_util_TimerFdUtils(
- env, "com/android/net/moduletests/util/TimerFdUtils") < 0)
+ if (register_com_android_net_module_util_ServiceConnectivityJni(
+ env, "com/android/net/moduletests/util/ServiceConnectivityJni") < 0)
return JNI_ERR;
return JNI_VERSION_1_6;
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 86aa8f1..ec486fb 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -93,6 +93,7 @@
libs: ["tradefed"],
test_suites: [
"ats",
+ "automotive-general-tests",
"device-tests",
"general-tests",
"cts",
diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
index 8e27c62..c42d9e5 100644
--- a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -19,7 +19,7 @@
import android.Manifest.permission.NETWORK_SETTINGS
import android.content.pm.PackageManager.FEATURE_TELEPHONY
import android.content.pm.PackageManager.FEATURE_WIFI
-import android.net.LinkAddress
+import android.net.InetAddresses.parseNumericAddress
import android.net.Network
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI
@@ -66,7 +66,8 @@
// Skip IPv6 checks on virtual devices which do not support it. Tests that require IPv6 will
// still fail even if the preparer does not.
private fun ipv6Unsupported(wifiSsid: String?) = ConnectUtil.VIRTUAL_SSIDS.contains(
- WifiInfo.sanitizeSsid(wifiSsid))
+ WifiInfo.sanitizeSsid(wifiSsid)
+ )
@Test
fun testCheckWifiSetup() {
@@ -89,13 +90,25 @@
pos = 0,
timeoutMs = 30_000L
) {
- it is LinkPropertiesChanged &&
- it.network == network &&
- it.lp.allLinkAddresses.any(LinkAddress::isIpv4) &&
- (ipv6Unsupported(ssid) || it.lp.hasGlobalIpv6Address())
+ if (it !is LinkPropertiesChanged || it.network != network) {
+ false
+ } else {
+ // Same check as used by DnsResolver for AI_ADDRCONFIG (have_ipv4)
+ val ipv4Reachable = it.lp.isReachable(parseNumericAddress("8.8.8.8"))
+ // Same check as used by DnsResolver for AI_ADDRCONFIG (have_ipv6)
+ val ipv6Reachable = it.lp.isReachable(parseNumericAddress("2000::"))
+ ipv4Reachable && (ipv6Unsupported(ssid) || ipv6Reachable)
+ }
}
- assertNotNull(lpChange, "Wifi network $network needs an IPv4 address" +
- if (ipv6Unsupported(ssid)) "" else " and a global IPv6 address")
+ assertNotNull(
+ lpChange,
+ "Wifi network $network needs an IPv4 address and default route" +
+ if (ipv6Unsupported(ssid)) {
+ ""
+ } else {
+ " and a global IPv6 address and default route"
+ }
+ )
Pair(network, ssid)
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
index 0624e5f..c7d6850 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
@@ -168,7 +168,8 @@
.addCapability(NET_CAPABILITY_INTERNET)
.addTransportType(TRANSPORT_WIFI)
.addTransportType(TRANSPORT_CELLULAR)
- .build(), networkCallback
+ .build(),
+ networkCallback
)
}
}
@@ -184,9 +185,12 @@
// when iterating on failing tests.
if (!runOnFailure(failure.exception)) return
if (outputFiles.size >= MAX_DUMPS) return
- Log.i(TAG, "Collecting diagnostics for test failure. Disable by running tests with: " +
+ Log.i(
+ TAG,
+ "Collecting diagnostics for test failure. Disable by running tests with: " +
"atest MyModule -- " +
- "--module-arg MyModule:instrumentation-arg:$ARG_RUN_ON_FAILURE:=false")
+ "--module-arg MyModule:instrumentation-arg:$ARG_RUN_ON_FAILURE:=false"
+ )
collectTestFailureDiagnostics(failure.exception)
val baseFilename = "${description.className}#${description.methodName}_failure"
@@ -326,8 +330,11 @@
}
}
} else {
- Log.w(TAG, "The test is still holding shell permissions, cannot collect privileged " +
- "device info")
+ Log.w(
+ TAG,
+ "The test is still holding shell permissions, cannot collect privileged " +
+ "device info"
+ )
headerObj.put("shellPermissionsUnavailable", true)
}
failureHeader = headerObj.apply {
@@ -379,7 +386,9 @@
cbHelper.registerNetworkCallback(
NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
- .addCapability(NET_CAPABILITY_INTERNET).build(), cb)
+ .addCapability(NET_CAPABILITY_INTERNET).build(),
+ cb
+ )
return try {
cb.wifiInfoFuture.get(1L, TimeUnit.SECONDS)
} catch (e: TimeoutException) {
@@ -410,15 +419,29 @@
* @param exceptionContext An exception to write a stacktrace to the dump for context.
*/
fun collectDumpsysConnectivity(exceptionContext: Throwable? = null) {
- Log.i(TAG, "Collecting dumpsys connectivity for test artifacts")
+ collectDumpsys("connectivity --dump-priority HIGH", exceptionContext)
+ }
+
+ /**
+ * Add a dumpsys to the test data dump.
+ *
+ * <p>The dump will be collected immediately, and exported to a test artifact file when the
+ * test ends.
+ * @param dumpsysCmd The dumpsys command to run (for example "connectivity").
+ * @param exceptionContext An exception to write a stacktrace to the dump for context.
+ */
+ fun collectDumpsys(dumpsysCmd: String, exceptionContext: Throwable? = null) {
+ Log.i(TAG, "Collecting dumpsys $dumpsysCmd for test artifacts")
PrintWriter(buffer).let {
- it.println("--- Dumpsys connectivity at ${ZonedDateTime.now()} ---")
+ it.println("--- Dumpsys $dumpsysCmd at ${ZonedDateTime.now()} ---")
maybeWriteExceptionContext(it, exceptionContext)
it.flush()
}
ParcelFileDescriptor.AutoCloseInputStream(
InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand(
- "dumpsys connectivity --dump-priority HIGH")).use {
+ "dumpsys $dumpsysCmd"
+ )
+ ).use {
it.copyTo(buffer)
}
}
@@ -437,4 +460,4 @@
writer.println("At: ")
exceptionContext.printStackTrace(writer)
}
-}
\ No newline at end of file
+}
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index 949be85..a082a95 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -22,6 +22,7 @@
main: "run_tests.py",
srcs: [
"apfv4_test.py",
+ "apfv6_test.py",
"connectivity_multi_devices_test.py",
"run_tests.py",
],
diff --git a/tests/cts/multidevices/apfv6_test.py b/tests/cts/multidevices/apfv6_test.py
new file mode 100644
index 0000000..fc732d2
--- /dev/null
+++ b/tests/cts/multidevices/apfv6_test.py
@@ -0,0 +1,84 @@
+# 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.
+
+from mobly import asserts
+from net_tests_utils.host.python import apf_test_base, apf_utils, adb_utils, assert_utils, packet_utils
+
+APFV6_VERSION = 6000
+ARP_OFFLOAD_REPLY_LEN = 60
+
+class ApfV6Test(apf_test_base.ApfTestBase):
+ def setup_class(self):
+ super().setup_class()
+
+ # Skip tests for APF version < 6000
+ apf_utils.assume_apf_version_support_at_least(
+ self.clientDevice, self.client_iface_name, APFV6_VERSION
+ )
+
+ def teardown_class(self):
+ # force to stop capture on the server device if any test case failed
+ try:
+ apf_utils.stop_capture_packets(self.serverDevice, self.server_iface_name)
+ except assert_utils.UnexpectedBehaviorError:
+ pass
+ super().teardown_class()
+
+ def test_unicast_arp_request_offload(self):
+ arp_request = packet_utils.construct_arp_packet(
+ src_mac=self.server_mac_address,
+ dst_mac=self.client_mac_address,
+ src_ip=self.server_ipv4_addresses[0],
+ dst_ip=self.client_ipv4_addresses[0],
+ op=packet_utils.ARP_REQUEST_OP
+ )
+
+ arp_reply = packet_utils.construct_arp_packet(
+ src_mac=self.client_mac_address,
+ dst_mac=self.server_mac_address,
+ src_ip=self.client_ipv4_addresses[0],
+ dst_ip=self.server_ipv4_addresses[0],
+ op=packet_utils.ARP_REPLY_OP
+ )
+
+ # Add zero padding up to 60 bytes, since APFv6 ARP offload always sent out 60 bytes reply
+ arp_reply = arp_reply.ljust(ARP_OFFLOAD_REPLY_LEN * 2, "0")
+
+ self.send_packet_and_expect_reply_received(
+ arp_request, "DROPPED_ARP_REQUEST_REPLIED", arp_reply
+ )
+
+ def test_broadcast_arp_request_offload(self):
+ arp_request = packet_utils.construct_arp_packet(
+ src_mac=self.server_mac_address,
+ dst_mac=packet_utils.ETHER_BROADCAST_MAC_ADDRESS,
+ src_ip=self.server_ipv4_addresses[0],
+ dst_ip=self.client_ipv4_addresses[0],
+ op=packet_utils.ARP_REQUEST_OP
+ )
+
+ arp_reply = packet_utils.construct_arp_packet(
+ src_mac=self.client_mac_address,
+ dst_mac=self.server_mac_address,
+ src_ip=self.client_ipv4_addresses[0],
+ dst_ip=self.server_ipv4_addresses[0],
+ op=packet_utils.ARP_REPLY_OP
+ )
+
+ # Add zero padding up to 60 bytes, since APFv6 ARP offload always sent out 60 bytes reply
+ arp_reply = arp_reply.ljust(ARP_OFFLOAD_REPLY_LEN * 2, "0")
+
+ self.send_packet_and_expect_reply_received(
+ arp_request, "DROPPED_ARP_REQUEST_REPLIED", arp_reply
+ )
diff --git a/tests/cts/multidevices/run_tests.py b/tests/cts/multidevices/run_tests.py
index 1391d13..a0d0bec 100644
--- a/tests/cts/multidevices/run_tests.py
+++ b/tests/cts/multidevices/run_tests.py
@@ -16,6 +16,7 @@
import sys
from apfv4_test import ApfV4Test
+from apfv6_test import ApfV6Test
from connectivity_multi_devices_test import ConnectivityMultiDevicesTest
from mobly import suite_runner
@@ -35,4 +36,4 @@
index = sys.argv.index("--")
sys.argv = sys.argv[:1] + sys.argv[index + 1 :]
# TODO: make the tests can be executed without manually list classes.
- suite_runner.run_suite([ConnectivityMultiDevicesTest, ApfV4Test], sys.argv)
+ suite_runner.run_suite([ConnectivityMultiDevicesTest, ApfV4Test, ApfV6Test], sys.argv)
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index a9ac29c..1ba581a 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -95,6 +95,7 @@
"NetworkStackApiCurrentShims",
],
test_suites: [
+ "automotive-general-tests",
"cts",
"mts-tethering",
"mcts-tethering",
@@ -160,6 +161,7 @@
min_sdk_version: "30",
// Tag this module as a cts test artifact
test_suites: [
+ "automotive-general-tests",
"cts",
"general-tests",
],
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTapTest.kt b/tests/cts/net/src/android/net/cts/DnsResolverTapTest.kt
index b1b6e0d..ff608f2 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTapTest.kt
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTapTest.kt
@@ -26,6 +26,7 @@
import android.os.CancellationSignal
import android.os.HandlerThread
import android.os.SystemClock
+import android.platform.test.annotations.AppModeFull
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_NETD_NATIVE
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -58,6 +59,7 @@
private val TAG = DnsResolverTapTest::class.java.simpleName
private const val TEST_TIMEOUT_MS = 10_000L
+@AppModeFull(reason = "Test networks cannot be created in instant app mode")
@DnsResolverModuleTest
@RunWith(AndroidJUnit4::class)
class DnsResolverTapTest {
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index fa44ae9..b66b853 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -58,6 +58,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.DnsPacket;
+import com.android.testutils.ConnectivityDiagnosticsCollector;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DeviceConfigRule;
@@ -394,7 +395,22 @@
@Test
@DnsResolverModuleTest
public void testRawQueryNXDomainWithPrivateDns() throws Exception {
- doTestRawQueryNXDomainWithPrivateDns(mExecutor);
+ try {
+ doTestRawQueryNXDomainWithPrivateDns(mExecutor);
+ } catch (Throwable e) {
+ final ConnectivityDiagnosticsCollector collector =
+ ConnectivityDiagnosticsCollector.getInstance();
+ if (collector != null) {
+ // IWLAN on U QPR3 release may cause failures in this test, see
+ // CarrierConfigSetupTest which is supposed to avoid the issue. Collect IWLAN
+ // related dumpsys if the test still fails.
+ collector.collectDumpsys("carrier_config", e);
+ collector.collectDumpsys("telecom", e);
+ collector.collectDumpsys("telephony_ims", e);
+ collector.collectDumpsys("telephony.registry", e);
+ }
+ throw e;
+ }
}
@Test
diff --git a/tests/cts/net/src/android/net/cts/L2capNetworkSpecifierTest.kt b/tests/cts/net/src/android/net/cts/L2capNetworkSpecifierTest.kt
index b593baf..484cce8 100644
--- a/tests/cts/net/src/android/net/cts/L2capNetworkSpecifierTest.kt
+++ b/tests/cts/net/src/android/net/cts/L2capNetworkSpecifierTest.kt
@@ -18,7 +18,9 @@
import android.net.L2capNetworkSpecifier
import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN
+import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_ANY
import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_NONE
+import android.net.L2capNetworkSpecifier.PSM_ANY
import android.net.L2capNetworkSpecifier.ROLE_CLIENT
import android.net.L2capNetworkSpecifier.ROLE_SERVER
import android.net.MacAddress
@@ -28,6 +30,8 @@
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.assertParcelingIsLossless
import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,14 +55,63 @@
fun testGetters() {
val remoteMac = MacAddress.fromString("11:22:33:44:55:66")
val specifier = L2capNetworkSpecifier.Builder()
- .setRole(ROLE_SERVER)
+ .setRole(ROLE_CLIENT)
.setHeaderCompression(HEADER_COMPRESSION_NONE)
.setPsm(123)
.setRemoteAddress(remoteMac)
.build()
- assertEquals(ROLE_SERVER, specifier.getRole())
+ assertEquals(ROLE_CLIENT, specifier.getRole())
assertEquals(HEADER_COMPRESSION_NONE, specifier.getHeaderCompression())
assertEquals(123, specifier.getPsm())
assertEquals(remoteMac, specifier.getRemoteAddress())
}
+
+ @Test
+ fun testCanBeSatisfiedBy() {
+ val blanketOffer = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_ANY)
+ .setPsm(PSM_ANY)
+ .build()
+
+ val reservedOffer = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+ .setPsm(42)
+ .build()
+
+ val clientOffer = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_CLIENT)
+ .setHeaderCompression(HEADER_COMPRESSION_ANY)
+ .build()
+
+ val serverReservation = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+ .build()
+
+ assertTrue(serverReservation.canBeSatisfiedBy(blanketOffer))
+ assertTrue(serverReservation.canBeSatisfiedBy(reservedOffer))
+ // Note: serverReservation can be filed using reserveNetwork, or it could be a regular
+ // request filed using requestNetwork.
+ assertFalse(serverReservation.canBeSatisfiedBy(clientOffer))
+
+ val clientRequest = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_CLIENT)
+ .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+ .setRemoteAddress(MacAddress.fromString("00:01:02:03:04:05"))
+ .setPsm(42)
+ .build()
+
+ assertTrue(clientRequest.canBeSatisfiedBy(clientOffer))
+ // Note: the BlanketOffer also includes a RES_ID_MATCH_ALL_RESERVATIONS. Since the
+ // clientRequest is not a reservation, it won't match that request to begin with.
+ assertFalse(clientRequest.canBeSatisfiedBy(blanketOffer))
+ assertFalse(clientRequest.canBeSatisfiedBy(reservedOffer))
+
+ val matchAny = L2capNetworkSpecifier.Builder().build()
+ assertTrue(matchAny.canBeSatisfiedBy(blanketOffer))
+ assertTrue(matchAny.canBeSatisfiedBy(reservedOffer))
+ assertTrue(matchAny.canBeSatisfiedBy(clientOffer))
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt b/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt
index 0b10ef6..f05bf15 100644
--- a/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkReservationTest.kt
@@ -33,6 +33,7 @@
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
+import android.platform.test.annotations.AppModeFull
import androidx.test.platform.app.InstrumentationRegistry
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
@@ -71,6 +72,7 @@
private const val NO_CB_TIMEOUT_MS = 200L
// TODO: integrate with CSNetworkReservationTest and move to common tests.
+@AppModeFull(reason = "CHANGE_NETWORK_STATE, MANAGE_TEST_NETWORKS not grantable to instant apps")
@ConnectivityModuleTest
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
diff --git a/tests/cts/netpermission/updatestatspermission/Android.bp b/tests/cts/netpermission/updatestatspermission/Android.bp
index b324dc8..0ff98e7 100644
--- a/tests/cts/netpermission/updatestatspermission/Android.bp
+++ b/tests/cts/netpermission/updatestatspermission/Android.bp
@@ -33,6 +33,7 @@
// Tag this module as a cts test artifact
test_suites: [
+ "automotive-general-tests",
"cts",
"general-tests",
],
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 5e94c06..2420026 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -45,6 +45,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -387,18 +388,21 @@
mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
- try {
- final int ret = runAsShell(TETHER_PRIVILEGED, () -> mTM.tether(wifiTetheringIface));
- // There is no guarantee that the wifi interface will be available after disabling
- // the hotspot, so don't fail the test if the call to tether() fails.
- if (ret == TETHER_ERROR_NO_ERROR) {
- // If calling #tether successful, there is a callback to tell the result of
- // tethering setup.
- tetherEventCallback.expectErrorOrTethered(
- new TetheringInterface(TETHERING_WIFI, wifiTetheringIface));
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ try {
+ final int ret = runAsShell(TETHER_PRIVILEGED,
+ () -> mTM.tether(wifiTetheringIface));
+ // There is no guarantee that the wifi interface will be available after
+ // disabling the hotspot, so don't fail the test if the call to tether() fails.
+ if (ret == TETHER_ERROR_NO_ERROR) {
+ // If calling #tether successful, there is a callback to tell the result of
+ // tethering setup.
+ tetherEventCallback.expectErrorOrTethered(
+ new TetheringInterface(TETHERING_WIFI, wifiTetheringIface));
+ }
+ } finally {
+ runAsShell(TETHER_PRIVILEGED, () -> mTM.untether(wifiTetheringIface));
}
- } finally {
- runAsShell(TETHER_PRIVILEGED, () -> mTM.untether(wifiTetheringIface));
}
} finally {
mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
@@ -623,4 +627,11 @@
}
}
}
+
+ @Test
+ public void testLegacyTetherApisThrowUnsupportedOperationExceptionAfterV() {
+ assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM);
+ assertThrows(UnsupportedOperationException.class, () -> mTM.tether("iface"));
+ assertThrows(UnsupportedOperationException.class, () -> mTM.untether("iface"));
+ }
}
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index 9a77c89..b415382 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -44,6 +44,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
@@ -65,6 +66,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.ConnectivityManager.NetworkCallback;
+import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
@@ -669,4 +671,12 @@
// No callbacks overridden -> do not use the optimization
eq(~0));
}
+
+ @Test
+ public void testLegacyTetherApisThrowUnsupportedOperationExceptionAfterV() {
+ assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM);
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ assertThrows(UnsupportedOperationException.class, () -> manager.tether("iface"));
+ assertThrows(UnsupportedOperationException.class, () -> manager.untether("iface"));
+ }
}
diff --git a/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt b/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt
new file mode 100644
index 0000000..5a7515e
--- /dev/null
+++ b/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt
@@ -0,0 +1,216 @@
+/*
+ * 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
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.TYPE_NONE
+import android.net.L2capNetworkSpecifier
+import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN
+import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_NONE
+import android.net.L2capNetworkSpecifier.ROLE_SERVER
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
+import android.net.NetworkProvider
+import android.net.NetworkProvider.NetworkOfferCallback
+import android.net.NetworkRequest
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+const val TAG = "L2capNetworkProviderTest"
+
+val RESERVATION_CAPS = NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ .build()
+
+val RESERVATION = NetworkRequest(
+ NetworkCapabilities(RESERVATION_CAPS),
+ TYPE_NONE,
+ 42 /* rId */,
+ NetworkRequest.Type.RESERVATION
+)
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class L2capNetworkProviderTest {
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var deps: L2capNetworkProvider.Dependencies
+ @Mock private lateinit var provider: NetworkProvider
+ @Mock private lateinit var cm: ConnectivityManager
+ @Mock private lateinit var pm: PackageManager
+
+ private val handlerThread = HandlerThread("$TAG handler thread").apply { start() }
+ private val handler = Handler(handlerThread.looper)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ doReturn(provider).`when`(deps).getNetworkProvider(any(), any())
+ doReturn(handlerThread).`when`(deps).getHandlerThread()
+ doReturn(cm).`when`(context).getSystemService(eq(ConnectivityManager::class.java))
+ doReturn(pm).`when`(context).getPackageManager()
+ doReturn(true).`when`(pm).hasSystemFeature(FEATURE_BLUETOOTH_LE)
+ }
+
+ @After
+ fun tearDown() {
+ handlerThread.quitSafely()
+ handlerThread.join()
+ }
+
+ @Test
+ fun testNetworkProvider_registeredWhenSupported() {
+ L2capNetworkProvider(deps, context).start()
+ verify(cm).registerNetworkProvider(eq(provider))
+ verify(provider).registerNetworkOffer(any(), any(), any(), any())
+ }
+
+ @Test
+ fun testNetworkProvider_notRegisteredWhenNotSupported() {
+ doReturn(false).`when`(pm).hasSystemFeature(FEATURE_BLUETOOTH_LE)
+ L2capNetworkProvider(deps, context).start()
+ verify(cm, never()).registerNetworkProvider(eq(provider))
+ }
+
+ fun doTestBlanketOfferIgnoresRequest(request: NetworkRequest) {
+ clearInvocations(provider)
+ L2capNetworkProvider(deps, context).start()
+
+ val blanketOfferCaptor = ArgumentCaptor.forClass(NetworkOfferCallback::class.java)
+ verify(provider).registerNetworkOffer(any(), any(), any(), blanketOfferCaptor.capture())
+
+ blanketOfferCaptor.value.onNetworkNeeded(request)
+ verify(provider).registerNetworkOffer(any(), any(), any(), any())
+ }
+
+ fun doTestBlanketOfferCreatesReservation(
+ request: NetworkRequest,
+ reservation: NetworkCapabilities
+ ) {
+ clearInvocations(provider)
+ L2capNetworkProvider(deps, context).start()
+
+ val blanketOfferCaptor = ArgumentCaptor.forClass(NetworkOfferCallback::class.java)
+ verify(provider).registerNetworkOffer(any(), any(), any(), blanketOfferCaptor.capture())
+
+ blanketOfferCaptor.value.onNetworkNeeded(request)
+
+ val capsCaptor = ArgumentCaptor.forClass(NetworkCapabilities::class.java)
+ verify(provider, times(2)).registerNetworkOffer(any(), capsCaptor.capture(), any(), any())
+
+ assertTrue(reservation.satisfiedByNetworkCapabilities(capsCaptor.value))
+ }
+
+ @Test
+ fun testBlanketOffer_reservationWithoutSpecifier() {
+ val caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ .build()
+ val nr = NetworkRequest(caps, TYPE_NONE, 42 /* rId */, NetworkRequest.Type.RESERVATION)
+
+ doTestBlanketOfferIgnoresRequest(nr)
+ }
+
+ @Test
+ fun testBlanketOffer_reservationWithCorrectSpecifier() {
+ var specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
+ .build()
+ var caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ .setNetworkSpecifier(specifier)
+ .build()
+ var nr = NetworkRequest(caps, TYPE_NONE, 42 /* rId */, NetworkRequest.Type.RESERVATION)
+ doTestBlanketOfferCreatesReservation(nr, caps)
+
+ specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .build()
+ caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ .setNetworkSpecifier(specifier)
+ .build()
+ nr = NetworkRequest(caps, TYPE_NONE, 43 /* rId */, NetworkRequest.Type.RESERVATION)
+ doTestBlanketOfferCreatesReservation(nr, caps)
+ }
+
+ @Test
+ fun testBlanketOffer_reservationWithIncorrectSpecifier() {
+ var specifier = L2capNetworkSpecifier.Builder().build()
+ var caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ .setNetworkSpecifier(specifier)
+ .build()
+ var nr = NetworkRequest(caps, TYPE_NONE, 42 /* rId */, NetworkRequest.Type.RESERVATION)
+ doTestBlanketOfferIgnoresRequest(nr)
+
+ specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .build()
+ caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ .setNetworkSpecifier(specifier)
+ .build()
+ nr = NetworkRequest(caps, TYPE_NONE, 44 /* rId */, NetworkRequest.Type.RESERVATION)
+ doTestBlanketOfferIgnoresRequest(nr)
+
+ specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_SERVER)
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .setPsm(0x81)
+ .build()
+ caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ .setNetworkSpecifier(specifier)
+ .build()
+ nr = NetworkRequest(caps, TYPE_NONE, 45 /* rId */, NetworkRequest.Type.RESERVATION)
+ doTestBlanketOfferIgnoresRequest(nr)
+
+ specifier = L2capNetworkSpecifier.Builder()
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .build()
+ caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ .addTransportType(TRANSPORT_BLUETOOTH)
+ .setNetworkSpecifier(specifier)
+ .build()
+ nr = NetworkRequest(caps, TYPE_NONE, 47 /* rId */, NetworkRequest.Type.RESERVATION)
+ doTestBlanketOfferIgnoresRequest(nr)
+ }
+}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index e6aba22..b7cfaf9 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -166,9 +166,10 @@
EthernetTracker.parseStaticIpConfiguration(configAsString));
}
- private NetworkCapabilities.Builder makeEthernetCapabilitiesBuilder(boolean clearAll) {
+ private NetworkCapabilities.Builder makeEthernetCapabilitiesBuilder(boolean clearDefaults) {
final NetworkCapabilities.Builder builder =
- clearAll ? NetworkCapabilities.Builder.withoutDefaultCapabilities()
+ clearDefaults
+ ? NetworkCapabilities.Builder.withoutDefaultCapabilities()
: new NetworkCapabilities.Builder();
return builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
@@ -176,21 +177,20 @@
}
/**
- * Test: Attempt to create a capabilties with various valid sets of capabilities/transports
+ * Test: Attempt to create a capabilities with various valid sets of capabilities/transports
*/
@Test
public void createNetworkCapabilities() {
-
// Particularly common expected results
- NetworkCapabilities defaultEthernetCleared =
- makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ NetworkCapabilities defaultCapabilities =
+ makeEthernetCapabilitiesBuilder(false /* clearDefaults */)
.setLinkUpstreamBandwidthKbps(100000)
.setLinkDownstreamBandwidthKbps(100000)
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.build();
NetworkCapabilities ethernetClearedWithCommonCaps =
- makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ makeEthernetCapabilitiesBuilder(true /* clearDefaults */)
.setLinkUpstreamBandwidthKbps(100000)
.setLinkDownstreamBandwidthKbps(100000)
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
@@ -200,89 +200,71 @@
.addCapability(15)
.build();
- // Empty capabilities and transports lists with a "please clear defaults" should
- // yield an empty capabilities set with TRANPORT_ETHERNET
- assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "");
+ // Empty capabilities and transports should return the default capabilities set
+ // with TRANSPORT_ETHERNET
+ assertParsedNetworkCapabilities(defaultCapabilities, "", "");
- // Empty capabilities and transports without the clear defaults flag should return the
- // default capabilities set with TRANSPORT_ETHERNET
- assertParsedNetworkCapabilities(
- makeEthernetCapabilitiesBuilder(false /* clearAll */)
- .setLinkUpstreamBandwidthKbps(100000)
- .setLinkDownstreamBandwidthKbps(100000)
- .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
- .build(),
- false, "", "");
-
- // A list of capabilities without the clear defaults flag should return the default
- // capabilities, mixed with the desired capabilities, and TRANSPORT_ETHERNET
- assertParsedNetworkCapabilities(
- makeEthernetCapabilitiesBuilder(false /* clearAll */)
- .setLinkUpstreamBandwidthKbps(100000)
- .setLinkDownstreamBandwidthKbps(100000)
- .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
- .addCapability(11)
- .addCapability(12)
- .build(),
- false, "11,12", "");
-
- // Adding a list of capabilities with a clear defaults will leave exactly those capabilities
- // with a default TRANSPORT_ETHERNET since no overrides are specified
- assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15", "");
+ // Adding a list of capabilities will leave exactly those capabilities with a default
+ // TRANSPORT_ETHERNET since no overrides are specified
+ assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, "12,13,14,15", "");
// Adding any invalid capabilities to the list will cause them to be ignored
- assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,65,73", "");
- assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,abcdefg", "");
+ assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, "12,13,14,15,65,73", "");
+ assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, "12,13,14,15,abcdefg", "");
// Adding a valid override transport will remove the default TRANSPORT_ETHERNET transport
- // and apply only the override to the capabiltities object
+ // and apply only the override to the capabilities object
assertParsedNetworkCapabilities(
- makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ makeEthernetCapabilitiesBuilder(false /* clearDefaults */)
.setLinkUpstreamBandwidthKbps(100000)
.setLinkDownstreamBandwidthKbps(100000)
.addTransportType(0)
.build(),
- true, "", "0");
+ "",
+ "0");
assertParsedNetworkCapabilities(
- makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ makeEthernetCapabilitiesBuilder(false /* clearDefaults */)
.setLinkUpstreamBandwidthKbps(100000)
.setLinkDownstreamBandwidthKbps(100000)
.addTransportType(1)
.build(),
- true, "", "1");
+ "",
+ "1");
assertParsedNetworkCapabilities(
- makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ makeEthernetCapabilitiesBuilder(false /* clearDefaults */)
.setLinkUpstreamBandwidthKbps(100000)
.setLinkDownstreamBandwidthKbps(100000)
.addTransportType(2)
.build(),
- true, "", "2");
+ "",
+ "2");
assertParsedNetworkCapabilities(
- makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ makeEthernetCapabilitiesBuilder(false /* clearDefaults */)
.setLinkUpstreamBandwidthKbps(100000)
.setLinkDownstreamBandwidthKbps(100000)
.addTransportType(3)
.build(),
- true, "", "3");
+ "",
+ "3");
- // "4" is TRANSPORT_VPN, which is unsupported. Should default back to TRANPORT_ETHERNET
- assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "4");
+ // "4" is TRANSPORT_VPN, which is unsupported. Should default back to TRANSPORT_ETHERNET
+ assertParsedNetworkCapabilities(defaultCapabilities, "", "4");
// "5" is TRANSPORT_WIFI_AWARE, which is currently supported due to no legacy TYPE_NONE
// conversion. When that becomes available, this test must be updated
- assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "5");
+ assertParsedNetworkCapabilities(defaultCapabilities, "", "5");
// "6" is TRANSPORT_LOWPAN, which is currently supported due to no legacy TYPE_NONE
// conversion. When that becomes available, this test must be updated
- assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "6");
+ assertParsedNetworkCapabilities(defaultCapabilities, "", "6");
// Adding an invalid override transport will leave the transport as TRANSPORT_ETHERNET
- assertParsedNetworkCapabilities(defaultEthernetCleared,true, "", "100");
- assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "abcdefg");
+ assertParsedNetworkCapabilities(defaultCapabilities, "", "100");
+ assertParsedNetworkCapabilities(defaultCapabilities, "", "abcdefg");
// Ensure the adding of both capabilities and transports work
assertParsedNetworkCapabilities(
- makeEthernetCapabilitiesBuilder(true /* clearAll */)
+ makeEthernetCapabilitiesBuilder(true /* clearDefaults */)
.setLinkUpstreamBandwidthKbps(100000)
.setLinkDownstreamBandwidthKbps(100000)
.addCapability(12)
@@ -291,17 +273,21 @@
.addCapability(15)
.addTransportType(3)
.build(),
- true, "12,13,14,15", "3");
+ "12,13,14,15",
+ "3");
// Ensure order does not matter for capability list
- assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "13,12,15,14", "");
+ assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, "13,12,15,14", "");
}
- private void assertParsedNetworkCapabilities(NetworkCapabilities expectedNetworkCapabilities,
- boolean clearCapabilties, String configCapabiltiies,String configTransports) {
- assertEquals(expectedNetworkCapabilities,
- EthernetTracker.createNetworkCapabilities(clearCapabilties, configCapabiltiies,
- configTransports).build());
+ private void assertParsedNetworkCapabilities(
+ NetworkCapabilities expectedNetworkCapabilities,
+ String configCapabiltiies,
+ String configTransports) {
+ assertEquals(
+ expectedNetworkCapabilities,
+ EthernetTracker.createNetworkCapabilities(configCapabiltiies, configTransports)
+ .build());
}
@Test
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index 50971e7..1a833e1 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -42,7 +42,7 @@
],
static_libs: [
"libnet_utils_device_common_bpfjni",
- "libnet_utils_device_common_timerfdjni",
+ "libserviceconnectivityjni",
"libtcutils",
],
shared_libs: [
diff --git a/tests/unit/jni/android_net_frameworktests_util/onload.cpp b/tests/unit/jni/android_net_frameworktests_util/onload.cpp
index a0ce4f8..f70b04b 100644
--- a/tests/unit/jni/android_net_frameworktests_util/onload.cpp
+++ b/tests/unit/jni/android_net_frameworktests_util/onload.cpp
@@ -24,7 +24,7 @@
int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
int register_com_android_net_module_util_TcUtils(JNIEnv* env, char const* class_name);
-int register_com_android_net_module_util_TimerFdUtils(JNIEnv *env,
+int register_com_android_net_module_util_ServiceConnectivityJni(JNIEnv *env,
char const *class_name);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -40,8 +40,8 @@
if (register_com_android_net_module_util_TcUtils(env,
"android/net/frameworktests/util/TcUtils") < 0) return JNI_ERR;
- if (register_com_android_net_module_util_TimerFdUtils(
- env, "android/net/frameworktests/util/TimerFdUtils") < 0)
+ if (register_com_android_net_module_util_ServiceConnectivityJni(
+ env, "android/net/frameworktests/util/ServiceConnectivityJni") < 0)
return JNI_ERR;
return JNI_VERSION_1_6;
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index c2959f4..af16d19 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -626,11 +626,14 @@
private OtDaemonConfiguration newOtDaemonConfig(ThreadConfiguration threadConfig) {
int srpServerConfig = R.bool.config_thread_srp_server_wait_for_border_routing_enabled;
boolean srpServerWaitEnabled = mResources.get().getBoolean(srpServerConfig);
+ int autoJoinConfig = R.bool.config_thread_border_router_auto_join_enabled;
+ boolean autoJoinEnabled = mResources.get().getBoolean(autoJoinConfig);
return new OtDaemonConfiguration.Builder()
.setBorderRouterEnabled(threadConfig.isBorderRouterEnabled())
.setNat64Enabled(threadConfig.isNat64Enabled())
.setDhcpv6PdEnabled(threadConfig.isDhcpv6PdEnabled())
.setSrpServerWaitForBorderRoutingEnabled(srpServerWaitEnabled)
+ .setBorderRouterAutoJoinEnabled(autoJoinEnabled)
.build();
}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 1d44ccb..bc8da8b 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -234,6 +234,8 @@
when(mResources.getBoolean(
eq(R.bool.config_thread_srp_server_wait_for_border_routing_enabled)))
.thenReturn(true);
+ when(mResources.getBoolean(eq(R.bool.config_thread_border_router_auto_join_enabled)))
+ .thenReturn(true);
when(mResources.getString(eq(R.string.config_thread_vendor_name)))
.thenReturn(TEST_VENDOR_NAME);
when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
@@ -291,6 +293,8 @@
when(mResources.getBoolean(
eq(R.bool.config_thread_srp_server_wait_for_border_routing_enabled)))
.thenReturn(false);
+ when(mResources.getBoolean(eq(R.bool.config_thread_border_router_auto_join_enabled)))
+ .thenReturn(false);
when(mResources.getString(eq(R.string.config_thread_vendor_name)))
.thenReturn(TEST_VENDOR_NAME);
when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
@@ -304,6 +308,7 @@
mTestLooper.dispatchAll();
assertThat(mFakeOtDaemon.getConfiguration().srpServerWaitForBorderRoutingEnabled).isFalse();
+ assertThat(mFakeOtDaemon.getConfiguration().borderRouterAutoJoinEnabled).isFalse();
MeshcopTxtAttributes meshcopTxts = mFakeOtDaemon.getOverriddenMeshcopTxtAttributes();
assertThat(meshcopTxts.vendorName).isEqualTo(TEST_VENDOR_NAME);
assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI_BYTES);