Merge "Change gid of cgroupskb/<ingress|egress>/stats"
diff --git a/Tethering/tests/jarjar-rules.txt b/Tethering/tests/jarjar-rules.txt
index 23d3f56..a7c7488 100644
--- a/Tethering/tests/jarjar-rules.txt
+++ b/Tethering/tests/jarjar-rules.txt
@@ -13,6 +13,9 @@
 # Classes from net-utils-framework-common
 rule com.android.net.module.util.** com.android.networkstack.tethering.util.@1
 
+# Classes from net-tests-utils
+rule com.android.testutils.TestBpfMap* com.android.networkstack.tethering.testutils.TestBpfMap@1
+
 # TODO: either stop using frameworks-base-testutils or remove the unit test classes it contains.
 # TestableLooper from "testables" can be used instead of TestLooper from frameworks-base-testutils.
 zap android.os.test.TestLooperTest*
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index c5969d2..d51f6fd 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -89,7 +89,6 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.test.TestLooper;
-import android.system.ErrnoException;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -100,7 +99,6 @@
 import com.android.net.module.util.BpfMap;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.NetworkStackConstants;
-import com.android.net.module.util.Struct;
 import com.android.net.module.util.netlink.ConntrackMessage;
 import com.android.net.module.util.netlink.NetlinkConstants;
 import com.android.net.module.util.netlink.NetlinkSocket;
@@ -110,6 +108,7 @@
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.TestBpfMap;
 import com.android.testutils.TestableNetworkStatsProviderCbBinder;
 
 import org.junit.Before;
@@ -128,10 +127,7 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.function.BiConsumer;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -157,60 +153,6 @@
             UPSTREAM_IFACE, UPSTREAM_IFINDEX, null /* macAddr, rawip */,
             NetworkStackConstants.ETHER_MTU);
 
-    // The test fake BPF map class is needed because the test has no privilege to access the BPF
-    // map. All member functions which eventually call JNI to access the real native BPF map need
-    // to be overridden.
-    // TODO: consider moving to an individual file.
-    private class TestBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
-        private final HashMap<K, V> mMap = new HashMap<K, V>();
-
-        TestBpfMap(final Class<K> key, final Class<V> value) {
-            super(key, value);
-        }
-
-        @Override
-        public void forEach(BiConsumer<K, V> action) throws ErrnoException {
-            // TODO: consider using mocked #getFirstKey and #getNextKey to iterate. It helps to
-            // implement the entry deletion in the iteration if required.
-            for (Map.Entry<K, V> entry : mMap.entrySet()) {
-                action.accept(entry.getKey(), entry.getValue());
-            }
-        }
-
-        @Override
-        public void updateEntry(K key, V value) throws ErrnoException {
-            mMap.put(key, value);
-        }
-
-        @Override
-        public void insertEntry(K key, V value) throws ErrnoException,
-                IllegalArgumentException {
-            // The entry is created if and only if it doesn't exist. See BpfMap#insertEntry.
-            if (mMap.get(key) != null) {
-                throw new IllegalArgumentException(key + " already exist");
-            }
-            mMap.put(key, value);
-        }
-
-        @Override
-        public boolean deleteEntry(Struct key) throws ErrnoException {
-            return mMap.remove(key) != null;
-        }
-
-        @Override
-        public V getValue(@NonNull K key) throws ErrnoException {
-            // Return value for a given key. Otherwise, return null without an error ENOENT.
-            // BpfMap#getValue treats that the entry is not found as no error.
-            return mMap.get(key);
-        }
-
-        @Override
-        public void clear() throws ErrnoException {
-            // TODO: consider using mocked #getFirstKey and #deleteEntry to implement.
-            mMap.clear();
-        }
-    };
-
     @Mock private NetworkStatsManager mStatsManager;
     @Mock private INetd mNetd;
     @Mock private IpServer mIpServer;
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 0443456..7977cc5 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -3,6 +3,8 @@
 
   public final class NsdManager {
     method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener);
+    method public void discoverServices(@NonNull String, int, @Nullable android.net.Network, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void discoverServices(@NonNull String, int, @NonNull android.net.NetworkRequest, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
     method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
     method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
     method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
@@ -43,12 +45,14 @@
     method public int describeContents();
     method public java.util.Map<java.lang.String,byte[]> getAttributes();
     method public java.net.InetAddress getHost();
+    method @Nullable public android.net.Network getNetwork();
     method public int getPort();
     method public String getServiceName();
     method public String getServiceType();
     method public void removeAttribute(String);
     method public void setAttribute(String, String);
     method public void setHost(java.net.InetAddress);
+    method public void setNetwork(@Nullable android.net.Network);
     method public void setPort(int);
     method public void setServiceName(String);
     method public void setServiceType(String);
diff --git a/framework/api/current.txt b/framework/api/current.txt
index a373b71..547b7e2 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -205,6 +205,21 @@
     method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String);
   }
 
+  public final class IpConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.net.ProxyInfo getHttpProxy();
+    method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR;
+  }
+
+  public static final class IpConfiguration.Builder {
+    ctor public IpConfiguration.Builder();
+    method @NonNull public android.net.IpConfiguration build();
+    method @NonNull public android.net.IpConfiguration.Builder setHttpProxy(@Nullable android.net.ProxyInfo);
+    method @NonNull public android.net.IpConfiguration.Builder setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+  }
+
   public final class IpPrefix implements android.os.Parcelable {
     ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
     method public boolean contains(@NonNull java.net.InetAddress);
@@ -485,6 +500,25 @@
     method public void onStopped();
   }
 
+  public final class StaticIpConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
+    method @Nullable public String getDomains();
+    method @Nullable public java.net.InetAddress getGateway();
+    method @NonNull public android.net.LinkAddress getIpAddress();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+  }
+
+  public static final class StaticIpConfiguration.Builder {
+    ctor public StaticIpConfiguration.Builder();
+    method @NonNull public android.net.StaticIpConfiguration build();
+    method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@NonNull android.net.LinkAddress);
+  }
+
   public interface TransportInfo {
   }
 
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 5961e72..64e159b 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -124,6 +124,7 @@
 
   public final class NetworkAgentConfig implements android.os.Parcelable {
     method @Nullable public String getSubscriberId();
+    method public boolean getVpnRequiresValidation();
     method public boolean isBypassableVpn();
   }
 
