Merge "Add mainline module controller for tethering" into main
diff --git a/bpf/headers/include/bpf_helpers.h b/bpf/headers/include/bpf_helpers.h
index c94f1d8..ca0ca76 100644
--- a/bpf/headers/include/bpf_helpers.h
+++ b/bpf/headers/include/bpf_helpers.h
@@ -383,13 +383,17 @@
 static int (*bpf_probe_read_user_str)(void* dst, int size, const void* unsafe_ptr) = (void*) BPF_FUNC_probe_read_user_str;
 static unsigned long long (*bpf_ktime_get_ns)(void) = (void*) BPF_FUNC_ktime_get_ns;
 static unsigned long long (*bpf_ktime_get_boot_ns)(void) = (void*)BPF_FUNC_ktime_get_boot_ns;
-static int (*bpf_trace_printk)(const char* fmt, int fmt_size, ...) = (void*) BPF_FUNC_trace_printk;
 static unsigned long long (*bpf_get_current_pid_tgid)(void) = (void*) BPF_FUNC_get_current_pid_tgid;
 static unsigned long long (*bpf_get_current_uid_gid)(void) = (void*) BPF_FUNC_get_current_uid_gid;
 static unsigned long long (*bpf_get_smp_processor_id)(void) = (void*) BPF_FUNC_get_smp_processor_id;
 static long (*bpf_get_stackid)(void* ctx, void* map, uint64_t flags) = (void*) BPF_FUNC_get_stackid;
 static long (*bpf_get_current_comm)(void* buf, uint32_t buf_size) = (void*) BPF_FUNC_get_current_comm;
 
+static int (*bpf_trace_printk)(const char* fmt, int fmt_size, ...) = (void*) BPF_FUNC_trace_printk;
+#define bpf_printf(s, n...) bpf_trace_printk(s, sizeof(s), ## n)
+// Note: bpf only supports up to 3 arguments, log via: bpf_printf("msg %d %d %d", 1, 2, 3);
+// and read via the blocking: sudo cat /sys/kernel/debug/tracing/trace_pipe
+
 #define DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv,  \
                             min_loader, max_loader, opt, selinux, pindir, ignore_eng,    \
                             ignore_user, ignore_userdebug)                               \
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index 59712b1..c058433 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -604,6 +604,9 @@
     if (type == BPF_MAP_TYPE_DEVMAP || type == BPF_MAP_TYPE_DEVMAP_HASH)
         desired_map_flags |= BPF_F_RDONLY_PROG;
 
+    if (type == BPF_MAP_TYPE_LPM_TRIE)
+        desired_map_flags |= BPF_F_NO_PREALLOC;
+
     // The .h file enforces that this is a power of two, and page size will
     // also always be a power of two, so this logic is actually enough to
     // force it to be a multiple of the page size, as required by the kernel.
@@ -779,13 +782,17 @@
               .key_size = md[i].key_size,
               .value_size = md[i].value_size,
               .max_entries = max_entries,
-              .map_flags = md[i].map_flags,
+              .map_flags = md[i].map_flags | (type == BPF_MAP_TYPE_LPM_TRIE ? BPF_F_NO_PREALLOC : 0),
             };
             if (isAtLeastKernelVersion(4, 15, 0))
                 strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
             fd.reset(bpf(BPF_MAP_CREATE, req));
             saved_errno = errno;
-            ALOGD("bpf_create_map name %s, ret: %d", mapNames[i].c_str(), fd.get());
+            if (fd.ok()) {
+              ALOGD("bpf_create_map[%s] -> %d", mapNames[i].c_str(), fd.get());
+            } else {
+              ALOGE("bpf_create_map[%s] -> %d errno:%d", mapNames[i].c_str(), fd.get(), saved_errno);
+            }
         }
 
         if (!fd.ok()) return -saved_errno;
@@ -839,7 +846,8 @@
 
         int mapId = bpfGetFdMapId(fd);
         if (mapId == -1) {
-            ALOGE("bpfGetFdMapId failed, ret: %d [%d]", mapId, errno);
+            if (isAtLeastKernelVersion(4, 14, 0))
+                ALOGE("bpfGetFdMapId failed, ret: %d [%d]", mapId, errno);
         } else {
             ALOGI("map %s id %d", mapPinLoc.c_str(), mapId);
         }
