Merge changes I4bc29b10,I94a56b2b,I9ca025b1 into main
* changes:
Add a queue system server-side for NetworkAgent
Unify registration and NM assignment in NAI
Return the registry in registerNetworkAgent.
diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
index 9e4e4a1..60ca885 100644
--- a/OWNERS_core_networking_xts
+++ b/OWNERS_core_networking_xts
@@ -10,3 +10,5 @@
# In addition to cherry-picks, flaky test fixes and no-op refactors, also for
# NsdManager tests
reminv@google.com #{LAST_RESORT_SUGGESTION}
+# Only for APF firmware tests (to verify correct behaviour of the wifi APF interpreter)
+yuyanghuang@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 2878f79..531489d 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -47,15 +47,6 @@
// as the above target may have different "enabled" values
// depending on the branch
-apex_defaults {
- name: "CronetInTetheringApexDefaults",
- jni_libs: [
- "cronet_aml_components_cronet_android_cronet",
- "//external/cronet/third_party/boringssl:libcrypto",
- "//external/cronet/third_party/boringssl:libssl",
- ],
-}
-
apex {
name: "com.android.tethering",
defaults: [
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index b553f46..f501a50 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -173,7 +173,21 @@
@Override
public void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
boolean showEntitlementUi, String callerPkg, String callingAttributionTag) {
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, receiver)) return;
+ // Wrap the app-provided ResultReceiver in an IIntResultListener in order to call
+ // checkAndNotifyCommonError with it.
+ IIntResultListener listener = new IIntResultListener() {
+ @Override
+ public void onResult(int i) {
+ receiver.send(i, null);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException("asBinder unexpectedly called on"
+ + " internal-only listener");
+ }
+ };
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
mTethering.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
}
@@ -277,27 +291,6 @@
return false;
}
- private boolean checkAndNotifyCommonError(final String callerPkg,
- final String callingAttributionTag, final ResultReceiver receiver) {
- if (!checkPackageNameMatchesUid(getBinderCallingUid(), callerPkg)) {
- Log.e(TAG, "Package name " + callerPkg + " does not match UID "
- + getBinderCallingUid());
- receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
- return true;
- }
- if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
- false /* onlyAllowPrivileged */)) {
- receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
- return true;
- }
- if (!mTethering.isTetheringSupported() || !mTethering.isTetheringAllowed()) {
- receiver.send(TETHER_ERROR_UNSUPPORTED, null);
- return true;
- }
-
- return false;
- }
-
private boolean hasNetworkSettingsPermission() {
return checkCallingOrSelfPermission(NETWORK_SETTINGS);
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 2a22c6d..fe3b201 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -2030,7 +2030,7 @@
@Test
public void failureEnablingIpForwarding() throws Exception {
initTetheringOnTestThread();
- when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
+ when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
// Emulate pressing the WiFi tethering button.
@@ -2080,9 +2080,9 @@
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
verify(mTetheringMetrics, times(0)).maybeUpdateUpstreamType(any());
- verify(mTetheringMetrics, times(2)).updateErrorCode(eq(TETHERING_WIFI),
+ verify(mTetheringMetrics, times(1)).updateErrorCode(eq(TETHERING_WIFI),
eq(TETHER_ERROR_INTERNAL_ERROR));
- verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_WIFI));
+ verify(mTetheringMetrics, times(1)).sendReport(eq(TETHERING_WIFI));
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
index fb42c03..41b58fa 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -33,8 +33,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Collection;
import java.util.Optional;
/** Helper class to download certificate transparency log files. */
@@ -48,28 +47,21 @@
private final DownloadHelper mDownloadHelper;
private final SignatureVerifier mSignatureVerifier;
private final CertificateTransparencyLogger mLogger;
-
- private final List<CompatibilityVersion> mCompatVersions = new ArrayList<>();
+ private final Collection<CompatibilityVersion> mCompatVersions;
CertificateTransparencyDownloader(
Context context,
DataStore dataStore,
DownloadHelper downloadHelper,
SignatureVerifier signatureVerifier,
- CertificateTransparencyLogger logger) {
+ CertificateTransparencyLogger logger,
+ Collection<CompatibilityVersion> compatVersions) {
mContext = context;
mSignatureVerifier = signatureVerifier;
mDataStore = dataStore;
mDownloadHelper = downloadHelper;
mLogger = logger;
- }
-
- void addCompatibilityVersion(CompatibilityVersion compatVersion) {
- mCompatVersions.add(compatVersion);
- }
-
- void clearCompatibilityVersions() {
- mCompatVersions.clear();
+ mCompatVersions = compatVersions;
}
long startPublicKeyDownload() {
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
index f1b9a4f..286f326 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyJob.java
@@ -28,6 +28,8 @@
import android.os.SystemClock;
import android.util.Log;
+import java.util.Collection;
+
/** Implementation of the Certificate Transparency job */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public class CertificateTransparencyJob extends BroadcastReceiver {
@@ -37,8 +39,8 @@
private final Context mContext;
private final DataStore mDataStore;
private final CertificateTransparencyDownloader mCertificateTransparencyDownloader;
- private final CompatibilityVersion mCompatVersion;
private final SignatureVerifier mSignatureVerifier;
+ private final Collection<CompatibilityVersion> mCompatVersions;
private final AlarmManager mAlarmManager;
private final PendingIntent mPendingIntent;
@@ -50,13 +52,13 @@
Context context,
DataStore dataStore,
CertificateTransparencyDownloader certificateTransparencyDownloader,
- CompatibilityVersion compatVersion,
- SignatureVerifier signatureVerifier) {
+ SignatureVerifier signatureVerifier,
+ Collection<CompatibilityVersion> compatVersions) {
mContext = context;
mDataStore = dataStore;
mCertificateTransparencyDownloader = certificateTransparencyDownloader;
- mCompatVersion = compatVersion;
mSignatureVerifier = signatureVerifier;
+ mCompatVersions = compatVersions;
mAlarmManager = context.getSystemService(AlarmManager.class);
mPendingIntent =
@@ -99,7 +101,9 @@
}
mDependenciesReady = false;
- mCompatVersion.delete();
+ for (CompatibilityVersion compatVersion : mCompatVersions) {
+ compatVersion.delete();
+ }
if (Config.DEBUG) {
Log.d(TAG, "CertificateTransparencyJob canceled.");
@@ -129,7 +133,6 @@
private void startDependencies() {
mDataStore.load();
- mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
mSignatureVerifier.loadAllowedKeys();
mContext.registerReceiver(
mCertificateTransparencyDownloader,
@@ -144,7 +147,6 @@
private void stopDependencies() {
mContext.unregisterReceiver(mCertificateTransparencyDownloader);
mSignatureVerifier.clearAllowedKeys();
- mCertificateTransparencyDownloader.clearCompatibilityVersions();
mDataStore.delete();
if (Config.DEBUG) {
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
index 2e910b2..5e530c7 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -30,6 +30,8 @@
import com.android.server.SystemService;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.concurrent.Executors;
/** Implementation of the Certificate Transparency service. */
@@ -51,8 +53,18 @@
/** Creates a new {@link CertificateTransparencyService} object. */
public CertificateTransparencyService(Context context) {
DataStore dataStore = new DataStore(Config.PREFERENCES_FILE);
-
SignatureVerifier signatureVerifier = new SignatureVerifier(context);
+ Collection<CompatibilityVersion> compatVersions =
+ Arrays.asList(
+ new CompatibilityVersion(
+ Config.COMPATIBILITY_VERSION_V1,
+ Config.URL_SIGNATURE_V1,
+ Config.URL_LOG_LIST_V1),
+ new CompatibilityVersion(
+ Config.COMPATIBILITY_VERSION_V2,
+ Config.URL_SIGNATURE_V2,
+ Config.URL_LOG_LIST_V2));
+
mCertificateTransparencyJob =
new CertificateTransparencyJob(
context,
@@ -62,13 +74,10 @@
dataStore,
new DownloadHelper(context),
signatureVerifier,
- new CertificateTransparencyLoggerImpl(dataStore)),
- new CompatibilityVersion(
- Config.COMPATIBILITY_VERSION,
- Config.URL_SIGNATURE,
- Config.URL_LOG_LIST,
- Config.CT_ROOT_DIRECTORY_PATH),
- signatureVerifier);
+ new CertificateTransparencyLoggerImpl(dataStore),
+ compatVersions),
+ signatureVerifier,
+ compatVersions);
}
/**
diff --git a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
index e8a6e64..0a91963 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CompatibilityVersion.java
@@ -23,6 +23,8 @@
import android.system.Os;
import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
import com.android.server.net.ct.CertificateTransparencyLogger.CTLogListUpdateState;
import org.json.JSONException;
@@ -40,6 +42,8 @@
private static final String TAG = "CompatibilityVersion";
+ private static File sRootDirectory = new File(Config.CT_ROOT_DIRECTORY_PATH);
+
static final String LOGS_DIR_PREFIX = "logs-";
static final String LOGS_LIST_FILE_NAME = "log_list.json";
static final String CURRENT_LOGS_DIR_SYMLINK_NAME = "current";
@@ -48,23 +52,21 @@
private final String mMetadataUrl;
private final String mContentUrl;
- private final File mRootDirectory;
private final File mVersionDirectory;
private final File mCurrentLogsDirSymlink;
CompatibilityVersion(
- String compatVersion, String metadataUrl, String contentUrl, File rootDirectory) {
+ String compatVersion, String metadataUrl, String contentUrl) {
mCompatVersion = compatVersion;
mMetadataUrl = metadataUrl;
mContentUrl = contentUrl;
- mRootDirectory = rootDirectory;
- mVersionDirectory = new File(rootDirectory, compatVersion);
+ mVersionDirectory = new File(sRootDirectory, compatVersion);
mCurrentLogsDirSymlink = new File(mVersionDirectory, CURRENT_LOGS_DIR_SYMLINK_NAME);
}
- CompatibilityVersion(
- String compatVersion, String metadataUrl, String contentUrl, String rootDirectoryPath) {
- this(compatVersion, metadataUrl, contentUrl, new File(rootDirectoryPath));
+ @VisibleForTesting
+ static void setRootDirectoryForTesting(File rootDirectory) {
+ sRootDirectory = rootDirectory;
}
/**
@@ -75,8 +77,8 @@
* @return true if the log list was installed successfully, false otherwise.
* @throws IOException if the list cannot be saved in the CT directory.
*/
- LogListUpdateStatus install(
- InputStream newContent, LogListUpdateStatus.Builder statusBuilder) throws IOException {
+ LogListUpdateStatus install(InputStream newContent, LogListUpdateStatus.Builder statusBuilder)
+ throws IOException {
String content = new String(newContent.readAllBytes(), UTF_8);
try {
JSONObject contentJson = new JSONObject(content);
@@ -98,7 +100,7 @@
// there's a bunch of steps. We create a new directory with the logs and then do
// an atomic update of the current symlink to point to the new directory.
// 1. Ensure the path to the root and version directories exist and are readable.
- DirectoryUtils.makeDir(mRootDirectory);
+ DirectoryUtils.makeDir(sRootDirectory);
DirectoryUtils.makeDir(mVersionDirectory);
File newLogsDir = new File(mVersionDirectory, LOGS_DIR_PREFIX + version);
diff --git a/networksecurity/service/src/com/android/server/net/ct/Config.java b/networksecurity/service/src/com/android/server/net/ct/Config.java
index 5fdba09..72b715a 100644
--- a/networksecurity/service/src/com/android/server/net/ct/Config.java
+++ b/networksecurity/service/src/com/android/server/net/ct/Config.java
@@ -33,18 +33,14 @@
private static final String PREFERENCES_FILE_NAME = "ct.preferences";
static final File PREFERENCES_FILE = new File(DEVICE_PROTECTED_DATA_DIR, PREFERENCES_FILE_NAME);
- // CT directory
+ // CT paths
static final String CT_ROOT_DIRECTORY_PATH = "/data/misc/keychain/ct/";
- static final String COMPATIBILITY_VERSION = "v1";
+ static final String URL_PREFIX = "https://www.gstatic.com/android/certificate_transparency/";
// Phenotype flags
static final String NAMESPACE_NETWORK_SECURITY = "network_security";
private static final String FLAGS_PREFIX = "CertificateTransparencyLogList__";
static final String FLAG_SERVICE_ENABLED = FLAGS_PREFIX + "service_enabled";
- static final String FLAG_CONTENT_URL = FLAGS_PREFIX + "content_url";
- static final String FLAG_METADATA_URL = FLAGS_PREFIX + "metadata_url";
- static final String FLAG_VERSION = FLAGS_PREFIX + "version";
- static final String FLAG_PUBLIC_KEY = FLAGS_PREFIX + "public_key";
// properties
static final String VERSION = "version";
@@ -53,9 +49,18 @@
static final String PUBLIC_KEY_DOWNLOAD_ID = "public_key_download_id";
static final String LOG_LIST_UPDATE_FAILURE_COUNT = "log_list_update_failure_count";
- // URLs
- static final String URL_PREFIX = "https://www.gstatic.com/android/certificate_transparency/";
- static final String URL_LOG_LIST = URL_PREFIX + "log_list.json";
- static final String URL_SIGNATURE = URL_PREFIX + "log_list.sig";
+ // Public Key URLs
static final String URL_PUBLIC_KEY = URL_PREFIX + "log_list.pub";
+
+ // Compatibility Version v1
+ static final String COMPATIBILITY_VERSION_V1 = "v1";
+ static final String URL_PREFIX_V1 = URL_PREFIX;
+ static final String URL_LOG_LIST_V1 = URL_PREFIX_V1 + "log_list.json";
+ static final String URL_SIGNATURE_V1 = URL_PREFIX_V1 + "log_list.sig";
+
+ // Compatibility Version v2
+ static final String COMPATIBILITY_VERSION_V2 = "v2";
+ static final String URL_PREFIX_V2 = URL_PREFIX + COMPATIBILITY_VERSION_V2 + "/";
+ static final String URL_LOG_LIST_V2 = URL_PREFIX_V2 + "log_list.json";
+ static final String URL_SIGNATURE_V2 = URL_PREFIX_V2 + "log_list.sig";
}
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
index 22dc6ab..956bad5 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
@@ -60,6 +60,7 @@
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
+import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
@@ -94,24 +95,25 @@
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mDataStore = new DataStore(File.createTempFile("datastore-test", ".properties"));
mSignatureVerifier = new SignatureVerifier(mContext);
+
+ CompatibilityVersion.setRootDirectoryForTesting(mContext.getFilesDir());
+ mCompatVersion =
+ new CompatibilityVersion(
+ /* compatVersion= */ "v666",
+ Config.URL_SIGNATURE_V1,
+ Config.URL_LOG_LIST_V1);
mCertificateTransparencyDownloader =
new CertificateTransparencyDownloader(
mContext,
mDataStore,
new DownloadHelper(mDownloadManager),
mSignatureVerifier,
- mLogger);
- mCompatVersion =
- new CompatibilityVersion(
- /* compatVersion= */ "v666",
- Config.URL_SIGNATURE,
- Config.URL_LOG_LIST,
- mContext.getFilesDir());
+ mLogger,
+ Arrays.asList(mCompatVersion));
prepareDownloadManager();
mSignatureVerifier.addAllowedKey(mPublicKey);
mDataStore.load();
- mCertificateTransparencyDownloader.addCompatibilityVersion(mCompatVersion);
}
@After
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java
index 2b8b3cd..0d15183 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CompatibilityVersionTest.java
@@ -27,6 +27,7 @@
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -47,9 +48,16 @@
private final File mTestDir =
InstrumentationRegistry.getInstrumentation().getContext().getFilesDir();
- private final CompatibilityVersion mCompatVersion =
- new CompatibilityVersion(
- TEST_VERSION, Config.URL_SIGNATURE, Config.URL_LOG_LIST, mTestDir);
+
+ private CompatibilityVersion mCompatVersion;
+
+ @Before
+ public void setUp() {
+ CompatibilityVersion.setRootDirectoryForTesting(mTestDir);
+ mCompatVersion =
+ new CompatibilityVersion(
+ TEST_VERSION, Config.URL_SIGNATURE_V1, Config.URL_LOG_LIST_V1);
+ }
@After
public void tearDown() {
@@ -111,9 +119,7 @@
JSONObject logList = makeLogList(version, "i_am_content");
try (InputStream inputStream = asStream(logList)) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
@@ -142,9 +148,7 @@
@Test
public void testCompatibilityVersion_deleteSuccessfully() throws Exception {
try (InputStream inputStream = asStream(makeLogList(/* version= */ "123"))) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
@@ -156,9 +160,7 @@
@Test
public void testCompatibilityVersion_invalidLogList() throws Exception {
try (InputStream inputStream = new ByteArrayInputStream(("not_a_valid_list".getBytes()))) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(LogListUpdateStatus.builder().setState(LOG_LIST_INVALID).build());
}
@@ -179,9 +181,7 @@
JSONObject newLogList = makeLogList(existingVersion, "i_am_the_real_content");
try (InputStream inputStream = asStream(newLogList)) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
@@ -193,16 +193,12 @@
String existingVersion = "666";
JSONObject existingLogList = makeLogList(existingVersion, "i_was_installed_successfully");
try (InputStream inputStream = asStream(existingLogList)) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(getSuccessfulUpdateStatus());
}
try (InputStream inputStream = asStream(makeLogList(existingVersion, "i_am_ignored"))) {
- assertThat(
- mCompatVersion.install(
- inputStream, LogListUpdateStatus.builder()))
+ assertThat(mCompatVersion.install(inputStream, LogListUpdateStatus.builder()))
.isEqualTo(
LogListUpdateStatus.builder()
.setState(VERSION_ALREADY_EXISTS)
diff --git a/service-t/Android.bp b/service-t/Android.bp
index ab38c7a..81378f5 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -109,13 +109,15 @@
":service-mdns-droidstubs",
],
exclude_srcs: [
+ "src/com/android/server/connectivity/mdns/internal/MdnsRealtimeScheduler.java",
"src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java",
- "src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java",
"src/com/android/server/connectivity/mdns/MdnsAdvertiser.java",
"src/com/android/server/connectivity/mdns/MdnsAnnouncer.java",
"src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java",
"src/com/android/server/connectivity/mdns/MdnsProber.java",
"src/com/android/server/connectivity/mdns/MdnsRecordRepository.java",
+ "src/com/android/server/connectivity/mdns/SchedulerFactory.java",
+ "src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java",
],
static_libs: [
"net-utils-device-common-mdns-standalone-build-test",
@@ -132,7 +134,10 @@
droidstubs {
name: "service-mdns-droidstubs",
- srcs: ["src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java"],
+ srcs: [
+ "src/com/android/server/connectivity/mdns/SchedulerFactory.java",
+ "src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java",
+ ],
libs: [
"net-utils-device-common-mdns-standalone-build-test",
"service-connectivity-tiramisu-pre-jarjar",
diff --git a/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java b/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java
index 21af1a1..99354f8 100644
--- a/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java
+++ b/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java
@@ -49,7 +49,13 @@
@NonNull
private final ArrayList<Pair<Runnable, Long>> mPendingTasks = new ArrayList<>();
- DiscoveryExecutor(@Nullable Looper defaultLooper) {
+ @GuardedBy("mPendingTasks")
+ @Nullable
+ Scheduler mScheduler;
+ @NonNull private final MdnsFeatureFlags mMdnsFeatureFlags;
+
+ DiscoveryExecutor(@Nullable Looper defaultLooper, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
+ mMdnsFeatureFlags = mdnsFeatureFlags;
if (defaultLooper != null) {
this.mHandlerThread = null;
synchronized (mPendingTasks) {
@@ -62,7 +68,7 @@
synchronized (mPendingTasks) {
mHandler = new Handler(getLooper());
for (Pair<Runnable, Long> pendingTask : mPendingTasks) {
- mHandler.postDelayed(pendingTask.first, pendingTask.second);
+ executeDelayed(pendingTask.first, pendingTask.second);
}
mPendingTasks.clear();
}
@@ -95,15 +101,33 @@
/** Execute the given function after the specified amount of time elapses. */
public void executeDelayed(Runnable function, long delayMillis) {
final Handler handler;
+ final Scheduler scheduler;
synchronized (mPendingTasks) {
if (this.mHandler == null) {
mPendingTasks.add(Pair.create(function, delayMillis));
return;
} else {
handler = this.mHandler;
+ if (mMdnsFeatureFlags.mIsAccurateDelayCallbackEnabled
+ && this.mScheduler == null) {
+ this.mScheduler = SchedulerFactory.createScheduler(mHandler);
+ }
+ scheduler = this.mScheduler;
}
}
- handler.postDelayed(function, delayMillis);
+ if (scheduler != null) {
+ if (delayMillis == 0L) {
+ handler.post(function);
+ return;
+ }
+ if (HandlerUtils.isRunningOnHandlerThread(handler)) {
+ scheduler.postDelayed(function, delayMillis);
+ } else {
+ handler.post(() -> scheduler.postDelayed(function, delayMillis));
+ }
+ } else {
+ handler.postDelayed(function, delayMillis);
+ }
}
/** Shutdown the thread if necessary. */
@@ -111,6 +135,11 @@
if (this.mHandlerThread != null) {
this.mHandlerThread.quitSafely();
}
+ synchronized (mPendingTasks) {
+ if (mScheduler != null) {
+ mScheduler.close();
+ }
+ }
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 33bcb70..8cd3662 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -125,7 +125,7 @@
this.sharedLog = sharedLog;
this.perSocketServiceTypeClients = new PerSocketServiceTypeClients();
this.mdnsFeatureFlags = mdnsFeatureFlags;
- this.discoveryExecutor = new DiscoveryExecutor(socketClient.getLooper());
+ this.discoveryExecutor = new DiscoveryExecutor(socketClient.getLooper(), mdnsFeatureFlags);
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 56d4b9a..95f4fff 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -37,7 +37,6 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.DnsUtils;
-import com.android.net.module.util.RealtimeScheduler;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -96,9 +95,9 @@
private final boolean removeServiceAfterTtlExpires =
MdnsConfigs.removeServiceAfterTtlExpires();
private final Clock clock;
- // Use RealtimeScheduler for query scheduling, which allows for more accurate sending of
+ // Use MdnsRealtimeScheduler for query scheduling, which allows for more accurate sending of
// queries.
- @Nullable private final RealtimeScheduler realtimeScheduler;
+ @Nullable private final Scheduler scheduler;
@Nullable private MdnsSearchOptions searchOptions;
@@ -193,7 +192,7 @@
sharedLog.log(String.format("Query sent with transactionId: %d. "
+ "Next run: sessionId: %d, in %d ms",
sentResult.transactionId, args.sessionId, timeToNextTaskMs));
- if (realtimeScheduler != null) {
+ if (scheduler != null) {
setDelayedTask(args, timeToNextTaskMs);
} else {
dependencies.sendMessageDelayed(
@@ -264,11 +263,11 @@
}
/**
- * @see RealtimeScheduler
+ * @see Scheduler
*/
@Nullable
- public RealtimeScheduler createRealtimeScheduler(@NonNull Handler handler) {
- return new RealtimeScheduler(handler);
+ public Scheduler createScheduler(@NonNull Handler handler) {
+ return SchedulerFactory.createScheduler(handler);
}
}
@@ -317,8 +316,8 @@
this.mdnsQueryScheduler = new MdnsQueryScheduler();
this.cacheKey = new MdnsServiceCache.CacheKey(serviceType, socketKey);
this.featureFlags = featureFlags;
- this.realtimeScheduler = featureFlags.isAccurateDelayCallbackEnabled()
- ? dependencies.createRealtimeScheduler(handler) : null;
+ this.scheduler = featureFlags.isAccurateDelayCallbackEnabled()
+ ? dependencies.createScheduler(handler) : null;
}
/**
@@ -328,8 +327,8 @@
removeScheduledTask();
mdnsQueryScheduler.cancelScheduledRun();
serviceCache.unregisterServiceExpiredCallback(cacheKey);
- if (realtimeScheduler != null) {
- realtimeScheduler.close();
+ if (scheduler != null) {
+ scheduler.close();
}
}
@@ -339,8 +338,8 @@
}
private void setDelayedTask(ScheduledQueryTaskArgs args, long timeToNextTaskMs) {
- realtimeScheduler.removeDelayedMessage(EVENT_START_QUERYTASK);
- realtimeScheduler.sendDelayedMessage(
+ scheduler.removeDelayedMessage(EVENT_START_QUERYTASK);
+ scheduler.sendDelayedMessage(
handler.obtainMessage(EVENT_START_QUERYTASK, args), timeToNextTaskMs);
}
@@ -404,7 +403,7 @@
final long timeToNextTaskMs = calculateTimeToNextTask(args, now);
sharedLog.log(String.format("Schedule a query. Next run: sessionId: %d, in %d ms",
args.sessionId, timeToNextTaskMs));
- if (realtimeScheduler != null) {
+ if (scheduler != null) {
setDelayedTask(args, timeToNextTaskMs);
} else {
dependencies.sendMessageDelayed(
@@ -451,8 +450,8 @@
}
private void removeScheduledTask() {
- if (realtimeScheduler != null) {
- realtimeScheduler.removeDelayedMessage(EVENT_START_QUERYTASK);
+ if (scheduler != null) {
+ scheduler.removeDelayedMessage(EVENT_START_QUERYTASK);
} else {
dependencies.removeMessages(handler, EVENT_START_QUERYTASK);
}
@@ -541,8 +540,8 @@
}
}
}
- final boolean hasScheduledTask = realtimeScheduler != null
- ? realtimeScheduler.hasDelayedMessage(EVENT_START_QUERYTASK)
+ final boolean hasScheduledTask = scheduler != null
+ ? scheduler.hasDelayedMessage(EVENT_START_QUERYTASK)
: dependencies.hasMessages(handler, EVENT_START_QUERYTASK);
if (hasScheduledTask) {
final long now = clock.elapsedRealtime();
@@ -556,7 +555,7 @@
final long timeToNextTaskMs = calculateTimeToNextTask(args, now);
sharedLog.log(String.format("Reschedule a query. Next run: sessionId: %d, in %d ms",
args.sessionId, timeToNextTaskMs));
- if (realtimeScheduler != null) {
+ if (scheduler != null) {
setDelayedTask(args, timeToNextTaskMs);
} else {
dependencies.sendMessageDelayed(
diff --git a/service-t/src/com/android/server/connectivity/mdns/Scheduler.java b/service-t/src/com/android/server/connectivity/mdns/Scheduler.java
new file mode 100644
index 0000000..85a8e76
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/Scheduler.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.os.Message;
+
+import androidx.annotation.NonNull;
+
+/**
+ * The interface for scheduler.
+ */
+public interface Scheduler {
+ /**
+ * Set a message to be sent after a specified delay.
+ */
+ boolean sendDelayedMessage(@NonNull Message message, long delayMs);
+
+ /**
+ * Remove a scheduled message.
+ */
+ void removeDelayedMessage(int what);
+
+ /**
+ * Check if there is a scheduled message.
+ */
+ boolean hasDelayedMessage(int what);
+
+ /**
+ * Set a runnable to be executed after a specified delay.
+ */
+ boolean postDelayed(@NonNull Runnable runnable, long delayMs);
+
+ /**
+ * Close this object.
+ */
+ void close();
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/SchedulerFactory.java b/service-t/src/com/android/server/connectivity/mdns/SchedulerFactory.java
new file mode 100644
index 0000000..1cc9a6b
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/SchedulerFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import com.android.server.connectivity.mdns.internal.MdnsRealtimeScheduler;
+
+/**
+ * The factory class for creating a scheduler.
+ */
+public class SchedulerFactory {
+
+ /**
+ * Creates an realtime delay callback.
+ */
+ public static Scheduler createScheduler(@NonNull Handler handler) {
+ return new MdnsRealtimeScheduler(handler);
+ }
+
+ private SchedulerFactory() {
+ }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/internal/MdnsRealtimeScheduler.java b/service-t/src/com/android/server/connectivity/mdns/internal/MdnsRealtimeScheduler.java
new file mode 100644
index 0000000..eff7085
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/internal/MdnsRealtimeScheduler.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns.internal;
+
+import android.os.Handler;
+import android.os.Message;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.RealtimeScheduler;
+import com.android.server.connectivity.mdns.Scheduler;
+
+/**
+ * The delay callback for delivering scheduled tasks accurately.
+ */
+public class MdnsRealtimeScheduler extends RealtimeScheduler implements
+ Scheduler {
+ private static final String TAG = MdnsRealtimeScheduler.class.getSimpleName();
+
+ public MdnsRealtimeScheduler(@NonNull Handler handler) {
+ super(handler);
+ }
+
+ public boolean sendDelayedMessage(@NonNull Message message, long delayMs) {
+ return super.sendDelayedMessage(message, delayMs);
+ }
+
+ public void removeDelayedMessage(int what) {
+ super.removeDelayedMessage(what);
+ }
+
+ public boolean hasDelayedMessage(int what) {
+ return super.hasDelayedMessage(what);
+ }
+
+ public boolean postDelayed(@NonNull Runnable runnable, long delayMs) {
+ return super.postDelayed(runnable, delayMs);
+ }
+
+ public void close() {
+ super.close();
+ }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index 5f66f47..5ff708d 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -21,7 +21,6 @@
import static android.net.NetworkStats.UID_ALL;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.net.NetworkStats;
import android.net.UnderlyingNetworkInfo;
@@ -31,6 +30,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.BpfNetMaps;
+import com.android.server.connectivity.InterfaceTracker;
import java.io.IOException;
import java.net.ProtocolException;
@@ -108,7 +108,7 @@
/** Create a new {@link BpfNetMaps}. */
public BpfNetMaps createBpfNetMaps(@NonNull Context ctx) {
- return new BpfNetMaps(ctx);
+ return new BpfNetMaps(ctx, new InterfaceTracker(ctx));
}
}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 36c0cf9..25c0617 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -49,6 +49,8 @@
import static com.android.server.connectivity.NetworkPermissions.TRAFFIC_PERMISSION_UNINSTALLED;
import static com.android.server.connectivity.NetworkPermissions.TRAFFIC_PERMISSION_UPDATE_DEVICE_STATS;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.StatsManager;
import android.content.Context;
import android.net.BpfNetMapsUtils;
@@ -85,12 +87,14 @@
import com.android.net.module.util.bpf.IngressDiscardKey;
import com.android.net.module.util.bpf.IngressDiscardValue;
import com.android.net.module.util.bpf.LocalNetAccessKey;
+import com.android.server.connectivity.InterfaceTracker;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
@@ -142,6 +146,7 @@
Pair.create(TRAFFIC_PERMISSION_INTERNET, "PERMISSION_INTERNET"),
Pair.create(TRAFFIC_PERMISSION_UPDATE_DEVICE_STATS, "PERMISSION_UPDATE_DEVICE_STATS")
);
+ private final InterfaceTracker mInterfaceTracker;
/**
* Set configurationMap for test.
@@ -423,23 +428,27 @@
/** Constructor used after T that doesn't need to use netd anymore. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
- public BpfNetMaps(final Context context) {
- this(context, null);
+ public BpfNetMaps(final Context context, @NonNull final InterfaceTracker interfaceTracker) {
+ this(context, null, interfaceTracker);
if (!SdkLevel.isAtLeastT()) throw new IllegalArgumentException("BpfNetMaps need to use netd before T");
}
- public BpfNetMaps(final Context context, final INetd netd) {
- this(context, netd, new Dependencies());
+ public BpfNetMaps(final Context context, final INetd netd, @NonNull final InterfaceTracker
+ interfaceTracker) {
+ this(context, netd, new Dependencies(), interfaceTracker);
}
@VisibleForTesting
- public BpfNetMaps(final Context context, final INetd netd, final Dependencies deps) {
+ public BpfNetMaps(final Context context, final INetd netd, final Dependencies deps,
+ @NonNull final InterfaceTracker interfaceTracker) {
+ Objects.requireNonNull(interfaceTracker);
if (SdkLevel.isAtLeastT()) {
ensureInitialized(context);
}
mNetd = netd;
mDeps = deps;
+ mInterfaceTracker = interfaceTracker;
}
private void maybeThrow(final int err, final String msg) {
@@ -902,7 +911,7 @@
* @param isAllowed is the local network call allowed or blocked.
*/
@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
- public void addLocalNetAccess(final int lpmBitlen, final String iface,
+ public void addLocalNetAccess(final int lpmBitlen, @Nullable final String iface,
final InetAddress address, final int protocol, final int remotePort,
final boolean isAllowed) {
throwIfPre25Q2("addLocalNetAccess is not available on pre-B devices");
@@ -910,7 +919,7 @@
if (iface == null) {
ifIndex = 0;
} else {
- ifIndex = mDeps.getIfIndex(iface);
+ ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
}
if (ifIndex == 0) {
Log.e(TAG, "Failed to get if index, skip addLocalNetAccess for " + address
@@ -937,14 +946,14 @@
* @param remotePort src/dst port for ingress/egress
*/
@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
- public void removeLocalNetAccess(final int lpmBitlen, final String iface,
+ public void removeLocalNetAccess(final int lpmBitlen, @Nullable final String iface,
final InetAddress address, final int protocol, final int remotePort) {
throwIfPre25Q2("removeLocalNetAccess is not available on pre-B devices");
final int ifIndex;
if (iface == null) {
ifIndex = 0;
} else {
- ifIndex = mDeps.getIfIndex(iface);
+ ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
}
if (ifIndex == 0) {
Log.e(TAG, "Failed to get if index, skip removeLocalNetAccess for " + address
@@ -973,14 +982,14 @@
* is not local network or if configuration is allowed like local dns servers.
*/
@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
- public boolean getLocalNetAccess(final int lpmBitlen, final String iface,
+ public boolean getLocalNetAccess(final int lpmBitlen, @Nullable final String iface,
final InetAddress address, final int protocol, final int remotePort) {
throwIfPre25Q2("getLocalNetAccess is not available on pre-B devices");
final int ifIndex;
if (iface == null) {
ifIndex = 0;
} else {
- ifIndex = mDeps.getIfIndex(iface);
+ ifIndex = mInterfaceTracker.getInterfaceIndex(iface);
}
if (ifIndex == 0) {
Log.e(TAG, "Failed to get if index, returning default from getLocalNetAccess for "
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index e7441a9..a95d95c 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -366,6 +366,7 @@
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.DscpPolicyTracker;
import com.android.server.connectivity.FullScore;
+import com.android.server.connectivity.InterfaceTracker;
import com.android.server.connectivity.InvalidTagException;
import com.android.server.connectivity.KeepaliveResourceUtil;
import com.android.server.connectivity.KeepaliveTracker;
@@ -579,6 +580,7 @@
private final NetworkStatsManager mStatsManager;
private final NetworkPolicyManager mPolicyManager;
private final BpfNetMaps mBpfNetMaps;
+ private final InterfaceTracker mInterfaceTracker;
/**
* TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple
@@ -1664,8 +1666,17 @@
* @param netd a netd binder
* @return BpfNetMaps implementation.
*/
- public BpfNetMaps getBpfNetMaps(Context context, INetd netd) {
- return new BpfNetMaps(context, netd);
+ public BpfNetMaps getBpfNetMaps(Context context, INetd netd,
+ InterfaceTracker interfaceTracker) {
+ return new BpfNetMaps(context, netd, interfaceTracker);
+ }
+
+ /**
+ * Get the InterfaceTracker implementation to use in ConnectivityService.
+ * @return InterfaceTracker implementation.
+ */
+ public InterfaceTracker getInterfaceTracker(Context context) {
+ return new InterfaceTracker(context);
}
/**
@@ -1888,7 +1899,8 @@
mWakeUpMask = mask;
mNetd = netd;
- mBpfNetMaps = mDeps.getBpfNetMaps(mContext, netd);
+ mInterfaceTracker = mDeps.getInterfaceTracker(mContext);
+ mBpfNetMaps = mDeps.getBpfNetMaps(mContext, netd, mInterfaceTracker);
mHandlerThread = mDeps.makeHandlerThread("ConnectivityServiceThread");
mPermissionMonitorDeps = mPermDeps;
mPermissionMonitor =
@@ -9624,8 +9636,6 @@
updateIngressToVpnAddressFiltering(newLp, oldLp, networkAgent);
- updateLocalNetworkAddresses(newLp, oldLp);
-
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -9788,16 +9798,23 @@
wakeupModifyInterface(iface, nai, true);
mDeps.reportNetworkInterfaceForTransports(mContext, iface,
nai.networkCapabilities.getTransportTypes());
+ mInterfaceTracker.addInterface(iface);
} catch (Exception e) {
logw("Exception adding interface: " + e);
}
}
}
+
+ // The local network addresses needs to be updated before interfaces are removed because
+ // modifying bpf map local_net_access requires mapping interface name to index.
+ updateLocalNetworkAddresses(newLp, oldLp);
+
for (final String iface : interfaceDiff.removed) {
try {
if (DBG) log("Removing iface " + iface + " from network " + netId);
wakeupModifyInterface(iface, nai, false);
mRoutingCoordinatorService.removeInterfaceFromNetwork(netId, iface);
+ mInterfaceTracker.removeInterface(iface);
} catch (Exception e) {
loge("Exception removing interface: " + e);
}
diff --git a/service/src/com/android/server/connectivity/InterfaceTracker.java b/service/src/com/android/server/connectivity/InterfaceTracker.java
new file mode 100644
index 0000000..0b4abeb
--- /dev/null
+++ b/service/src/com/android/server/connectivity/InterfaceTracker.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.system.Os;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.BpfNetMaps;
+
+import java.util.Map;
+
+/**
+ * InterfaceTracker is responsible for providing interface mapping and tracking.
+ * @hide
+ */
+public class InterfaceTracker {
+ static {
+ if (BpfNetMaps.isAtLeast25Q2()) {
+ System.loadLibrary("service-connectivity");
+ }
+ }
+ private static final String TAG = "InterfaceTracker";
+ private final Dependencies mDeps;
+ private final Map<String, Integer> mInterfaceMap;
+
+ public InterfaceTracker(final Context context) {
+ this(context, new Dependencies());
+ }
+
+ @VisibleForTesting
+ public InterfaceTracker(final Context context, final Dependencies deps) {
+ this.mInterfaceMap = new ArrayMap<>();
+ this.mDeps = deps;
+ }
+
+ /**
+ * To add interface to tracking
+ * @param interfaceName name of interface added.
+ */
+ public void addInterface(@Nullable final String interfaceName) {
+ final int interfaceIndex;
+ if (interfaceName == null) {
+ interfaceIndex = 0;
+ } else {
+ interfaceIndex = mDeps.getIfIndex(interfaceName);
+ }
+ if (interfaceIndex == 0) {
+ Log.e(TAG, "Failed to get interface index for " + interfaceName);
+ return;
+ }
+ synchronized (mInterfaceMap) {
+ mInterfaceMap.put(interfaceName, interfaceIndex);
+ }
+ }
+
+ /**
+ * To remove interface from tracking
+ * @param interfaceName name of interface removed.
+ * @return true if the value was present and now removed.
+ */
+ public boolean removeInterface(@Nullable final String interfaceName) {
+ if (interfaceName == null) return false;
+ synchronized (mInterfaceMap) {
+ return mInterfaceMap.remove(interfaceName) != null;
+ }
+ }
+
+ /**
+ * Get interface index from interface name.
+ * @param interfaceName name of interface
+ * @return interface index for given interface name or 0 if interface is not found.
+ */
+ public int getInterfaceIndex(@Nullable final String interfaceName) {
+ final int interfaceIndex;
+ if (interfaceName != null) {
+ synchronized (mInterfaceMap) {
+ interfaceIndex = mInterfaceMap.getOrDefault(interfaceName, 0);
+ }
+ } else {
+ interfaceIndex = 0;
+ }
+ return interfaceIndex;
+ }
+
+ /**
+ * Dependencies of InterfaceTracker, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * Get interface index.
+ */
+ public int getIfIndex(final String ifName) {
+ return Os.if_nametoindex(ifName);
+ }
+
+ /**
+ * Get interface name
+ */
+ public String getIfName(final int ifIndex) {
+ return Os.if_indextoname(ifIndex);
+ }
+
+ }
+}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index b064723..abfc447 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -427,11 +427,7 @@
srcs: [
"device/com/android/net/module/util/FdEventsReader.java",
"device/com/android/net/module/util/HandlerUtils.java",
- "device/com/android/net/module/util/JniUtil.java",
- "device/com/android/net/module/util/RealtimeScheduler.java",
"device/com/android/net/module/util/SharedLog.java",
- "device/com/android/net/module/util/ServiceConnectivityJni.java",
- "device/com/android/net/module/util/TimerFdUtils.java",
"framework/com/android/net/module/util/ByteUtils.java",
"framework/com/android/net/module/util/CollectionUtils.java",
"framework/com/android/net/module/util/DnsUtils.java",
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index c19a124..5d49fa3 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -22,7 +22,6 @@
import static com.android.net.module.util.netlink.NetlinkConstants.IFF_UP;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETLINK;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
-import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST_ACK;
import android.net.MacAddress;
@@ -307,7 +306,7 @@
}
return RtNetlinkLinkMessage.build(
- new StructNlMsgHdr(0, RTM_GETLINK, NLM_F_REQUEST, sequenceNumber),
+ new StructNlMsgHdr(0, RTM_GETLINK, NLM_F_REQUEST_ACK, sequenceNumber),
new StructIfinfoMsg((short) AF_UNSPEC, (short) 0, interfaceIndex, 0, 0),
DEFAULT_MTU, null, null);
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
index 13710b1..08cab03 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
@@ -290,7 +290,7 @@
@Test
public void testCreateGetLinkMessage() {
final String expectedHexBytes =
- "20000000120001006824000000000000" // struct nlmsghdr
+ "20000000120005006824000000000000" // struct nlmsghdr
+ "00000000080000000000000000000000"; // struct ifinfomsg
final String interfaceName = "wlan0";
final int interfaceIndex = 8;
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index 49ffad6..c2ad18e 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -18,6 +18,7 @@
from mobly.controllers import android_device
from mobly.controllers.android_device_lib.adb import AdbError
from net_tests_utils.host.python import adb_utils, assert_utils
+import functools
class PatternNotFoundException(Exception):
@@ -400,6 +401,20 @@
f" {expected_version}",
)
+def at_least_B():
+ def decorator(test_function):
+ @functools.wraps(test_function)
+ def wrapper(self, *args, **kwargs):
+ asserts.abort_class_if(
+ (not hasattr(self, 'client')) or (not hasattr(self.client, 'isAtLeastB')),
+ "client device is not B+"
+ )
+
+ asserts.abort_class_if(not self.client.isAtLeastB(), "not B+")
+ return test_function(self, *args, **kwargs)
+ return wrapper
+ return decorator
+
class AdbOutputHandler:
def __init__(self, ad, cmd):
self._ad = ad
diff --git a/staticlibs/testutils/host/python/multi_devices_test_base.py b/staticlibs/testutils/host/python/multi_devices_test_base.py
index 677329a..72bac0c 100644
--- a/staticlibs/testutils/host/python/multi_devices_test_base.py
+++ b/staticlibs/testutils/host/python/multi_devices_test_base.py
@@ -53,3 +53,4 @@
raise_on_exception=True,
)
self.client = self.clientDevice.connectivity_multi_devices_snippet
+ self.server = self.serverDevice.connectivity_multi_devices_snippet
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index c730b86..00fb934 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -29,6 +29,7 @@
libs: [
"absl-py",
"mobly",
+ "scapy",
"net-tests-utils-host-python-common",
],
test_suites: [
diff --git a/tests/cts/multidevices/apfv6_test.py b/tests/cts/multidevices/apfv6_test.py
index fc732d2..61f1bfc 100644
--- a/tests/cts/multidevices/apfv6_test.py
+++ b/tests/cts/multidevices/apfv6_test.py
@@ -13,6 +13,8 @@
# limitations under the License.
from mobly import asserts
+from scapy.layers.inet import IP, ICMP
+from scapy.layers.l2 import Ether
from net_tests_utils.host.python import apf_test_base, apf_utils, adb_utils, assert_utils, packet_utils
APFV6_VERSION = 6000
@@ -82,3 +84,18 @@
self.send_packet_and_expect_reply_received(
arp_request, "DROPPED_ARP_REQUEST_REPLIED", arp_reply
)
+
+ @apf_utils.at_least_B()
+ def test_ipv4_icmp_echo_request_offload(self):
+ eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
+ ip = IP(src=self.server_ipv4_addresses[0], dst=self.client_ipv4_addresses[0])
+ icmp = ICMP(id=1, seq=123)
+ echo_request = bytes(eth/ip/icmp/b"hello").hex()
+
+ eth = Ether(src=self.client_mac_address, dst=self.server_mac_address)
+ ip = IP(src=self.client_ipv4_addresses[0], dst=self.server_ipv4_addresses[0])
+ icmp = ICMP(type=0, id=1, seq=123)
+ expected_echo_reply = bytes(eth/ip/icmp/b"hello").hex()
+ self.send_packet_and_expect_reply_received(
+ echo_request, "DROPPED_IPV4_PING_REQUEST_REPLIED", expected_echo_reply
+ )
\ No newline at end of file
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index 252052e..e1c6bf1 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -33,6 +33,8 @@
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiSsid
+import android.os.Build.VERSION.CODENAME
+import android.os.Build.VERSION.SDK_INT
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.PropertyUtil
import com.android.modules.utils.build.SdkLevel
@@ -62,6 +64,17 @@
cbHelper.unregisterAll()
}
+ private fun isAtLeastPreReleaseCodename(codeName: String): Boolean {
+ // Special case "REL", which means the build is not a pre-release build.
+ if ("REL".equals(CODENAME)) {
+ return false
+ }
+
+ // Otherwise lexically compare them. Return true if the build codename is equal to or
+ // greater than the requested codename.
+ return CODENAME.compareTo(codeName) >= 0
+ }
+
@Rpc(description = "Check whether the device has wifi feature.")
fun hasWifiFeature() = pm.hasSystemFeature(FEATURE_WIFI)
@@ -77,6 +90,11 @@
@Rpc(description = "Return whether the Sdk level is at least V.")
fun isAtLeastV() = SdkLevel.isAtLeastV()
+ @Rpc(description = "Check whether the device is at least B.")
+ fun isAtLeastB(): Boolean {
+ return SDK_INT >= 36 || (SDK_INT == 35 && isAtLeastPreReleaseCodename("Baklava"))
+ }
+
@Rpc(description = "Return the API level that the VSR requirement must be fulfilled.")
fun getVsrApiLevel() = PropertyUtil.getVsrApiLevel()
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 437eb81..de39215 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -55,11 +55,11 @@
import com.android.networkstack.apishim.TelephonyManagerShimImpl
import com.android.server.BpfNetMaps
import com.android.server.ConnectivityService
-import com.android.server.L2capNetworkProvider
import com.android.server.NetworkAgentWrapper
import com.android.server.TestNetIdManager
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
import com.android.server.connectivity.ConnectivityResources
+import com.android.server.connectivity.InterfaceTracker
import com.android.server.connectivity.MockableSystemProperties
import com.android.server.connectivity.MultinetworkPolicyTracker
import com.android.server.connectivity.PermissionMonitor
@@ -114,6 +114,8 @@
@Mock
private lateinit var netd: INetd
@Mock
+ private lateinit var interfaceTracker: InterfaceTracker
+ @Mock
private lateinit var dnsResolver: IDnsResolver
@Mock
private lateinit var systemConfigManager: SystemConfigManager
@@ -140,11 +142,15 @@
private val realContext get() = InstrumentationRegistry.getInstrumentation().context
private val httpProbeUrl get() =
- realContext.getResources().getString(com.android.server.net.integrationtests.R.string
- .config_captive_portal_http_url)
+ realContext.getResources().getString(
+ com.android.server.net.integrationtests.R.string
+ .config_captive_portal_http_url
+ )
private val httpsProbeUrl get() =
- realContext.getResources().getString(com.android.server.net.integrationtests.R.string
- .config_captive_portal_https_url)
+ realContext.getResources().getString(
+ com.android.server.net.integrationtests.R.string
+ .config_captive_portal_https_url
+ )
private class InstrumentationServiceConnection : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
@@ -166,12 +172,19 @@
fun setUpClass() {
val intent = Intent(realContext, NetworkStackInstrumentationService::class.java)
intent.action = INetworkStackInstrumentation::class.qualifiedName
- assertTrue(realContext.bindService(intent, InstrumentationServiceConnection(),
- BIND_AUTO_CREATE or BIND_IMPORTANT),
- "Error binding to instrumentation service")
- assertTrue(bindingCondition.block(SERVICE_BIND_TIMEOUT_MS),
+ assertTrue(
+ realContext.bindService(
+ intent,
+ InstrumentationServiceConnection(),
+ BIND_AUTO_CREATE or BIND_IMPORTANT
+ ),
+ "Error binding to instrumentation service"
+ )
+ assertTrue(
+ bindingCondition.block(SERVICE_BIND_TIMEOUT_MS),
"Timed out binding to instrumentation service " +
- "after $SERVICE_BIND_TIMEOUT_MS ms")
+ "after $SERVICE_BIND_TIMEOUT_MS ms"
+ )
}
}
@@ -201,7 +214,8 @@
// We don't test the actual notification value strings, so just return an empty array.
// It doesn't matter what the values are as long as it's not null.
doReturn(emptyArray<String>()).`when`(resources).getStringArray(
- R.array.network_switch_type_name)
+ R.array.network_switch_type_name
+ )
doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi)
doReturn(R.array.config_networkSupportedKeepaliveCount).`when`(resources)
.getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any())
@@ -223,7 +237,13 @@
}
private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService(
- context, dnsResolver, log, netd, deps, PermissionMonitorDependencies())
+ context,
+ dnsResolver,
+ log,
+ netd,
+ deps,
+ PermissionMonitorDependencies()
+ )
private inner class TestDependencies : ConnectivityService.Dependencies() {
override fun getNetworkStack() = networkStackClient
@@ -231,7 +251,11 @@
mock(ProxyTracker::class.java)
override fun getSystemProperties() = mock(MockableSystemProperties::class.java)
override fun makeNetIdManager() = TestNetIdManager()
- override fun getBpfNetMaps(context: Context?, netd: INetd?) = mock(BpfNetMaps::class.java)
+ override fun getBpfNetMaps(
+ context: Context?,
+ netd: INetd?,
+ interfaceTracker: InterfaceTracker?
+ ) = mock(BpfNetMaps::class.java)
.also {
doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
}
@@ -241,13 +265,17 @@
c: Context,
h: Handler,
r: Runnable
- ) = MultinetworkPolicyTracker(c, h, r,
+ ) = MultinetworkPolicyTracker(
+ c,
+ h,
+ r,
object : MultinetworkPolicyTracker.Dependencies() {
override fun getResourcesForActiveSubId(
connResources: ConnectivityResources,
activeSubId: Int
) = resources
- })
+ }
+ )
override fun makeHandlerThread(tag: String): HandlerThread =
super.makeHandlerThread(tag).also { handlerThreads.add(it) }
@@ -259,13 +287,18 @@
listener: BiConsumer<Int, Int>,
handler: Handler
): CarrierPrivilegeAuthenticator {
- return CarrierPrivilegeAuthenticator(context,
+ return CarrierPrivilegeAuthenticator(
+ context,
object : CarrierPrivilegeAuthenticator.Dependencies() {
override fun makeHandlerThread(): HandlerThread =
super.makeHandlerThread().also { handlerThreads.add(it) }
},
- tm, TelephonyManagerShimImpl.newInstance(tm),
- requestRestrictedWifiEnabled, listener, handler)
+ tm,
+ TelephonyManagerShimImpl.newInstance(tm),
+ requestRestrictedWifiEnabled,
+ listener,
+ handler
+ )
}
override fun makeSatelliteAccessController(
@@ -273,7 +306,8 @@
updateSatellitePreferredUid: Consumer<MutableSet<Int>>?,
connectivityServiceInternalHandler: Handler
): SatelliteAccessController? = mock(
- SatelliteAccessController::class.java)
+ SatelliteAccessController::class.java
+ )
override fun makeL2capNetworkProvider(context: Context) = null
}
@@ -305,8 +339,12 @@
nsInstrumentation.addHttpResponse(HttpResponse(httpProbeUrl, responseCode = 204))
nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204))
- val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), null /* ncTemplate */,
- context)
+ val na = NetworkAgentWrapper(
+ TRANSPORT_CELLULAR,
+ LinkProperties(),
+ null /* ncTemplate */,
+ context
+ )
networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
na.addCapability(NET_CAPABILITY_INTERNET)
@@ -344,7 +382,8 @@
| "user-portal-url": "https://login.capport.android.com",
| "venue-info-url": "https://venueinfo.capport.android.com"
|}
- """.trimMargin()))
+ """.trimMargin()
+ ))
// Tests will fail if a non-mocked query is received: mock the HTTPS probe, but not the
// HTTP probe as it should not be sent.
@@ -398,8 +437,10 @@
BpfUtils.BPF_CGROUP_INET_EGRESS,
BpfUtils.BPF_CGROUP_INET_SOCK_CREATE
).forEach {
- val ret = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd connectivity bpf-get-cgroup-program-id $it").trim()
+ val ret = SystemUtil.runShellCommand(
+ InstrumentationRegistry.getInstrumentation(),
+ "cmd connectivity bpf-get-cgroup-program-id $it"
+ ).trim()
assertTrue(Integer.parseInt(ret) > 0, "Unexpected output $ret for type $it")
}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index caf1765..1d2e8b0 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -109,6 +109,7 @@
import com.android.net.module.util.bpf.IngressDiscardKey;
import com.android.net.module.util.bpf.IngressDiscardValue;
import com.android.net.module.util.bpf.LocalNetAccessKey;
+import com.android.server.connectivity.InterfaceTracker;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -170,6 +171,8 @@
@Mock INetd mNetd;
@Mock BpfNetMaps.Dependencies mDeps;
@Mock Context mContext;
+
+ @Mock InterfaceTracker mInterfaceTracker;
private final IBpfMap<S32, U32> mConfigurationMap = new TestBpfMap<>(S32.class, U32.class);
private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap =
new TestBpfMap<>(S32.class, UidOwnerValue.class);
@@ -188,6 +191,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME);
+ doReturn(TEST_IF_INDEX).when(mInterfaceTracker).getInterfaceIndex(TEST_IF_NAME);
doReturn(TEST_IF_NAME).when(mDeps).getIfName(TEST_IF_INDEX);
doReturn(0).when(mDeps).synchronizeKernelRCU();
BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
@@ -202,7 +206,7 @@
BpfNetMaps.setDataSaverEnabledMapForTest(mDataSaverEnabledMap);
mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED));
BpfNetMaps.setIngressDiscardMapForTest(mIngressDiscardMap);
- mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps);
+ mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps, mInterfaceTracker);
}
@Test
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index c5e1a2a..3eefa0f 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -413,6 +413,7 @@
import com.android.server.connectivity.ClatCoordinator;
import com.android.server.connectivity.ConnectivityFlags;
import com.android.server.connectivity.ConnectivityResources;
+import com.android.server.connectivity.InterfaceTracker;
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.MultinetworkPolicyTracker;
import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies;
@@ -2263,7 +2264,8 @@
}
@Override
- public BpfNetMaps getBpfNetMaps(Context context, INetd netd) {
+ public BpfNetMaps getBpfNetMaps(Context context, INetd netd,
+ InterfaceTracker interfaceTracker) {
return mBpfNetMaps;
}
diff --git a/tests/unit/java/com/android/server/connectivity/InterfaceTrackerTest.java b/tests/unit/java/com/android/server/connectivity/InterfaceTrackerTest.java
new file mode 100644
index 0000000..8a9ada0
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/InterfaceTrackerTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.ConnectivityModuleTest;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@ConnectivityModuleTest
+public class InterfaceTrackerTest {
+ private static final String TAG = "InterfaceTrackerTest";
+ private static final String TEST_IF_NAME = "wlan10";
+ private static final String TEST_INCORRECT_IF_NAME = "wlan20";
+ private static final int TEST_IF_INDEX = 7;
+
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
+ private InterfaceTracker mInterfaceTracker;
+
+ @Mock Context mContext;
+ @Mock InterfaceTracker.Dependencies mDeps;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME);
+ mInterfaceTracker = new InterfaceTracker(mContext, mDeps);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddingInterface_InterfaceNameIndexMappingAdded() {
+ mInterfaceTracker.addInterface(TEST_IF_NAME);
+ assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddingNullInterface_InterfaceNameIndexMappingNotAdded() {
+ mInterfaceTracker.addInterface(null);
+ assertEquals(0, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testAddingIncorrectInterface_InterfaceNameIndexMappingNotAdded() {
+ mInterfaceTracker.addInterface(TEST_INCORRECT_IF_NAME);
+
+ assertEquals(0, mInterfaceTracker.getInterfaceIndex(TEST_INCORRECT_IF_NAME));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemovingInterface_InterfaceNameIndexMappingRemoved() {
+ mInterfaceTracker.addInterface(TEST_IF_NAME);
+ assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ mInterfaceTracker.removeInterface(TEST_IF_NAME);
+ assertEquals(0, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemovingNullInterface_InterfaceNameIndexMappingNotRemoved() {
+ mInterfaceTracker.addInterface(TEST_IF_NAME);
+ mInterfaceTracker.removeInterface(null);
+ assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemovingIncorrectInterface_InterfaceNameIndexMappingNotRemoved() {
+ mInterfaceTracker.addInterface(TEST_IF_NAME);
+ mInterfaceTracker.removeInterface(TEST_INCORRECT_IF_NAME);
+ assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME));
+ }
+
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt
index 51539a0..67fb428 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt
@@ -17,6 +17,7 @@
package com.android.server.connectivity.mdns
import android.os.Build
+import android.os.Handler
import android.os.HandlerThread
import android.testing.TestableLooper
import com.android.testutils.DevSdkIgnoreRule
@@ -36,6 +37,8 @@
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class DiscoveryExecutorTest {
private val thread = HandlerThread(DiscoveryExecutorTest::class.simpleName).apply { start() }
+ private val handler by lazy { Handler(thread.looper) }
+ private val testableLooper by lazy { TestableLooper(thread.looper) }
@After
fun tearDown() {
@@ -45,8 +48,10 @@
@Test
fun testCheckAndRunOnHandlerThread() {
- val testableLooper = TestableLooper(thread.looper)
- val executor = DiscoveryExecutor(testableLooper.looper)
+ val executor = DiscoveryExecutor(
+ testableLooper.looper,
+ MdnsFeatureFlags.newBuilder().build()
+ )
try {
val future = CompletableFuture<Boolean>()
executor.checkAndRunOnHandlerThread { future.complete(true) }
@@ -58,17 +63,17 @@
// Create a DiscoveryExecutor with the null defaultLooper and verify the task can execute
// normally.
- val executor2 = DiscoveryExecutor(null /* defaultLooper */)
+ val executor2 = DiscoveryExecutor(
+ null /* defaultLooper */,
+ MdnsFeatureFlags.newBuilder().build()
+ )
val future2 = CompletableFuture<Boolean>()
executor2.checkAndRunOnHandlerThread { future2.complete(true) }
assertTrue(future2.get(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS))
executor2.shutDown()
}
- @Test
- fun testExecute() {
- val testableLooper = TestableLooper(thread.looper)
- val executor = DiscoveryExecutor(testableLooper.looper)
+ private fun verifyExecute(executor: DiscoveryExecutor) {
try {
val future = CompletableFuture<Boolean>()
executor.execute { future.complete(true) }
@@ -81,9 +86,27 @@
}
@Test
+ fun testExecute() {
+ verifyExecute(DiscoveryExecutor(
+ testableLooper.looper,
+ MdnsFeatureFlags.newBuilder().build()
+ ))
+ }
+
+ @Test
+ fun testExecute_RealtimeScheduler() {
+ verifyExecute(DiscoveryExecutor(
+ testableLooper.looper,
+ MdnsFeatureFlags.newBuilder().setIsAccurateDelayCallbackEnabled(true).build()
+ ))
+ }
+
+ @Test
fun testExecuteDelayed() {
- val testableLooper = TestableLooper(thread.looper)
- val executor = DiscoveryExecutor(testableLooper.looper)
+ val executor = DiscoveryExecutor(
+ testableLooper.looper,
+ MdnsFeatureFlags.newBuilder().build()
+ )
try {
// Verify the executeDelayed method
val future = CompletableFuture<Boolean>()
@@ -107,4 +130,21 @@
testableLooper.destroy()
}
}
+
+ @Test
+ fun testExecuteDelayed_RealtimeScheduler() {
+ val executor = DiscoveryExecutor(
+ thread.looper,
+ MdnsFeatureFlags.newBuilder().setIsAccurateDelayCallbackEnabled(true).build()
+ )
+ try {
+ // Verify the executeDelayed method
+ val future = CompletableFuture<Boolean>()
+ // Schedule a task with 50ms delay
+ executor.executeDelayed({ future.complete(true) }, 50L)
+ assertTrue(future.get(500L, TimeUnit.MILLISECONDS))
+ } finally {
+ testableLooper.destroy()
+ }
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index dad03e0..b9c0d2f 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -59,7 +59,6 @@
import android.text.TextUtils;
import com.android.net.module.util.CollectionUtils;
-import com.android.net.module.util.RealtimeScheduler;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -129,7 +128,7 @@
@Mock
private MdnsServiceTypeClient.Dependencies mockDeps;
@Mock
- private RealtimeScheduler mockRealtimeScheduler;
+ private Scheduler mockScheduler;
@Captor
private ArgumentCaptor<MdnsServiceInfo> serviceInfoCaptor;
@@ -250,14 +249,14 @@
doAnswer(inv -> {
realHandler = (Handler) inv.getArguments()[0];
- return mockRealtimeScheduler;
- }).when(mockDeps).createRealtimeScheduler(any(Handler.class));
+ return mockScheduler;
+ }).when(mockDeps).createScheduler(any(Handler.class));
doAnswer(inv -> {
message = (Message) inv.getArguments()[0];
latestDelayMs = (long) inv.getArguments()[1];
return null;
- }).when(mockRealtimeScheduler).sendDelayedMessage(any(), anyLong());
+ }).when(mockScheduler).sendDelayedMessage(any(), anyLong());
client = makeMdnsServiceTypeClient(featureFlags);
}
@@ -2137,7 +2136,7 @@
.setNumOfQueriesBeforeBackoff(numOfQueriesBeforeBackoff)
.build();
startSendAndReceive(mockListenerOne, searchOptions);
- verify(mockRealtimeScheduler, times(1)).removeDelayedMessage(EVENT_START_QUERYTASK);
+ verify(mockScheduler, times(1)).removeDelayedMessage(EVENT_START_QUERYTASK);
// Verify that the first query has been sent.
verifyAndSendQuery(0 /* index */, 0 /* timeInMs */, true /* expectsUnicastResponse */,
@@ -2159,13 +2158,13 @@
// 0.8 * smallestRemainingTtl is larger than time to next run.
long currentTime = TEST_TTL / 2 + TEST_ELAPSED_REALTIME;
doReturn(currentTime).when(mockDecoderClock).elapsedRealtime();
- doReturn(true).when(mockRealtimeScheduler).hasDelayedMessage(EVENT_START_QUERYTASK);
+ doReturn(true).when(mockScheduler).hasDelayedMessage(EVENT_START_QUERYTASK);
processResponse(createResponse(
"service-instance-1", "192.0.2.123", 5353,
SERVICE_TYPE_LABELS,
Collections.emptyMap(), TEST_TTL), socketKey);
// Verify that the message removal occurred.
- verify(mockRealtimeScheduler, times(6)).removeDelayedMessage(EVENT_START_QUERYTASK);
+ verify(mockScheduler, times(6)).removeDelayedMessage(EVENT_START_QUERYTASK);
assertNotNull(message);
verifyAndSendQuery(3 /* index */, (long) (TEST_TTL / 2 * 0.8) /* timeInMs */,
true /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
@@ -2174,7 +2173,7 @@
// Stop sending packets.
stopSendAndReceive(mockListenerOne);
- verify(mockRealtimeScheduler, times(8)).removeDelayedMessage(EVENT_START_QUERYTASK);
+ verify(mockScheduler, times(8)).removeDelayedMessage(EVENT_START_QUERYTASK);
}
@Test
@@ -2184,12 +2183,12 @@
// Start query
startSendAndReceive(mockListenerOne, MdnsSearchOptions.newBuilder().build());
- verify(mockRealtimeScheduler, times(1)).removeDelayedMessage(EVENT_START_QUERYTASK);
+ verify(mockScheduler, times(1)).removeDelayedMessage(EVENT_START_QUERYTASK);
// Stop query and verify the close() method has been called.
stopSendAndReceive(mockListenerOne);
- verify(mockRealtimeScheduler, times(2)).removeDelayedMessage(EVENT_START_QUERYTASK);
- verify(mockRealtimeScheduler).close();
+ verify(mockScheduler, times(2)).removeDelayedMessage(EVENT_START_QUERYTASK);
+ verify(mockScheduler).close();
}
private static MdnsServiceInfo matchServiceName(String name) {
@@ -2247,8 +2246,7 @@
.sendMessage(any(Handler.class), any(Message.class));
// Verify the task has been scheduled.
if (useAccurateDelayCallback) {
- verify(mockRealtimeScheduler, times(scheduledCount))
- .sendDelayedMessage(any(), anyLong());
+ verify(mockScheduler, times(scheduledCount)).sendDelayedMessage(any(), anyLong());
} else {
verify(mockDeps, times(scheduledCount))
.sendMessageDelayed(any(Handler.class), any(Message.class), anyLong());
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index e941b1f..48333c5 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -68,6 +68,7 @@
import com.android.server.connectivity.CarrierPrivilegeAuthenticator
import com.android.server.connectivity.ClatCoordinator
import com.android.server.connectivity.ConnectivityFlags
+import com.android.server.connectivity.InterfaceTracker
import com.android.server.connectivity.MulticastRoutingCoordinatorService
import com.android.server.connectivity.MultinetworkPolicyTracker
import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies
@@ -194,6 +195,7 @@
val connResources = makeMockConnResources(sysResources, packageManager)
val netd = mock<INetd>()
+ val interfaceTracker = mock<InterfaceTracker>()
val bpfNetMaps = mock<BpfNetMaps>().also {
doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
}
@@ -280,7 +282,11 @@
inner class CSDeps : ConnectivityService.Dependencies() {
override fun getResources(ctx: Context) = connResources
- override fun getBpfNetMaps(context: Context, netd: INetd) = this@CSTest.bpfNetMaps
+ override fun getBpfNetMaps(
+ context: Context,
+ netd: INetd,
+ interfaceTracker: InterfaceTracker
+ ) = this@CSTest.bpfNetMaps
override fun getClatCoordinator(netd: INetd?) = this@CSTest.clatCoordinator
override fun getNetworkStack() = this@CSTest.networkStack
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index 520a434..2f2a5d1 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -326,12 +326,13 @@
private static LinkAddress newLinkAddress(
Ipv6AddressInfo addressInfo, boolean hasActiveOmrAddress) {
// Mesh-local addresses and OMR address have the same scope, to distinguish them we set
- // mesh-local addresses as deprecated when there is an active OMR address.
+ // mesh-local addresses as deprecated when there is an active OMR address. If OMR address
+ // is missing, only ML-EID in mesh-local addresses will be set preferred.
// For OMR address and link-local address we only use the value isPreferred set by
// ot-daemon.
boolean isPreferred = addressInfo.isPreferred;
- if (addressInfo.isMeshLocal && hasActiveOmrAddress) {
- isPreferred = false;
+ if (addressInfo.isMeshLocal) {
+ isPreferred = (!hasActiveOmrAddress && addressInfo.isMeshLocalEid);
}
final long deprecationTimeMillis =
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
index 5ba76b8..5be8f49 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
@@ -19,12 +19,10 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkManager;
import android.os.Build;
@@ -41,8 +39,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.List;
-
/** Tests for {@link ThreadNetworkManager}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index 195f6d2..5b07e0a 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -22,12 +22,14 @@
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.getIpv6Addresses;
import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
import static android.net.thread.utils.IntegrationTestUtils.getPrefixesFromNetData;
import static android.net.thread.utils.IntegrationTestUtils.getThreadNetwork;
import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static android.net.thread.utils.ThreadNetworkControllerWrapper.JOIN_TIMEOUT;
+import static android.os.SystemClock.elapsedRealtime;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
@@ -38,6 +40,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -46,23 +49,26 @@
import android.net.ConnectivityManager;
import android.net.InetAddresses;
import android.net.IpPrefix;
-import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.thread.utils.FullThreadDevice;
import android.net.thread.utils.OtDaemonController;
+import android.net.thread.utils.TapTestNetworkTracker;
import android.net.thread.utils.ThreadFeatureCheckerRule;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.net.thread.utils.ThreadNetworkControllerWrapper;
import android.net.thread.utils.ThreadStateListener;
+import android.os.HandlerThread;
import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
+import com.google.common.collect.FluentIterable;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -102,6 +108,12 @@
private static final Duration NETWORK_CALLBACK_TIMEOUT = Duration.ofSeconds(10);
+ // The duration between attached and OMR address shows up on thread-wpan
+ private static final Duration OMR_LINK_ADDR_TIMEOUT = Duration.ofSeconds(30);
+
+ // The duration between attached and addresses show up on thread-wpan
+ private static final Duration LINK_ADDR_TIMEOUT = Duration.ofSeconds(2);
+
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
private static final byte[] DEFAULT_DATASET_TLVS =
base16().decode(
@@ -128,6 +140,8 @@
ThreadNetworkControllerWrapper.newInstance(mContext);
private OtDaemonController mOtCtl;
private FullThreadDevice mFtd;
+ private HandlerThread mHandlerThread;
+ private TapTestNetworkTracker mTestNetworkTracker;
public final boolean mIsBorderRouterEnabled;
private final ThreadConfiguration mConfig;
@@ -152,6 +166,14 @@
mController.setEnabledAndWait(true);
mController.setConfigurationAndWait(mConfig);
mController.leaveAndWait();
+
+ mHandlerThread = new HandlerThread("ThreadIntegrationTest");
+ mHandlerThread.start();
+
+ mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper());
+ assertThat(mTestNetworkTracker).isNotNull();
+ mController.setTestNetworkAsUpstreamAndWait(mTestNetworkTracker.getInterfaceName());
+
mFtd = new FullThreadDevice(10 /* nodeId */);
}
@@ -159,6 +181,13 @@
public void tearDown() throws Exception {
ThreadStateListener.stopAllListeners();
+ if (mTestNetworkTracker != null) {
+ mTestNetworkTracker.tearDown();
+ }
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ mHandlerThread.join();
+ }
mController.setTestNetworkAsUpstreamAndWait(null);
mController.leaveAndWait();
@@ -265,23 +294,51 @@
}
@Test
- public void joinNetworkWithBrDisabled_meshLocalAddressesArePreferred() throws Exception {
- // When BR feature is disabled, there is no OMR address, so the mesh-local addresses are
- // expected to be preferred.
- mOtCtl.executeCommand("br disable");
+ public void joinNetwork_borderRouterEnabled_allMlAddrAreNotPreferredAndOmrIsPreferred()
+ throws Exception {
+ assumeTrue(mConfig.isBorderRouterEnabled());
+
+ mController.setTestNetworkAsUpstreamAndWait(mTestNetworkTracker.getInterfaceName());
mController.joinAndWait(DEFAULT_DATASET);
+ waitFor(
+ () -> getIpv6Addresses("thread-wpan").contains(mOtCtl.getOmrAddress()),
+ OMR_LINK_ADDR_TIMEOUT);
IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix();
- List<LinkAddress> linkAddresses = getIpv6LinkAddresses("thread-wpan");
- for (LinkAddress address : linkAddresses) {
- if (meshLocalPrefix.contains(address.getAddress())) {
- assertThat(address.getDeprecationTime())
- .isGreaterThan(SystemClock.elapsedRealtime());
- assertThat(address.isPreferred()).isTrue();
- }
- }
+ var linkAddrs = FluentIterable.from(getIpv6LinkAddresses("thread-wpan"));
+ var meshLocalAddrs = linkAddrs.filter(addr -> meshLocalPrefix.contains(addr.getAddress()));
+ assertThat(meshLocalAddrs).isNotEmpty();
+ assertThat(meshLocalAddrs.allMatch(addr -> !addr.isPreferred())).isTrue();
+ assertThat(meshLocalAddrs.allMatch(addr -> addr.getDeprecationTime() <= elapsedRealtime()))
+ .isTrue();
+ var omrAddrs = linkAddrs.filter(addr -> addr.getAddress().equals(mOtCtl.getOmrAddress()));
+ assertThat(omrAddrs).hasSize(1);
+ assertThat(omrAddrs.get(0).isPreferred()).isTrue();
+ assertThat(omrAddrs.get(0).getDeprecationTime() > elapsedRealtime()).isTrue();
+ }
- mOtCtl.executeCommand("br enable");
+ @Test
+ public void joinNetwork_borderRouterDisabled_onlyMlEidIsPreferred() throws Exception {
+ assumeFalse(mConfig.isBorderRouterEnabled());
+
+ mController.joinAndWait(DEFAULT_DATASET);
+ waitFor(
+ () -> getIpv6Addresses("thread-wpan").contains(mOtCtl.getMlEid()),
+ LINK_ADDR_TIMEOUT);
+
+ IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix();
+ var linkAddrs = FluentIterable.from(getIpv6LinkAddresses("thread-wpan"));
+ var meshLocalAddrs = linkAddrs.filter(addr -> meshLocalPrefix.contains(addr.getAddress()));
+ var mlEidAddrs = meshLocalAddrs.filter(addr -> addr.getAddress().equals(mOtCtl.getMlEid()));
+ var nonMlEidAddrs = meshLocalAddrs.filter(addr -> !mlEidAddrs.contains(addr));
+ assertThat(mlEidAddrs).hasSize(1);
+ assertThat(mlEidAddrs.allMatch(addr -> addr.isPreferred())).isTrue();
+ assertThat(mlEidAddrs.allMatch(addr -> addr.getDeprecationTime() > elapsedRealtime()))
+ .isTrue();
+ assertThat(nonMlEidAddrs).isNotEmpty();
+ assertThat(nonMlEidAddrs.allMatch(addr -> !addr.isPreferred())).isTrue();
+ assertThat(nonMlEidAddrs.allMatch(addr -> addr.getDeprecationTime() <= elapsedRealtime()))
+ .isTrue();
}
@Test
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
index f41e903..f7b4d19 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
@@ -479,6 +479,12 @@
return addresses
}
+ /** Returns the list of [InetAddress] of the given network. */
+ @JvmStatic
+ fun getIpv6Addresses(interfaceName: String): List<InetAddress> {
+ return getIpv6LinkAddresses(interfaceName).map { it.address }
+ }
+
/** Return the first discovered service of `serviceType`. */
@JvmStatic
@Throws(Exception::class)
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
index 272685f..d35b94e 100644
--- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
+++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
@@ -24,9 +24,11 @@
import com.android.compatibility.common.util.SystemUtil;
import java.net.Inet6Address;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Wrapper of the "/system/bin/ot-ctl" which can be used to send CLI commands to ot-daemon to
@@ -72,6 +74,25 @@
.toList();
}
+ /** Returns the OMR address of this device or {@code null} if it doesn't exist. */
+ @Nullable
+ public Inet6Address getOmrAddress() {
+ List<Inet6Address> allAddresses = new ArrayList<>(getAddresses());
+ allAddresses.removeAll(getMeshLocalAddresses());
+
+ List<Inet6Address> omrAddresses =
+ allAddresses.stream()
+ .filter(addr -> !addr.isLinkLocalAddress())
+ .collect(Collectors.toList());
+ if (omrAddresses.isEmpty()) {
+ return null;
+ } else if (omrAddresses.size() > 1) {
+ throw new IllegalStateException();
+ }
+
+ return omrAddresses.getFirst();
+ }
+
/** Returns {@code true} if the Thread interface is up. */
public boolean isInterfaceUp() {
String output = executeCommand("ifconfig");