@@ -131,6 +132,7 @@
     method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean);
     method @NonNull public android.net.NetworkAgentConfig.Builder setExcludeLocalRoutesVpn(boolean);
     method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setVpnRequiresValidation(boolean);
   }
 
   public final class NetworkCapabilities implements android.os.Parcelable {
diff --git a/framework/api/module-lib-lint-baseline.txt b/framework/api/module-lib-lint-baseline.txt
deleted file mode 100644
index c7b0db5..0000000
--- a/framework/api/module-lib-lint-baseline.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-NoByteOrShort: android.net.DhcpOption#DhcpOption(byte, byte[]) parameter #0:
-    Should avoid odd sized primitives; use `int` instead of `byte` in parameter type in android.net.DhcpOption(byte type, byte[] value)
-NoByteOrShort: android.net.DhcpOption#describeContents():
-    Should avoid odd sized primitives; use `int` instead of `byte` in method android.net.DhcpOption.describeContents()
-NoByteOrShort: android.net.DhcpOption#getType():
-    Should avoid odd sized primitives; use `int` instead of `byte` in method android.net.DhcpOption.getType()
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index d420958..764cffa 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -133,17 +133,12 @@
   public final class IpConfiguration implements android.os.Parcelable {
     ctor public IpConfiguration();
     ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
-    method public int describeContents();
-    method @Nullable public android.net.ProxyInfo getHttpProxy();
     method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment();
     method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings();
-    method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration();
     method public void setHttpProxy(@Nullable android.net.ProxyInfo);
     method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment);
     method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings);
     method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR;
   }
 
   public enum IpConfiguration.IpAssignment {
@@ -484,23 +479,7 @@
     ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
     method public void addDnsServer(@NonNull java.net.InetAddress);
     method public void clear();
-    method public int describeContents();
-    method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
-    method @Nullable public String getDomains();
-    method @Nullable public java.net.InetAddress getGateway();
-    method @Nullable public android.net.LinkAddress getIpAddress();
     method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String);
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
-  }
-
-  public static final class StaticIpConfiguration.Builder {
-    ctor public StaticIpConfiguration.Builder();
-    method @NonNull public android.net.StaticIpConfiguration build();
-    method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>);
-    method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String);
-    method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress);
-    method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress);
   }
 
   public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
diff --git a/framework/src/android/net/DhcpOption.java b/framework/src/android/net/DhcpOption.java
index a125290..b30470a 100644
--- a/framework/src/android/net/DhcpOption.java
+++ b/framework/src/android/net/DhcpOption.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -35,12 +36,13 @@
     /**
      * Constructs a DhcpOption object.
      *
-     * @param type the type of this option
+     * @param type the type of this option. For more information, see
+     *           https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml.
      * @param value the value of this option. If {@code null}, DHCP packets containing this option
      *              will include the option type in the Parameter Request List. Otherwise, DHCP
      *              packets containing this option will include the option in the options section.
      */
-    public DhcpOption(byte type, @Nullable byte[] value) {
+    public DhcpOption(@SuppressLint("NoByteOrShort") byte type, @Nullable byte[] value) {
         mType = type;
         mValue = value;
     }
@@ -69,6 +71,7 @@
             };
 
     /** Get the type of DHCP option */
+    @SuppressLint("NoByteOrShort")
     public byte getType() {
         return mType;
     }
diff --git a/framework/src/android/net/IpConfiguration.java b/framework/src/android/net/IpConfiguration.java
index d5f8b2e..99835aa 100644
--- a/framework/src/android/net/IpConfiguration.java
+++ b/framework/src/android/net/IpConfiguration.java
@@ -28,16 +28,16 @@
 import java.util.Objects;
 
 /**
- * A class representing a configured network.
- * @hide
+ * A class representing the IP configuration of a network.
  */
-@SystemApi
 public final class IpConfiguration implements Parcelable {
     private static final String TAG = "IpConfiguration";
 
     // This enum has been used by apps through reflection for many releases.
     // Therefore they can't just be removed. Duplicating these constants to
     // give an alternate SystemApi is a worse option than exposing them.
+    /** @hide */
+    @SystemApi
     @SuppressLint("Enum")
     public enum IpAssignment {
         /* Use statically configured IP settings. Configuration can be accessed
@@ -59,6 +59,8 @@
     // This enum has been used by apps through reflection for many releases.
     // Therefore they can't just be removed. Duplicating these constants to
     // give an alternate SystemApi is a worse option than exposing them.
+    /** @hide */
+    @SystemApi
     @SuppressLint("Enum")
     public enum ProxySettings {
         /* No proxy is to be used. Any existing proxy settings
@@ -94,6 +96,8 @@
                 null : new ProxyInfo(httpProxy);
     }
 
+    /** @hide */
+    @SystemApi
     public IpConfiguration() {
         init(IpAssignment.UNASSIGNED, ProxySettings.UNASSIGNED, null, null);
     }
@@ -107,6 +111,8 @@
         init(ipAssignment, proxySettings, staticIpConfiguration, httpProxy);
     }
 
+    /** @hide */
+    @SystemApi
     public IpConfiguration(@NonNull IpConfiguration source) {
         this();
         if (source != null) {
@@ -115,34 +121,58 @@
         }
     }
 
+    /** @hide */
+    @SystemApi
     public @NonNull IpAssignment getIpAssignment() {
         return ipAssignment;
     }
 
+    /** @hide */
+    @SystemApi
     public void setIpAssignment(@NonNull IpAssignment ipAssignment) {
         this.ipAssignment = ipAssignment;
     }
 
+    /**
+     * Get the current static IP configuration (possibly null). Configured via
+     * {@link Builder#setStaticIpConfiguration(StaticIpConfiguration)}.
+     *
+     * @return Current static IP configuration.
+     */
     public @Nullable StaticIpConfiguration getStaticIpConfiguration() {
         return staticIpConfiguration;
     }
 
+    /** @hide */
+    @SystemApi
     public void setStaticIpConfiguration(@Nullable StaticIpConfiguration staticIpConfiguration) {
         this.staticIpConfiguration = staticIpConfiguration;
     }
 
+    /** @hide */
+    @SystemApi
     public @NonNull ProxySettings getProxySettings() {
         return proxySettings;
     }
 
+    /** @hide */
+    @SystemApi
     public void setProxySettings(@NonNull ProxySettings proxySettings) {
         this.proxySettings = proxySettings;
     }
 
+    /**
+     * The proxy configuration of this object.
+     *
+     * @return The proxy information of this object configured via
+     * {@link Builder#setHttpProxy(ProxyInfo)}.
+     */
     public @Nullable ProxyInfo getHttpProxy() {
         return httpProxy;
     }
 
+    /** @hide */
+    @SystemApi
     public void setHttpProxy(@Nullable ProxyInfo httpProxy) {
         this.httpProxy = httpProxy;
     }
@@ -220,4 +250,56 @@
                 return new IpConfiguration[size];
             }
         };