@@ -1007,17 +1015,20 @@
                   cs[i].name.c_str(), fd.get(), (!fd.ok() ? std::strerror(errno) : "no error"));
 
             if (!fd.ok()) {
-                vector<string> lines = android::base::Split(log_buf.data(), "\n");
+                if (log_buf.size()) {
+                    vector<string> lines = android::base::Split(log_buf.data(), "\n");
 
-                ALOGW("BPF_PROG_LOAD - BEGIN log_buf contents:");
-                for (const auto& line : lines) ALOGW("%s", line.c_str());
-                ALOGW("BPF_PROG_LOAD - END log_buf contents.");
+                    ALOGW("BPF_PROG_LOAD - BEGIN log_buf contents:");
+                    for (const auto& line : lines) ALOGW("%s", line.c_str());
+                    ALOGW("BPF_PROG_LOAD - END log_buf contents.");
+                }
 
                 if (cs[i].prog_def->optional) {
-                    ALOGW("failed program is marked optional - continuing...");
+                    ALOGW("failed program %s is marked optional - continuing...",
+                          cs[i].name.c_str());
                     continue;
                 }
-                ALOGE("non-optional program failed to load.");
+                ALOGE("non-optional program %s failed to load.", cs[i].name.c_str());
             }
         }
 
diff --git a/bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR3.rc b/bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR3.rc
new file mode 100644
index 0000000..8f3f462
--- /dev/null
+++ b/bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR3.rc
@@ -0,0 +1,11 @@
+on load_bpf_programs
+    exec_start bpfloader
+
+service bpfloader /system/bin/netbpfload
+    capabilities CHOWN SYS_ADMIN NET_ADMIN
+    group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+    user root
+    rlimit memlock 1073741824 1073741824
+    oneshot
+    reboot_on_failure reboot,bpfloader-failed
+    updatable
diff --git a/bpf/loader/initrc-doc/bpfloader-sdk35-15-V.rc b/bpf/loader/initrc-doc/bpfloader-sdk35-15-V.rc
new file mode 100644
index 0000000..066cfc8
--- /dev/null
+++ b/bpf/loader/initrc-doc/bpfloader-sdk35-15-V.rc
@@ -0,0 +1,8 @@
+on load_bpf_programs
+    exec_start bpfloader
+
+service bpfloader /system/bin/false
+    user root
+    oneshot
+    reboot_on_failure reboot,netbpfload-missing
+    updatable
diff --git a/netd/Android.bp b/bpf/netd/Android.bp
similarity index 100%
rename from netd/Android.bp
rename to bpf/netd/Android.bp
diff --git a/netd/BpfBaseTest.cpp b/bpf/netd/BpfBaseTest.cpp
similarity index 100%
rename from netd/BpfBaseTest.cpp
rename to bpf/netd/BpfBaseTest.cpp
diff --git a/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp
similarity index 100%
rename from netd/BpfHandler.cpp
rename to bpf/netd/BpfHandler.cpp
diff --git a/netd/BpfHandler.h b/bpf/netd/BpfHandler.h
similarity index 100%
rename from netd/BpfHandler.h
rename to bpf/netd/BpfHandler.h
diff --git a/netd/BpfHandlerTest.cpp b/bpf/netd/BpfHandlerTest.cpp
similarity index 100%
rename from netd/BpfHandlerTest.cpp
rename to bpf/netd/BpfHandlerTest.cpp
diff --git a/netd/NetdUpdatable.cpp b/bpf/netd/NetdUpdatable.cpp
similarity index 100%
rename from netd/NetdUpdatable.cpp
rename to bpf/netd/NetdUpdatable.cpp
diff --git a/netd/include/NetdUpdatablePublic.h b/bpf/netd/include/NetdUpdatablePublic.h
similarity index 100%
rename from netd/include/NetdUpdatablePublic.h
rename to bpf/netd/include/NetdUpdatablePublic.h
diff --git a/netd/libnetd_updatable.map.txt b/bpf/netd/libnetd_updatable.map.txt
similarity index 100%
rename from netd/libnetd_updatable.map.txt
rename to bpf/netd/libnetd_updatable.map.txt
diff --git a/bpf/progs/Android.bp b/bpf/progs/Android.bp
index f6717c5..dc1f56d 100644
--- a/bpf/progs/Android.bp
+++ b/bpf/progs/Android.bp
@@ -47,8 +47,8 @@
         "com.android.tethering",
     ],
     visibility: [
+        "//packages/modules/Connectivity/bpf/netd",
         "//packages/modules/Connectivity/DnsResolver",
-        "//packages/modules/Connectivity/netd",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/service/native/libs/libclat",
         "//packages/modules/Connectivity/Tethering",
diff --git a/bpf/progs/dscpPolicy.c b/bpf/progs/dscpPolicy.c
index baabb02..39f2961 100644
--- a/bpf/progs/dscpPolicy.c
+++ b/bpf/progs/dscpPolicy.c
@@ -23,7 +23,10 @@
 #define ECN_MASK 3
 #define UPDATE_TOS(dscp, tos) ((dscp) << 2) | ((tos) & ECN_MASK)
 
-DEFINE_BPF_MAP_GRW(socket_policy_cache_map, HASH, uint64_t, RuleEntry, CACHE_MAP_SIZE, AID_SYSTEM)
+// The cache is never read nor written by userspace and is indexed by socket cookie % CACHE_MAP_SIZE
+#define CACHE_MAP_SIZE 32  // should be a power of two so we can % cheaply
+DEFINE_BPF_MAP_GRO(socket_policy_cache_map, PERCPU_ARRAY, uint32_t, RuleEntry, CACHE_MAP_SIZE,
+                   AID_SYSTEM)
 
 DEFINE_BPF_MAP_GRW(ipv4_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES, AID_SYSTEM)
 DEFINE_BPF_MAP_GRW(ipv6_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES, AID_SYSTEM)
@@ -43,6 +46,8 @@
     uint64_t cookie = bpf_get_socket_cookie(skb);
     if (!cookie) return;
 
+    uint32_t cacheid = cookie % CACHE_MAP_SIZE;
+
     __be16 sport = 0;
     uint16_t dport = 0;
     uint8_t protocol = 0;  // TODO: Use are reserved value? Or int (-1) and cast to uint below?
@@ -105,7 +110,8 @@
             return;
     }
 
