Merge "BpfHandler.cpp: change LOG_TAG "BpfHandler" to "NetdUpdatable"" into main
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index 02ef8b7..c726dab 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -118,7 +118,7 @@
      * @hide
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = 35)  // TODO: change to VANILLA_ICE_CREAM.
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     public static final long NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION = 333340911L;
 
     private ConnectivityCompatChanges() {
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index ccb6acb..0e1da93 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -306,6 +306,7 @@
 
         if (bad && !isGSI()) {
             ALOGE("Unsupported kernel version (%07x).", kernelVersion());
+            sleep(60);
         }
     }
 
diff --git a/service/Android.bp b/service/Android.bp
index 1d74efc..1dd09a9 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -188,7 +188,7 @@
         "androidx.annotation_annotation",
         "connectivity-net-module-utils-bpf",
         "connectivity_native_aidl_interface-lateststable-java",
-        "dnsresolver_aidl_interface-V14-java",
+        "dnsresolver_aidl_interface-V15-java",
         "modules-utils-shell-command-handler",
         "net-utils-device-common",
         "net-utils-device-common-ip",
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 39ea286..fff809e 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -554,6 +554,8 @@
      * default network for each app.
      * Order ints passed to netd must be in the 0~999 range. Larger values code for
      * a lower priority, see {@link NativeUidRangeConfig}.
+     * Note that only the highest priority preference is applied if the uid is the target of
+     * multiple preferences.
      *
      * Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
      * The default request uses PREFERENCE_ORDER_DEFAULT.
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 74ce3e9..634a8fa 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -524,31 +524,6 @@
         }
     }
 
-    private void maybeCleanUp(ParcelFileDescriptor tunFd, ParcelFileDescriptor readSock6,
-            ParcelFileDescriptor writeSock6) {
-        if (tunFd != null) {
-            try {
-                tunFd.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Fail to close tun file descriptor " + e);
-            }
-        }
-        if (readSock6 != null) {
-            try {
-                readSock6.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Fail to close read socket " + e);
-            }
-        }
-        if (writeSock6 != null) {
-            try {
-                writeSock6.close();
-            } catch (IOException e) {
-                Log.e(TAG, "Fail to close write socket " + e);
-            }
-        }
-    }
-
     private void tagSocketAsClat(long cookie) throws IOException {
         if (mCookieTagMap == null) {
             throw new IOException("Cookie tag map is not initialized");
@@ -604,39 +579,6 @@
             throw new IOException("Prefix must be 96 bits long: " + nat64Prefix);
         }
 
-        // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
-        final String v4Str;
-        try {
-            v4Str = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN);
-        } catch (IOException e) {
-            throw new IOException("no IPv4 addresses were available for clat: " + e);
-        }
-
-        final Inet4Address v4;
-        try {
-            v4 = (Inet4Address) InetAddresses.parseNumericAddress(v4Str);
-        } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
-            throw new IOException("Invalid IPv4 address " + v4Str);
-        }
-
-        // [2] Generate a checksum-neutral IID.
-        final Integer fwmark = getFwmark(netId);
-        final String pfx96Str = nat64Prefix.getAddress().getHostAddress();
-        final String v6Str;
-        try {
-            v6Str = mDeps.generateIpv6Address(iface, v4Str, pfx96Str, fwmark);
-        } catch (IOException e) {
-            throw new IOException("no IPv6 addresses were available for clat: " + e);
-        }
-
-        final Inet6Address pfx96 = (Inet6Address) nat64Prefix.getAddress();
-        final Inet6Address v6;
-        try {
-            v6 = (Inet6Address) InetAddresses.parseNumericAddress(v6Str);
-        } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
-            throw new IOException("Invalid IPv6 address " + v6Str);
-        }
-
         // Initialize all required file descriptors with null pointer. This makes the following
         // error handling easier. Simply always call #maybeCleanUp for closing file descriptors,
         // if any valid ones, in error handling.
@@ -644,150 +586,112 @@
         ParcelFileDescriptor readSock6 = null;
         ParcelFileDescriptor writeSock6 = null;
 