+
+    /**
+     * Builder used to construct {@link IpConfiguration} objects.
+     */
+    public static final class Builder {
+        private StaticIpConfiguration mStaticIpConfiguration;
+        private ProxyInfo mProxyInfo;
+
+        /**
+         * Set a static IP configuration.
+         *
+         * @param config Static IP configuration.
+         * @return A {@link Builder} object to allow chaining.
+         */
+        public @NonNull Builder setStaticIpConfiguration(@Nullable StaticIpConfiguration config) {
+            mStaticIpConfiguration = config;
+            return this;
+        }
+
+        /**
+         * Set a proxy configuration.
+         *
+         * @param proxyInfo Proxy configuration.
+         * @return A {@link Builder} object to allow chaining.
+         */
+        public @NonNull Builder setHttpProxy(@Nullable ProxyInfo proxyInfo) {
+            mProxyInfo = proxyInfo;
+            return this;
+        }
+
+        /**
+         * Construct an {@link IpConfiguration}.
+         *
+         * @return A new {@link IpConfiguration} object.
+         */
+        public @NonNull IpConfiguration build() {
+            IpConfiguration config = new IpConfiguration();
+            config.setStaticIpConfiguration(mStaticIpConfiguration);
+            config.setIpAssignment(
+                    mStaticIpConfiguration == null ? IpAssignment.DHCP : IpAssignment.STATIC);
+
+            config.setHttpProxy(mProxyInfo);
+            if (mProxyInfo == null) {
+                config.setProxySettings(ProxySettings.NONE);
+            } else {
+                config.setProxySettings(
+                        mProxyInfo.getPacFileUrl() == null ? ProxySettings.STATIC
+                                : ProxySettings.PAC);
+            }
+            return config;
+        }
+    }
 }
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index 040bf31..3f5d5e5 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -34,6 +34,8 @@
  */
 @SystemApi
 public final class NetworkAgentConfig implements Parcelable {
+    // TODO : make this object immutable. The fields that should stay mutable should likely
+    // migrate to NetworkAgentInfo.
 
     /**
      * If the {@link Network} is a VPN, whether apps are allowed to bypass the
@@ -246,6 +248,27 @@
         return excludeLocalRouteVpn;
     }
 
+    /**
+     * Whether network validation should be performed for this VPN network.
+     * {@see #getVpnRequiresValidation}
+     * @hide
+     */
+    private boolean mVpnRequiresValidation = false;
+
+    /**
+     * Whether network validation should be performed for this VPN network.
+     *
+     * If this network isn't a VPN this should always be {@code false}, and will be ignored
+     * if set.
+     * If this network is a VPN, false means this network should always be considered validated;
+     * true means it follows the same validation semantics as general internet networks.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public boolean getVpnRequiresValidation() {
+        return mVpnRequiresValidation;
+    }
+
     /** @hide */
     public NetworkAgentConfig() {
     }
@@ -266,6 +289,7 @@
             legacySubTypeName = nac.legacySubTypeName;
             mLegacyExtraInfo = nac.mLegacyExtraInfo;
             excludeLocalRouteVpn = nac.excludeLocalRouteVpn;
+            mVpnRequiresValidation = nac.mVpnRequiresValidation;
         }
     }
 
@@ -409,6 +433,25 @@
         }
 
         /**
+         * Sets whether network validation should be performed for this VPN network.
+         *
+         * Only agents registering a VPN network should use this setter. On other network
+         * types it will be ignored.
+         * False means this network should always be considered validated;
+         * true means it follows the same validation semantics as general internet.
+         *
+         * @param vpnRequiresValidation whether this VPN requires validation.
+         *                              Default is {@code false}.
+         * @hide
+         */
+        @NonNull
+        @SystemApi(client = MODULE_LIBRARIES)
+        public Builder setVpnRequiresValidation(boolean vpnRequiresValidation) {
+            mConfig.mVpnRequiresValidation = vpnRequiresValidation;
+            return this;
+        }
+
+        /**
          * Sets whether the apps can bypass the VPN connection.
          *
          * @return this builder, to facilitate chaining.
@@ -458,14 +501,16 @@
                 && Objects.equals(subscriberId, that.subscriberId)
                 && Objects.equals(legacyTypeName, that.legacyTypeName)
                 && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo)
-                && excludeLocalRouteVpn == that.excludeLocalRouteVpn;
+                && excludeLocalRouteVpn == that.excludeLocalRouteVpn
+                && mVpnRequiresValidation == that.mVpnRequiresValidation;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated,
                 acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId,
-                skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo, excludeLocalRouteVpn);
+                skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo, excludeLocalRouteVpn,
+                mVpnRequiresValidation);
     }
 
     @Override
@@ -483,6 +528,7 @@
                 + ", legacyTypeName = '" + legacyTypeName + '\''
                 + ", legacyExtraInfo = '" + mLegacyExtraInfo + '\''
                 + ", excludeLocalRouteVpn = '" + excludeLocalRouteVpn + '\''
+                + ", vpnRequiresValidation = '" + mVpnRequiresValidation + '\''
                 + "}";
     }
 
@@ -506,6 +552,7 @@
         out.writeString(legacySubTypeName);
         out.writeString(mLegacyExtraInfo);
         out.writeInt(excludeLocalRouteVpn ? 1 : 0);
+        out.writeInt(mVpnRequiresValidation ? 1 : 0);
     }
 
     public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
@@ -526,6 +573,7 @@
             networkAgentConfig.legacySubTypeName = in.readString();
             networkAgentConfig.mLegacyExtraInfo = in.readString();
             networkAgentConfig.excludeLocalRouteVpn = in.readInt() != 0;
+            networkAgentConfig.mVpnRequiresValidation = in.readInt() != 0;
             return networkAgentConfig;
         }
 
diff --git a/framework/src/android/net/StaticIpConfiguration.java b/framework/src/android/net/StaticIpConfiguration.java
index 7904f7a..194cffd 100644
--- a/framework/src/android/net/StaticIpConfiguration.java
+++ b/framework/src/android/net/StaticIpConfiguration.java
@@ -26,6 +26,7 @@
 
 import com.android.net.module.util.InetAddressUtils;
 
+import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.List;
@@ -33,24 +34,7 @@
 
 /**
  * Class that describes static IP configuration.
- *
- * <p>This class is different from {@link LinkProperties} because it represents
- * configuration intent. The general contract is that if we can represent
- * a configuration here, then we should be able to configure it on a network.
- * The intent is that it closely match the UI we have for configuring networks.
- *
- * <p>In contrast, {@link LinkProperties} represents current state. It is much more
- * expressive. For example, it supports multiple IP addresses, multiple routes,
- * stacked interfaces, and so on. Because LinkProperties is so expressive,
- * using it to represent configuration intent as well as current state causes
- * problems. For example, we could unknowingly save a configuration that we are
- * not in fact capable of applying, or we could save a configuration that the
- * UI cannot display, which has the potential for malicious code to hide
- * hostile or unexpected configuration from the user.
- *
- * @hide
  */