-    RuleEntry* existing_rule = bpf_socket_policy_cache_map_lookup_elem(&cookie);
+    // this array lookup cannot actually fail
+    RuleEntry* existing_rule = bpf_socket_policy_cache_map_lookup_elem(&cacheid);
 
     if (existing_rule &&
         v6_equal(src_ip, existing_rule->src_ip) &&
@@ -192,7 +198,7 @@
     };
 
     // Update cache with found policy.
-    bpf_socket_policy_cache_map_update_elem(&cookie, &value, BPF_ANY);
+    bpf_socket_policy_cache_map_update_elem(&cacheid, &value, BPF_ANY);
 
     if (new_dscp < 0) return;
 
diff --git a/bpf/progs/dscpPolicy.h b/bpf/progs/dscpPolicy.h
index dc431a7..6a6b711 100644
--- a/bpf/progs/dscpPolicy.h
+++ b/bpf/progs/dscpPolicy.h
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#define CACHE_MAP_SIZE 1024
 #define MAX_POLICIES 16
 
 #define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 45cbb78..4c6d8ba 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -147,3 +147,12 @@
   bug: "335680025"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "tethering_active_sessions_metrics"
+  is_exported: true
+  namespace: "android_core_networking"
+  description: "Flag for collecting tethering active sessions metrics"
+  bug: "354619988"
+  is_fixed_read_only: true
+}
diff --git a/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
index 450f380..241d5fa 100644
--- a/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
+++ b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
@@ -39,7 +39,7 @@
                                          uint32_t poll_ms) {
   // Always schedule another run of ourselves to recursively poll periodically.
   // The task runner is sequential so these can't run on top of each other.
-  runner->PostDelayedTask([=]() { PollAndSchedule(runner, poll_ms); }, poll_ms);
+  runner->PostDelayedTask([=, this]() { PollAndSchedule(runner, poll_ms); }, poll_ms);
 
   if (mMutex.try_lock()) {
     ConsumeAllLocked();
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 53b1eb2..9af250b 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -4439,9 +4439,8 @@
         pw.println();
         pw.println("Multicast routing supported: " +
                 (mMulticastRoutingCoordinatorService != null));
-
-        pw.println();
         pw.println("Background firewall chain enabled: " + mBackgroundFirewallChainEnabled);
+        pw.println("IngressToVpnAddressFiltering: " + mIngressToVpnAddressFiltering);
     }
 
     private void dumpNetworks(IndentingPrintWriter pw) {
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index 0426ace..04ce2fa 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -22,6 +22,7 @@
 import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
 import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.DNS_RESOLVER_MODULE_ID;
 import static com.android.net.module.util.FeatureVersions.MODULE_MASK;
 import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID;
 import static com.android.net.module.util.FeatureVersions.VERSION_MASK;
@@ -68,7 +69,8 @@
     @VisibleForTesting
     public static void resetPackageVersionCacheForTest() {
         sPackageVersion = -1;
-        sModuleVersion = -1;
+        sTetheringModuleVersion = -1;
+        sResolvModuleVersion = -1;
         sNetworkStackModuleVersion = -1;
     }
 
@@ -243,23 +245,23 @@
         }
     }
 
-    // Guess the tethering module name based on the package prefix of the connectivity resources
-    // Take the resource package name, cut it before "connectivity" and append "tethering".
+    // Guess an APEX module name based on the package prefix of the connectivity resources
+    // Take the resource package name, cut it before "connectivity" and append the module name.
     // Then resolve that package version number with packageManager.