-        // [3] Open and configure local 464xlat read/write sockets.
-        // Opens a packet socket to receive IPv6 packets in clatd.
+        long cookie = 0;
+        boolean isSocketTagged = false;
 
         try {
+            // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
+            final String v4Str = mDeps.selectIpv4Address(INIT_V4ADDR_STRING,
+                    INIT_V4ADDR_PREFIX_LEN);
+            final Inet4Address v4 = (Inet4Address) InetAddresses.parseNumericAddress(v4Str);
+
+            // [2] Generate a checksum-neutral IID.
+            final Integer fwmark = getFwmark(netId);
+            final String pfx96Str = nat64Prefix.getAddress().getHostAddress();
+            final String v6Str = mDeps.generateIpv6Address(iface, v4Str, pfx96Str, fwmark);
+            final Inet6Address pfx96 = (Inet6Address) nat64Prefix.getAddress();
+            final Inet6Address v6 = (Inet6Address) InetAddresses.parseNumericAddress(v6Str);
+
+            // [3] Open and configure local 464xlat read/write sockets.
+            // Opens a packet socket to receive IPv6 packets in clatd.
+
             // Use a JNI call to get native file descriptor instead of Os.socket() because we would
             // like to use ParcelFileDescriptor to manage file descriptor. But ctor
             // ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file
             // descriptor to initialize ParcelFileDescriptor object instead.
             readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Open packet socket failed: " + e);
-        }
 
-        // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
-        try {
+            // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
             // Use a JNI call to get native file descriptor instead of Os.socket(). See above
             // reason why we use jniOpenPacketSocket6().
             writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Open raw socket failed: " + e);
-        }
 
-        final int ifIndex = mDeps.getInterfaceIndex(iface);
-        if (ifIndex == INVALID_IFINDEX) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Fail to get interface index for interface " + iface);
-        }
+            final int ifIndex = mDeps.getInterfaceIndex(iface);
+            if (ifIndex == INVALID_IFINDEX) {
+                throw new IOException("Fail to get interface index for interface " + iface);
+            }
 
-        // Start translating packets to the new prefix.
-        try {
+            // Start translating packets to the new prefix.
             mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6Str, ifIndex);
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("add anycast sockopt failed: " + e);
-        }
-
-        // Tag socket as AID_CLAT to avoid duplicated CLAT data usage accounting.
-        final long cookie;
-        try {
+            // Tag socket as AID_CLAT to avoid duplicated CLAT data usage accounting.
             cookie = mDeps.getSocketCookie(writeSock6.getFileDescriptor());
             tagSocketAsClat(cookie);
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("tag raw socket failed: " + e);
-        }
-
-        // Update our packet socket filter to reflect the new 464xlat IP address.
-        try {
+            isSocketTagged = true;
+            // Update our packet socket filter to reflect the new 464xlat IP address.
             mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6Str, ifIndex);
-        } catch (IOException e) {
-            try {
-                untagSocket(cookie);
-            } catch (IOException e2) {
-                Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
-            }
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("configure packet socket failed: " + e);
-        }
 