-@SystemApi
 public final class StaticIpConfiguration implements Parcelable {
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -69,10 +53,14 @@
     @Nullable
     public String domains;
 
+    /** @hide */
+    @SystemApi
     public StaticIpConfiguration() {
         dnsServers = new ArrayList<>();
     }
 
+    /** @hide */
+    @SystemApi
     public StaticIpConfiguration(@Nullable StaticIpConfiguration source) {
         this();
         if (source != null) {
@@ -84,6 +72,8 @@
         }
     }
 
+    /** @hide */
+    @SystemApi
     public void clear() {
         ipAddress = null;
         gateway = null;
@@ -94,7 +84,7 @@
     /**
      * Get the static IP address included in the configuration.
      */
-    public @Nullable LinkAddress getIpAddress() {
+    public @NonNull LinkAddress getIpAddress() {
         return ipAddress;
     }
 
@@ -130,10 +120,15 @@
         private String mDomains;
 
         /**
-         * Set the IP address to be included in the configuration; null by default.
+         * Set the IP address to be included in the configuration.
+         *
          * @return The {@link Builder} for chaining.
          */
-        public @NonNull Builder setIpAddress(@Nullable LinkAddress ipAddress) {
+        public @NonNull Builder setIpAddress(@NonNull LinkAddress ipAddress) {
+            if (ipAddress != null && !(ipAddress.getAddress() instanceof Inet4Address)) {
+                throw new IllegalArgumentException(
+                        "Only IPv4 addresses can be used for the IP configuration");
+            }
             mIpAddress = ipAddress;
             return this;
         }
@@ -143,6 +138,10 @@
          * @return The {@link Builder} for chaining.
          */
         public @NonNull Builder setGateway(@Nullable InetAddress gateway) {
+            if (gateway != null && !(gateway instanceof Inet4Address)) {
+                throw new IllegalArgumentException(
+                        "Only IPv4 addresses can be used for the gateway configuration");
+            }
             mGateway = gateway;
             return this;
         }
@@ -153,6 +152,12 @@
          */
         public @NonNull Builder setDnsServers(@NonNull Iterable<InetAddress> dnsServers) {
             Objects.requireNonNull(dnsServers);
+            for (InetAddress inetAddress: dnsServers) {
+                if (!(inetAddress instanceof Inet4Address)) {
+                    throw new IllegalArgumentException(
+                            "Only IPv4 addresses can be used for the DNS server configuration");
+                }
+            }
             mDnsServers = dnsServers;
             return this;
         }
@@ -171,6 +176,8 @@
         /**
          * Create a {@link StaticIpConfiguration} from the parameters in this {@link Builder}.
          * @return The newly created StaticIpConfiguration.
+         * @throws IllegalArgumentException if an invalid configuration is attempted, e.g.
+         * if an IP Address was not configured via {@link #setIpAddress(LinkAddress)}.
          */
         public @NonNull StaticIpConfiguration build() {
             final StaticIpConfiguration config = new StaticIpConfiguration();
@@ -188,7 +195,9 @@
 
     /**
      * Add a DNS server to this configuration.
+     * @hide
      */
+    @SystemApi
     public void addDnsServer(@NonNull InetAddress server) {
         dnsServers.add(server);
     }
@@ -197,7 +206,9 @@
      * Returns the network routes specified by this object. Will typically include a
      * directly-connected route for the IP address's local subnet and a default route.
      * @param iface Interface to include in the routes.
+     * @hide
      */
+    @SystemApi
     public @NonNull List<RouteInfo> getRoutes(@Nullable String iface) {
         List<RouteInfo> routes = new ArrayList<RouteInfo>(3);
         if (ipAddress != null) {
@@ -305,7 +316,7 @@
 
     /** Implement the Parcelable interface */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelable(ipAddress, flags);
         InetAddressUtils.parcelInetAddress(dest, gateway, flags);
         dest.writeInt(dnsServers.size());
@@ -316,7 +327,7 @@
     }
 
     /** @hide */
-    public static StaticIpConfiguration readFromParcel(Parcel in) {
+    public static @NonNull StaticIpConfiguration readFromParcel(Parcel in) {
         final StaticIpConfiguration s = new StaticIpConfiguration();
         s.ipAddress = in.readParcelable(null);
         s.gateway = InetAddressUtils.unparcelInetAddress(in);
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index bceeefa..d24b14b 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -61,11 +61,12 @@
         "-Wno-unused-parameter",
         "-Wthread-safety",
     ],
-    static_libs: ["libgmock"],
+    static_libs: [
+        "libgmock",
+        "libnetworkstats",
+    ],
     shared_libs: [
         "libbase",
         "liblog",
-        "libnetworkstats",
-        "libutils",
     ],
 }
diff --git a/service/Android.bp b/service/Android.bp
index 8d491b3..33fe3ea 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -62,7 +62,6 @@
     ],
     header_libs: [
         "bpf_connectivity_headers",
-        "libbase_headers",
     ],
     static_libs: [
         "libclat",
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 198190a..d833bc2 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -7043,7 +7043,7 @@
         nai.notifyRegistered();
         NetworkInfo networkInfo = nai.networkInfo;
         updateNetworkInfo(nai, networkInfo);
-        updateUids(nai, null, nai.networkCapabilities);
+        updateVpnUids(nai, null, nai.networkCapabilities);
     }
 
     private class NetworkOfferInfo implements IBinder.DeathRecipient {
@@ -7679,7 +7679,8 @@
         updateNetworkPermissions(nai, newNc);
         final NetworkCapabilities prevNc = nai.getAndSetNetworkCapabilities(newNc);
 
-        updateUids(nai, prevNc, newNc);
+        updateVpnUids(nai, prevNc, newNc);
+        updateAccessUids(nai, prevNc, newNc);
         nai.updateScoreForNetworkAgentUpdate();
 
         if (nai.getCurrentScore() == oldScore && newNc.equalRequestableCapabilities(prevNc)) {
@@ -7849,12 +7850,6 @@
         }
     }
 
-    private void updateUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
-            @Nullable NetworkCapabilities newNc) {
-        updateVpnUids(nai, prevNc, newNc);
-        updateAccessUids(nai, prevNc, newNc);
-    }
-
     private void updateVpnUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
             @Nullable NetworkCapabilities newNc) {
         Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUidRanges();
@@ -7911,7 +7906,7 @@
             }
         } catch (Exception e) {
             // Never crash!
-            loge("Exception in updateUids: ", e);
+            loge("Exception in updateVpnUids: ", e);
         }
     }
 
@@ -7950,6 +7945,9 @@
                         intsToUidRangeStableParcels(toRemove),
                         PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
             }
+        } catch (ServiceSpecificException e) {
+            // Has the interface disappeared since the network was built ?
+            Log.i(TAG, "Can't set access UIDs for network " + nai.network, e);
         } catch (RemoteException e) {
             // Netd died. This usually causes a runtime restart anyway.
         }
@@ -8864,6 +8862,7 @@
             }
             networkAgent.created = true;
             networkAgent.onNetworkCreated();
+            updateAccessUids(networkAgent, null, networkAgent.networkCapabilities);
         }
 
         if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