-    // If that fails retry by appending "go.tethering" instead
-    private static long resolveTetheringModuleVersion(@NonNull Context context)
+    // If that fails retry by appending "go.<moduleName>" instead.
+    private static long resolveApexModuleVersion(@NonNull Context context, String moduleName)
             throws PackageManager.NameNotFoundException {
         final String pkgPrefix = resolvePkgPrefix(context);
         final PackageManager packageManager = context.getPackageManager();
         try {
-            return packageManager.getPackageInfo(pkgPrefix + "tethering",
+            return packageManager.getPackageInfo(pkgPrefix + moduleName,
                     PackageManager.MATCH_APEX).getLongVersionCode();
         } catch (PackageManager.NameNotFoundException e) {
             Log.d(TAG, "Device is using go modules");
             // fall through
         }
 
-        return packageManager.getPackageInfo(pkgPrefix + "go.tethering",
+        return packageManager.getPackageInfo(pkgPrefix + "go." + moduleName,
                 PackageManager.MATCH_APEX).getLongVersionCode();
     }
 
@@ -274,19 +276,35 @@
         return connResourcesPackage.substring(0, pkgPrefixLen);
     }
 
-    private static volatile long sModuleVersion = -1;
+    private static volatile long sTetheringModuleVersion = -1;
+
     private static long getTetheringModuleVersion(@NonNull Context context) {
-        if (sModuleVersion >= 0) return sModuleVersion;
+        if (sTetheringModuleVersion >= 0) return sTetheringModuleVersion;
 
         try {
-            sModuleVersion = resolveTetheringModuleVersion(context);
+            sTetheringModuleVersion = resolveApexModuleVersion(context, "tethering");
         } catch (PackageManager.NameNotFoundException e) {
             // It's expected to fail tethering module version resolution on the devices with
             // flattened apex
             Log.e(TAG, "Failed to resolve tethering module version: " + e);
             return DEFAULT_PACKAGE_VERSION;
         }
-        return sModuleVersion;
+        return sTetheringModuleVersion;
+    }
+
+    private static volatile long sResolvModuleVersion = -1;
+    private static long getResolvModuleVersion(@NonNull Context context) {
+        if (sResolvModuleVersion >= 0) return sResolvModuleVersion;
+
+        try {
+            sResolvModuleVersion = resolveApexModuleVersion(context, "resolv");
+        } catch (PackageManager.NameNotFoundException e) {
+            // It's expected to fail resolv module version resolution on the devices with
+            // flattened apex
+            Log.e(TAG, "Failed to resolve resolv module version: " + e);
+            return DEFAULT_PACKAGE_VERSION;
+        }
+        return sResolvModuleVersion;
     }
 
     private static volatile long sNetworkStackModuleVersion = -1;
@@ -342,6 +360,8 @@
             moduleVersion = getTetheringModuleVersion(context);
         } else if (moduleId == NETWORK_STACK_MODULE_ID) {
             moduleVersion = getNetworkStackModuleVersion(context);
+        } else if (moduleId == DNS_RESOLVER_MODULE_ID) {
+            moduleVersion = getResolvModuleVersion(context);
         } else {
             throw new IllegalArgumentException("Unknown module " + moduleId);
         }
diff --git a/staticlibs/device/com/android/net/module/util/FeatureVersions.java b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
index d5f8124..d0cf3fd 100644
--- a/staticlibs/device/com/android/net/module/util/FeatureVersions.java
+++ b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
@@ -37,6 +37,7 @@
     public static final long VERSION_MASK = 0x00F_FFFF_FFFFL;
     public static final long CONNECTIVITY_MODULE_ID = 0x01L << MODULE_SHIFT;
     public static final long NETWORK_STACK_MODULE_ID = 0x02L << MODULE_SHIFT;
+    public static final long DNS_RESOLVER_MODULE_ID = 0x03L << MODULE_SHIFT;
     // CLAT_ADDRESS_TRANSLATE is a feature of the network stack, which doesn't throw when system
     // try to add a NAT-T keepalive packet filter with v6 address, introduced in version
     // M-2023-Sept on July 3rd, 2023.
@@ -48,4 +49,11 @@
     // by BPF for the given uid and conditions, introduced in version M-2024-Feb on Nov 6, 2023.
     public static final long FEATURE_IS_UID_NETWORKING_BLOCKED =
             CONNECTIVITY_MODULE_ID + 34_14_00_000L;
+
+    // DDR is a feature implemented across NetworkStack, ConnectivityService and DnsResolver.
+    // The flag that enables this feature is in NetworkStack.
+    public static final long FEATURE_DDR_IN_CONNECTIVITY =
+            CONNECTIVITY_MODULE_ID + 35_11_00_000L;
+    public static final long FEATURE_DDR_IN_DNSRESOLVER =
+            DNS_RESOLVER_MODULE_ID + 35_11_00_000L;
 }