-        // [4] Open, configure and bring up the tun interface.
-        // Create the v4-... tun interface.
-
-        final String tunIface = CLAT_PREFIX + iface;
-        try {
+            // [4] Open, configure and bring up the tun interface.
+            // Create the v4-... tun interface.
+            final String tunIface = CLAT_PREFIX + iface;
             tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Create tun interface " + tunIface + " failed: " + e);
-        }
-
-        final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
-        if (tunIfIndex == INVALID_IFINDEX) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Fail to get interface index for interface " + tunIface);
-        }
-
-        // disable IPv6 on it - failing to do so is not a critical error
-        try {
-            mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
-        } catch (RemoteException | ServiceSpecificException e) {
-            Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
-        }
-
-        // Detect ipv4 mtu.
-        final int detectedMtu;
-        try {
-            detectedMtu = mDeps.detectMtu(pfx96Str,
-                    ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
-        } catch (IOException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Detect MTU on " + tunIface + " failed: " + e);
-        }
-        final int mtu = adjustMtu(detectedMtu);
-        Log.i(TAG, "detected ipv4 mtu of " + detectedMtu + " adjusted to " + mtu);
-
-        // Config tun interface mtu, address and bring up.
-        try {
-            mNetd.interfaceSetMtu(tunIface, mtu);
-        } catch (RemoteException | ServiceSpecificException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
-        }
-        final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
-        ifConfig.ifName = tunIface;
-        ifConfig.ipv4Addr = v4Str;
-        ifConfig.prefixLength = 32;
-        ifConfig.hwAddr = "";
-        ifConfig.flags = new String[] {IF_STATE_UP};
-        try {
-            mNetd.interfaceSetCfg(ifConfig);
-        } catch (RemoteException | ServiceSpecificException e) {
-            maybeCleanUp(tunFd, readSock6, writeSock6);
-            throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
-                    + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
-        }
-
-        // [5] Start clatd.
-        final int pid;
-        try {
-            pid = mDeps.startClatd(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(),
-                    writeSock6.getFileDescriptor(), iface, pfx96Str, v4Str, v6Str);
-        } catch (IOException e) {
-            try {
-                untagSocket(cookie);
-            } catch (IOException e2) {
-                Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
+            final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
+            if (tunIfIndex == INVALID_IFINDEX) {
+                throw new IOException("Fail to get interface index for interface " + tunIface);
             }
-            throw new IOException("Error start clatd on " + iface + ": " + e);
-        } finally {
-            // The file descriptors have been duplicated (dup2) to clatd in native_startClatd().
-            // Close these file descriptor stubs which are unused anymore.
-            maybeCleanUp(tunFd, readSock6, writeSock6);
+            // disable IPv6 on it - failing to do so is not a critical error
+            mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
+            // Detect ipv4 mtu.
+            final int detectedMtu = mDeps.detectMtu(pfx96Str,
+                    ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
+            final int mtu = adjustMtu(detectedMtu);
+            Log.i(TAG, "detected ipv4 mtu of " + detectedMtu + " adjusted to " + mtu);
+            // Config tun interface mtu, address and bring up.
+            mNetd.interfaceSetMtu(tunIface, mtu);
+            final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
+            ifConfig.ifName = tunIface;
+            ifConfig.ipv4Addr = v4Str;
+            ifConfig.prefixLength = 32;
+            ifConfig.hwAddr = "";
+            ifConfig.flags = new String[] {IF_STATE_UP};
+            mNetd.interfaceSetCfg(ifConfig);
+
+            // [5] Start clatd.
+            final int pid = mDeps.startClatd(tunFd.getFileDescriptor(),
+                    readSock6.getFileDescriptor(), writeSock6.getFileDescriptor(), iface, pfx96Str,
+                    v4Str, v6Str);
+
+            // [6] Initialize and store clatd tracker object.
+            mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96,
+                    pid, cookie);
+
+            // [7] Start BPF
+            maybeStartBpf(mClatdTracker);
+
+            return v6Str;
+        } catch (IOException | RemoteException | ServiceSpecificException | ClassCastException
+                 | IllegalArgumentException | NullPointerException e) {
+            if (isSocketTagged) {
+                try {
+                    untagSocket(cookie);
+                } catch (IOException e2) {
+                    Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
+                }
+            }
+            try {
+                if (tunFd != null) {
+                    tunFd.close();
+                }
+                if (readSock6 != null) {
+                    readSock6.close();
+                }
+                if (writeSock6 != null) {
+                    writeSock6.close();
+                }
+            } catch (IOException e2) {
+                Log.e(TAG, "Fail to cleanup fd ", e);
+            }
+            throw new IOException("Failed to start clat ", e);
         }
-
-        // [6] Initialize and store clatd tracker object.
-        mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96,
-                pid, cookie);
-
-        // [7] Start BPF
-        maybeStartBpf(mClatdTracker);
-
-        return v6Str;
     }
 
     private void maybeStopBpf(final ClatdTracker tracker) {
diff --git a/service/src/com/android/server/connectivity/DnsManager.java b/service/src/com/android/server/connectivity/DnsManager.java
index 8e6854a..ac02229 100644
--- a/service/src/com/android/server/connectivity/DnsManager.java
+++ b/service/src/com/android/server/connectivity/DnsManager.java
@@ -390,6 +390,7 @@
                 : new String[0];            // Off
         paramsParcel.transportTypes = nc.getTransportTypes();
         paramsParcel.meteredNetwork = nc.isMetered();
+        paramsParcel.interfaceNames = lp.getAllInterfaceNames().toArray(new String[0]);
         // Prepare to track the validation status of the DNS servers in the
         // resolver config when private DNS is in opportunistic or strict mode.
         if (useTls) {
@@ -403,13 +404,14 @@
         }
 
         Log.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
-                + "%d, %d, %s, %s, %s, %b)", paramsParcel.netId,
+                + "%d, %d, %s, %s, %s, %b, %s)", paramsParcel.netId,
                 Arrays.toString(paramsParcel.servers), Arrays.toString(paramsParcel.domains),
                 paramsParcel.sampleValiditySeconds, paramsParcel.successThreshold,
                 paramsParcel.minSamples, paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
                 paramsParcel.retryCount, paramsParcel.tlsName,
                 Arrays.toString(paramsParcel.tlsServers),
-                Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork));
+                Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork,
+                Arrays.toString(paramsParcel.interfaceNames)));
 
         try {
             mDnsResolver.setResolverConfiguration(paramsParcel);
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index 065922d..489dab5 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -202,13 +202,13 @@
             try {
                 addrStr = mClatCoordinator.clatStart(baseIface, getNetId(), mNat64PrefixInUse);
             } catch (IOException e) {
-                Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
+                Log.e(TAG, "Error starting clatd on " + baseIface, e);
             }
         } else {
             try {
                 addrStr = mNetd.clatdStart(baseIface, mNat64PrefixInUse.toString());
             } catch (RemoteException | ServiceSpecificException e) {
-                Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
+                Log.e(TAG, "Error starting clatd on " + baseIface, e);
             }
         }
         mIface = CLAT_PREFIX + baseIface;