@@ -8917,7 +8916,7 @@
         } else if (state == NetworkInfo.State.DISCONNECTED) {
             networkAgent.disconnect();
             if (networkAgent.isVPN()) {
-                updateUids(networkAgent, networkAgent.networkCapabilities, null);
+                updateVpnUids(networkAgent, networkAgent.networkCapabilities, null);
             }
             disconnectAndDestroyNetwork(networkAgent);
             if (networkAgent.isVPN()) {
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index ce955fd..b761762 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -19,6 +19,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 
+import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_NOT_EXPORTED;
+
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -158,7 +160,7 @@
     private void registerForCarrierChanges() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
-        mContext.registerReceiver(this, filter, null, mHandler);
+        mContext.registerReceiver(this, filter, null, mHandler, RECEIVER_NOT_EXPORTED /* flags */);
         registerCarrierPrivilegesListeners();
     }
 
diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt
index b339a27..11d3ba0 100644
--- a/tests/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/common/java/android/net/NetworkAgentConfigTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.modules.utils.build.SdkLevel.isAtLeastS
+import com.android.modules.utils.build.SdkLevel.isAtLeastT
 import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -49,6 +50,10 @@
             if (isAtLeastS()) {
                 setBypassableVpn(true)
             }
+            if (isAtLeastT()) {
+                setExcludeLocalRoutesVpn(true)
+                setVpnRequiresValidation(true)
+            }
         }.build()
         assertParcelingIsLossless(config)
     }
@@ -69,6 +74,10 @@
                 setProvisioningNotificationEnabled(false)
                 setBypassableVpn(true)
             }
+            if (isAtLeastT()) {
+                setExcludeLocalRoutesVpn(true)
+                setVpnRequiresValidation(true)
+            }
         }.build()
 
         assertTrue(config.isExplicitlySelected())
@@ -77,6 +86,10 @@
         assertFalse(config.isPartialConnectivityAcceptable())
         assertTrue(config.isUnvalidatedConnectivityAcceptable())
         assertEquals("TEST_NETWORK", config.getLegacyTypeName())
+        if (isAtLeastT()) {
+            assertTrue(config.getExcludeLocalRouteVpn())
+            assertTrue(config.getVpnRequiresValidation())
+        }
         if (isAtLeastS()) {
             assertEquals(testExtraInfo, config.getLegacyExtraInfo())
             assertFalse(config.isNat64DetectionEnabled())
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index 4c9bccf..01c8cd2 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -22,7 +22,10 @@
     name: "CtsHostsideNetworkTestsApp2",
     defaults: ["cts_support_defaults"],
     sdk_version: "test_current",
-    static_libs: ["CtsHostsideNetworkTestsAidl"],
+    static_libs: [
+        "CtsHostsideNetworkTestsAidl",
+        "NetworkStackApiStableShims",
+    ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
     test_suites: [
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index 3b5e46f..f2a7b3f 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -21,6 +21,7 @@
 import static com.android.cts.net.hostside.app2.Common.ACTION_SNOOZE_WARNING;
 import static com.android.cts.net.hostside.app2.Common.DYNAMIC_RECEIVER;
 import static com.android.cts.net.hostside.app2.Common.TAG;
+import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
 
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -40,6 +41,7 @@
 
 import com.android.cts.net.hostside.IMyService;
 import com.android.cts.net.hostside.INetworkCallback;
+import com.android.modules.utils.build.SdkLevel;
 
 /**
  * Service used to dynamically register a broadcast receiver.
@@ -64,11 +66,14 @@
                 return;
             }
             final Context context = getApplicationContext();
+            final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
             mReceiver = new MyBroadcastReceiver(DYNAMIC_RECEIVER);
-            context.registerReceiver(mReceiver, new IntentFilter(ACTION_RECEIVER_READY));
             context.registerReceiver(mReceiver,
-                    new IntentFilter(ACTION_RESTRICT_BACKGROUND_CHANGED));
-            context.registerReceiver(mReceiver, new IntentFilter(ACTION_SNOOZE_WARNING));
+                    new IntentFilter(ACTION_RECEIVER_READY), flags);
+            context.registerReceiver(mReceiver,
+                    new IntentFilter(ACTION_RESTRICT_BACKGROUND_CHANGED), flags);
+            context.registerReceiver(mReceiver,
+                    new IntentFilter(ACTION_SNOOZE_WARNING), flags);
             Log.d(TAG, "receiver registered");
         }
 
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index 232114e..68fa38d 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -278,18 +278,23 @@
 
         assertTrue("Didn't receive broadcast for ACTION_CARRIER_CONFIG_CHANGED for subId=" + subId,
                 carrierConfigReceiver.waitForCarrierConfigChanged());
-        assertTrue("Don't have Carrier Privileges after adding cert for this package",
-                mTelephonyManager.createForSubscriptionId(subId).hasCarrierPrivileges());
 
         // Wait for CarrierPrivilegesTracker to receive the ACTION_CARRIER_CONFIG_CHANGED
         // broadcast. CPT then needs to update the corresponding DataConnection, which then
         // updates ConnectivityService. Unfortunately, this update to the NetworkCapabilities in
         // CS does not trigger NetworkCallback#onCapabilitiesChanged as changing the
         // administratorUids is not a publicly visible change. In lieu of a better signal to
-        // detministically wait for, use Thread#sleep here.
+        // deterministically wait for, use Thread#sleep here.
         // TODO(b/157949581): replace this Thread#sleep with a deterministic signal
         Thread.sleep(DELAY_FOR_ADMIN_UIDS_MILLIS);
 
+        // TODO(b/217559768): Receiving carrier config change and immediately checking carrier
+        //  privileges is racy, as the CP status is updated after receiving the same signal. Move
+        //  the CP check after sleep to temporarily reduce the flakiness. This will soon be fixed
+        //  by switching to CarrierPrivilegesListener.
+        assertTrue("Don't have Carrier Privileges after adding cert for this package",
+                mTelephonyManager.createForSubscriptionId(subId).hasCarrierPrivileges());
+
         final TestConnectivityDiagnosticsCallback connDiagsCallback =
                 createAndRegisterConnectivityDiagnosticsCallback(CELLULAR_NETWORK_REQUEST);
 
diff --git a/tests/cts/net/src/android/net/cts/IpConfigurationTest.java b/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
index 385bf9e..d221694 100644
--- a/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
+++ b/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
@@ -25,12 +25,16 @@
 import android.net.LinkAddress;
 import android.net.ProxyInfo;
 import android.net.StaticIpConfiguration;
+import android.os.Build;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.DevSdkIgnoreRule;
+
 import libcore.net.InetAddressUtils;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -50,6 +54,9 @@
     private StaticIpConfiguration mStaticIpConfig;
     private ProxyInfo mProxy;
 
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
     @Before
     public void setUp() {
         dnsServers.add(DNS1);
@@ -99,6 +106,18 @@
         assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
     }
 
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testBuilder() {
+        final IpConfiguration c = new IpConfiguration.Builder()
+                .setStaticIpConfiguration(mStaticIpConfig)
+                .setHttpProxy(mProxy)
+                .build();
+
+        assertEquals(mStaticIpConfig, c.getStaticIpConfiguration());
+        assertEquals(mProxy, c.getHttpProxy());
+    }
+
     private void checkEmpty(IpConfiguration config) {
         assertEquals(IpConfiguration.IpAssignment.UNASSIGNED,
                 config.getIpAssignment().UNASSIGNED);
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 23814c9..9506081 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -15,6 +15,19 @@
  */
 package android.net.cts
 
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkRequest
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.net.TestNetworkSpecifier
 import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
 import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
 import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
@@ -32,14 +45,27 @@
 import android.net.nsd.NsdManager.RegistrationListener
 import android.net.nsd.NsdManager.ResolveListener
 import android.net.nsd.NsdServiceInfo
+import android.os.HandlerThread
 import android.platform.test.annotations.AppModeFull
 import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.net.module.util.TrackRecord
+import com.android.networkstack.apishim.ConstantsShim
+import com.android.networkstack.apishim.NsdShimImpl
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.SC_V2
+import com.android.testutils.TestableNetworkAgent
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
+import com.android.testutils.tryTest
+import org.junit.After
 import org.junit.Assert.assertArrayEquals
 import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.net.ServerSocket
@@ -57,12 +83,37 @@
 private const val TIMEOUT_MS = 2000L
 private const val DBG = false
 
+private val nsdShim = NsdShimImpl.newInstance()
+
 @AppModeFull(reason = "Socket cannot bind in instant app mode")
 @RunWith(AndroidJUnit4::class)
 class NsdManagerTest {
+    // NsdManager is not updatable before S, so tests do not need to be backwards compatible
+    @get:Rule
+    val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
+
     private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
     private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) }
+
+    private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
     private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
+    private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
+
+    private lateinit var testNetwork1: TestTapNetwork
+    private lateinit var testNetwork2: TestTapNetwork
+
+    private class TestTapNetwork(
+        val iface: TestNetworkInterface,
+        val requestCb: NetworkCallback,
+        val agent: TestableNetworkAgent,
+        val network: Network
+    ) {
+        fun close(cm: ConnectivityManager) {
+            cm.unregisterNetworkCallback(requestCb)
+            agent.unregister()
+            iface.fileDescriptor.close()
+        }
+    }
 
     private interface NsdEvent
     private open class NsdRecord<T : NsdEvent> private constructor(
@@ -163,9 +214,14 @@
             add(ServiceLost(si))
         }
 
-        fun waitForServiceDiscovered(serviceName: String): NsdServiceInfo {
+        fun waitForServiceDiscovered(
+            serviceName: String,
+            expectedNetwork: Network? = null
+        ): NsdServiceInfo {
             return expectCallbackEventually<ServiceFound> {
-                it.serviceInfo.serviceName == serviceName
+                it.serviceInfo.serviceName == serviceName &&
+                        (expectedNetwork == null ||
+                                expectedNetwork == nsdShim.getNetwork(it.serviceInfo))
             }.serviceInfo
         }
     }
@@ -188,6 +244,58 @@
         }
     }
 