diff --git a/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java b/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java
index f6bee69..28c33f3 100644
--- a/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java
+++ b/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java
@@ -16,6 +16,8 @@
 
 package com.android.net.module.util;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+
 import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.Nullable;
@@ -112,47 +114,9 @@
      *
      * @return {@link LocationPermissionCheckStatus} the result of the location permission check.
      */
-    public @LocationPermissionCheckStatus int checkLocationPermissionWithDetailInfo(
+    @VisibleForTesting(visibility = PRIVATE)
+    public @LocationPermissionCheckStatus int checkLocationPermissionInternal(
             String pkgName, @Nullable String featureId, int uid, @Nullable String message) {
-        final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message);
-        switch (result) {
-            case ERROR_LOCATION_MODE_OFF:
-                Log.e(TAG, "Location mode is disabled for the device");
-                break;
-            case ERROR_LOCATION_PERMISSION_MISSING:
-                Log.e(TAG, "UID " + uid + " has no location permission");
-                break;
-        }
-        return result;
-    }
-
-    /**
-     * Enforce the caller has location permission.
-     *
-     * This API determines if the location mode enabled for the caller and the caller has
-     * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
-     * SecurityException is thrown if the caller has no permission or the location mode is disabled.
-     *
-     * @param pkgName package name of the application requesting access
-     * @param featureId The feature in the package
-     * @param uid The uid of the package
-     * @param message A message describing why the permission was checked. Only needed if this is
-     *                not inside of a two-way binder call from the data receiver
-     */
-    public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid,
-            @Nullable String message) throws SecurityException {
-        final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message);
-
-        switch (result) {
-            case ERROR_LOCATION_MODE_OFF:
-                throw new SecurityException("Location mode is disabled for the device");
-            case ERROR_LOCATION_PERMISSION_MISSING:
-                throw new SecurityException("UID " + uid + " has no location permission");
-        }
-    }
-
-    private int checkLocationPermissionInternal(String pkgName, @Nullable String featureId,
-            int uid, @Nullable String message) {
         checkPackage(uid, pkgName);
 
         // Apps with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD, NETWORK_STACK & MAINLINE_NETWORK_STACK
@@ -221,7 +185,7 @@
     /**
      * Retrieves a handle to LocationManager (if not already done) and check if location is enabled.
      */
-    public boolean isLocationModeEnabled() {
+    private boolean isLocationModeEnabled() {
         final LocationManager LocationManager = mContext.getSystemService(LocationManager.class);
         try {
             return LocationManager.isLocationEnabledForUser(UserHandle.of(
@@ -278,7 +242,7 @@
     /**
      * Returns true if the |uid| holds NETWORK_SETTINGS permission.
      */
-    public boolean checkNetworkSettingsPermission(int uid) {
+    private boolean checkNetworkSettingsPermission(int uid) {
         return getUidPermission(android.Manifest.permission.NETWORK_SETTINGS, uid)
                 == PackageManager.PERMISSION_GRANTED;
     }
@@ -286,7 +250,7 @@
     /**
      * Returns true if the |uid| holds NETWORK_SETUP_WIZARD permission.
      */
-    public boolean checkNetworkSetupWizardPermission(int uid) {
+    private boolean checkNetworkSetupWizardPermission(int uid) {
         return getUidPermission(android.Manifest.permission.NETWORK_SETUP_WIZARD, uid)
                 == PackageManager.PERMISSION_GRANTED;
     }
@@ -294,7 +258,7 @@
     /**
      * Returns true if the |uid| holds NETWORK_STACK permission.
      */
-    public boolean checkNetworkStackPermission(int uid) {
+    private boolean checkNetworkStackPermission(int uid) {
         return getUidPermission(android.Manifest.permission.NETWORK_STACK, uid)
                 == PackageManager.PERMISSION_GRANTED;
     }
@@ -302,7 +266,7 @@
     /**
      * Returns true if the |uid| holds MAINLINE_NETWORK_STACK permission.
      */
-    public boolean checkMainlineNetworkStackPermission(int uid) {
+    private boolean checkMainlineNetworkStackPermission(int uid) {
         return getUidPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, uid)
                 == PackageManager.PERMISSION_GRANTED;
     }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index 9fb61d9..a5af09b 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -24,6 +24,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.DNS_RESOLVER_MODULE_ID;
 import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID;
 
 import static org.junit.Assert.assertEquals;
@@ -77,7 +78,9 @@
     private static final int TEST_DEFAULT_FLAG_VALUE = 0;
     private static final int TEST_MAX_FLAG_VALUE = 1000;
     private static final int TEST_MIN_FLAG_VALUE = 100;
-    private static final long TEST_PACKAGE_VERSION = 290000000;
+    private static final long TEST_PACKAGE_VERSION = 290500000;
+    private static final long TEST_GO_PACKAGE_VERSION = 290000000;  // Not updated
+    private static final long TEST_RESOLV_PACKAGE_VERSION = 290300000;  // Updated, but older.
     private static final String TEST_PACKAGE_NAME = "test.package.name";
     // The APEX name is the name of the APEX module, as in android.content.pm.ModuleInfo, and is
     // used for its mount point in /apex. APEX packages are actually APKs with a different
@@ -85,14 +88,18 @@
     // that manifest, and is reflected in android.content.pm.ApplicationInfo. Contrary to the APEX
     // (module) name, different package names are typically used to identify the organization that
     // built and signed the APEX modules.
-    private static final String TEST_APEX_PACKAGE_NAME = "com.prefix.android.tethering";
-    private static final String TEST_GO_APEX_PACKAGE_NAME = "com.prefix.android.go.tethering";
+    private static final String TEST_TETHERING_PACKAGE_NAME = "com.prefix.android.tethering";
+    private static final String TEST_GO_TETHERING_PACKAGE_NAME = "com.prefix.android.go.tethering";
+    private static final String TEST_RESOLV_PACKAGE_NAME = "com.prefix.android.resolv";
+    private static final String TEST_GO_RESOLV_PACKAGE_NAME = "com.prefix.android.go.resolv";
     private static final String TEST_CONNRES_PACKAGE_NAME =
             "com.prefix.android.connectivity.resources";
     private static final String TEST_NETWORKSTACK_NAME = "com.prefix.android.networkstack";
     private static final String TEST_GO_NETWORKSTACK_NAME = "com.prefix.android.go.networkstack";
     private final PackageInfo mPackageInfo = new PackageInfo();
-    private final PackageInfo mApexPackageInfo = new PackageInfo();
+    private final PackageInfo mGoApexPackageInfo = new PackageInfo();
+    private final PackageInfo mTetheringApexPackageInfo = new PackageInfo();
+    private final PackageInfo mResolvApexPackageInfo = new PackageInfo();
     private MockitoSession mSession;
 
     @Mock private Context mContext;
@@ -105,13 +112,22 @@
         mSession = mockitoSession().spyStatic(DeviceConfig.class).startMocking();
 
         mPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
-        mApexPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        mTetheringApexPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        mGoApexPackageInfo.setLongVersionCode(TEST_GO_PACKAGE_VERSION);
+        mResolvApexPackageInfo.setLongVersionCode(TEST_RESOLV_PACKAGE_VERSION);
 
         doReturn(mPm).when(mContext).getPackageManager();
         doReturn(TEST_PACKAGE_NAME).when(mContext).getPackageName();
         doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt());
         doReturn(mPackageInfo).when(mPm).getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt());
-        doReturn(mApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_APEX_PACKAGE_NAME), anyInt());
+        doReturn(mTetheringApexPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_TETHERING_PACKAGE_NAME), anyInt());
+        doReturn(mResolvApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_RESOLV_PACKAGE_NAME),
+                anyInt());
+        doReturn(mGoApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_GO_TETHERING_PACKAGE_NAME),
+                anyInt());
+        doReturn(mGoApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_GO_RESOLV_PACKAGE_NAME),
+                anyInt());
 
         doReturn(mResources).when(mContext).getResources();
 
@@ -342,9 +358,9 @@
     @Test
     public void testFeatureIsEnabledOnGo() throws Exception {
         doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(
-                eq(TEST_APEX_PACKAGE_NAME), anyInt());
-        doReturn(mApexPackageInfo).when(mPm).getPackageInfo(
-                eq(TEST_GO_APEX_PACKAGE_NAME), anyInt());
+                eq(TEST_TETHERING_PACKAGE_NAME), anyInt());
+        doReturn(mTetheringApexPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_GO_TETHERING_PACKAGE_NAME), anyInt());
         doReturn("0").when(() -> DeviceConfig.getProperty(
                 NAMESPACE_CONNECTIVITY, TEST_EXPERIMENT_FLAG));
         doReturn("0").when(() -> DeviceConfig.getProperty(
@@ -483,6 +499,31 @@
                 mContext, 889900000L + CONNECTIVITY_MODULE_ID));
     }
 
