Merge "Add diagnostics to testNativeDatagramTransmission" into main
diff --git a/bpf/syscall_wrappers/include/BpfSyscallWrappers.h b/bpf/syscall_wrappers/include/BpfSyscallWrappers.h
index 73cef89..a31445a 100644
--- a/bpf/syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/bpf/syscall_wrappers/include/BpfSyscallWrappers.h
@@ -16,24 +16,20 @@
 
 #pragma once
 
+#include <android-base/unique_fd.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <linux/bpf.h>
 #include <linux/unistd.h>
 #include <sys/file.h>
 
-#ifdef BPF_FD_JUST_USE_INT
-  #define BPF_FD_TYPE int
-  #define BPF_FD_TO_U32(x) static_cast<__u32>(x)
-#else
-  #include <android-base/unique_fd.h>
-  #define BPF_FD_TYPE base::unique_fd&
-  #define BPF_FD_TO_U32(x) static_cast<__u32>((x).get())
-#endif
 
 namespace android {
 namespace bpf {
 
+using ::android::base::borrowed_fd;
+using ::android::base::unique_fd;
+
 inline uint64_t ptr_to_u64(const void * const x) {
     return (uint64_t)(uintptr_t)x;
 }
@@ -69,58 +65,59 @@
 //   'inner_map_fd' is basically a template specifying {map_type, key_size, value_size, max_entries, map_flags}
 //   of the inner map type (and possibly only key_size/value_size actually matter?).
 inline int createOuterMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size,
-                          uint32_t max_entries, uint32_t map_flags, const BPF_FD_TYPE inner_map_fd) {
+                          uint32_t max_entries, uint32_t map_flags,
+                          const borrowed_fd& inner_map_fd) {
     return bpf(BPF_MAP_CREATE, {
                                        .map_type = map_type,
                                        .key_size = key_size,
                                        .value_size = value_size,
                                        .max_entries = max_entries,
                                        .map_flags = map_flags,
-                                       .inner_map_fd = BPF_FD_TO_U32(inner_map_fd),
+                                       .inner_map_fd = static_cast<__u32>(inner_map_fd.get()),
                                });
 }
 
-inline int writeToMapEntry(const BPF_FD_TYPE map_fd, const void* key, const void* value,
+inline int writeToMapEntry(const borrowed_fd& map_fd, const void* key, const void* value,
                            uint64_t flags) {
     return bpf(BPF_MAP_UPDATE_ELEM, {
-                                            .map_fd = BPF_FD_TO_U32(map_fd),
+                                            .map_fd = static_cast<__u32>(map_fd.get()),
                                             .key = ptr_to_u64(key),
                                             .value = ptr_to_u64(value),
                                             .flags = flags,
                                     });
 }
 
-inline int findMapEntry(const BPF_FD_TYPE map_fd, const void* key, void* value) {
+inline int findMapEntry(const borrowed_fd& map_fd, const void* key, void* value) {
     return bpf(BPF_MAP_LOOKUP_ELEM, {
-                                            .map_fd = BPF_FD_TO_U32(map_fd),
+                                            .map_fd = static_cast<__u32>(map_fd.get()),
                                             .key = ptr_to_u64(key),
                                             .value = ptr_to_u64(value),
                                     });
 }
 
-inline int deleteMapEntry(const BPF_FD_TYPE map_fd, const void* key) {
+inline int deleteMapEntry(const borrowed_fd& map_fd, const void* key) {
     return bpf(BPF_MAP_DELETE_ELEM, {
-                                            .map_fd = BPF_FD_TO_U32(map_fd),
+                                            .map_fd = static_cast<__u32>(map_fd.get()),
                                             .key = ptr_to_u64(key),
                                     });
 }
 
-inline int getNextMapKey(const BPF_FD_TYPE map_fd, const void* key, void* next_key) {
+inline int getNextMapKey(const borrowed_fd& map_fd, const void* key, void* next_key) {
     return bpf(BPF_MAP_GET_NEXT_KEY, {
-                                             .map_fd = BPF_FD_TO_U32(map_fd),
+                                             .map_fd = static_cast<__u32>(map_fd.get()),
                                              .key = ptr_to_u64(key),
                                              .next_key = ptr_to_u64(next_key),
                                      });
 }
 
-inline int getFirstMapKey(const BPF_FD_TYPE map_fd, void* firstKey) {
+inline int getFirstMapKey(const borrowed_fd& map_fd, void* firstKey) {
     return getNextMapKey(map_fd, NULL, firstKey);
 }
 
-inline int bpfFdPin(const BPF_FD_TYPE map_fd, const char* pathname) {
+inline int bpfFdPin(const borrowed_fd& map_fd, const char* pathname) {
     return bpf(BPF_OBJ_PIN, {
                                     .pathname = ptr_to_u64(pathname),
-                                    .bpf_fd = BPF_FD_TO_U32(map_fd),
+                                    .bpf_fd = static_cast<__u32>(map_fd.get()),
                             });
 }
 
@@ -131,22 +128,15 @@
                             });
 }
 
-int bpfGetFdMapId(const BPF_FD_TYPE map_fd);
+int bpfGetFdMapId(const borrowed_fd& map_fd);
 
 inline int bpfLock(int fd, short type) {
     if (fd < 0) return fd;  // pass any errors straight through
 #ifdef BPF_MAP_LOCKLESS_FOR_TEST
     return fd;
 #endif
-#ifdef BPF_FD_JUST_USE_INT
     int mapId = bpfGetFdMapId(fd);
     int saved_errno = errno;
-#else
-    base::unique_fd ufd(fd);
-    int mapId = bpfGetFdMapId(ufd);
-    int saved_errno = errno;
-    (void)ufd.release();
-#endif
     // 4.14+ required to fetch map id, but we don't want to call isAtLeastKernelVersion
     if (mapId == -1 && saved_errno == EINVAL) return fd;
     if (mapId <= 0) abort();  // should not be possible
@@ -193,37 +183,35 @@
 }
 
 inline bool usableProgram(const char* pathname) {
-    int fd = retrieveProgram(pathname);
-    bool ok = (fd >= 0);
-    if (ok) close(fd);
-    return ok;
+    unique_fd fd(retrieveProgram(pathname));
+    return fd.ok();
 }
 
-inline int attachProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd,
-                         const BPF_FD_TYPE cg_fd, uint32_t flags = 0) {
+inline int attachProgram(bpf_attach_type type, const borrowed_fd& prog_fd,
+                         const borrowed_fd& cg_fd, uint32_t flags = 0) {
     return bpf(BPF_PROG_ATTACH, {
-                                        .target_fd = BPF_FD_TO_U32(cg_fd),
-                                        .attach_bpf_fd = BPF_FD_TO_U32(prog_fd),
+                                        .target_fd = static_cast<__u32>(cg_fd.get()),
+                                        .attach_bpf_fd = static_cast<__u32>(prog_fd.get()),
                                         .attach_type = type,
                                         .attach_flags = flags,
                                 });
 }
 
-inline int detachProgram(bpf_attach_type type, const BPF_FD_TYPE cg_fd) {
+inline int detachProgram(bpf_attach_type type, const borrowed_fd& cg_fd) {
     return bpf(BPF_PROG_DETACH, {
-                                        .target_fd = BPF_FD_TO_U32(cg_fd),
+                                        .target_fd = static_cast<__u32>(cg_fd.get()),
                                         .attach_type = type,
                                 });
 }
 
-inline int queryProgram(const BPF_FD_TYPE cg_fd,
+inline int queryProgram(const borrowed_fd& cg_fd,
                         enum bpf_attach_type attach_type,
                         __u32 query_flags = 0,
                         __u32 attach_flags = 0) {
     int prog_id = -1;  // equivalent to an array of one integer.
     bpf_attr arg = {
             .query = {
-                    .target_fd = BPF_FD_TO_U32(cg_fd),
+                    .target_fd = static_cast<__u32>(cg_fd.get()),
                     .attach_type = attach_type,
                     .query_flags = query_flags,
                     .attach_flags = attach_flags,
@@ -237,21 +225,21 @@
     return prog_id;  // return actual id
 }
 
-inline int detachSingleProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd,
-                               const BPF_FD_TYPE cg_fd) {
+inline int detachSingleProgram(bpf_attach_type type, const borrowed_fd& prog_fd,
+                               const borrowed_fd& cg_fd) {
     return bpf(BPF_PROG_DETACH, {
-                                        .target_fd = BPF_FD_TO_U32(cg_fd),
-                                        .attach_bpf_fd = BPF_FD_TO_U32(prog_fd),
+                                        .target_fd = static_cast<__u32>(cg_fd.get()),
+                                        .attach_bpf_fd = static_cast<__u32>(prog_fd.get()),
                                         .attach_type = type,
                                 });
 }
 
 // Available in 4.12 and later kernels.
-inline int runProgram(const BPF_FD_TYPE prog_fd, const void* data,
+inline int runProgram(const borrowed_fd& prog_fd, const void* data,
                       const uint32_t data_size) {
     return bpf(BPF_PROG_RUN, {
                                      .test = {
-                                             .prog_fd = BPF_FD_TO_U32(prog_fd),
+                                             .prog_fd = static_cast<__u32>(prog_fd.get()),
                                              .data_size_in = data_size,
                                              .data_in = ptr_to_u64(data),
                                      },
@@ -265,10 +253,10 @@
 // supported/returned by the running kernel.  We do this by checking it is fully
 // within the bounds of the struct size as reported by the kernel.
 #define DEFINE_BPF_GET_FD(TYPE, NAME, FIELD) \
-inline int bpfGetFd ## NAME(const BPF_FD_TYPE fd) { \
+inline int bpfGetFd ## NAME(const borrowed_fd& fd) { \
     struct bpf_ ## TYPE ## _info info = {}; \
     union bpf_attr attr = { .info = { \
-        .bpf_fd = BPF_FD_TO_U32(fd), \
+        .bpf_fd = static_cast<__u32>(fd.get()), \
         .info_len = sizeof(info), \
         .info = ptr_to_u64(&info), \
     }}; \
@@ -283,19 +271,16 @@
 
 // All 7 of these fields are already present in Linux v4.14 (even ACK 4.14-P)
 // while BPF_OBJ_GET_INFO_BY_FD is not implemented at all in v4.9 (even ACK 4.9-Q)
-DEFINE_BPF_GET_FD(map, MapType, type)            // int bpfGetFdMapType(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(map, MapId, id)                // int bpfGetFdMapId(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(map, KeySize, key_size)        // int bpfGetFdKeySize(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(map, ValueSize, value_size)    // int bpfGetFdValueSize(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(map, MaxEntries, max_entries)  // int bpfGetFdMaxEntries(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(map, MapFlags, map_flags)      // int bpfGetFdMapFlags(const BPF_FD_TYPE map_fd)
-DEFINE_BPF_GET_FD(prog, ProgId, id)              // int bpfGetFdProgId(const BPF_FD_TYPE prog_fd)
+DEFINE_BPF_GET_FD(map, MapType, type)            // int bpfGetFdMapType(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(map, MapId, id)                // int bpfGetFdMapId(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(map, KeySize, key_size)        // int bpfGetFdKeySize(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(map, ValueSize, value_size)    // int bpfGetFdValueSize(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(map, MaxEntries, max_entries)  // int bpfGetFdMaxEntries(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(map, MapFlags, map_flags)      // int bpfGetFdMapFlags(const borrowed_fd& map_fd)
+DEFINE_BPF_GET_FD(prog, ProgId, id)              // int bpfGetFdProgId(const borrowed_fd& prog_fd)
 
 #undef DEFINE_BPF_GET_FD
 
 }  // namespace bpf
 }  // namespace android
 
-#undef BPF_FD_TO_U32
-#undef BPF_FD_TYPE
-#undef BPF_FD_JUST_USE_INT
diff --git a/framework/Android.bp b/framework/Android.bp
index 0334e11..c641de2 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -64,6 +64,7 @@
         ":net-utils-framework-common-srcs",
         ":framework-connectivity-api-shared-srcs",
         ":framework-networksecurity-sources",
+        ":statslog-framework-connectivity-java-gen",
     ],
     aidl: {
         generate_get_transaction_name: true,
@@ -105,6 +106,7 @@
         "app-compat-annotations",
         "framework-connectivity-t.stubs.module_lib",
         "unsupportedappusage",
+        "framework-statsd.stubs.module_lib",
     ],
     apex_available: [
         "com.android.tethering",
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 574ab2f..cefa1ea 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -31,12 +31,14 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
 import android.telephony.data.EpsBearerQosSessionAttributes;
 import android.telephony.data.NrQosSessionAttributes;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.FrameworkConnectivityStatsLog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -943,6 +945,19 @@
 
     private void queueOrSendMessage(@NonNull RegistryAction action) {
         synchronized (mPreConnectedQueue) {
+            if (mNetwork == null && !Process.isApplicationUid(Process.myUid())) {
+                // Theoretically, it should not be valid to queue messages here before
+                // registering the NetworkAgent. However, practically, with the way
+                // queueing works right now, it ends up working out just fine.
+                // Log a statistic so that we know if this is happening in the
+                // wild. The check for isApplicationUid is to prevent logging the
+                // metric from test code.
+
+                FrameworkConnectivityStatsLog.write(
+                        FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED,
+                        FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_MESSAGE_QUEUED_BEFORE_CONNECT
+                );
+            }
             if (mRegistry != null) {
                 try {
                     action.execute(mRegistry);
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 67d0891..07469b1 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -215,9 +215,6 @@
         // Note: processNetlinkMessage is called on the handler thread.
         @Override
         protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
-            // ignore all updates when ethernet is disabled.
-            if (mEthernetState == ETHERNET_STATE_DISABLED) return;
-
             if (nlMsg instanceof RtNetlinkLinkMessage) {
                 processRtNetlinkLinkMessage((RtNetlinkLinkMessage) nlMsg);
             } else {
@@ -596,10 +593,13 @@
             // Read the flags before attempting to bring up the interface. If the interface is
             // already running an UP event is created after adding the interface.
             config = NetdUtils.getInterfaceConfigParcel(mNetd, iface);
-            if (NetdUtils.hasFlag(config, INetd.IF_STATE_DOWN)) {
+            // Only bring the interface up when ethernet is enabled.
+            if (mEthernetState == ETHERNET_STATE_ENABLED) {
                 // As a side-effect, NetdUtils#setInterfaceUp() also clears the interface's IPv4
                 // address and readds it which *could* lead to unexpected behavior in the future.
                 NetdUtils.setInterfaceUp(mNetd, iface);
+            } else if (mEthernetState == ETHERNET_STATE_DISABLED) {
+                NetdUtils.setInterfaceDown(mNetd, iface);
             }
         } catch (IllegalStateException e) {
             // Either the system is crashing or the interface has disappeared. Just ignore the
@@ -646,6 +646,10 @@
     }
 
     private void setInterfaceAdministrativeState(String iface, boolean up, EthernetCallback cb) {
+        if (mEthernetState == ETHERNET_STATE_DISABLED) {
+            cb.onError("Cannot enable/disable interface when ethernet is disabled");
+            return;
+        }
         if (getInterfaceState(iface) == EthernetManager.STATE_ABSENT) {
             cb.onError("Failed to enable/disable absent interface: " + iface);
             return;
@@ -965,22 +969,26 @@
 
             mEthernetState = newState;
 
-            if (enabled) {
-                trackAvailableInterfaces();
-            } else {
-                // TODO: maybe also disable server mode interface as well.
-                untrackFactoryInterfaces();
+            // Interface in server mode should also be included.
+            ArrayList<String> interfaces =
+                    new ArrayList<>(
+                    List.of(mFactory.getAvailableInterfaces(/* includeRestricted */ true)));
+
+            if (mTetheringInterfaceMode == INTERFACE_MODE_SERVER) {
+                interfaces.add(mTetheringInterface);
+            }
+
+            for (String iface : interfaces) {
+                if (enabled) {
+                    NetdUtils.setInterfaceUp(mNetd, iface);
+                } else {
+                    NetdUtils.setInterfaceDown(mNetd, iface);
+                }
             }
             broadcastEthernetStateChange(mEthernetState);
         });
     }
 
-    private void untrackFactoryInterfaces() {
-        for (String iface : mFactory.getAvailableInterfaces(true /* includeRestricted */)) {
-            stopTrackingInterface(iface);
-        }
-    }
-
     private void unicastEthernetStateChange(@NonNull IEthernetServiceListener listener,
             int state) {
         ensureRunningOnEthernetServiceThread();
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index f484027..c29004c 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -725,3 +725,10 @@
     ],
     apex_available: ["com.android.wifi"],
 }
+
+genrule {
+    name: "statslog-framework-connectivity-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module connectivity --javaPackage com.android.net.module.util --javaClass FrameworkConnectivityStatsLog",
+    out: ["com/android/net/module/util/FrameworkConnectivityStatsLog.java"],
+}
diff --git a/staticlibs/native/bpfmapjni/Android.bp b/staticlibs/native/bpfmapjni/Android.bp
index 969ebd4..9a58a93 100644
--- a/staticlibs/native/bpfmapjni/Android.bp
+++ b/staticlibs/native/bpfmapjni/Android.bp
@@ -26,6 +26,7 @@
     header_libs: [
         "bpf_headers",
         "jni_headers",
+        "libbase_headers",
     ],
     shared_libs: [
         "liblog",
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
index 1923ceb..d862f6b 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -24,37 +24,38 @@
 #include "nativehelper/scoped_primitive_array.h"
 #include "nativehelper/scoped_utf_chars.h"
 
-#define BPF_FD_JUST_USE_INT
+#include <android-base/unique_fd.h>
 #include "BpfSyscallWrappers.h"
-
 #include "bpf/KernelUtils.h"
 
 namespace android {
 
+using ::android::base::unique_fd;
+
 static jint com_android_net_module_util_BpfMap_nativeBpfFdGet(JNIEnv *env, jclass clazz,
         jstring path, jint mode, jint keySize, jint valueSize) {
     ScopedUtfChars pathname(env, path);
 
-    jint fd = -1;
+    unique_fd fd;
     switch (mode) {
       case 0:
-        fd = bpf::mapRetrieveRW(pathname.c_str());
+        fd.reset(bpf::mapRetrieveRW(pathname.c_str()));
         break;
       case BPF_F_RDONLY:
-        fd = bpf::mapRetrieveRO(pathname.c_str());
+        fd.reset(bpf::mapRetrieveRO(pathname.c_str()));
         break;
       case BPF_F_WRONLY:
-        fd = bpf::mapRetrieveWO(pathname.c_str());
+        fd.reset(bpf::mapRetrieveWO(pathname.c_str()));
         break;
       case BPF_F_RDONLY|BPF_F_WRONLY:
-        fd = bpf::mapRetrieveExclusiveRW(pathname.c_str());
+        fd.reset(bpf::mapRetrieveExclusiveRW(pathname.c_str()));
         break;
       default:
         errno = EINVAL;
         break;
     }
 
-    if (fd < 0) {
+    if (!fd.ok()) {
         jniThrowErrnoException(env, "nativeBpfFdGet", errno);
         return -1;
     }
@@ -62,18 +63,16 @@
     if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
         // These likely fail with -1 and set errno to EINVAL on <4.14
         if (bpf::bpfGetFdKeySize(fd) != keySize) {
-            close(fd);
             jniThrowErrnoException(env, "nativeBpfFdGet KeySize", EBADFD);
             return -1;
         }
         if (bpf::bpfGetFdValueSize(fd) != valueSize) {
-            close(fd);
             jniThrowErrnoException(env, "nativeBpfFdGet ValueSize", EBADFD);
             return -1;
         }
     }
 
-    return fd;
+    return fd.release();
 }
 
 static void com_android_net_module_util_BpfMap_nativeWriteToMapEntry(JNIEnv *env, jobject self,
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 1e2a212..9be579b 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -145,6 +145,8 @@
 
     private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null
 
+    private var ethernetEnabled = true
+
     private class EthernetTestInterface(
         context: Context,
         private val handler: Handler,
@@ -428,7 +430,7 @@
 
         // when an interface comes up, we should always see a down cb before an up cb.
         ifaceListener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
-        if (hasCarrier) {
+        if (hasCarrier && ethernetEnabled) {
             ifaceListener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
         }
         return iface
@@ -514,6 +516,7 @@
     private fun setEthernetEnabled(enabled: Boolean) {
         runAsShell(NETWORK_SETTINGS) { em.setEthernetEnabled(enabled) }
 
+        ethernetEnabled = enabled
         val listener = EthernetStateListener()
         addEthernetStateListener(listener)
         listener.eventuallyExpect(if (enabled) ETHERNET_STATE_ENABLED else ETHERNET_STATE_DISABLED)
@@ -600,26 +603,6 @@
         }
     }
 
-    @Test
-    fun testCallbacks_withRunningInterface() {
-        assumeFalse(isAdbOverEthernet())
-        // Only run this test when no non-restricted / physical interfaces are present.
-        assumeNoInterfaceForTetheringAvailable()
-
-        val iface = createInterface()
-        val listener = EthernetStateListener()
-        addInterfaceStateListener(listener)
-        listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
-
-        // Remove running interface. The interface stays running but is no longer tracked.
-        setEthernetEnabled(false)
-        listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
-
-        setEthernetEnabled(true)
-        listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
-        listener.assertNoCallback()
-    }
-
     private fun assumeNoInterfaceForTetheringAvailable() {
         // Interfaces that have configured NetworkCapabilities will never be used for tethering,
         // see aosp/2123900.
@@ -911,6 +894,30 @@
     }
 
     @Test
+    fun testEnableDisableInterface_disableEnableEthernet() {
+        val iface = createInterface()
+        val listener = EthernetStateListener()
+        addInterfaceStateListener(listener)
+        listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+
+        // When ethernet is disabled, interface should be down and enable/disableInterface()
+        // should not bring the interfaces up.
+        setEthernetEnabled(false)
+        listener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+        enableInterface(iface).expectError()
+        disableInterface(iface).expectError()
+        listener.assertNoCallback()
+
+        // When ethernet is enabled, enable/disableInterface() should succeed.
+        setEthernetEnabled(true)
+        listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+        disableInterface(iface).expectResult(iface.name)
+        listener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+        enableInterface(iface).expectResult(iface.name)
+        listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+    }
+
+    @Test
     fun testUpdateConfiguration_forBothIpConfigAndCapabilities() {
         val iface = createInterface()
         val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
@@ -1018,4 +1025,68 @@
         cb.eventuallyExpectCapabilities(TEST_CAPS)
         cb.eventuallyExpectLpForStaticConfig(STATIC_IP_CONFIGURATION.staticIpConfiguration)
     }
+
+    @Test
+    fun testAddInterface_disableEnableEthernet() {
+        val listener = EthernetStateListener()
+        addInterfaceStateListener(listener)
+
+        // When ethernet is disabled, newly added interfaces should not be brought up.
+        setEthernetEnabled(false)
+        val iface = createInterface(/* hasCarrier */ true)
+        listener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+
+        // When ethernet is re-enabled after interface is added, it will be brought up.
+        setEthernetEnabled(true)
+        listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+    }
+
+
+    @Test
+    fun testRemoveInterface_disableEnableEthernet() {
+        // Set up 2 interfaces for testing
+        val iface1 = createInterface()
+        val listener = EthernetStateListener()
+        addInterfaceStateListener(listener)
+        listener.eventuallyExpect(iface1, STATE_LINK_UP, ROLE_CLIENT)
+        val iface2 = createInterface()
+        listener.eventuallyExpect(iface2, STATE_LINK_UP, ROLE_CLIENT)
+
+        // Removing interfaces when ethernet is enabled will first send link down, then
+        // STATE_ABSENT/ROLE_NONE.
+        removeInterface(iface1)
+        listener.expectCallback(iface1, STATE_LINK_DOWN, ROLE_CLIENT)
+        listener.expectCallback(iface1, STATE_ABSENT, ROLE_NONE)
+
+        // Removing interfaces after ethernet is disabled will first send link down when ethernet is
+        // disabled, then STATE_ABSENT/ROLE_NONE when interface is removed.
+        setEthernetEnabled(false)
+        listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+        removeInterface(iface2)
+        listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
+    }
+
+    @Test
+    fun testSetTetheringInterfaceMode_disableEnableEthernet() {
+        val listener = EthernetStateListener()
+        addInterfaceStateListener(listener)
+
+        val iface = createInterface()
+        requestTetheredInterface().expectOnAvailable()
+        listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
+
+        // (b/234743836): Currently the state of server mode interfaces always returns true due to
+        // that interface state for server mode interfaces is not tracked properly.
+        // So we do not get any state change when disabling ethernet.
+        setEthernetEnabled(false)
+        listener.assertNoCallback()
+
+        // When ethernet is disabled, change interface mode will not bring the interface up.
+        releaseTetheredInterface()
+        listener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+
+        // When ethernet is re-enabled, interface will be brought up.
+        setEthernetEnabled(true)
+        listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+    }
 }