+    @Before
+    fun setUp() {
+        handlerThread.start()
+
+        runAsShell(MANAGE_TEST_NETWORKS) {
+            testNetwork1 = createTestNetwork()
+            testNetwork2 = createTestNetwork()
+        }
+    }
+
+    private fun createTestNetwork(): TestTapNetwork {
+        val tnm = context.getSystemService(TestNetworkManager::class.java)
+        val iface = tnm.createTapInterface()
+        val cb = TestableNetworkCallback()
+        val testNetworkSpecifier = TestNetworkSpecifier(iface.interfaceName)
+        cm.requestNetwork(NetworkRequest.Builder()
+                .removeCapability(NET_CAPABILITY_TRUSTED)
+                .addTransportType(TRANSPORT_TEST)
+                .setNetworkSpecifier(testNetworkSpecifier)
+                .build(), cb)
+        val agent = registerTestNetworkAgent(iface.interfaceName)
+        val network = agent.network ?: fail("Registered agent should have a network")
+        // The network has no INTERNET capability, so will be marked validated immediately
+        cb.expectAvailableThenValidatedCallbacks(network)
+        return TestTapNetwork(iface, cb, agent, network)
+    }
+
+    private fun registerTestNetworkAgent(ifaceName: String): TestableNetworkAgent {
+        val agent = TestableNetworkAgent(context, handlerThread.looper,
+                NetworkCapabilities().apply {
+                    removeCapability(NET_CAPABILITY_TRUSTED)
+                    addTransportType(TRANSPORT_TEST)
+                    setNetworkSpecifier(TestNetworkSpecifier(ifaceName))
+                },
+                LinkProperties().apply {
+                    interfaceName = ifaceName
+                },
+                NetworkAgentConfig.Builder().build())
+        agent.register()
+        agent.markConnected()
+        return agent
+    }
+
+    @After
+    fun tearDown() {
+        runAsShell(MANAGE_TEST_NETWORKS) {
+            testNetwork1.close(cm)
+            testNetwork2.close(cm)
+        }
+        handlerThread.quitSafely()
+    }
+
     @Test
     fun testNsdManager() {
         val si = NsdServiceInfo()
@@ -298,6 +406,149 @@
         registrationRecord2.expectCallback<ServiceUnregistered>()
     }
 