@@ -217,7 +217,7 @@
         try {
             mIPv6Address = (Inet6Address) InetAddresses.parseNumericAddress(addrStr);
         } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
-            Log.e(TAG, "Invalid IPv6 address " + addrStr);
+            Log.e(TAG, "Invalid IPv6 address " + addrStr , e);
         }
         if (mPrefixDiscoveryRunning && !isPrefixDiscoveryNeeded()) {
             stopPrefixDiscovery();
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 92ec0c4..df7010e 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -41,6 +41,10 @@
     public static final short IFLA_ADDRESS   = 1;
     public static final short IFLA_IFNAME    = 3;
     public static final short IFLA_MTU       = 4;
+    public static final short IFLA_INET6_ADDR_GEN_MODE = 8;
+    public static final short IFLA_AF_SPEC = 26;
+
+    public static final short IN6_ADDR_GEN_MODE_NONE = 1;
 
     private int mMtu;
     @NonNull
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
index 02d1574..28eeaea 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
@@ -49,7 +49,7 @@
     @Field(order = 4, type = Type.U32)
     public final long change;
 
-    StructIfinfoMsg(short family, int type, int index, long flags, long change) {
+    public StructIfinfoMsg(short family, int type, int index, long flags, long change) {
         this.family = family;
         this.type = type;
         this.index = index;
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
index 4185b05..d5e91c2 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
@@ -24,6 +24,7 @@
  * A JUnit Rule that sets feature flags based on `@FeatureFlag` annotations.
  *
  * This rule enables dynamic control of feature flag states during testing.
+ * And restores the original values after performing tests.
  *
  * **Usage:**
  * ```kotlin
@@ -31,6 +32,8 @@
  *   @get:Rule
  *   val setFeatureFlagsRule = SetFeatureFlagsRule(setFlagsMethod = (name, enabled) -> {
  *     // Custom handling code.
+ *   }, (name) -> {
+ *     // Custom getter code to retrieve the original values.
  *   })
  *
  *   // ... test methods with @FeatureFlag annotations
@@ -41,7 +44,10 @@
  * }
  * ```
  */
-class SetFeatureFlagsRule(val setFlagsMethod: (name: String, enabled: Boolean) -> Unit) : TestRule {
+class SetFeatureFlagsRule(
+    val setFlagsMethod: (name: String, enabled: Boolean?) -> Unit,
+                          val getFlagsMethod: (name: String) -> Boolean?
+) : TestRule {
     /**
      * This annotation marks a test method as requiring a specific feature flag to be configured.
      *
@@ -69,13 +75,20 @@
                     FeatureFlag::class.java
                 )
 
+                val valuesToBeRestored = mutableMapOf<String, Boolean?>()
                 for (featureFlagAnnotation in featureFlagAnnotations) {
+                    valuesToBeRestored[featureFlagAnnotation.name] =
+                            getFlagsMethod(featureFlagAnnotation.name)
                     setFlagsMethod(featureFlagAnnotation.name, featureFlagAnnotation.enabled)
                 }
 
                 // Execute the test method, which includes methods annotated with
                 // @Before, @Test and @After.
                 base.evaluate()
+
+                valuesToBeRestored.forEach {
+                    setFlagsMethod(it.key, it.value)
+                }
             }
         }
     }
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index 44512bb..ea3d2dd 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -29,8 +29,6 @@
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
 
-import static com.android.testutils.MiscAsserts.assertContainsExactly;
-import static com.android.testutils.MiscAsserts.assertContainsStringsExactly;
 import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
@@ -38,12 +36,12 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.ConnectivitySettingsManager;
@@ -74,7 +72,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -122,29 +119,6 @@
         assertFieldCountEquals(3, ResolverOptionsParcel.class);
     }
 
-    private void assertResolverParamsEquals(@NonNull ResolverParamsParcel actual,
-            @NonNull ResolverParamsParcel expected) {
-        assertEquals(actual.netId, expected.netId);
-        assertEquals(actual.sampleValiditySeconds, expected.sampleValiditySeconds);
-        assertEquals(actual.successThreshold, expected.successThreshold);
-        assertEquals(actual.minSamples, expected.minSamples);
-        assertEquals(actual.maxSamples, expected.maxSamples);
-        assertEquals(actual.baseTimeoutMsec, expected.baseTimeoutMsec);
-        assertEquals(actual.retryCount, expected.retryCount);
-        assertContainsStringsExactly(actual.servers, expected.servers);
-        assertContainsStringsExactly(actual.domains, expected.domains);
-        assertEquals(actual.tlsName, expected.tlsName);
-        assertContainsStringsExactly(actual.tlsServers, expected.tlsServers);
-        assertContainsStringsExactly(actual.tlsFingerprints, expected.tlsFingerprints);
-        assertEquals(actual.caCertificate, expected.caCertificate);
-        assertEquals(actual.tlsConnectTimeoutMs, expected.tlsConnectTimeoutMs);
-        assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions);
-        assertContainsExactly(actual.transportTypes, expected.transportTypes);
-        assertEquals(actual.meteredNetwork, expected.meteredNetwork);
-        assertEquals(actual.dohParams, expected.dohParams);
-        assertFieldCountEquals(18, ResolverParamsParcel.class);
-    }
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -365,11 +339,6 @@
         mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
         mDnsManager.flushVmDnsCache();
 
-        final ArgumentCaptor<ResolverParamsParcel> resolverParamsParcelCaptor =
-                ArgumentCaptor.forClass(ResolverParamsParcel.class);
-        verify(mMockDnsResolver, times(1)).setResolverConfiguration(
-                resolverParamsParcelCaptor.capture());
-        final ResolverParamsParcel actualParams = resolverParamsParcelCaptor.getValue();
         final ResolverParamsParcel expectedParams = new ResolverParamsParcel();
         expectedParams.netId = TEST_NETID;
         expectedParams.sampleValiditySeconds = TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS;
@@ -384,7 +353,8 @@
         expectedParams.resolverOptions = null;
         expectedParams.meteredNetwork = true;
         expectedParams.dohParams = null;
-        assertResolverParamsEquals(actualParams, expectedParams);
+        expectedParams.interfaceNames = new String[]{TEST_IFACENAME};
+        verify(mMockDnsResolver, times(1)).setResolverConfiguration(eq(expectedParams));
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 6425daa..9026481 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -314,7 +314,7 @@
             new SetFeatureFlagsRule((name, enabled) -> {
                 mFeatureFlags.put(name, enabled);
                 return null;
-            });
+            }, (name) -> mFeatureFlags.getOrDefault(name, false));
 
     private class MockContext extends BroadcastInterceptingContext {
         private final Context mBaseContext;
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index c3f6ace..976f93d 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -16,30 +16,40 @@
 
 package com.android.server.thread;
 
+import static android.system.OsConstants.AF_INET6;
 import static android.system.OsConstants.EADDRINUSE;
+import static android.system.OsConstants.IFF_MULTICAST;
+import static android.system.OsConstants.IFF_NOARP;
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_AF_SPEC;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_INET6_ADDR_GEN_MODE;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IN6_ADDR_GEN_MODE_NONE;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
 
 import android.annotation.Nullable;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.RouteInfo;
-import android.net.util.SocketUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.system.OsConstants;
 import android.util.Log;
 
+import com.android.net.module.util.HexDump;
 import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
 import com.android.net.module.util.netlink.NetlinkUtils;
-import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
+import com.android.net.module.util.netlink.StructIfinfoMsg;
+import com.android.net.module.util.netlink.StructNlAttr;
+import com.android.net.module.util.netlink.StructNlMsgHdr;
 import com.android.server.thread.openthread.Ipv6AddressInfo;
 import com.android.server.thread.openthread.OnMeshPrefixConfig;
 
-import java.io.FileDescriptor;
 import java.io.IOException;
-import java.io.InterruptedIOException;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -47,12 +57,15 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.List;
 
 /** Controller for virtual/tunnel network interfaces. */
 public class TunInterfaceController {
     private static final String TAG = "TunIfController";
+    private static final boolean DBG = false;
     private static final long INFINITE_LIFETIME = 0xffffffffL;
     static final int MTU = 1280;
 
@@ -62,14 +75,13 @@
 
     private final String mIfName;
     private final LinkProperties mLinkProperties = new LinkProperties();
-    private ParcelFileDescriptor mParcelTunFd;
-    private FileDescriptor mNetlinkSocket;
-    private static int sNetlinkSeqNo = 0;
     private final MulticastSocket mMulticastSocket; // For join group and leave group
-    private NetworkInterface mNetworkInterface;
     private final List<InetAddress> mMulticastAddresses = new ArrayList<>();
     private final List<RouteInfo> mNetDataPrefixes = new ArrayList<>();
 
+    private ParcelFileDescriptor mParcelTunFd;
+    private NetworkInterface mNetworkInterface;
+
     /** Creates a new {@link TunInterfaceController} instance for given interface. */
     public TunInterfaceController(String interfaceName) {
         mIfName = interfaceName;
@@ -91,26 +103,21 @@
     public void createTunInterface() throws IOException {
         mParcelTunFd = ParcelFileDescriptor.adoptFd(nativeCreateTunInterface(mIfName, MTU));
         try {
-            mNetlinkSocket = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_ROUTE);
-        } catch (ErrnoException e) {
-            throw new IOException("Failed to create netlink socket", e);
-        }
-        try {
             mNetworkInterface = NetworkInterface.getByName(mIfName);
         } catch (SocketException e) {
             throw new IOException("Failed to get NetworkInterface", e);
         }
+
+        setAddrGenModeToNone();
     }
 
     public void destroyTunInterface() {
         try {
             mParcelTunFd.close();
-            SocketUtils.closeSocket(mNetlinkSocket);
         } catch (IOException e) {
             // Should never fail
         }
         mParcelTunFd = null;
-        mNetlinkSocket = null;
         mNetworkInterface = null;
     }
 
@@ -128,6 +135,10 @@
             for (LinkAddress address : mLinkProperties.getAllLinkAddresses()) {
                 removeAddress(address);
             }
+            for (RouteInfo route : mLinkProperties.getAllRoutes()) {
+                mLinkProperties.removeRoute(route);
+            }
+            mNetDataPrefixes.clear();
         }
         nativeSetInterfaceUp(mIfName, isUp);
     }
@@ -138,14 +149,14 @@
     public void addAddress(LinkAddress address) {
         Log.d(TAG, "Adding address " + address + " with flags: " + address.getFlags());
 
-        long validLifetimeSeconds;
         long preferredLifetimeSeconds;
+        long validLifetimeSeconds;
 
         if (address.getDeprecationTime() == LinkAddress.LIFETIME_PERMANENT
                 || address.getDeprecationTime() == LinkAddress.LIFETIME_UNKNOWN) {
-            validLifetimeSeconds = INFINITE_LIFETIME;
+            preferredLifetimeSeconds = INFINITE_LIFETIME;
         } else {
-            validLifetimeSeconds =
+            preferredLifetimeSeconds =
                     Math.max(
                             (address.getDeprecationTime() - SystemClock.elapsedRealtime()) / 1000L,
                             0L);
@@ -153,28 +164,23 @@
 
         if (address.getExpirationTime() == LinkAddress.LIFETIME_PERMANENT
                 || address.getExpirationTime() == LinkAddress.LIFETIME_UNKNOWN) {
-            preferredLifetimeSeconds = INFINITE_LIFETIME;
+            validLifetimeSeconds = INFINITE_LIFETIME;
         } else {
-            preferredLifetimeSeconds =
+            validLifetimeSeconds =
                     Math.max(
                             (address.getExpirationTime() - SystemClock.elapsedRealtime()) / 1000L,
                             0L);
         }
 
-        byte[] message =
-                RtNetlinkAddressMessage.newRtmNewAddressMessage(
-                        sNetlinkSeqNo++,
-                        address.getAddress(),
-                        (short) address.getPrefixLength(),
-                        address.getFlags(),
-                        (byte) address.getScope(),
-                        Os.if_nametoindex(mIfName),
-                        validLifetimeSeconds,
-                        preferredLifetimeSeconds);
-        try {
-            Os.write(mNetlinkSocket, message, 0, message.length);
-        } catch (ErrnoException | InterruptedIOException e) {
-            Log.e(TAG, "Failed to add address " + address, e);
+        if (!NetlinkUtils.sendRtmNewAddressRequest(
+                Os.if_nametoindex(mIfName),
+                address.getAddress(),
+                (short) address.getPrefixLength(),
+                address.getFlags(),
+                (byte) address.getScope(),
+                preferredLifetimeSeconds,
+                validLifetimeSeconds)) {
+            Log.w(TAG, "Failed to add address " + address.getAddress().getHostAddress());
             return;
         }
         mLinkProperties.addLinkAddress(address);
@@ -184,22 +190,17 @@
     /** Removes an address from the interface. */
     public void removeAddress(LinkAddress address) {
         Log.d(TAG, "Removing address " + address);
-        byte[] message =
-                RtNetlinkAddressMessage.newRtmDelAddressMessage(
-                        sNetlinkSeqNo++,
-                        address.getAddress(),
-                        (short) address.getPrefixLength(),
-                        Os.if_nametoindex(mIfName));
 
         // Intentionally update the mLinkProperties before send netlink message because the
         // address is already removed from ot-daemon and apps can't reach to the address even
         // when the netlink request below fails
         mLinkProperties.removeLinkAddress(address);
         mLinkProperties.removeRoute(getRouteForAddress(address));
-        try {
-            Os.write(mNetlinkSocket, message, 0, message.length);
-        } catch (ErrnoException | InterruptedIOException e) {
-            Log.e(TAG, "Failed to remove address " + address, e);
+        if (!NetlinkUtils.sendRtmDelAddressRequest(
+                Os.if_nametoindex(mIfName),
+                (Inet6Address) address.getAddress(),
+                (short) address.getPrefixLength())) {
+            Log.w(TAG, "Failed to remove address " + address.getAddress().getHostAddress());
         }
     }
 
@@ -362,4 +363,66 @@
             Log.e(TAG, "failed to leave group " + address.getHostAddress(), e);
         }
     }
+
+    /**
+     * Sets the address generation mode to {@code IN6_ADDR_GEN_MODE_NONE}.
+     *
+     * <p>So that the "thread-wpan" interface has only one IPv6 link local address which is
+     * generated by OpenThread.
+     */
+    private void setAddrGenModeToNone() {
+        StructNlMsgHdr header = new StructNlMsgHdr();
+        header.nlmsg_type = RTM_NEWLINK;
+        header.nlmsg_pid = 0;
+        header.nlmsg_seq = 0;
+        header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+        StructIfinfoMsg ifInfo =
+                new StructIfinfoMsg(
+                        (short) 0 /* family */,
+                        0 /* type */,
+                        Os.if_nametoindex(mIfName),
+                        (IFF_MULTICAST | IFF_NOARP) /* flags */,
+                        0xffffffff /* change */);
+
+        // Nested attributes
+        // IFLA_AF_SPEC
+        //   AF_INET6
+        //     IFLA_INET6_ADDR_GEN_MODE
+        StructNlAttr addrGenMode =
+                new StructNlAttr(IFLA_INET6_ADDR_GEN_MODE, (byte) IN6_ADDR_GEN_MODE_NONE);
+        StructNlAttr afInet6 = new StructNlAttr((short) AF_INET6, addrGenMode);
+        StructNlAttr afSpec = new StructNlAttr(IFLA_AF_SPEC, afInet6);
+
+        final int msgLength =
+                StructNlMsgHdr.STRUCT_SIZE
+                        + StructIfinfoMsg.STRUCT_SIZE
+                        + afSpec.getAlignedLength();
+        byte[] msg = new byte[msgLength];
+        ByteBuffer buf = ByteBuffer.wrap(msg);
+        buf.order(ByteOrder.nativeOrder());
+
+        header.nlmsg_len = msgLength;
+        header.pack(buf);
+        ifInfo.pack(buf);
+        afSpec.pack(buf);
+
+        if (buf.position() != msgLength) {
+            throw new AssertionError(
+                    String.format(
+                            "Unexpected netlink message size (actual = %d, expected = %d)",
+                            buf.position(), msgLength));
+        }
+
+        if (DBG) {
+            Log.d(TAG, "ADDR_GEN_MODE message is:");
+            Log.d(TAG, HexDump.dumpHexString(msg));
+        }
+
+        try {
+            NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed to set ADDR_GEN_MODE to NONE", e);
+        }
+    }
 }
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index 4028014..61b6eac 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -176,7 +176,7 @@
 
     // TODO (b/323300829): add test for removing an OT address
     @Test
-    public void tunInterface_joinedNetwork_otAddressesAddedToTunInterface() throws Exception {
+    public void tunInterface_joinedNetwork_otAndTunAddressesMatch() throws Exception {
         mController.joinAndWait(DEFAULT_DATASET);
 
         List<Inet6Address> otAddresses = mOtCtl.getAddresses();
@@ -185,9 +185,12 @@
         // that we can write assertThat() in the Predicate
         waitFor(
                 () -> {
-                    String ifconfig = runShellCommand("ifconfig thread-wpan");
-                    return otAddresses.stream()
-                            .allMatch(addr -> ifconfig.contains(addr.getHostAddress()));
+                    List<Inet6Address> tunAddresses =
+                            getIpv6LinkAddresses("thread-wpan").stream()
+                                    .map(linkAddr -> (Inet6Address) linkAddr.getAddress())
+                                    .toList();
+                    return otAddresses.containsAll(tunAddresses)
+                            && tunAddresses.containsAll(otAddresses);
                 },
                 TUN_ADDR_UPDATE_TIMEOUT);
     }
@@ -307,6 +310,23 @@
                 .isFalse();
     }
 
+    @Test
+    public void toggleThreadNetwork_routeFromPreviousNetDataIsRemoved() throws Exception {
+        ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        mController.joinAndWait(DEFAULT_DATASET);
+        mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
+        mOtCtl.executeCommand("netdata register");
+
+        mController.leaveAndWait();
+        mOtCtl.factoryReset();
+        mController.joinAndWait(DEFAULT_DATASET);
+
+        LinkProperties lp = cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT));
+        assertThat(lp).isNotNull();
+        assertThat(lp.getRoutes().stream().anyMatch(r -> r.matches(TEST_NO_SLAAC_PREFIX_ADDRESS)))
+                .isFalse();
+    }
+
     // TODO (b/323300829): add more tests for integration with linux platform and
     // ConnectivityService
 
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index 78f5770..ada46c8 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -301,7 +301,7 @@
         return false;
     }
 
-    public static List<LinkAddress> getIpv6LinkAddresses(String interfaceName) throws IOException {
+    public static List<LinkAddress> getIpv6LinkAddresses(String interfaceName) {
         List<LinkAddress> addresses = new ArrayList<>();
         final String cmd = " ip -6 addr show dev " + interfaceName;
         final String output = runShellCommandOrThrow(cmd);