+
+    @Test
+    public void testIsFeatureSupported_resolvFeature() throws Exception {
+        assertTrue(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_RESOLV_PACKAGE_VERSION + DNS_RESOLVER_MODULE_ID));
+        // Return false because feature requires a future version.
+        assertFalse(DeviceConfigUtils.isFeatureSupported(
+                mContext, 889900000L + DNS_RESOLVER_MODULE_ID));
+    }
+
+    @Test
+    public void testIsFeatureSupported_goResolvFeature() throws Exception {
+        doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(eq(TEST_RESOLV_PACKAGE_NAME),
+                anyInt());
+        doReturn(mGoApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_GO_RESOLV_PACKAGE_NAME),
+                anyInt());
+        assertFalse(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_RESOLV_PACKAGE_VERSION + DNS_RESOLVER_MODULE_ID));
+        assertTrue(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_GO_PACKAGE_VERSION + DNS_RESOLVER_MODULE_ID));
+        // Return false because feature requires a future version.
+        assertFalse(DeviceConfigUtils.isFeatureSupported(
+                mContext, 889900000L + DNS_RESOLVER_MODULE_ID));
+    }
+
     @Test
     public void testIsFeatureSupported_illegalModule() throws Exception {
         assertThrows(IllegalArgumentException.class,
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java
index 3239244..c8f8656 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java
@@ -211,7 +211,7 @@
         setupTestCase();
 
         final int result =
-                mChecker.checkLocationPermissionWithDetailInfo(
+                mChecker.checkLocationPermissionInternal(
                         TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
         assertEquals(LocationPermissionChecker.SUCCEEDED, result);
     }
@@ -228,7 +228,7 @@
         setupTestCase();
 
         final int result =
-                mChecker.checkLocationPermissionWithDetailInfo(
+                mChecker.checkLocationPermissionInternal(
                         TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
         assertEquals(LocationPermissionChecker.SUCCEEDED, result);
     }
@@ -243,7 +243,7 @@
         setupTestCase();
 
         assertThrows(SecurityException.class,
-                () -> mChecker.checkLocationPermissionWithDetailInfo(
+                () -> mChecker.checkLocationPermissionInternal(
                         TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
     }
 
@@ -254,7 +254,7 @@
         setupTestCase();
 
         final int result =
-                mChecker.checkLocationPermissionWithDetailInfo(
+                mChecker.checkLocationPermissionInternal(
                         TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
         assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result);
     }
@@ -270,7 +270,7 @@
         setupTestCase();
 
         final int result =
-                mChecker.checkLocationPermissionWithDetailInfo(
+                mChecker.checkLocationPermissionInternal(
                         TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
         assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result);
         verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString());
@@ -287,7 +287,7 @@
         setupTestCase();
 
         final int result =
-                mChecker.checkLocationPermissionWithDetailInfo(
+                mChecker.checkLocationPermissionInternal(
                         TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
         assertEquals(LocationPermissionChecker.ERROR_LOCATION_MODE_OFF, result);
     }
@@ -301,7 +301,7 @@
         setupTestCase();
 
         final int result =
-                mChecker.checkLocationPermissionWithDetailInfo(
+                mChecker.checkLocationPermissionInternal(
                         TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
         assertEquals(LocationPermissionChecker.SUCCEEDED, result);
     }
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 61fa5d0..93b2f70 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -125,6 +125,7 @@
 import com.android.server.thread.openthread.IOtDaemon;
 import com.android.server.thread.openthread.IOtDaemonCallback;
 import com.android.server.thread.openthread.IOtStatusReceiver;
+import com.android.server.thread.openthread.InfraLinkState;
 import com.android.server.thread.openthread.Ipv6AddressInfo;
 import com.android.server.thread.openthread.MeshcopTxtAttributes;
 import com.android.server.thread.openthread.OnMeshPrefixConfig;
@@ -214,6 +215,7 @@
     private boolean mForceStopOtDaemonEnabled;
 
     private OtDaemonConfiguration mOtDaemonConfig;
+    private InfraLinkState mInfraLinkState;
 
     @VisibleForTesting
     ThreadNetworkControllerService(
@@ -238,11 +240,8 @@
         mInfraIfController = infraIfController;
         mUpstreamNetworkRequest = newUpstreamNetworkRequest();
         mNetworkToInterface = new HashMap<Network, String>();
-        mOtDaemonConfig =
-                new OtDaemonConfiguration.Builder()
-                        .setIsBorderRoutingEnabled(true)
-                        .setInfraInterfaceName(null)
-                        .build();
+        mOtDaemonConfig = new OtDaemonConfiguration.Builder().build();
+        mInfraLinkState = new InfraLinkState.Builder().build();
         mPersistentSettings = persistentSettings;
         mNsdPublisher = nsdPublisher;
         mUserManager = userManager;
@@ -571,6 +570,7 @@
                 }
             }
         }
+        // TODO: set the configuration at ot-daemon
     }
 
     @Override
@@ -1232,51 +1232,45 @@
         }
     }
 
-    private void configureBorderRouter(OtDaemonConfiguration otDaemonConfig) {
-        if (mOtDaemonConfig.equals(otDaemonConfig)) {
+    private void setInfraLinkState(InfraLinkState infraLinkState) {
+        if (mInfraLinkState.equals(infraLinkState)) {
             return;
         }
-        Log.i(TAG, "Configuring Border Router: " + mOtDaemonConfig + " -> " + otDaemonConfig);
-        mOtDaemonConfig = otDaemonConfig;
+        Log.i(TAG, "Infra link state changed: " + mInfraLinkState + " -> " + infraLinkState);
+        mInfraLinkState = infraLinkState;
         ParcelFileDescriptor infraIcmp6Socket = null;
-        if (mOtDaemonConfig.infraInterfaceName != null) {
+        if (mInfraLinkState.interfaceName != null) {
             try {
                 infraIcmp6Socket =
-                        mInfraIfController.createIcmp6Socket(mOtDaemonConfig.infraInterfaceName);
+                        mInfraIfController.createIcmp6Socket(mInfraLinkState.interfaceName);
             } catch (IOException e) {
                 Log.i(TAG, "Failed to create ICMPv6 socket on infra network interface", e);
             }
         }
         try {
             getOtDaemon()
-                    .setConfiguration(
-                            mOtDaemonConfig,
+                    .setInfraLinkState(
+                            mInfraLinkState,
                             infraIcmp6Socket,
-                            new ConfigureBorderRouterStatusReceiver());
+                            new setInfraLinkStateStatusReceiver());
         } catch (RemoteException | ThreadNetworkException e) {
             Log.w(TAG, "Failed to configure border router " + mOtDaemonConfig, e);
         }
     }
 
     private void enableBorderRouting(String infraIfName) {
-        OtDaemonConfiguration otDaemonConfig =
-                newOtDaemonConfigBuilder(mOtDaemonConfig)
-                        .setIsBorderRoutingEnabled(true)
-                        .setInfraInterfaceName(infraIfName)
-                        .build();
+        InfraLinkState infraLinkState =
+                newInfraLinkStateBuilder(mInfraLinkState).setInterfaceName(infraIfName).build();
         Log.i(TAG, "Enable border routing on AIL: " + infraIfName);
-        configureBorderRouter(otDaemonConfig);
+        setInfraLinkState(infraLinkState);
     }
 
     private void disableBorderRouting() {
         mUpstreamNetwork = null;
-        OtDaemonConfiguration otDaemonConfig =
-                newOtDaemonConfigBuilder(mOtDaemonConfig)
-                        .setIsBorderRoutingEnabled(false)
-                        .setInfraInterfaceName(null)
-                        .build();
+        InfraLinkState infraLinkState =
+                newInfraLinkStateBuilder(mInfraLinkState).setInterfaceName(null).build();
         Log.i(TAG, "Disabling border routing");
-        configureBorderRouter(otDaemonConfig);
+        setInfraLinkState(infraLinkState);
     }
 
     private void handleThreadInterfaceStateChanged(boolean isUp) {
@@ -1378,10 +1372,12 @@
     }
 
     private static OtDaemonConfiguration.Builder newOtDaemonConfigBuilder(
-            OtDaemonConfiguration brConfig) {
-        return new OtDaemonConfiguration.Builder()
-                .setIsBorderRoutingEnabled(brConfig.isBorderRoutingEnabled)
-                .setInfraInterfaceName(brConfig.infraInterfaceName);
+            OtDaemonConfiguration config) {
+        return new OtDaemonConfiguration.Builder();
+    }
+
+    private static InfraLinkState.Builder newInfraLinkStateBuilder(InfraLinkState infraLinkState) {
+        return new InfraLinkState.Builder().setInterfaceName(infraLinkState.interfaceName);
     }
 
     private static final class CallbackMetadata {
@@ -1405,8 +1401,9 @@
         }
     }
 
-    private static final class ConfigureBorderRouterStatusReceiver extends IOtStatusReceiver.Stub {
-        public ConfigureBorderRouterStatusReceiver() {}
+    private static final class setOtDaemonConfigurationStatusReceiver
+            extends IOtStatusReceiver.Stub {
+        public setOtDaemonConfigurationStatusReceiver() {}
 
         @Override
         public void onSuccess() {
@@ -1415,7 +1412,21 @@
 
         @Override
         public void onError(int i, String s) {
-            Log.w(TAG, String.format("Failed to configure border router: %d %s", i, s));
+            Log.w(TAG, String.format("Failed to set configurations: %d %s", i, s));
+        }
+    }
+
+    private static final class setInfraLinkStateStatusReceiver extends IOtStatusReceiver.Stub {
+        public setInfraLinkStateStatusReceiver() {}
+
+        @Override
+        public void onSuccess() {
+            Log.i(TAG, "Set the infra link state successfully");
+        }
+
+        @Override
+        public void onError(int i, String s) {
+            Log.w(TAG, String.format("Failed to set the infra link state: %d %s", i, s));
         }
     }