+    @Test
+    fun testNsdManager_DiscoverOnNetwork() {
+        // This tests requires shims supporting T+ APIs (discovering on specific network)
+        assumeTrue(ConstantsShim.VERSION > SC_V2)
+
+        val si = NsdServiceInfo()
+        si.serviceType = SERVICE_TYPE
+        si.serviceName = this.serviceName
+        si.port = 12345 // Test won't try to connect so port does not matter
+
+        val registrationRecord = NsdRegistrationRecord()
+        val registeredInfo = registerService(registrationRecord, si)
+
+        tryTest {
+            val discoveryRecord = NsdDiscoveryRecord()
+            nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, discoveryRecord)
+
+            val foundInfo = discoveryRecord.waitForServiceDiscovered(
+                    serviceName, testNetwork1.network)
+            assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo))
+
+            // Rewind to ensure the service is not found on the other interface
+            discoveryRecord.nextEvents.rewind(0)
+            assertNull(discoveryRecord.nextEvents.poll(timeoutMs = 100L) {
+                it is ServiceFound &&
+                        it.serviceInfo.serviceName == registeredInfo.serviceName &&
+                        nsdShim.getNetwork(it.serviceInfo) != testNetwork1.network
+            }, "The service should not be found on this network")
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord)
+        }
+    }
+
+    @Test
+    fun testNsdManager_DiscoverWithNetworkRequest() {
+        // This tests requires shims supporting T+ APIs (discovering on network request)
+        assumeTrue(ConstantsShim.VERSION > SC_V2)
+
+        val si = NsdServiceInfo()
+        si.serviceType = SERVICE_TYPE
+        si.serviceName = this.serviceName
+        si.port = 12345 // Test won't try to connect so port does not matter
+
+        val registrationRecord = NsdRegistrationRecord()
+        val registeredInfo1 = registerService(registrationRecord, si)
+        val discoveryRecord = NsdDiscoveryRecord()
+
+        tryTest {
+            val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
+            nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+                    NetworkRequest.Builder()
+                            .removeCapability(NET_CAPABILITY_TRUSTED)
+                            .addTransportType(TRANSPORT_TEST)
+                            .setNetworkSpecifier(specifier)
+                            .build(),
+                    discoveryRecord)
+
+            val discoveryStarted = discoveryRecord.expectCallback<DiscoveryStarted>()
+            assertEquals(SERVICE_TYPE, discoveryStarted.serviceType)
+
+            val serviceDiscovered = discoveryRecord.expectCallback<ServiceFound>()
+            assertEquals(registeredInfo1.serviceName, serviceDiscovered.serviceInfo.serviceName)
+            assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered.serviceInfo))
+
+            // Unregister, then register the service back: it should be lost and found again
+            nsdManager.unregisterService(registrationRecord)
+            val serviceLost1 = discoveryRecord.expectCallback<ServiceLost>()
+            assertEquals(registeredInfo1.serviceName, serviceLost1.serviceInfo.serviceName)
+            assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceLost1.serviceInfo))
+
+            registrationRecord.expectCallback<ServiceUnregistered>()
+            val registeredInfo2 = registerService(registrationRecord, si)
+            val serviceDiscovered2 = discoveryRecord.expectCallback<ServiceFound>()
+            assertEquals(registeredInfo2.serviceName, serviceDiscovered2.serviceInfo.serviceName)
+            assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered2.serviceInfo))
+
+            // Teardown, then bring back up a network on the test interface: the service should
+            // go away, then come back
+            testNetwork1.agent.unregister()
+            val serviceLost = discoveryRecord.expectCallback<ServiceLost>()
+            assertEquals(registeredInfo2.serviceName, serviceLost.serviceInfo.serviceName)
+            assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceLost.serviceInfo))
+
+            val newAgent = runAsShell(MANAGE_TEST_NETWORKS) {
+                registerTestNetworkAgent(testNetwork1.iface.interfaceName)
+            }
+            val newNetwork = newAgent.network ?: fail("Registered agent should have a network")
+            val serviceDiscovered3 = discoveryRecord.expectCallback<ServiceFound>()
+            assertEquals(registeredInfo2.serviceName, serviceDiscovered3.serviceInfo.serviceName)
+            assertEquals(newNetwork, nsdShim.getNetwork(serviceDiscovered3.serviceInfo))
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+            discoveryRecord.expectCallback<DiscoveryStopped>()
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord)
+        }
+    }
+
+    @Test
+    fun testNsdManager_ResolveOnNetwork() {
+        // This tests requires shims supporting T+ APIs (NsdServiceInfo.network)
+        assumeTrue(ConstantsShim.VERSION > SC_V2)
+
+        val si = NsdServiceInfo()
+        si.serviceType = SERVICE_TYPE
+        si.serviceName = this.serviceName
+        si.port = 12345 // Test won't try to connect so port does not matter
+
+        val registrationRecord = NsdRegistrationRecord()
+        val registeredInfo = registerService(registrationRecord, si)
+        tryTest {
+            val resolveRecord = NsdResolveRecord()
+
+            val discoveryRecord = NsdDiscoveryRecord()
+            nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+
+            val foundInfo1 = discoveryRecord.waitForServiceDiscovered(
+                    serviceName, testNetwork1.network)
+            assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo1))
+            // Rewind as the service could be found on each interface in any order
+            discoveryRecord.nextEvents.rewind(0)
+            val foundInfo2 = discoveryRecord.waitForServiceDiscovered(
+                    serviceName, testNetwork2.network)
+            assertEquals(testNetwork2.network, nsdShim.getNetwork(foundInfo2))
+
+            nsdManager.resolveService(foundInfo1, resolveRecord)
+            val cb = resolveRecord.expectCallback<ServiceResolved>()
+            cb.serviceInfo.let {
+                // Resolved service type has leading dot
+                assertEquals(".$SERVICE_TYPE", it.serviceType)
+                assertEquals(registeredInfo.serviceName, it.serviceName)
+                assertEquals(si.port, it.port)
+                assertEquals(testNetwork1.network, nsdShim.getNetwork(it))
+            }
+            // TODO: check that MDNS packets are sent only on testNetwork1.
+        } cleanupStep {
+            nsdManager.unregisterService(registrationRecord)
+        } cleanup {
+            registrationRecord.expectCallback<ServiceUnregistered>()
+        }
+    }
+
     /**
      * Register a service and return its registration record.
      */
diff --git a/tests/common/java/android/net/StaticIpConfigurationTest.java b/tests/cts/net/src/android/net/cts/StaticIpConfigurationTest.java
similarity index 81%
rename from tests/common/java/android/net/StaticIpConfigurationTest.java
rename to tests/cts/net/src/android/net/cts/StaticIpConfigurationTest.java
index b5f23bf..9b2756c 100644
--- a/tests/common/java/android/net/StaticIpConfigurationTest.java
+++ b/tests/cts/net/src/android/net/cts/StaticIpConfigurationTest.java
@@ -14,20 +14,30 @@
  * limitations under the License.
  */
 
-package android.net;
+package android.net.cts;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+import android.net.StaticIpConfiguration;
+import android.os.Build;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.DevSdkIgnoreRule;
+
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -42,15 +52,20 @@
 
     private static final String ADDRSTR = "192.0.2.2/25";
     private static final LinkAddress ADDR = new LinkAddress(ADDRSTR);
-    private static final InetAddress GATEWAY = IpAddress("192.0.2.1");
-    private static final InetAddress OFFLINKGATEWAY = IpAddress("192.0.2.129");
-    private static final InetAddress DNS1 = IpAddress("8.8.8.8");
-    private static final InetAddress DNS2 = IpAddress("8.8.4.4");
-    private static final InetAddress DNS3 = IpAddress("4.2.2.2");
+    private static final InetAddress GATEWAY = ipAddress("192.0.2.1");
+    private static final InetAddress OFFLINKGATEWAY = ipAddress("192.0.2.129");
+    private static final InetAddress DNS1 = ipAddress("8.8.8.8");
+    private static final InetAddress DNS2 = ipAddress("8.8.4.4");
+    private static final InetAddress DNS3 = ipAddress("4.2.2.2");
+    private static final InetAddress IPV6_ADDRESS = ipAddress("2001:4860:800d::68");
+    private static final LinkAddress IPV6_LINK_ADDRESS = new LinkAddress("2001:db8::1/64");
     private static final String IFACE = "eth0";
     private static final String FAKE_DOMAINS = "google.com";
 
-    private static InetAddress IpAddress(String addr) {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
+    private static InetAddress ipAddress(String addr) {
         return InetAddress.parseNumericAddress(addr);
     }
 
@@ -241,6 +256,29 @@
         assertEquals(DNS1, s.getDnsServers().get(0));
     }
 
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testIllegalBuilders() {
+        assertThrows("Can't set IP Address to IPv6!", IllegalArgumentException.class, () -> {
+            StaticIpConfiguration.Builder b = new StaticIpConfiguration.Builder().setIpAddress(
+                    IPV6_LINK_ADDRESS);
+        });
+
+        assertThrows("Can't set gateway to IPv6!", IllegalArgumentException.class, () -> {
+            StaticIpConfiguration.Builder b = new StaticIpConfiguration.Builder().setGateway(
+                    IPV6_ADDRESS);
+        });
+
+        assertThrows("Can't set DNS servers using IPv6!", IllegalArgumentException.class, () -> {
+            final ArrayList<InetAddress> dnsServers = new ArrayList<>();
+            dnsServers.add(DNS1);
+            dnsServers.add(IPV6_ADDRESS);
+
+            StaticIpConfiguration.Builder b = new StaticIpConfiguration.Builder().setDnsServers(
+                    dnsServers);
+        });
+    }
+
     @Test
     public void testAddDnsServers() {
         final StaticIpConfiguration s = new StaticIpConfiguration((StaticIpConfiguration) null);
diff --git a/tests/unit/jarjar-rules.txt b/tests/unit/jarjar-rules.txt
index ca88672..eb3e32a 100644
--- a/tests/unit/jarjar-rules.txt
+++ b/tests/unit/jarjar-rules.txt
@@ -1,2 +1,3 @@
 # Module library in frameworks/libs/net
 rule com.android.net.module.util.** android.net.frameworktests.util.@1
+rule com.android.testutils.TestBpfMap* android.net.frameworktests.testutils.TestBpfMap@1
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
index ca8cf07..e5e7ebc 100644
--- a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.net.Network;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -123,6 +124,7 @@
         fullInfo.setServiceType("_kitten._tcp");
         fullInfo.setPort(4242);
         fullInfo.setHost(LOCALHOST);
+        fullInfo.setNetwork(new Network(123));
         checkParcelable(fullInfo);
 
         NsdServiceInfo noHostInfo = new NsdServiceInfo();
@@ -172,6 +174,7 @@
         assertEquals(original.getServiceType(), result.getServiceType());
         assertEquals(original.getHost(), result.getHost());
         assertTrue(original.getPort() == result.getPort());
+        assertEquals(original.getNetwork(), result.getNetwork());
 
         // Assert equality of attribute map.
         Map<String, byte[]> originalMap = original.getAttributes();
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
index 960a9f1..943a559 100644
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
@@ -50,6 +50,7 @@
     private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
     private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
     private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
+    private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26;
 
     @Test
     public void testDefaults() throws Exception {
@@ -78,10 +79,13 @@
         assertEquals(1360, p.maxMtu);
         assertFalse(p.areAuthParamsInline);
         assertFalse(p.isRestrictedToTestNetworks);
+        assertFalse(p.excludeLocalRoutes);
+        assertFalse(p.requiresInternetValidation);
     }
 
     private VpnProfile getSampleIkev2Profile(String key) {
-        final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */);
+        final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+                false /* excludesLocalRoutes */, true /* requiresPlatformValidation */);
 
         p.name = "foo";
         p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
@@ -129,8 +133,8 @@
     @Test
     public void testParcelUnparcel() {
         if (isAtLeastT()) {
-            // excludeLocalRoutes is added in T.
-            assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 24);
+            // excludeLocalRoutes, requiresPlatformValidation were added in T.
+            assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 25);
         } else {
             assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
         }
@@ -174,7 +178,8 @@
                 getEncodedDecodedIkev2ProfileMissingValues(
                         ENCODED_INDEX_AUTH_PARAMS_INLINE,
                         ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
-                        ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE /* missingIndices */);
+                        ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+                        ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION /* missingIndices */);
 
         assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
     }
@@ -194,14 +199,26 @@
     public void testEncodeDecodeMissingExcludeLocalRoutes() {
         final String tooFewValues =
                 getEncodedDecodedIkev2ProfileMissingValues(
-                        ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE /* missingIndices */);
+                        ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+                        ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION /* missingIndices */);
 
-        // Verify decoding without isRestrictedToTestNetworks defaults to false
+        // Verify decoding without excludeLocalRoutes defaults to false
         final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
         assertFalse(decoded.excludeLocalRoutes);
     }
 
     @Test
+    public void testEncodeDecodeMissingRequiresValidation() {
+        final String tooFewValues =
+                getEncodedDecodedIkev2ProfileMissingValues(
+                        ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION /* missingIndices */);
+
+        // Verify decoding without requiresValidation defaults to false
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+        assertFalse(decoded.requiresInternetValidation);
+    }
+
+    @Test
     public void testEncodeDecodeLoginsNotSaved() {
         final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
         profile.saveLogin = false;
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 6d1d765..5086943 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -218,14 +218,14 @@
         client.discoverServices("a_type", PROTOCOL, listener2);
         waitForIdle();
         verify(mDaemon, times(1)).maybeStart();
-        verifyDaemonCommand("discover 3 a_type");
+        verifyDaemonCommand("discover 3 a_type 0");
 
         // Client resolve request
         NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
         client.resolveService(request, listener3);
         waitForIdle();
         verify(mDaemon, times(1)).maybeStart();
-        verifyDaemonCommand("resolve 4 a_name a_type local.");
+        verifyDaemonCommand("resolve 4 a_name a_type local. 0");
 
         // Client disconnects, stop the daemon after CLEANUP_DELAY_MS.
         deathRecipient.binderDied();
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
index 157507b..553cb83 100644
--- a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -69,6 +69,8 @@
 @RunWith(DevSdkIgnoreRunner.class)
 @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
 public class CarrierPrivilegeAuthenticatorTest {
+    // TODO : use ConstantsShim.RECEIVER_NOT_EXPORTED when it's available in tests.
+    private static final int RECEIVER_NOT_EXPORTED = 4;
     private static final int SUBSCRIPTION_COUNT = 2;
     private static final int TEST_SUBSCRIPTION_ID = 1;
 
@@ -115,7 +117,7 @@
 
     private IntentFilter getIntentFilter() {
         final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class);
-        verify(mContext).registerReceiver(any(), captor.capture(), any(), any());
+        verify(mContext).registerReceiver(any(), captor.capture(), any(), any(), anyInt());
         return captor.getValue();
     }
 
@@ -138,10 +140,11 @@
     @Test
     public void testConstructor() throws Exception {
         verify(mContext).registerReceiver(
-                        eq(mCarrierPrivilegeAuthenticator),
-                        any(IntentFilter.class),
-                        any(),
-                        any());
+                eq(mCarrierPrivilegeAuthenticator),
+                any(IntentFilter.class),
+                any(),
+                any(),
+                eq(RECEIVER_NOT_EXPORTED));
         final IntentFilter filter = getIntentFilter();
         assertEquals(1, filter.countActions());
         assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED));